Repository: wicket
Updated Branches:
  refs/heads/WICKET-6318-configurable-property-expression-resolver fa0f12624 -> 
7fd219c3b


WICKET-4008 property expression parser implementation


Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/1e5618f1
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/1e5618f1
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/1e5618f1

Branch: refs/heads/WICKET-6318-configurable-property-expression-resolver
Commit: 1e5618f13c396ab6c06cc04d607db276ba67b1fb
Parents: 5b7547f
Author: Pedro Henrique Oliveira dos Santos <[email protected]>
Authored: Wed Sep 7 00:45:23 2016 -0300
Committer: Pedro Henrique Oliveira dos Santos <[email protected]>
Committed: Thu Sep 15 02:09:51 2016 -0300

----------------------------------------------------------------------
 .../wicket/core/util/lang/ParserException.java  |  33 ++++
 .../core/util/lang/PropertyExpression.java      |  88 ++++++++++
 .../util/lang/PropertyExpressionParser.java     | 172 +++++++++++++++++++
 .../util/lang/PropertyExpressionParserTest.java | 121 +++++++++++++
 .../wicket/util/lang/PropertyResolverTest.java  |  80 ++++++---
 5 files changed, 473 insertions(+), 21 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/1e5618f1/wicket-core/src/main/java/org/apache/wicket/core/util/lang/ParserException.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/core/util/lang/ParserException.java
 
b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/ParserException.java
new file mode 100644
index 0000000..62712e8
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/ParserException.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.core.util.lang;
+
+/**
+ * @author Pedro Santos
+ */
+public class ParserException extends RuntimeException
+{
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * @param message
+        */
+       public ParserException(String message)
+       {
+               super(message);
+       }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/wicket/blob/1e5618f1/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyExpression.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyExpression.java
 
b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyExpression.java
new file mode 100644
index 0000000..2021c21
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyExpression.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.core.util.lang;
+
+public class PropertyExpression
+{
+       Property property;
+       PropertyExpression next;
+
+       static class Property
+       {
+               CharSequence javaIdentifier;
+               CharSequence index;
+               public boolean hasMethodSign;
+
+               public Property()
+               {
+               }
+
+               public Property(String javaIdentifier, String index, boolean 
hasMethodSign)
+               {
+                       this.javaIdentifier = javaIdentifier;
+                       this.index = index;
+                       this.hasMethodSign = hasMethodSign;
+               }
+
+               @Override
+               public int hashCode()
+               {
+                       final int prime = 31;
+                       int result = 1;
+                       result = prime * result + (hasMethodSign ? 1231 : 1237);
+                       result = prime * result + ((index == null) ? 0 : 
index.hashCode());
+                       result = prime * result + ((javaIdentifier == null) ? 0 
: javaIdentifier.hashCode());
+                       return result;
+               }
+
+               @Override
+               public boolean equals(Object obj)
+               {
+                       if (this == obj)
+                               return true;
+                       if (obj == null)
+                               return false;
+                       if (getClass() != obj.getClass())
+                               return false;
+                       Property other = (Property)obj;
+                       if (hasMethodSign != other.hasMethodSign)
+                               return false;
+                       if (index == null)
+                       {
+                               if (other.index != null)
+                                       return false;
+                       }
+                       else if (!index.equals(other.index))
+                               return false;
+                       if (javaIdentifier == null)
+                       {
+                               if (other.javaIdentifier != null)
+                                       return false;
+                       }
+                       else if (!javaIdentifier.equals(other.javaIdentifier))
+                               return false;
+                       return true;
+               }
+
+               @Override
+               public String toString()
+               {
+                       return javaIdentifier + (index == null ? "" : "[" + 
index + "]");
+               }
+       }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/wicket/blob/1e5618f1/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyExpressionParser.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyExpressionParser.java
 
b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyExpressionParser.java
new file mode 100644
index 0000000..4588218
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyExpressionParser.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.core.util.lang;
+
+import static java.lang.Character.isJavaIdentifierPart;
+import static java.lang.Character.isJavaIdentifierStart;
+import static java.lang.String.format;
+
+import org.apache.wicket.core.util.lang.PropertyExpression.Property;
+
+/**
+ * EBNF like description of the property expression syntax <code>
+ * 
+ *  java letter                                = "_" | "$" | "A" | "a" | "B" | 
"b" | (...) ;
+ *  java letter or digit       = java letter | "0" | "1" | (...) ;
+ *  char                                       = java letter or digit | "." | 
"(" | ")" | "[" | "]" | "!" | "@" | "#" | (...);
+ *  index char                         = char - "]"
+ *  
+ *  java identifier                    = java letter , {java letter or digit}
+ *  method sign                                = "(" , ")"
+ *  index                                      = "[" , index char , {index 
char} , "]" ;
+ *  
+ *  property                           = java identifier , [ index | method 
sign ];
+ *  property expression                = property , { "." , property 
expression } ;
+ *  
+ * </code>
+ * 
+ * @author Pedro Santos
+ */
+public class PropertyExpressionParser
+{
+
+       private static final char END_OF_EXPRESSION = (char)-1;
+       private String text;
+       private int currentPosition = 0;
+       private int nextPosition = 1;
+       private char currentToken;
+       private char lookaheadToken;
+
+       private char advance()
+       {
+               currentPosition = nextPosition;
+               currentToken = lookaheadToken;
+
+               nextPosition += 1;
+               if (nextPosition >= text.length())
+                       lookaheadToken = END_OF_EXPRESSION;
+               else
+                       lookaheadToken = text.charAt(nextPosition);
+               return currentToken;
+       }
+
+       PropertyExpression parse(String text)
+       {
+               this.text = text;
+               if (text == null || text.isEmpty())
+               {
+                       throw new ParserException("No expression was given to 
be parsed.");
+               }
+               else if (text.length() == 1)
+               {
+                       lookaheadToken = END_OF_EXPRESSION;
+               }
+               else
+               {
+                       lookaheadToken = text.charAt(1);
+               }
+               currentToken = text.charAt(0);
+               return expression();
+
+       }
+
+       private PropertyExpression expression()
+       {
+               PropertyExpression expression = new PropertyExpression();
+               expression.property = property();
+               switch (lookaheadToken)
+               {
+                       case '.' :
+                               advance();// skips the dot
+                               advance();// advances to the next expression
+                               expression.next = expression();
+                               return expression;
+                       case END_OF_EXPRESSION :
+                               return expression;
+                       default :
+                               throw new ParserException("expecting a new 
expression but got: " + currentToken);
+               }
+       }
+
+       private Property property()
+       {
+               Property property = new Property();
+               if (currentToken == '[')
+               {
+                       property.index = index();
+                       return property;
+               }
+               else
+               {
+                       property.javaIdentifier = javaIdentifier();
+                       if (lookaheadToken == '[')
+                       {
+                               advance();// skips left bracket
+                               property.index = index();
+                       }
+                       else if (lookaheadToken == '(')
+                       {
+                               advance(); // skips left bracket
+                               property.hasMethodSign = methodSign();
+                       }
+                       return property;
+               }
+       }
+
+
+       private CharSequence javaIdentifier()
+       {
+               if (!isJavaIdentifierStart(currentToken))
+               {
+                       throw new ParserException("Expeting a java identifier 
but got a :" + currentToken);
+               }
+               int begin = currentPosition;
+               while (isJavaIdentifierPart(lookaheadToken))
+               {
+                       advance();
+               }
+               return text.substring(begin, nextPosition);
+       }
+
+       private CharSequence index()
+       {
+               advance();// escape bracket
+               if (currentToken == ']')
+               {
+                       throw new ParserException(
+                               format("Expecting a property index but found 
empty brakets: '%s<--'",
+                                       text.substring(0, nextPosition)));
+               }
+               int begin = currentPosition;
+               while (lookaheadToken != ']')
+               {
+                       advance();
+               }
+               advance();// escape bracket
+               return text.substring(begin, currentPosition);
+       }
+
+       private boolean methodSign()
+       {
+               if (lookaheadToken != ')')
+               {
+                       throw new ParserException("expecting a method sign but 
got: " + currentToken);
+               }
+               advance();// skips right bracket
+               return true;
+       }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/wicket/blob/1e5618f1/wicket-core/src/test/java/org/apache/wicket/core/util/lang/PropertyExpressionParserTest.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/core/util/lang/PropertyExpressionParserTest.java
 
b/wicket-core/src/test/java/org/apache/wicket/core/util/lang/PropertyExpressionParserTest.java
new file mode 100644
index 0000000..96d3739
--- /dev/null
+++ 
b/wicket-core/src/test/java/org/apache/wicket/core/util/lang/PropertyExpressionParserTest.java
@@ -0,0 +1,121 @@
+package org.apache.wicket.core.util.lang;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import org.apache.wicket.core.util.lang.ParserException;
+import org.apache.wicket.core.util.lang.PropertyExpression;
+import org.apache.wicket.core.util.lang.PropertyExpressionParser;
+import org.apache.wicket.core.util.lang.PropertyExpression.Property;
+import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class PropertyExpressionParserTest
+{
+
+       @Rule
+       public ExpectedException expectedException = ExpectedException.none();
+       private PropertyExpressionParser parser = new 
PropertyExpressionParser();
+
+       @Test
+       public void shouldParsePropertyExpressions()
+       {
+               PropertyExpression parse = parser.parse("a");
+               assertThat(parse.property, is(new Property("a", null, false)));
+               assertThat(parse.next, CoreMatchers.nullValue());
+       }
+
+       @Test
+       public void shouldParseShortPropertyExpressions()
+       {
+               PropertyExpression parse = parser.parse("person");
+               assertThat(parse.property, is(new Property("person", null, 
false)));
+               assertThat(parse.next, CoreMatchers.nullValue());
+       }
+
+       @Test
+       public void shouldParseIndexedPropertyExpressions()
+       {
+               PropertyExpression parse = parser.parse("person[age]");
+               assertThat(parse.property, is(new Property("person", "age", 
false)));
+               assertThat(parse.next, CoreMatchers.nullValue());
+       }
+
+       @Test
+       public void shouldParseMethodExpressions()
+       {
+               PropertyExpression parse = parser.parse("person()");
+               assertThat(parse.property, is(new Property("person", null, 
true)));
+               assertThat(parse.next, CoreMatchers.nullValue());
+       }
+
+       @Test
+       public void shouldParseIndexExpressions()
+       {
+               PropertyExpression parse = parser.parse("[person#name]");
+               assertThat(parse.property, is(new Property(null, "person#name", 
false)));
+               assertThat(parse.next, CoreMatchers.nullValue());
+       }
+
+       @Test
+       public void shouldParseChainedPropertyExpressions()
+       {
+               PropertyExpression parse = parser.parse("person.child");
+               assertThat(parse.property, is(new Property("person", null, 
false)));
+               assertThat(parse.next.property, is(new Property("child", null, 
false)));
+       }
+
+       @Test
+       public void shouldParseShortChainedPropertyExpressions()
+       {
+               PropertyExpression parse = parser.parse("a.b");
+               assertThat(parse.property, is(new Property("a", null, false)));
+               assertThat(parse.next.property, is(new Property("b", null, 
false)));
+       }
+
+       @Test
+       public void shouldParseChainedIndexedPropertyExpressions()
+       {
+               PropertyExpression parse = parser.parse("person[1].child");
+               assertThat(parse.property, is(new Property("person", "1", 
false)));
+               assertThat(parse.next.property, is(new Property("child", null, 
false)));
+       }
+
+       @Test
+       public void shouldParseChainedMethodExpressions()
+       {
+               PropertyExpression parse = parser.parse("person().child");
+               assertThat(parse.property, is(new Property("person", null, 
true)));
+               assertThat(parse.next.property, is(new Property("child", null, 
false)));
+       }
+
+       @Test
+       public void shouldParseDeeperChainedPropertyExpressions()
+       {
+               PropertyExpression parse = parser.parse("person.child.name");
+               assertThat(parse.property, is(new Property("person", null, 
false)));
+               assertThat(parse.next.property, is(new Property("child", null, 
false)));
+               assertThat(parse.next.next.property, is(new Property("name", 
null, false)));
+       }
+
+
+       @Test
+       public void shouldCheckExpressions()
+       {
+               expectedException.expect(ParserException.class);
+               expectedException.expectMessage("No expression was given to be 
parsed.");
+               parser.parse("");
+       }
+
+       @Test
+       public void shouldReportEmptyIndexBrackets()
+       {
+               expectedException.expect(ParserException.class);
+               expectedException
+                       .expectMessage("Expecting a property index but found 
empty brakets: 'person[]<--'");
+               parser.parse("person[]");
+       }
+
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/1e5618f1/wicket-core/src/test/java/org/apache/wicket/util/lang/PropertyResolverTest.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/util/lang/PropertyResolverTest.java
 
b/wicket-core/src/test/java/org/apache/wicket/util/lang/PropertyResolverTest.java
index 1a3278d..c2a19e6 100644
--- 
a/wicket-core/src/test/java/org/apache/wicket/util/lang/PropertyResolverTest.java
+++ 
b/wicket-core/src/test/java/org/apache/wicket/util/lang/PropertyResolverTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.wicket.util.lang;
 
+import static org.hamcrest.CoreMatchers.is;
+
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
@@ -52,10 +54,13 @@ import org.junit.Test;
 public class PropertyResolverTest extends WicketTestCase
 {
 
+       private static final int AN_INTEGER = 10;
+
        private static final PropertyResolverConverter CONVERTER = new 
PropertyResolverConverter(
                new ConverterLocator(), Locale.US);
 
        private Person person;
+       private Map<String, Integer> integerMap = new HashMap<String, 
Integer>();
 
        /**
         * @throws Exception
@@ -81,7 +86,7 @@ public class PropertyResolverTest extends WicketTestCase
        @Test
        public void simpleExpression() throws Exception
        {
-               String name = (String) PropertyResolver.getValue("name", 
person);
+               String name = (String)PropertyResolver.getValue("name", person);
                assertNull(name);
 
                PropertyResolver.setValue("name", person, "wicket", CONVERTER);
@@ -208,6 +213,28 @@ public class PropertyResolverTest extends WicketTestCase
         * @throws Exception
         */
        @Test
+       public void shouldAccessConflictingMapEntries() throws Exception
+       {
+               PropertyResolver.setValue("integerMap.class", this, AN_INTEGER, 
CONVERTER);
+               assertThat(PropertyResolver.getValue("integerMap.class", this), 
is(AN_INTEGER));
+       }
+
+       /**
+        * @throws Exception
+        */
+       @Test
+       public void mapMethodExpressionasdf() throws Exception
+       {
+               HashMap<String, Integer> map = new HashMap<String, Integer>();
+               PropertyResolver.setValue("class", map, 10, CONVERTER);
+               Integer mySize = (Integer)PropertyResolver.getValue("class", 
map);
+               assertEquals(mySize, new Integer(1));
+       }
+
+       /**
+        * @throws Exception
+        */
+       @Test
        public void mapWithDotLookup() throws Exception
        {
                Address address = new Address();
@@ -217,7 +244,8 @@ public class PropertyResolverTest extends WicketTestCase
                assertNotNull(hm.get("address.test"));
                PropertyResolver.setValue("addressMap[address.test].street", 
person, "wicket-street",
                        CONVERTER);
-               String street = 
(String)PropertyResolver.getValue("addressMap[address.test].street", person);
+               String street = 
(String)PropertyResolver.getValue("addressMap[address.test].street",
+                       person);
                assertEquals(street, "wicket-street");
        }
 
@@ -746,63 +774,73 @@ public class PropertyResolverTest extends WicketTestCase
                Object actual = converter.convert(date, Long.class);
                assertEquals(date.getTime(), actual);
        }
-       
+
        /**
         * WICKET-5623 custom properties
         */
        @Test
-       public void custom() {
+       public void custom()
+       {
                Document document = new Document();
                document.setType("type");
                document.setProperty("string", "string");
-               
+
                Document nestedCustom = new Document();
                nestedCustom.setProperty("string", "string2");
                document.setProperty("nested", nestedCustom);
-               
-               PropertyResolver.setLocator(tester.getApplication(), new 
CachingPropertyLocator(new CustomGetAndSetLocator()));
-               
+
+               PropertyResolver.setLocator(tester.getApplication(),
+                       new CachingPropertyLocator(new 
CustomGetAndSetLocator()));
+
                assertEquals("type", PropertyResolver.getValue("type", 
document));
                assertEquals("string", PropertyResolver.getValue("string", 
document));
                assertEquals("string2", 
PropertyResolver.getValue("nested.string", document));
        }
-       
-       class CustomGetAndSetLocator implements IPropertyLocator {
+
+       class CustomGetAndSetLocator implements IPropertyLocator
+       {
 
                private IPropertyLocator locator = new DefaultPropertyLocator();
-               
+
                @Override
-               public IGetAndSet get(Class<?> clz, String exp) {
+               public IGetAndSet get(Class<?> clz, String exp)
+               {
                        // first try default properties
                        IGetAndSet getAndSet = locator.get(clz, exp);
-                       if (getAndSet == null && 
Document.class.isAssignableFrom(clz)) {
+                       if (getAndSet == null && 
Document.class.isAssignableFrom(clz))
+                       {
                                // fall back to document properties
                                getAndSet = new DocumentPropertyGetAndSet(exp);
                        }
                        return getAndSet;
                }
-               
-               public class DocumentPropertyGetAndSet extends 
AbstractGetAndSet {
+
+               public class DocumentPropertyGetAndSet extends AbstractGetAndSet
+               {
 
                        private String name;
 
-                       public DocumentPropertyGetAndSet(String name) {
+                       public DocumentPropertyGetAndSet(String name)
+                       {
                                this.name = name;
                        }
 
                        @Override
-                       public Object getValue(Object object) {
-                               return ((Document) object).getProperty(name);
+                       public Object getValue(Object object)
+                       {
+                               return ((Document)object).getProperty(name);
                        }
 
                        @Override
-                       public Object newValue(Object object) {
+                       public Object newValue(Object object)
+                       {
                                return new Document();
                        }
 
                        @Override
-                       public void setValue(Object object, Object value, 
PropertyResolverConverter converter) {
-                               ((Document) object).setProperty(name, value);
+                       public void setValue(Object object, Object value, 
PropertyResolverConverter converter)
+                       {
+                               ((Document)object).setProperty(name, value);
                        }
                }
        }

Reply via email to