Author: robertdzeigler Date: Fri May 6 03:26:02 2011 New Revision: 1100036
URL: http://svn.apache.org/viewvc?rev=1100036&view=rev Log: TAP5-1495: Tapestry's property expression language should support map creation Fix minor typo in plastic documentation Clarify "parameters" parameter documentation in AbstractLink. Modified: tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/InstructionBuilder.java tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpressionLexer.g tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpressionParser.g tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractLink.java tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java tapestry/tapestry5/trunk/tapestry-core/src/test/app1/LinkQueryParameters.tml tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/CoreBehaviorsTests.java tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/EchoBean.java tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImplTest.java Modified: tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/InstructionBuilder.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/InstructionBuilder.java?rev=1100036&r1=1100035&r2=1100036&view=diff ============================================================================== --- tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/InstructionBuilder.java (original) +++ tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/InstructionBuilder.java Fri May 6 03:26:02 2011 @@ -28,7 +28,7 @@ import java.lang.reflect.Method; * try/catch blocks}, is more like a DSL (domain specific language), and is based on callbacks. This looks better in * Groovy and will be more reasonable once JDK 1.8 closures are available; in the meantime, it means some deeply nested * inner classes, but helps ensure that correct bytecode is generated and helps to limit the amount of bookkeeping is - * necessary on the part of coce using InstructionBuilder. + * necessary on the part of code using InstructionBuilder. */ @SuppressWarnings("rawtypes") public interface InstructionBuilder Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpressionLexer.g URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpressionLexer.g?rev=1100036&r1=1100035&r2=1100036&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpressionLexer.g (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpressionLexer.g Fri May 6 03:26:02 2011 @@ -1,4 +1,4 @@ -// Copyright 2008, 2010 The Apache Software Foundation +// Copyright 2008, 2010, 2011 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -53,7 +53,10 @@ RPAREN : ')'; LBRACKET: '['; RBRACKET: ']'; COMMA : ','; -BANG : '!'; +BANG : '!'; +LBRACE : '{'; +RBRACE : '}'; +COLON : ':'; fragment QUOTE : '\''; Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpressionParser.g URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpressionParser.g?rev=1100036&r1=1100035&r2=1100036&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpressionParser.g (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpressionParser.g Fri May 6 03:26:02 2011 @@ -1,4 +1,4 @@ -// Copyright 2008, 2009 The Apache Software Foundation +// Copyright 2008, 2009, 2011 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -30,6 +30,8 @@ tokens INVOKE; // A List (top level, or as method parameter) LIST; + //A Map (top level, or as method parameter) + MAP; // Not operation (invert a boolean) NOT; } @@ -49,8 +51,9 @@ expression | propertyChain | list | notOp + | map ; - + keyword : NULL | TRUE | FALSE | THIS; constant: INTEGER| DECIMAL | STRING; @@ -73,7 +76,7 @@ methodInvocation expressionList : expression (COMMA! expression)* ; - + rangeOp : from=rangeopArg RANGEOP to=rangeopArg -> ^(RANGEOP $from $to) ; @@ -87,6 +90,16 @@ list : LBRACKET RBRACKET -> ^(LIST) | LBRACKET expressionList RBRACKET -> ^(LIST expressionList) ; - notOp : BANG expression -> ^(NOT expression) ; + +map : LBRACE RBRACE -> ^(MAP) + | LBRACE mapEntryList RBRACE -> ^(MAP mapEntryList) + ; + +mapEntryList : mapEntry (COMMA! mapEntry)*; + +mapEntry : mapKey COLON! expression; + +mapKey : keyword | constant | propertyChain; + Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractLink.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractLink.java?rev=1100036&r1=1100035&r2=1100036&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractLink.java (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractLink.java Fri May 6 03:26:02 2011 @@ -45,7 +45,7 @@ public abstract class AbstractLink imple /** * If specified, the parameters are added to the link as query parameters in key=value fashion. - * Both values will be coerced to string using value encoder. + * Values will be coerced to string using value encoder; keys should be Strings. * @since 5.3 */ @Parameter(allowNull = false) Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java?rev=1100036&r1=1100035&r2=1100036&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java Fri May 6 03:26:02 2011 @@ -21,6 +21,7 @@ import static org.apache.tapestry5.inter import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.INTEGER; import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.INVOKE; import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.LIST; +import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.MAP; import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.NOT; import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.NULL; import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.RANGEOP; @@ -37,6 +38,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -108,6 +110,11 @@ public class PropertyConduitSourceImpl i static final Method ADD = getMethod(ArrayList.class, "add", Object.class); } + static class HashMapMethods + { + static final Method PUT = getMethod(HashMap.class, "put", Object.class, Object.class); + } + private static InstructionBuilderCallback RETURN_NULL = new InstructionBuilderCallback() { public void doBuild(InstructionBuilder builder) @@ -450,6 +457,15 @@ public class PropertyConduitSourceImpl i return; + case MAP: + implementMapGetter(node); + implementNoOpSetter(); + + conduitPropertyType = Map.class; + + return; + + case NOT: implementNotOpGetter(node); implementNoOpSetter(); @@ -751,6 +767,9 @@ public class PropertyConduitSourceImpl i return implementListConstructor(builder, node); + case MAP: + return implementMapConstructor(builder, node); + case NOT: return implementNotExpression(builder, node); @@ -804,6 +823,46 @@ public class PropertyConduitSourceImpl i return ArrayList.class; } + private void implementMapGetter(final Tree mapNode) + { + plasticClass.introduceMethod(ConduitMethods.GET, new InstructionBuilderCallback() + { + public void doBuild(InstructionBuilder builder) + { + implementMapConstructor(builder, mapNode); + + builder.returnResult(); + } + }); + } + + private Type implementMapConstructor(InstructionBuilder builder, Tree mapNode) + { + int count = mapNode.getChildCount(); + builder.newInstance(HashMap.class); + builder.dupe().loadConstant(count).invokeConstructor(HashMap.class, int.class); + + for (int i = 0; i < count; i+=2) + { + builder.dupe(); + + //build the key: + Type keyType = implementSubexpression(builder, null, mapNode.getChild(i)); + boxIfPrimitive(builder, GenericsUtils.asClass(keyType)); + + //and the value: + Type valueType = implementSubexpression(builder, null, mapNode.getChild(i+1)); + boxIfPrimitive(builder, GenericsUtils.asClass(valueType)); + + //put the value into the array, then pop off the returned object. + builder.invoke(HashMapMethods.PUT).pop(); + + } + + return HashMap.class; + } + + private void implementNoOpSetter() { implementNoOpMethod(ConduitMethods.SET, "Expression '%s' for class %s is read-only.", expression, Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/app1/LinkQueryParameters.tml URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/app1/LinkQueryParameters.tml?rev=1100036&r1=1100035&r2=1100036&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/test/app1/LinkQueryParameters.tml (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/test/app1/LinkQueryParameters.tml Fri May 6 03:26:02 2011 @@ -13,12 +13,18 @@ <li><a t:type="eventlink" event="parameterCheck">Event Link With No Parameters</a></li> <li><a t:type="eventlink" event="parameterCheck" parameters="emptyParameters">Event Link With Explicitly Empty Parameters</a></li> <li><a t:type="eventlink" event="parameterCheck" parameters="nonEmptyParameters">Event Link With Parameters</a></li> + + <li> + <a t:type="pagelink" page="LinkQueryParameters" parameters="{'parama': 'valuea', 'paramb': 'valueb'}"> + Two Element Map + </a> + </li> </ul> <div id="parametercheck"> <t:if test="hasParameters"> <ul> - <li t:type="loop" source="parameters" value="paramName" class="qparam"> + <li t:type="loop" source="parameters" value="paramName" class="${paramName}"> ${paramName}: ${paramVal} </li> </ul> Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/CoreBehaviorsTests.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/CoreBehaviorsTests.java?rev=1100036&r1=1100035&r2=1100036&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/CoreBehaviorsTests.java (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/CoreBehaviorsTests.java Fri May 6 03:26:02 2011 @@ -467,19 +467,33 @@ public class CoreBehaviorsTests extends openLinkParameterTest(); clickAndWait("link=Page Link With Parameters"); - assertText("xpath=(//li[@class='qparam'])[1]", "param1: value1"); - assertText("xpath=(//li[@class='qparam'])[2]", "param2: 10"); + assertText("css=li.param1", "param1: value1"); + assertText("css=li.param2", "param2: 10"); //re-open between checks to make sure there is no "bleedover" between checks. openLinkParameterTest(); clickAndWait("link=Action Link With Parameters"); - assertText("xpath=(//li[@class='qparam'])[1]", "param1: value1"); - assertText("xpath=(//li[@class='qparam'])[2]", "param2: 10"); + assertText("css=li.param1", "param1: value1"); + assertText("css=li.param2", "param2: 10"); openLinkParameterTest();; clickAndWait("link=Event Link With Parameters"); - assertText("xpath=(//li[@class='qparam'])[1]", "param1: value1"); - assertText("xpath=(//li[@class='qparam'])[2]", "param2: 10"); + assertText("css=li.param1", "param1: value1"); + assertText("css=li.param2", "param2: 10"); + } + + //TAP5-1495 + @Test + public void map_expression_support() + { + openLinkParameterTest(); + + //more extensive testing done in PropertyConduitSourceImplTest, including multiple key and value types, + //access from methods and nested methods, and map as method arguments. + //This just provides an integration sanity check. + clickAndWait("link=Two Element Map"); + assertText("css=li.parama", "parama: valuea"); + assertText("css=li.paramb", "paramb: valueb"); } @Test Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java?rev=1100036&r1=1100035&r2=1100036&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java Fri May 6 03:26:02 2011 @@ -1,4 +1,4 @@ -// Copyright 2006, 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation +// Copyright 2006, 2007, 2008, 2009, 2010 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/EchoBean.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/EchoBean.java?rev=1100036&r1=1100035&r2=1100036&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/EchoBean.java (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/EchoBean.java Fri May 6 03:26:02 2011 @@ -1,4 +1,4 @@ -// Copyright 2008 The Apache Software Foundation +// Copyright 2008, 2011 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ package org.apache.tapestry5.internal.services; import java.util.List; +import java.util.Map; public class EchoBean { @@ -103,4 +104,9 @@ public class EchoBean { return input; } + + public Map echoMap(Map input) + { + return input; + } } Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImplTest.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImplTest.java?rev=1100036&r1=1100035&r2=1100036&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImplTest.java (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImplTest.java Fri May 6 03:26:02 2011 @@ -1,4 +1,4 @@ -// Copyright 2007, 2008, 2009, 2010 The Apache Software Foundation +// Copyright 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.apache.tapestry5.internal.tes import org.apache.tapestry5.internal.util.Holder; import org.apache.tapestry5.internal.util.IntegerRange; import org.apache.tapestry5.ioc.internal.services.ClassFactoryImpl; +import org.apache.tapestry5.ioc.internal.util.CollectionFactory; import org.apache.tapestry5.ioc.services.ClassFab; import org.apache.tapestry5.ioc.services.ClassFactory; import org.apache.tapestry5.services.PropertyConduitSource; @@ -34,6 +35,7 @@ import org.testng.annotations.Test; import java.io.Serializable; import java.util.List; +import java.util.Map; /** * Most of the testing occurs inside {@link PropBindingFactoryTest} (due to @@ -622,6 +624,49 @@ public class PropertyConduitSourceImplTe } @Test + public void top_level_map() + { + PropertyConduit conduit = source.create(EchoBean.class, "{'one': true, 'two': 2.0, stringSource.value: 3, 'four': storedString}"); + EchoBean bean = new EchoBean(); + + bean.setStoredString("four"); + bean.setStringSource(new StringSource("three")); + + Map actual = (Map) conduit.get(bean); + assertEquals(actual.get("one"), true); + assertEquals(actual.get("two"), 2.0); + assertEquals(actual.get("three"), 3L); + assertEquals(actual.get("four"), "four"); + } + + @Test + public void empty_map() + { + PropertyConduit conduit = source.create(EchoBean.class, "{ }"); + EchoBean bean = new EchoBean(); + Map m = (Map) conduit.get(bean); + + assertTrue(m.isEmpty()); + + } + + @Test + public void map_as_method_argument() + { + PropertyConduit conduit = source.create(EchoBean.class, "echoMap({ 1: 'one', 2.0: 'two', storedString: stringSource.value })"); + EchoBean bean = new EchoBean(); + + bean.setStoredString( "3" ); + bean.setStringSource(new StringSource("three")); + + Map m = (Map) conduit.get(bean); + assertEquals("one", m.get(1L)); + assertEquals("two", m.get(2.0)); + assertEquals("three", m.get("3")); + + } + + @Test public void not_operator() { PropertyConduit conduit = source.create(IntegerHolder.class, "! value"); @@ -678,8 +723,10 @@ public class PropertyConduitSourceImplTe } catch (RuntimeException ex) { + //note that addition of map support changed how this expression was parsed such that the error is now + //reported at character 8, (, rather than 0: getValue(. assertEquals(ex.getMessage(), - "Error parsing property expression 'getValue(': line 1:0 no viable alternative at input 'getValue'."); + "Error parsing property expression 'getValue(': line 1:8 no viable alternative at input '('."); } } @@ -688,13 +735,13 @@ public class PropertyConduitSourceImplTe { try { - source.create(IntegerHolder.class, "fred {"); + source.create(IntegerHolder.class, "fred #"); unreachable(); } catch (RuntimeException ex) { assertEquals(ex.getMessage(), - "Error parsing property expression 'fred {': Unable to parse input at character position 6."); + "Error parsing property expression 'fred #': Unable to parse input at character position 6."); } }