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); } } }
