You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2008/12/12 02:46:12 UTC

svn commit: r725884 - in /tapestry/tapestry5/trunk: src/site/ src/site/apt/ src/site/apt/guide/ tapestry-core/ tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/ tapestry-core/src/main/java/org/apache/tapestry5/internal/antlr/ tapestry-c...

Author: hlship
Date: Thu Dec 11 17:46:11 2008
New Revision: 725884

URL: http://svn.apache.org/viewvc?rev=725884&view=rev
Log:
TAP5-79: Improve Tapestry's property expression language to include OGNL-like features
- Support for invoking methods with parameters

Added:
    tapestry/tapestry5/trunk/src/site/apt/guide/propexp.apt
    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
      - copied, changed from r723593, tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpression.g
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/antlr/
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/antlr/BaseLexer.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/StringSource.java
Removed:
    tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpression.g
Modified:
    tapestry/tapestry5/trunk/src/site/apt/guide/parameters.apt
    tapestry/tapestry5/trunk/src/site/apt/guide/templates.apt
    tapestry/tapestry5/trunk/src/site/apt/index.apt
    tapestry/tapestry5/trunk/src/site/site.xml
    tapestry/tapestry5/trunk/tapestry-core/pom.xml
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/BasePropertyConduit.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LiteralPropertyConduit.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/PropertyConduitSource.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/services/ServicesStrings.properties
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/bindings/PropBindingFactoryTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImplTest.java
    tapestry/tapestry5/trunk/tapestry-core/tapestry-core.iml
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalUtils.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/MethodSignature.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/InternalUtilsTest.java

Modified: tapestry/tapestry5/trunk/src/site/apt/guide/parameters.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/guide/parameters.apt?rev=725884&r1=725883&r2=725884&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/guide/parameters.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/guide/parameters.apt Thu Dec 11 17:46:11 2008
@@ -127,13 +127,13 @@
 *------------+----------------------------------------------------------------------------------+
 | message    | Retrieves a value from the component's {{{localization.html}message catalog}}.   |
 *------------+----------------------------------------------------------------------------------+
-| prop       | The name of a property of the containing component to read or update.            |
+| prop       | A {{{propexp.html}property expression}} to read or update.                       |
 *------------+----------------------------------------------------------------------------------+
 | translate  | The name of a configured translator.                                             |
 *------------+----------------------------------------------------------------------------------+
 | validate   | A <validator specification> used to create some number of field validators.      |
 *------------+----------------------------------------------------------------------------------+
-| var        | Allows a render variable of the component to be read or updated. |      |
+| var        | Allows a render variable of the component to be read or updated.                 |
 *------------+----------------------------------------------------------------------------------+
 
   Parameters have a default prefix, usually "prop:", that is used when the prefix is not provided.
@@ -177,54 +177,19 @@
 
   Render variable names are case insensitive.
 
-Property Bindings
+Property Expression Bindings
 
-  The "prop:" binding prefix indicates a property binding.
-  
-  The expression for a property binding is a dotted sequence of property names.  Simple
-  property expressions are just the name of a property, "prop:userName".  Complex property
-  expression may do a little navigation before getting to the property to read and/or update:
-  "prop:userData.name".  
-  
-  In addition to property names, you may also invoke arbitrary methods.  The methods must be public,
-  return a non-void value, throw no checked exceptions, and take no parameters.  To differentiate
-  a method name from a property name, you simply append the open and close parenthesis.  Thus
-  the prior examples could be rewritten as "prop:getUserName()" and "prop:getUserData().getName()".
-  Note that when the last term in the expression is a method name, the binding will be read-only,
-  rather than read/write.
-  
-  This feature is most useful for accessing a couple of propertys of standard collection classes
-  that aren't named as proper properties, such as Collection.size(), or Map.keySet().
-      
-  Ever get frustrated, tripping over null pointers inside such an expression?  You may use
-  "?." instead of "." as the separator.  This adds a null check and aborts the expression
-  if the term is null.  Thus "foo?.bar?.baz" will return null if either foo or bar are null.
-  Likewise, updating "foo?.bar?.baz" will turn into a no-op if foo or bar is null.    
-      
-  In addition, a few special cases are also supported. 
-  In most cases, these special values save you the trouble of adding a "literal:" prefix to
-  the value.  These special cases are <alternatives> to property expressions.
-  
-  * "true" and "false" will be converted to booleans. 
-  
-  * "null" will be the  value null. 
-    
-  * "this" will be the component itself.
-  
-  * Simple numeric values are also accepted. These will be parsed into Long or Double objects.
-    Ex: "prop:3.14".
+  The "prop:" binding prefix indicates a property expression binding.
+
+  Property expressions are used to link a parameter of a component to a property
+  of its container. Property expressions
+  can navigate a series of properties and/or invoke methods, as well as several
+  other useful patterns.  Property expressions have
+  {{{propexp.html}their own documentation}}.
+
+  The default binding prefix in most cases is "prop:", which is why it is usually
+  omitted.
     
-  * A range of integers separated by two periods. Ex: "1..10".  
-  
-  * Literal strings, inside single quotes.  Ex: "'Hello World'"
-  
-  []
-  
-  In all these cases, excess whitespace is ignored.  For the keywords ("true", "false", "this" and
-  "null"), case is ignored.
-  
-  Such values are read only and invariant.
-  
 Validate Bindings
 
   The "validate:" binding prefix is highly specialized. It allows a short string to be

Added: tapestry/tapestry5/trunk/src/site/apt/guide/propexp.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/guide/propexp.apt?rev=725884&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/guide/propexp.apt (added)
+++ tapestry/tapestry5/trunk/src/site/apt/guide/propexp.apt Thu Dec 11 17:46:11 2008
@@ -0,0 +1,97 @@
+ ---
+ Property Expressions
+ ---
+
+Property Expressions
+
+  Tapestry uses property expressions to move data between components.  Property expressions
+  are the basis of the {{{parameters.html}component parameters}} and
+  {{{templates.html}template expansions}}.
+
+  A property expression is a string that explains to Tapestry how to navigate from a root object
+  (the containing component) through some number of properties, to a final property that can be
+  read or updated.
+
+  As elsewhere in Tapestry, the names of properties and methods are recognized in a case-insensitive
+  manner.
+
+  The most basic form of a property expression is a simple property name, such as "userName".
+
+  A series of property names may be specified, seperated by commas:  "user.name", which is equivalent
+  to either <<<getUser().getName()>>> or <<<getUser().setName()>>>, depending on context.
+
+  The "." is called the "dereference operator".  A second operator is "?." or the "safe dereference operator".
+  This works the same as "." except that it allows any of the properties to be null.  When reading, a null
+  short-circuits the expression (which returns null).  When writing to a property, an intermediate null value will
+  cause the value to be discarded without error.
+
+  In other words, "user?.name" works, even when the user property may be null.
+
+  Property expressions can also reference public methods.  Methods may take parameters (or not), but must
+  not return void.  When the final term in a property expression is a method, then the property expression
+  is read-only.
+
+  Being able to invoke methods was originally added so that it was possible to access methods such as
+  java.util.Map.size() (which is not named like a property getter method). In Tapestry 5.1, property expressions
+  were extended so that parameters could be passed into methods.
+
+  Parameters to methods are, themselves, property expressions.  This means that you can write a property expression
+  that reads a property and passes it as a parameter to a method, and then access a property of the object returns
+  from the method.
+
+Compilation
+
+  Property expressions are compiled to Java classes at runtime; there is no runtime reflection (unlike
+  the OGNL expression language used in prior releases of Tapestry).
+
+Grammar
+
+---
+expression : keyword | rangeOp | constant | propertyChain;
+
+keyword : 'null' | 'true' | 'false' | 'this';
+
+constant : <integer> | <decimal> | <string>;
+
+rangeOp :  rangeOpArg '..' rangeOpArg;
+
+rangeOpArg : <integer> | propertyChain;
+
+propertyChain : term '.' propertyChain
+              | term '?.' propertyChain
+              | term;
+
+term :   <identifier>
+     |   <identifier> '(' ')'
+     |   <identifier> '(' expression (',' expression )* ')';
+
+     
+---
+
+  Notes:
+
+  * Whitespace is ignored.
+  
+  * Integers and decimals may have a leading sign ('+' or '-').
+
+  * Constants are in base 10 (sorry, octal and hex not yet supported). Decimals may contain a decimal point
+    (exponent notation not yet supported).
+
+  * Literal strings are enclosed in single quotes.
+
+  * The rangeOp creates a range object that will iterate between the two values. The upper and lower bounds
+    may be literal integers, or property expressions.
+
+  * An identifier by itself is a property name. An identifier with parenthesis is a method invocation.
+
+  * Property names, method names, and literals are case-insensitive.
+  
+  * 'this' is the root object (i.e., the containing component).
+
+  []
+
+  Method matching is based on method name and number of parameters, but not parameter types.  The
+  {{{coercion.html}TypeCoercer}} service is used to convert parameters to the correct type to
+  be passed into the method.
+
+  
\ No newline at end of file

Modified: tapestry/tapestry5/trunk/src/site/apt/guide/templates.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/guide/templates.apt?rev=725884&r1=725883&r2=725884&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/guide/templates.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/guide/templates.apt Thu Dec 11 17:46:11 2008
@@ -236,7 +236,7 @@
   
   Under the covers, expansions are the same as
   {{{parameters.html}parameter bindings}}.  The default
-  binding prefix for expansions is "prop:" (that is, the name of a property), but
+  binding prefix for expansions is "prop:" (that is, the name of a property or a {{{propexp.html}property expression}}), but
   other binding prefixes are useful, especially "message:" (to access a localized
   message from the component's message catalog).
   

Modified: tapestry/tapestry5/trunk/src/site/apt/index.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/index.apt?rev=725884&r1=725883&r2=725884&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/index.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/index.apt Thu Dec 11 17:46:11 2008
@@ -77,6 +77,8 @@
 
 New And Of Note
 
+  * Property expressions can now invoke methods with parameters.Á
+
   * IoC Service contributions may now be made in terms of classes (that are automatically instantiated) as well as
     instances.
 

Modified: tapestry/tapestry5/trunk/src/site/site.xml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/site.xml?rev=725884&r1=725883&r2=725884&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/site.xml (original)
+++ tapestry/tapestry5/trunk/src/site/site.xml Thu Dec 11 17:46:11 2008
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="ISO-8859-1"?>
 <!-- 
-   Copyright 2007 The Apache Software Foundation
+   Copyright 2007, 2008 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.
@@ -96,6 +96,7 @@
             <item name="Page Lifecycle" href="guide/lifecycle.html"/>
             <item name="Page Navigation" href="guide/pagenav.html"/>
             <item name="Persistent Data" href="guide/persist.html"/>
+            <item name="Property Expressions" href="guide/propexp.html"/>
             <item name="Project Layout" href="guide/project-layout.html"/>
             <item name="Request Processing" href="guide/request.html"/>
             <item name="Service Status" href="guide/servicestatus.html"/>

Modified: tapestry/tapestry5/trunk/tapestry-core/pom.xml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/pom.xml?rev=725884&r1=725883&r2=725884&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/pom.xml (original)
+++ tapestry/tapestry5/trunk/tapestry-core/pom.xml Thu Dec 11 17:46:11 2008
@@ -66,6 +66,12 @@
                         <goals>
                             <goal>antlr</goal>
                         </goals>
+                        <configuration>
+                            <!-- This is a hack so that the parser grammar can locate the tokens file generated
+                                 by the lexer grammar. -->
+                            <libDirectory>target/generated-sources/antlr/org/apache/tapestry5/internal/antlr
+                            </libDirectory>
+                        </configuration>
                     </execution>
                 </executions>
             </plugin>

Added: 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=725884&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpressionLexer.g (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpressionLexer.g Thu Dec 11 17:46:11 2008
@@ -0,0 +1,132 @@
+// Copyright 2008 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.
+// 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.
+
+lexer grammar PropertyExpressionLexer;
+
+
+options
+{
+  superClass='org.apache.tapestry5.internal.antlr.BaseLexer';
+}
+
+@header
+{
+package org.apache.tapestry5.internal.antlr;
+}
+
+	
+// Integer constant
+fragment INTEGER
+	:	;	
+	
+// Read a property or invoke a method.
+fragment DEREF 
+	:	;
+	
+// Range operator, ".." between two integers.	
+fragment RANGEOP
+	:	;
+	
+// Decimal number	
+fragment DECIMAL
+	:	;
+			
+fragment LETTER 
+	:	('a'..'z'|'A'..'Z');
+fragment DIGIT 
+	:	'0'..'9';	
+fragment SIGN
+	:	('+'|'-');
+LPAREN 	:	'(';
+RPAREN 	:	')';
+COMMA	:	',';
+
+fragment QUOTE
+	:	'\'';
+
+// Clumsy but effective approach to case-insensitive identifiers.
+
+fragment A
+	:	('a' | 'A');
+fragment E
+	:	('e' | 'E');
+fragment F
+	:	('f' | 'F');	
+fragment H
+	:	('h' | 'H');
+fragment I
+	:	('i' | 'I');
+fragment L 
+	: 	('l' | 'L');
+fragment N 
+	:	('n'|'N');
+fragment R
+	:	('r' | 'R');
+fragment S
+	:	('s' | 'S');
+fragment T 
+	:	('t' | 'T');
+fragment U 
+	:	('u' | 'U');
+
+// Identifiers are case insensitive
+
+NULL 	:	N U L L;
+TRUE	:	T R U E;
+FALSE	:	F A L S E;
+THIS	:	T H I S;
+
+IDENTIFIER 
+	:	LETTER (LETTER | DIGIT | '_')+;
+
+// The Safe Dereference operator understands not to de-reference through
+// a null.
+
+SAFEDEREF 
+	: 	'?.';
+
+WS 	:	(' '|'\t'|'\n'|'\r')+ { skip(); };
+
+
+// Literal strings are always inside single quotes.
+STRING
+	:	QUOTE (options {greedy=false;} : .)* QUOTE { setText(getText().substring(1, getText().length()-1)); };
+
+
+// Special rule that uses parsing tricks to identify numbers and ranges; it's all about
+// the dot ('.').
+// Recognizes:
+// '.' as DEREF
+// '..' as RANGEOP
+// INTEGER (sign? digit+)
+// DECIMAL (sign? digits* . digits+)
+// Has to watch out for embedded rangeop (i.e. "1..10" is not "1." and ".10").
+
+NUMBER_OR_RANGEOP
+	:	SIGN? DIGIT+
+		(
+			{ input.LA(2) != '.' }? => '.' DIGIT* {   $type = DECIMAL; stripLeadingPlus(); }
+			| {  $type = INTEGER;  stripLeadingPlus(); }
+		)
+		
+	|	SIGN '.' DIGIT+ {  $type = DECIMAL;  stripLeadingPlus(); }
+	
+	|	'.'
+		( 
+			DIGIT+ { $type = DECIMAL; stripLeadingPlus();}
+			| '.' {$type = RANGEOP; }
+			| {$type = DEREF; }
+		)
+	;	
+

Copied: tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpressionParser.g (from r723593, tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpression.g)
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpressionParser.g?p2=tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpressionParser.g&p1=tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpression.g&r1=723593&r2=725884&rev=725884&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpression.g (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/antlr/org/apache/tapestry5/internal/antlr/PropertyExpressionParser.g Thu Dec 11 17:46:11 2008
@@ -12,13 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-grammar PropertyExpression;
+parser grammar PropertyExpressionParser;
 
 
 options
 {
   output=AST;		
   ASTLabelType=CommonTree;
+  tokenVocab=PropertyExpressionLexer;
+  backtrack=true;
 }
 
 tokens
@@ -32,127 +34,43 @@
 package org.apache.tapestry5.internal.antlr;
 }
 
-@lexer::header
-{
-package org.apache.tapestry5.internal.antlr;
-}
 	
 start 	:	expression^ EOF!;
 		
+expression
+	:	keyword
+	|	rangeOp
+	|	constant
+	|	propertyChain
+	;
+	
+keyword	:	NULL | TRUE | FALSE | THIS;
 
-// Integer constant
-fragment INTEGER
-	:	;	
-	
-// Read a property or invoke a method.
-fragment DEREF 
-	:	;
-	
-// Range operator, ".." between two integers.	
-fragment RANGEOP
-	:	;
-	
-// Decimal number	
-fragment DECIMAL
-	:	;
-			
-fragment LETTER 
-	:	('a'..'z'|'A'..'Z');
-fragment DIGIT 
-	:	'0'..'9';	
-fragment SIGN
-	:	('+'|'-');
-LPAREN 	:	'(';
-RPAREN 	:	')';
-
-fragment QUOTE
-	:	'\'';
-
-// Clumsy but effective approach to case-insensitive identifiers.
-
-fragment A
-	:	('a' | 'A');
-fragment E
-	:	('e' | 'E');
-fragment F
-	:	('f' | 'F');	
-fragment H
-	:	('h' | 'H');
-fragment I
-	:	('i' | 'I');
-fragment L 
-	: 	('l' | 'L');
-fragment N 
-	:	('n'|'N');
-fragment R
-	:	('r' | 'R');
-fragment S
-	:	('s' | 'S');
-fragment T 
-	:	('t' | 'T');
-fragment U 
-	:	('u' | 'U');
-
-// Identifiers are case insensitive
-
-NULL 	:	N U L L;
-TRUE	:	T R U E;
-FALSE	:	F A L S E;
-THIS	:	T H I S;
-
-IDENTIFIER 
-	:	LETTER (LETTER | DIGIT | '_')+;
-
-// The Safe Dereference operator understands not to de-reference through
-// a null.
-
-SAFEDEREF 
-	: 	'?.';
-
-WS 	:	(' '|'\t'|'\n'|'\r')+ { skip(); };
-
-
-// Literal strings are always inside single quotes.
-STRING
-	:	QUOTE (options {greedy=false;} : .)* QUOTE { setText(getText().substring(1, getText().length()-1)); };
-
-
-// Special rule that uses parsing tricks to identify numbers and ranges; it's all about
-// the dot ('.').
-// Recognizes:
-// '.' as DEREF
-// '..' as RANGEOP
-// INTEGER (sign? digit+)
-// DECIMAL (sign? digits* . digits+)
-// Has to watch out for embedded rangeop (i.e. "1..10" is not "1." and ".10").
-
-NUMBER_OR_RANGEOP
-	:	SIGN? DIGIT+
-		(
-			{ input.LA(2) != '.' }? => '.' DIGIT* { $type = DECIMAL; }
-			| { $type = INTEGER; }
-		)
-		
-	|	SIGN '.' DIGIT+ { $type = DECIMAL; }
+constant:	INTEGER| DECIMAL | STRING;	
 	
-	|	'.'
-		( 
-			DIGIT+ { $type = DECIMAL; }
-			| '.' {$type = RANGEOP; }
-			| {$type = DEREF; }
-		)
+propertyChain
+	:	term DEREF propertyChain -> ^(DEREF term propertyChain)
+	|	term SAFEDEREF propertyChain -> ^(SAFEDEREF term propertyChain)
+	|	term
 	;	
-
-expression
-	:	term DEREF expression -> ^(DEREF term expression)
-	|	term SAFEDEREF expression -> ^(SAFEDEREF term expression)
-	|	term;
-	
-term	:	(NULL | TRUE | FALSE | THIS)
-	|	from=INTEGER  RANGEOP to=INTEGER -> ^(RANGEOP $from $to)
-	|	INTEGER
-	|	DECIMAL 
-	|	IDENTIFIER
-	|	STRING
-	|	id=IDENTIFIER LPAREN RPAREN -> ^(INVOKE $id)
+	
+term	:	IDENTIFIER
+	|	methodInvocation
 	;
+	
+methodInvocation
+	:	id=IDENTIFIER LPAREN RPAREN -> ^(INVOKE $id)
+	|	id=IDENTIFIER LPAREN expressionList RPAREN -> ^(INVOKE $id expressionList)
+	;	
+	
+expressionList
+	:	expression (COMMA! expression)*
+	;	
+	
+rangeOp
+	:	from=rangeopArg  RANGEOP to=rangeopArg -> ^(RANGEOP $from $to)
+	;	
+	
+rangeopArg 
+	:	INTEGER
+	|	propertyChain;	

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/antlr/BaseLexer.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/antlr/BaseLexer.java?rev=725884&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/antlr/BaseLexer.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/antlr/BaseLexer.java Thu Dec 11 17:46:11 2008
@@ -0,0 +1,45 @@
+// Copyright 2008 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.
+// 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.tapestry5.internal.antlr;
+
+import org.antlr.runtime.CharStream;
+import org.antlr.runtime.Lexer;
+import org.antlr.runtime.RecognizerSharedState;
+
+public abstract class BaseLexer extends Lexer
+{
+    protected BaseLexer()
+    {
+    }
+
+    protected BaseLexer(CharStream charStream,
+                        RecognizerSharedState recognizerSharedState)
+    {
+        super(charStream, recognizerSharedState);
+    }
+
+    protected void stripLeadingPlus()
+    {
+        String text = getText();
+
+        // For compatibility with Tapestry 5.0, we need to allow a sign of '+', which Long.parseLong()
+        // doesn't accept. To keep things downstream simple, we eliminate the '+' here.
+
+        if (text.startsWith("+"))
+        {
+            setText(text.substring(1));
+        }
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/BasePropertyConduit.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/BasePropertyConduit.java?rev=725884&r1=725883&r2=725884&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/BasePropertyConduit.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/BasePropertyConduit.java Thu Dec 11 17:46:11 2008
@@ -17,6 +17,7 @@
 import org.apache.tapestry5.PropertyConduit;
 import org.apache.tapestry5.ioc.AnnotationProvider;
 import org.apache.tapestry5.ioc.internal.util.Defense;
+import org.apache.tapestry5.ioc.services.TypeCoercer;
 
 import java.lang.annotation.Annotation;
 
@@ -32,30 +33,45 @@
 
     private final String description;
 
-    public BasePropertyConduit(Class propertyType, AnnotationProvider annotationProvider, String description)
+    private final TypeCoercer typeCoercer;
+
+    public BasePropertyConduit(Class propertyType, AnnotationProvider annotationProvider, String description,
+                               TypeCoercer typeCoercer)
     {
         Defense.notNull(propertyType, "propertyType");
         Defense.notNull(annotationProvider, "annotationProvider");
         Defense.notBlank(description, "description");
+        Defense.notNull(typeCoercer, "typeCoercer");
 
         this.propertyType = propertyType;
         this.annotationProvider = annotationProvider;
         this.description = description;
+        this.typeCoercer = typeCoercer;
     }
 
     @Override
-    public String toString()
+    public final String toString()
     {
         return description;
     }
 
-    public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
+    public final <T extends Annotation> T getAnnotation(Class<T> annotationClass)
     {
         return annotationProvider.getAnnotation(annotationClass);
     }
 
-    public Class getPropertyType()
+    public final Class getPropertyType()
     {
         return propertyType;
     }
+
+    protected final int toInt(Object value)
+    {
+        return coerce(value, int.class).intValue();
+    }
+
+    protected final <T> T coerce(Object value, Class<T> type)
+    {
+        return typeCoercer.coerce(value, type);
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LiteralPropertyConduit.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LiteralPropertyConduit.java?rev=725884&r1=725883&r2=725884&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LiteralPropertyConduit.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LiteralPropertyConduit.java Thu Dec 11 17:46:11 2008
@@ -15,6 +15,7 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.ioc.AnnotationProvider;
+import org.apache.tapestry5.ioc.services.TypeCoercer;
 
 /**
  * A PropertyConduit for a literal value in an expression, such as a number, or "true", "false" or "null".
@@ -24,9 +25,10 @@
     private final Object value;
 
     public LiteralPropertyConduit(Class propertyType, AnnotationProvider annotationProvider, String description,
+                                  TypeCoercer typeCoercer,
                                   Object value)
     {
-        super(propertyType, annotationProvider, description);
+        super(propertyType, annotationProvider, description, typeCoercer);
 
         this.value = value;
     }

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=725884&r1=725883&r2=725884&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 Thu Dec 11 17:46:11 2008
@@ -25,6 +25,7 @@
 import org.apache.tapestry5.internal.util.IntegerRange;
 import org.apache.tapestry5.internal.util.MultiKey;
 import org.apache.tapestry5.ioc.AnnotationProvider;
+import org.apache.tapestry5.ioc.internal.NullAnnotationProvider;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.Defense;
 import org.apache.tapestry5.ioc.internal.util.GenericsUtils;
@@ -53,6 +54,39 @@
                                                                              new Class[] {Object.class, Object.class},
                                                                              null);
 
+    private final AnnotationProvider nullAnnotationProvider = new NullAnnotationProvider();
+
+    private static class ConstructorParameter
+    {
+        private final String fieldName;
+
+        private final Class type;
+
+        private final Object value;
+
+        ConstructorParameter(String fieldName, Class type, Object value)
+        {
+            this.fieldName = fieldName;
+            this.type = type;
+            this.value = value;
+        }
+
+        public String getFieldName()
+        {
+            return fieldName;
+        }
+
+        public Class getType()
+        {
+            return type;
+        }
+
+        public Object getValue()
+        {
+            return value;
+        }
+    }
+
     /**
      * Describes all the gory details of one term (one property or method invocation) from within the expression.
      */
@@ -60,15 +94,15 @@
     {
 
         /**
-         * The name of the method to invoke to read the property value, or null.
+         * The method to invoke to read the property value, or null.
          */
-        String getReadMethodName();
+        Method getReadMethod();
 
         /**
-         * The name of the method to invoke to write the property value, or null. Always null for method terms (which
-         * are inherently read-only).
+         * The method to invoke to write the property value, or null. Always null for method terms (which are inherently
+         * read-only).
          */
-        String getWriteMethodName();
+        Method getWriteMethod();
 
         /**
          * The return type of the method, or the type of the property.
@@ -86,10 +120,41 @@
         String getDescription();
     }
 
+    private class GeneratedTerm
+    {
+        private final Class type;
+
+        private final String variableName;
+
+        private GeneratedTerm(Class type, String variableName)
+        {
+            this.type = type;
+            this.variableName = variableName;
+        }
+
+        /**
+         * The name of the variable that contains the evaluation of the term.
+         */
+        String getVariableName()
+        {
+            return variableName;
+        }
+
+        /**
+         * The type of the variable (used to determine what methods/properties may be dereferenced).
+         */
+        Class getType()
+        {
+            return type;
+        }
+    }
+
     private final PropertyAccess access;
 
     private final ClassFactory classFactory;
 
+    private final TypeCoercer typeCoercer;
+
     /**
      * Because of stuff like Hibernate, we sometimes start with a subclass in some inaccessible class loader and need to
      * work up to a base class from a common class loader.
@@ -120,11 +185,11 @@
         }
     };
 
-    private final PropertyConduit literalTrue = newLiteralConduit(Boolean.class, true);
+    private final PropertyConduit literalTrue;
 
-    private final PropertyConduit literalFalse = newLiteralConduit(Boolean.class, false);
+    private final PropertyConduit literalFalse;
 
-    private final PropertyConduit literalNull = newLiteralConduit(Void.class, null);
+    private final PropertyConduit literalNull;
 
 
     /**
@@ -132,7 +197,7 @@
      */
     class PropertyConduitBuilder
     {
-        private final Class rootClass;
+        private final Class rootType;
 
         private final ClassFab classFab;
 
@@ -142,11 +207,19 @@
 
         private Class conduitPropertyType;
 
-        private AnnotationProvider annotationProvider;
+        private AnnotationProvider annotationProvider = nullAnnotationProvider;
+
+        // Used to create unique variable names.
+
+        private int variableIndex = 0;
 
-        PropertyConduitBuilder(Class rootClass, String expression, Tree tree)
+        private final List<ConstructorParameter> parameters = CollectionFactory.newList();
+
+        private final BodyBuilder navBuilder = new BodyBuilder();
+
+        PropertyConduitBuilder(Class rootType, String expression, Tree tree)
         {
-            this.rootClass = rootClass;
+            this.rootType = rootType;
             this.expression = expression;
             this.tree = tree;
 
@@ -159,18 +232,13 @@
         {
             createAccessors();
 
-            classFab.addConstructor(new Class[] {Class.class, AnnotationProvider.class, String.class}, null,
-                                    "super($$);");
-
-            String description = String.format("PropertyConduit[%s %s]", rootClass.getName(), expression);
+            Object[] parameters = createConstructor();
 
             Class conduitClass = classFab.createClass();
 
             try
             {
-                return (PropertyConduit) conduitClass.getConstructors()[0].newInstance(conduitPropertyType,
-                                                                                       annotationProvider,
-                                                                                       description);
+                return (PropertyConduit) conduitClass.getConstructors()[0].newInstance(parameters);
             }
             catch (Exception ex)
             {
@@ -178,6 +246,63 @@
             }
         }
 
+        private Object[] createConstructor()
+        {
+            List<Class> types = CollectionFactory.newList();
+
+            // $1, $2, $3, $4 ...
+
+            types.add(Class.class);
+            types.add(AnnotationProvider.class);
+            types.add(String.class);
+            types.add(TypeCoercer.class);
+
+            List<Object> values = CollectionFactory.newList();
+
+            values.add(conduitPropertyType);
+            values.add(annotationProvider);
+            values.add(String.format("PropertyConduit[%s %s]", rootType.getName(), expression));
+            values.add(typeCoercer);
+
+            BodyBuilder builder = new BodyBuilder().begin();
+
+            builder.addln("super($1,$2,$3,$4);");
+
+            int index = 5;
+
+            for (ConstructorParameter p : parameters)
+            {
+                types.add(p.getType());
+                values.add(p.getValue());
+
+                builder.addln("%s = $%d;", p.getFieldName(), index++);
+            }
+
+
+            builder.end();
+
+            Class[] arrayOfTypes = types.toArray(new Class[0]);
+
+
+            classFab.addConstructor(arrayOfTypes, null, builder.toString());
+
+            return values.toArray();
+        }
+
+        String addInjection(Class fieldType, Object fieldValue)
+        {
+            String fieldName =
+                    String.format("injected_%s_%d",
+                                  toSimpleName(fieldType),
+                                  parameters.size());
+
+            classFab.addField(fieldName, Modifier.PRIVATE | Modifier.FINAL, fieldType);
+
+            parameters.add(new ConstructorParameter(fieldName, fieldType, fieldValue));
+
+            return fieldName;
+        }
+
 
         private void createNoOp(ClassFab classFab, MethodSignature signature, String format, Object... values)
         {
@@ -190,84 +315,228 @@
 
         private boolean isLeaf(Tree node)
         {
-            return node.getType() == IDENTIFIER || node.getType() == INVOKE;
+            int type = node.getType();
+
+            return type == IDENTIFIER || type == INVOKE || type == RANGEOP;
         }
 
-        private void createAccessors()
+        private void createGetRoot()
         {
             BodyBuilder builder = new BodyBuilder().begin();
 
-            builder.addln("%s root = (%<s) $1;", ClassFabUtils.toJavaClassName(rootClass));
+            builder.addln("%s root = (%<s) $1;", ClassFabUtils.toJavaClassName(rootType));
 
             builder.addln(
                     "if (root == null) throw new NullPointerException(\"Root object of property expression '%s' is null.\");",
                     expression);
 
-            String previousVariableName = "root";
-            Class activeType = rootClass;
+            builder.addln("return root;");
+
+            builder.end();
+
+            MethodSignature sig = new MethodSignature(rootType, "getRoot", new Class[] {Object.class}, null);
+
+            classFab.addMethod(Modifier.PRIVATE, sig, builder.toString());
+        }
+
+        private void addRootVariable(BodyBuilder builder)
+        {
+            builder.addln("%s root = getRoot($1);",
+                          ClassFabUtils.toJavaClassName(rootType));
+        }
+
+        private void createAccessors()
+        {
+            createGetRoot();
 
-            int step = 0;
+            navBuilder.begin();
+
+            String previousVariableName = "$1";
+            Class activeType = rootType;
 
             Tree node = tree;
 
             while (!isLeaf(node))
             {
-                assertNodeType(node, DEREF, SAFEDEREF);
+                GeneratedTerm term = processDerefNode(navBuilder, activeType, node, previousVariableName);
+
+                activeType = term.getType();
+
+                previousVariableName = term.getVariableName();
 
-                step++;
+                // Second term is the continuation, possibly another chained DEREF, etc.
+                node = node.getChild(1);
+            }
 
-                String variableName = "step" + step;
+            navBuilder.addln("return %s;", previousVariableName);
 
-                activeType = addDereference(builder, activeType, node, previousVariableName, variableName);
+            navBuilder.end();
 
-                previousVariableName = variableName;
+            MethodSignature sig = new MethodSignature(activeType, "navigate", new Class[] {rootType},
+                                                      null);
 
-                // Second term is the continuation, possibly another chained DEREF, etc.,
-                // or at the end of the expression, an IDENTIFIER or INVOKE
-                node = node.getChild(1);
+            classFab.addMethod(Modifier.PRIVATE, sig, navBuilder.toString());
+
+
+            createGetterAndSetter(activeType, sig, node);
+        }
+
+        private void createGetterAndSetter(Class activeType, MethodSignature navigateMethod, Tree node)
+        {
+            switch (node.getType())
+            {
+                case IDENTIFIER:
+                case INVOKE:
+
+                    // So, a this point, we have the navigation method written and it covers all but the terminal
+                    // de-reference.  node is an IDENTIFIER or INVOKE. We're ready to use the navigation
+                    // method to implement get() and set().
+
+                    ExpressionTermInfo info = infoForPropertyOrMethod(activeType, node);
+
+                    createSetter(navigateMethod, info);
+                    createGetter(navigateMethod, node, info);
+
+                    conduitPropertyType = info.getType();
+                    annotationProvider = info;
+
+                    return;
+
+                case RANGEOP:
+
+                    // As currently implemented, RANGEOP can only appear as the top level, which
+                    // means we didn't need the navigate method after all.
+
+                    createRangeOpGetter(navigateMethod, node);
+                    createNoOpSetter();
+
+                    conduitPropertyType = IntegerRange.class;
+
+                    return;
+
+
+                default:
+                    throw unexpectedNodeType(node, IDENTIFIER, INVOKE, RANGEOP);
             }
+        }
+
+        private void createRangeOpGetter(MethodSignature navigateMethod, Tree node)
+        {
+            BodyBuilder builder = new BodyBuilder().begin();
+
+            addRootVariable(builder);
 
-            assertNodeType(node, IDENTIFIER, INVOKE);
+            String fromVar = subexpression(builder, node.getChild(0)).getVariableName();
+            String toVar = subexpression(builder, node.getChild(1)).getVariableName();
 
-            builder.addln("return %s;", previousVariableName);
+            builder.addln("return new %s(toInt(%s), toInt(%s));", IntegerRange.class.getName(), fromVar, toVar);
 
             builder.end();
 
-            MethodSignature sig = new MethodSignature(activeType, "navigate", new Class[] {Object.class},
-                                                      null);
+            classFab.addMethod(Modifier.PUBLIC, GET_SIGNATURE, builder.toString());
+        }
 
-            classFab.addMethod(Modifier.PRIVATE, sig, builder.toString());
 
-            // So, a this point, we have the navigation method written and it covers all but the terminal
-            // de-reference.  node is an IDENTIFIER or INVOKE. We're ready to use the navigation
-            // method to implement get() and set().
+        /**
+         * Evalutates the node as a sub expression, storing the result into a new variable, whose name is returned.
+         *
+         * @param builder to receive generated code
+         * @param node    root of tree of nodes to be evaluated
+         * @return name of variable containing expression
+         */
+        private GeneratedTerm subexpression(BodyBuilder builder, Tree node)
+        {
+            String previousVariableName = "root";
+            Class activeType = rootType;
+
+            while (node != null)
+            {
+                switch (node.getType())
+                {
+                    case INTEGER:
+
+                        Long integerValue = new Long(node.getText());
+
+                        previousVariableName = addInjection(Long.class, integerValue);
+                        activeType = Long.class;
+
+                        node = null;
+
+                        break;
+
+                    case DECIMAL:
+
+                        Double decimalValue = new Double(node.getText());
+                        previousVariableName = addInjection(Double.class, decimalValue);
+                        activeType = Double.class;
+
+                        node = null;
+
+                        break;
 
-            ExpressionTermInfo info = infoForParseNode(activeType, node);
+                    case STRING:
 
-            createSetter(sig, info);
-            createGetter(sig, info);
+                        String stringValue = node.getText();
+                        previousVariableName = addInjection(String.class, stringValue);
+                        activeType = String.class;
 
-            conduitPropertyType = info.getType();
-            annotationProvider = info;
+                        node = null;
+
+                        break;
+
+                    case DEREF:
+                    case SAFEDEREF:
+
+                        GeneratedTerm generated = processDerefNode(builder, activeType, node, previousVariableName);
+
+                        previousVariableName = generated.getVariableName();
+                        activeType = generated.getType();
+
+                        node = node.getChild(1);
+
+                        break;
+
+                    case IDENTIFIER:
+                    case INVOKE:
+
+                        generated = addAccessForPropertyOrMethod(builder, activeType, node, previousVariableName, true);
+
+                        previousVariableName = generated.getVariableName();
+                        activeType = generated.getType();
+
+                        node = null;
+
+                        break;
+
+                    default:
+                        throw unexpectedNodeType(node, INTEGER, DECIMAL, STRING, DEREF, SAFEDEREF, IDENTIFIER, INVOKE);
+                }
+            }
+
+            return new GeneratedTerm(activeType, previousVariableName);
         }
 
+
         private void createSetter(MethodSignature navigateMethod,
                                   ExpressionTermInfo info)
         {
-            String methodName = info.getWriteMethodName();
+            // A write method will only be identified if the info is a writable property.
+            // Other alternatives: a method as the final term, or a read-only property.
 
-            if (methodName == null)
+            Method method = info.getWriteMethod();
+
+            if (method == null)
             {
-                createNoOp(classFab, SET_SIGNATURE, "Expression '%s' for class %s is read-only.", expression,
-                           rootClass.getName());
+                createNoOpSetter();
                 return;
             }
 
             BodyBuilder builder = new BodyBuilder().begin();
 
-            builder.addln("%s target = %s($1);",
-                          ClassFabUtils.toJavaClassName(navigateMethod.getReturnType()),
-                          navigateMethod.getName());
+            addRootVariable(builder);
+
+            builder.addln("%s target = navigate(root);",
+                          ClassFabUtils.toJavaClassName(navigateMethod.getReturnType()));
 
             // I.e. due to ?. operator. The navigate method will already have checked for nulls
             // if they are not allowed.
@@ -276,81 +545,163 @@
 
             String propertyTypeName = ClassFabUtils.toJavaClassName(info.getType());
 
-            builder.addln("target.%s(%s);", methodName, ClassFabUtils.castReference("$2", propertyTypeName));
+            builder.addln("target.%s(%s);", method.getName(), ClassFabUtils.castReference("$2", propertyTypeName));
 
             builder.end();
 
             classFab.addMethod(Modifier.PUBLIC, SET_SIGNATURE, builder.toString());
         }
 
+        private void createNoOpSetter()
+        {
+            createNoOp(classFab, SET_SIGNATURE, "Expression '%s' for class %s is read-only.", expression,
+                       rootType.getName());
+        }
+
         private void createGetter(MethodSignature navigateMethod,
+                                  Tree node,
                                   ExpressionTermInfo info)
         {
-            String methodName = info.getReadMethodName();
+            Method method = info.getReadMethod();
 
-            if (methodName == null)
+            if (method == null)
             {
                 createNoOp(classFab, GET_SIGNATURE, "Expression %s for class %s is write-only.", expression,
-                           rootClass.getName());
+                           rootType.getName());
                 return;
             }
 
             BodyBuilder builder = new BodyBuilder().begin();
 
-            builder.addln("%s target = %s($1);", ClassFabUtils.toJavaClassName(navigateMethod.getReturnType()),
-                          navigateMethod.getName());
+            addRootVariable(builder);
+
+            builder.addln("%s target = navigate(root);", ClassFabUtils.toJavaClassName(navigateMethod.getReturnType()));
 
             // I.e. due to ?. operator. The navigate method will already have checked for nulls
             // if they are not allowed.
 
             builder.addln("if (target == null) return null;");
 
-            builder.addln("return ($w) target.%s();", methodName);
+            builder.addln("return ($w) target.%s;", createMethodInvocation(builder, node, method));
 
             builder.end();
 
             classFab.addMethod(Modifier.PUBLIC, GET_SIGNATURE, builder.toString());
         }
 
+        private String createMethodInvocation(BodyBuilder bodyBuilder, Tree methodInvocation, Method method)
+        {
+            Class[] parameterTypes = method.getParameterTypes();
+
+            StringBuilder builder = new StringBuilder();
+
+            builder.append(method.getName());
+            builder.append("(");
+
+            for (int i = 0; i < parameterTypes.length; i++)
+            {
+                // child(0) is the method name, child(1) is the first parameter, etc.
+
+                GeneratedTerm generatedTerm = subexpression(bodyBuilder, methodInvocation.getChild(i + 1));
+                String variableName = generatedTerm.getVariableName();
+
+                Class actualType = generatedTerm.getType();
+
+                Class parameterType = parameterTypes[i];
+
+                if (!parameterType.isAssignableFrom(actualType))
+                {
+                    String coerced = nextVariableName(parameterType);
+
+                    String call = String.format("coerce(($w) %s, %s)", variableName,
+                                                addInjection(Class.class, parameterType));
+
+                    String parameterTypeName = ClassFabUtils.toJavaClassName(parameterType);
+
+                    bodyBuilder.addln("%s %s = %s;",
+                                      parameterTypeName, coerced, ClassFabUtils.castReference(call, parameterTypeName));
+
+                    variableName = coerced;
+                }
+
+                // TODO: Casting, coercing, unwrapping primitives.
+
+                if (i > 0) builder.append(", ");
+
+                builder.append(variableName);
+            }
+
+            return builder.append(")").toString();
+        }
+
+
         /**
-         * Part of building the navigation method, adds a de-reference (the '.' or '?.' operator).
-         *
-         * @param builder              recieves the code for this step in the expression
-         * @param activeType           the current type of the expression (this changes with each de-reference, to the
-         *                             type just de-referenced)
-         * @param node                 the DEREF or SAFEDEREF node
-         * @param previousVariableName name of local variable holding previous step in expression
-         * @param variableName         name of variable to be assigned with this step in expression
+         * Extends the navigate method for a node, which will be a DEREF or SAFEDERF.
          */
-        private Class addDereference(BodyBuilder builder, Class activeType, Tree node, String previousVariableName,
-                                     String variableName)
-        {// The first child is the term.
+        private GeneratedTerm processDerefNode(BodyBuilder builder, Class activeType, Tree node,
+                                               String previousVariableName
+        )
+        {
+            // The first child is the term.
 
             Tree term = node.getChild(0);
 
+            boolean allowNull = node.getType() == SAFEDEREF;
+
+
+            // Returns the type of the method/property ... this is the wrapped (i.e. java.lang.Integer) type if
+            // the real type is primitive. It also reflects generics information that may have been associated
+            // with the underlying method.
+
+
+            return addAccessForPropertyOrMethod(builder, activeType, term, previousVariableName,
+                                                allowNull);
+        }
+
+        private String nextVariableName(Class type)
+        {
+            return String.format("var_%s_%d",
+                                 toSimpleName(type), variableIndex++);
+        }
+
+
+        private String toSimpleName(Class type)
+        {
+            // TODO: handle arrays types
+            return InternalUtils.lastTerm(type.getName());
+        }
+
+        private GeneratedTerm addAccessForPropertyOrMethod(BodyBuilder builder, Class activeType, Tree term,
+                                                           String previousVariableName,
+                                                           boolean allowNull)
+        {
             assertNodeType(term, IDENTIFIER, INVOKE);
 
             // Get info about this property or method.
 
-            ExpressionTermInfo info = infoForParseNode(activeType, term);
+            ExpressionTermInfo info = infoForPropertyOrMethod(activeType, term);
 
-            String methodName = info.getReadMethodName();
+            Method method = info.getReadMethod();
 
-            if (methodName == null)
+            if (method == null)
                 throw new PropertyExpressionException(
-                        ServicesMessages.writeOnlyProperty(info.getDescription(), activeType, expression), expression,
+                        ServicesMessages.writeOnlyProperty(info.getDescription(), activeType, expression),
+                        expression,
                         null);
 
 
-            boolean nullable = node.getType() == SAFEDEREF;
-
             // If a primitive type, convert to wrapper type
 
             Class termType = info.getType();
-            Class wrappedType = ClassFabUtils.getWrapperType(termType);
+            final Class wrappedType = ClassFabUtils.getWrapperType(termType);
+
+            String wrapperTypeName = ClassFabUtils.toJavaClassName(wrappedType);
+
+            final String variableName = nextVariableName(wrappedType);
+
+            String invocation = createMethodInvocation(builder, term, method);
 
-            String termJavaName = ClassFabUtils.toJavaClassName(wrappedType);
-            builder.add("%s %s = ", termJavaName, variableName);
+            builder.add("%s %s = ", wrapperTypeName, variableName);
 
             // Casts are needed for primitives, and for the case where
             // generics are involved.
@@ -361,25 +712,24 @@
             }
             else if (info.isCastRequired())
             {
-                builder.add(" (%s) ", termJavaName);
+                builder.add(" (%s) ", wrapperTypeName);
             }
 
-            builder.addln("%s.%s();", previousVariableName, info.getReadMethodName());
+            builder.addln("%s.%s;", previousVariableName, invocation);
 
-            if (nullable)
+            if (allowNull)
             {
                 builder.addln("if (%s == null) return null;", variableName);
             }
             else
             {
                 // Perform a null check on intermediate terms.
-                builder.addln("if (%s == null) %s.nullTerm(\"%s\", \"%s\", root);",
+                builder.addln("if (%s == null) %s.nullTerm(\"%s\", \"%s\", $1);",
                               variableName, PropertyConduitSourceImpl.class.getName(), info.getDescription(),
                               expression);
             }
 
-            activeType = wrappedType;
-            return activeType;
+            return new GeneratedTerm(wrappedType, variableName);
         }
 
         private void assertNodeType(Tree node, int... expected)
@@ -391,25 +741,36 @@
                 if (type == e) return;
             }
 
+            throw unexpectedNodeType(node, expected);
+        }
+
+        private RuntimeException unexpectedNodeType(Tree node, int... expected)
+        {
             List<String> tokenNames = CollectionFactory.newList();
 
             for (int i = 0; i < expected.length; i++)
                 tokenNames.add(PropertyExpressionParser.tokenNames[expected[i]]);
 
             String message =
-                    String.format("Node %s (within expression '%s') was type %s, but was expected (one of) %s.",
-                                  PropertyExpressionParser.tokenNames[type],
+                    String.format("Node %s (within expression '%s') was type %s, but was expected to be (one of) %s.",
+                                  node.toStringTree(),
+                                  expression,
+                                  PropertyExpressionParser.tokenNames[node.getType()],
                                   InternalUtils.joinSorted(tokenNames));
 
-            throw new PropertyExpressionException(message, expression, null);
+            return new PropertyExpressionException(message, expression, null);
         }
 
-        private ExpressionTermInfo infoForParseNode(Class activeType, Tree node)
+        private ExpressionTermInfo infoForPropertyOrMethod(Class activeType, Tree node)
         {
             if (node.getType() == INVOKE)
                 return infoForInvokeNode(activeType, node);
 
+            return infoForPropertyNode(activeType, node);
+        }
 
+        private ExpressionTermInfo infoForPropertyNode(Class activeType, Tree node)
+        {
             String propertyName = node.getText();
 
             ClassPropertyAdapter classAdapter = access.getAdapter(activeType);
@@ -421,19 +782,14 @@
 
             return new ExpressionTermInfo()
             {
-                public String getReadMethodName()
+                public Method getReadMethod()
                 {
-                    return name(adapter.getReadMethod());
+                    return adapter.getReadMethod();
                 }
 
-                public String getWriteMethodName()
+                public Method getWriteMethod()
                 {
-                    return name(adapter.getWriteMethod());
-                }
-
-                private String name(Method m)
-                {
-                    return m == null ? null : m.getName();
+                    return adapter.getWriteMethod();
                 }
 
                 public Class getType()
@@ -462,26 +818,26 @@
         {
             String methodName = node.getChild(0).getText();
 
-            final String description = methodName + "()";
+            int parameterCount = node.getChildCount() - 1;
 
             try
             {
-                final Method method = findMethod(activeType, methodName);
+                final Method method = findMethod(activeType, methodName, parameterCount);
 
                 if (method.getReturnType().equals(void.class))
                     throw new PropertyExpressionException(
-                            ServicesMessages.methodIsVoid(description, activeType, expression), expression, null);
+                            ServicesMessages.methodIsVoid(methodName, activeType, expression), expression, null);
 
                 final Class genericType = GenericsUtils.extractGenericReturnType(activeType, method);
 
                 return new ExpressionTermInfo()
                 {
-                    public String getReadMethodName()
+                    public Method getReadMethod()
                     {
-                        return method.getName();
+                        return method;
                     }
 
-                    public String getWriteMethodName()
+                    public Method getWriteMethod()
                     {
                         return null;
                     }
@@ -498,7 +854,7 @@
 
                     public String getDescription()
                     {
-                        return description;
+                        return new MethodSignature(method).getUniqueId();
                     }
 
                     public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
@@ -510,16 +866,17 @@
             catch (NoSuchMethodException ex)
             {
                 throw new PropertyExpressionException(
-                        ServicesMessages.methodNotFound(description, activeType, expression), expression, ex);
+                        ServicesMessages.methodNotFound(methodName, activeType, expression), expression, ex);
             }
         }
 
-        private Method findMethod(Class activeType, String methodName) throws NoSuchMethodException
+        private Method findMethod(Class activeType, String methodName, int parameterCount) throws NoSuchMethodException
         {
             for (Method method : activeType.getMethods())
             {
 
-                if (method.getParameterTypes().length == 0 && method.getName().equalsIgnoreCase(methodName))
+                if (method.getParameterTypes().length == parameterCount && method.getName().equalsIgnoreCase(
+                        methodName))
                     return method;
             }
 
@@ -527,15 +884,21 @@
         }
     }
 
-    public PropertyConduitSourceImpl(PropertyAccess access, @ComponentLayer ClassFactory classFactory)
+    public PropertyConduitSourceImpl(PropertyAccess access, @ComponentLayer ClassFactory classFactory,
+                                     TypeCoercer typeCoercer)
     {
         this.access = access;
         this.classFactory = classFactory;
+        this.typeCoercer = typeCoercer;
+
+        literalTrue = createLiteralConduit(Boolean.class, true);
+        literalFalse = createLiteralConduit(Boolean.class, false);
+        literalNull = createLiteralConduit(Void.class, null);
     }
 
     public PropertyConduit create(Class rootClass, String expression)
     {
-        Defense.notNull(rootClass, "rootClass");
+        Defense.notNull(rootClass, "rootType");
         Defense.notBlank(expression, "expression");
 
         Class effectiveClass = toEffectiveClass(rootClass);
@@ -584,8 +947,8 @@
      * constructor. In a worst-case race condition, we may build two (or more) conduits for the same
      * rootClass/expression, and it will get sorted out when the conduit is stored into the cache.
      *
-     * @param rootClass
-     * @param expression
+     * @param rootClass  class of root object for expression evaluation
+     * @param expression expression to be evaluated
      * @return the conduit
      */
     private PropertyConduit build(final Class rootClass, String expression)
@@ -611,32 +974,36 @@
                 // Leading '+' may screw this up.
                 // TODO: Singleton instance for "0", maybe "1"?
 
-                return newLiteralConduit(Long.class, new Long(tree.getText()));
+                return createLiteralConduit(Long.class, new Long(tree.getText()));
 
             case DECIMAL:
 
                 // Leading '+' may screw this up.
                 // TODO: Singleton instance for "0.0"?
 
-                return newLiteralConduit(Double.class, new Double(tree.getText()));
+                return createLiteralConduit(Double.class, new Double(tree.getText()));
 
             case STRING:
 
-                return newLiteralConduit(String.class, tree.getText());
+                return createLiteralConduit(String.class, tree.getText());
 
             case RANGEOP:
 
-                // For the moment, we know that RANGEOP must be paired with two INTEGERS.
-
                 Tree fromNode = tree.getChild(0);
-                int from = Integer.parseInt(fromNode.getText());
-
                 Tree toNode = tree.getChild(1);
+
+                // If the range is defined as integers (not properties, etc.) then
+                // it is possible to calcualte the value here, once, and not build
+                // a new class.
+
+                if (fromNode.getType() != INTEGER || toNode.getType() != INTEGER) break;
+
+                int from = Integer.parseInt(fromNode.getText());
                 int to = Integer.parseInt(toNode.getText());
 
                 IntegerRange ir = new IntegerRange(from, to);
 
-                return newLiteralConduit(IntegerRange.class, ir);
+                return createLiteralConduit(IntegerRange.class, ir);
 
             case THIS:
 
@@ -670,10 +1037,10 @@
         return new PropertyConduitBuilder(rootClass, expression, tree).createInstance();
     }
 
-    private <T> PropertyConduit newLiteralConduit(Class<T> type, T value)
+    private <T> PropertyConduit createLiteralConduit(Class<T> type, T value)
     {
         return new LiteralPropertyConduit(type, invariantAnnotationProvider,
-                                          String.format("LiteralPropertyConduit[%s]", value), value);
+                                          String.format("LiteralPropertyConduit[%s]", value), typeCoercer, value);
     }
 
     private Tree parse(String expression)
@@ -708,7 +1075,7 @@
     }
 
     /**
-     * May be invoked from the fabricated PropertyConduit instances.
+     * May be invoked from fabricated PropertyConduit instances.
      */
     public static void nullTerm(String term, String expression, Object root)
     {

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/PropertyConduitSource.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/PropertyConduitSource.java?rev=725884&r1=725883&r2=725884&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/PropertyConduitSource.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/PropertyConduitSource.java Thu Dec 11 17:46:11 2008
@@ -36,9 +36,9 @@
      * so despite the name, this method does not always create a <em>new</em> conduit. The cache is cleared if a change
      * to component classes is observed.
      *
-     * @param rootClass  the class of the root object to which the expression is applied
+     * @param rootType   the type of the root object to which the expression is applied
      * @param expression expression to be evaluated on instances of the root class
      * @return RuntimeException if the expression is invalid (poorly formed, references non-existent properties, etc.)
      */
-    PropertyConduit create(Class rootClass, String expression);
+    PropertyConduit create(Class rootType, String expression);
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java?rev=725884&r1=725883&r2=725884&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java Thu Dec 11 17:46:11 2008
@@ -1132,11 +1132,9 @@
         return chainBuilder.build(Dispatcher.class, configuration);
     }
 
-    public PropertyConduitSource buildPropertyConduitSource(@ComponentLayer ClassFactory componentClassFactory,
+    public PropertyConduitSource buildPropertyConduitSource(@Autobuild PropertyConduitSourceImpl service,
                                                             @ComponentClasses InvalidationEventHub hub)
     {
-        PropertyConduitSourceImpl service = new PropertyConduitSourceImpl(propertyAccess, componentClassFactory);
-
         hub.addInvalidationListener(service);
 
         return service;

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/services/ServicesStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/services/ServicesStrings.properties?rev=725884&r1=725883&r2=725884&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/services/ServicesStrings.properties (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/services/ServicesStrings.properties Thu Dec 11 17:46:11 2008
@@ -64,8 +64,8 @@
 attribute-not-allowed=Element <%s> does not support any attributes.
 parameter-element-name-required=The name attribute of the <parameter> element must be specified.
 missing-application-state-persistence-strategy=No application state persistence strategy is available with name '%s'. Available strategies: %s.
-method-is-void=Method '%s' returns void (in class %s, within property expression '%s').
-method-not-found=No public method '%s' in class %s (within property expression '%s').
+method-is-void=Method '%s()' returns void (in class %s, within property expression '%s').
+method-not-found=No public method '%s()' in class %s (within property expression '%s').
 no-such-property=Class %s does not contain a property named '%s' (within property expression '%s').  Available properties: %s.
 write-only-property=Property '%s' of class %s (within property expression '%s') is not readable (it has no read accessor method).
 request-exception=Processing of request failed with uncaught exception: %s

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/bindings/PropBindingFactoryTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/bindings/PropBindingFactoryTest.java?rev=725884&r1=725883&r2=725884&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/bindings/PropBindingFactoryTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/bindings/PropBindingFactoryTest.java Thu Dec 11 17:46:11 2008
@@ -589,6 +589,9 @@
                         {" 5.", 5d},
                         {" -100.", -100d},
                         {" -0.0 ", -0d},
+                        {"+50", 50l},
+                        {"+7..+20", new IntegerRange(7, 20)},
+                        {"+5.5", 5.5d},
                         {"1..10", new IntegerRange(1, 10)},
                         {" -20 .. -30 ", new IntegerRange(-20, -30)},
                         {"0.", 0d},

Added: 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=725884&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/EchoBean.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/EchoBean.java Thu Dec 11 17:46:11 2008
@@ -0,0 +1,81 @@
+// Copyright 2008 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.
+// 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.tapestry5.internal.services;
+
+public class EchoBean
+{
+    public int storedInt;
+
+    private double storedDouble;
+
+    private String storedString;
+
+    private StringSource stringSource;
+
+    public StringSource getStringSource()
+    {
+        return stringSource;
+    }
+
+    public void setStringSource(StringSource stringSource)
+    {
+        this.stringSource = stringSource;
+    }
+
+    public int echoInt(int value, int multiplyBy)
+    {
+        return value * multiplyBy;
+    }
+
+    public double echoDouble(double value, double multiplyBy)
+    {
+        return value * multiplyBy;
+    }
+
+    public int getStoredInt()
+    {
+        return storedInt;
+    }
+
+    public void setStoredInt(int storedInt)
+    {
+        this.storedInt = storedInt;
+    }
+
+    public double getStoredDouble()
+    {
+        return storedDouble;
+    }
+
+    public void setStoredDouble(double storedDouble)
+    {
+        this.storedDouble = storedDouble;
+    }
+
+    public String getStoredString()
+    {
+        return storedString;
+    }
+
+    public void setStoredString(String storedString)
+    {
+        this.storedString = storedString;
+    }
+
+    public String echoString(String value, String before, String after)
+    {
+        return String.format("%s - %s - %s", before, value, after);
+    }
+}

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=725884&r1=725883&r2=725884&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 Thu Dec 11 17:46:11 2008
@@ -16,8 +16,10 @@
 
 import org.apache.tapestry5.PropertyConduit;
 import org.apache.tapestry5.beaneditor.Validate;
+import org.apache.tapestry5.integration.app1.data.IntegerHolder;
 import org.apache.tapestry5.internal.bindings.PropBindingFactoryTest;
 import org.apache.tapestry5.internal.test.InternalBaseTestCase;
+import org.apache.tapestry5.internal.util.IntegerRange;
 import org.apache.tapestry5.ioc.internal.services.ClassFactoryImpl;
 import org.apache.tapestry5.ioc.services.ClassFab;
 import org.apache.tapestry5.ioc.services.ClassFactory;
@@ -50,20 +52,51 @@
     @Test
     public void literal_conduits_have_invariant_annotation()
     {
-        PropertyConduit normal = source.create(CompositeBean.class, "12345");
+        PropertyConduit pc = source.create(CompositeBean.class, "12345");
 
-        assertNotNull(normal.getAnnotation(Invariant.class));
+        Invariant annotation = pc.getAnnotation(Invariant.class);
+
+        assertNotNull(annotation);
+
+        assertSame(annotation.annotationType(), Invariant.class);
+    }
+
+    @Test
+    public void range_variable_to()
+    {
+        PropertyConduit pc = source.create(IntegerHolder.class, "10..value");
+        IntegerHolder h = new IntegerHolder();
+
+        h.setValue(5);
+
+        IntegerRange ir = (IntegerRange) pc.get(h);
+
+        assertEquals(ir, new IntegerRange(10, 5));
     }
 
     @Test
+    public void range_variable_from()
+    {
+        PropertyConduit pc = source.create(IntegerHolder.class, "value..99");
+        IntegerHolder h = new IntegerHolder();
+
+        h.setValue(72);
+
+        IntegerRange ir = (IntegerRange) pc.get(h);
+
+        assertEquals(ir, new IntegerRange(72, 99));
+    }
+
+
+    @Test
     public void literal_conduits_are_not_updateable()
     {
-        PropertyConduit normal = source.create(CompositeBean.class, "12345");
+        PropertyConduit pc = source.create(CompositeBean.class, "12345");
         CompositeBean bean = new CompositeBean();
 
         try
         {
-            normal.set(bean, 42);
+            pc.set(bean, 42);
             unreachable();
         }
         catch (RuntimeException ex)
@@ -244,4 +277,55 @@
 
         assertEquals(annotation.value(), "required");
     }
+
+    @Test
+    public void method_invocation_with_integer_arguments()
+    {
+        PropertyConduit conduit = source.create(EchoBean.class, "echoInt(storedInt, 3)");
+        EchoBean bean = new EchoBean();
+
+        for (int i = 0; i < 10; i++)
+        {
+            bean.setStoredInt(i);
+            assertEquals(conduit.get(bean), new Integer(i * 3));
+        }
+    }
+
+    @Test
+    public void method_invocation_with_double_argument()
+    {
+        PropertyConduit conduit = source.create(EchoBean.class, "echoDouble(storedDouble, 2.0)");
+        EchoBean bean = new EchoBean();
+
+        double value = 22. / 7.;
+
+        bean.setStoredDouble(value);
+
+        assertEquals(conduit.get(bean), new Double(2. * value));
+    }
+
+    @Test
+    public void method_invocation_with_string_argument()
+    {
+        PropertyConduit conduit = source.create(EchoBean.class, "echoString(storedString, 'B4', 'AFTER')");
+        EchoBean bean = new EchoBean();
+
+        bean.setStoredString("Moe");
+
+        assertEquals(conduit.get(bean), "B4 - Moe - AFTER");
+    }
+
+    @Test
+    public void method_invocation_using_dereference()
+    {
+        PropertyConduit conduit = source.create(EchoBean.class, "echoString(storedString, stringSource.value, 'beta')");
+        EchoBean bean = new EchoBean();
+
+        StringSource source = new StringSource("alpha");
+
+        bean.setStringSource(source);
+        bean.setStoredString("Barney");
+
+        assertEquals(conduit.get(bean), "alpha - Barney - beta");
+    }
 }

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/StringSource.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/StringSource.java?rev=725884&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/StringSource.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/StringSource.java Thu Dec 11 17:46:11 2008
@@ -0,0 +1,30 @@
+// Copyright 2008 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.
+// 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.tapestry5.internal.services;
+
+public class StringSource
+{
+    private final String value;
+
+    public StringSource(String value)
+    {
+        this.value = value;
+    }
+
+    public String getValue()
+    {
+        return value;
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/tapestry-core.iml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/tapestry-core.iml?rev=725884&r1=725883&r2=725884&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/tapestry-core.iml (original)
+++ tapestry/tapestry5/trunk/tapestry-core/tapestry-core.iml Thu Dec 11 17:46:11 2008
@@ -10,7 +10,7 @@
       <sourceFolder url="file://$MODULE_DIR$/src/test/resources" isTestSource="true" />
       <sourceFolder url="file://$MODULE_DIR$/target/generated-sources/antlr" isTestSource="false" />
       <excludeFolder url="file://$MODULE_DIR$/target/classes" />
-      <excludeFolder url="file://$MODULE_DIR$/target/maven-archiver" />
+      <excludeFolder url="file://$MODULE_DIR$/target/surefire-reports" />
       <excludeFolder url="file://$MODULE_DIR$/target/test-classes" />
       <excludeFolder url="file://$MODULE_DIR$/test-output" />
     </content>

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalUtils.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalUtils.java?rev=725884&r1=725883&r2=725884&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalUtils.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalUtils.java Thu Dec 11 17:46:11 2008
@@ -104,6 +104,11 @@
         return array == null ? 0 : array.length;
     }
 
+    public static int size(Collection collection)
+    {
+        return collection == null ? 0 : collection.size();
+    }
+
     /**
      * Strips leading "_" and "$" and trailing "_" from the name.
      */

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/MethodSignature.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/MethodSignature.java?rev=725884&r1=725883&r2=725884&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/MethodSignature.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/MethodSignature.java Thu Dec 11 17:46:11 2008
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007 The Apache Software Foundation
+// Copyright 2006, 2007, 2008 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.
@@ -14,9 +14,8 @@
 
 package org.apache.tapestry5.ioc.services;
 
-import static org.apache.tapestry5.ioc.internal.util.Defense.notBlank;
-import static org.apache.tapestry5.ioc.internal.util.Defense.notNull;
-import static org.apache.tapestry5.ioc.internal.util.InternalUtils.size;
+import org.apache.tapestry5.ioc.internal.util.Defense;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 
 import java.lang.reflect.Method;
 import java.util.Arrays;
@@ -47,8 +46,8 @@
 
     public MethodSignature(Class returnType, String name, Class[] parameterTypes, Class[] exceptionTypes)
     {
-        this.returnType = notNull(returnType, "returnType");
-        this.name = notBlank(name, "name");
+        this.returnType = Defense.notNull(returnType, "returnType");
+        this.name = Defense.notBlank(name, "name");
 
         // Can be null!
         this.parameterTypes = parameterTypes;
@@ -96,12 +95,12 @@
 
             hashCode = 31 * hashCode + name.hashCode();
 
-            int count = size(parameterTypes);
+            int count = InternalUtils.size(parameterTypes);
 
             for (int i = 0; i < count; i++)
                 hashCode = 31 * hashCode + parameterTypes[i].hashCode();
 
-            count = size(exceptionTypes);
+            count = InternalUtils.size(exceptionTypes);
 
             for (int i = 0; i < count; i++)
                 hashCode = 31 * hashCode + exceptionTypes[i].hashCode();
@@ -134,8 +133,8 @@
 
     private boolean mismatch(Class[] a1, Class[] a2)
     {
-        int a1Count = size(a1);
-        int a2Count = size(a2);
+        int a1Count = InternalUtils.size(a1);
+        int a2Count = InternalUtils.size(a2);
 
         if (a1Count != a2Count) return true;
 
@@ -160,7 +159,7 @@
         buffer.append(name);
         buffer.append("(");
 
-        for (int i = 0; i < size(parameterTypes); i++)
+        for (int i = 0; i < InternalUtils.size(parameterTypes); i++)
         {
             if (i > 0) buffer.append(", ");
 
@@ -169,7 +168,7 @@
 
         buffer.append(")");
 
-        int _exceptionCount = size(exceptionTypes);
+        int _exceptionCount = InternalUtils.size(exceptionTypes);
         String _exceptionNames[] = new String[_exceptionCount];
         for (int i = 0; i < _exceptionCount; i++)
         {
@@ -190,7 +189,7 @@
     }
 
     /**
-     * Returns a string consisting of the name of the method and its parameter values. This is similar to {@link
+     * Returns a string consisting of the name of the method and its parameter types. This is similar to {@link
      * #toString()}, but omits the return type and information about thrown exceptions. A unique id is used by {@link
      * MethodIterator} to identify overlapping methods (methods with the same name and parameter types but with
      * different thrown exceptions).
@@ -202,7 +201,7 @@
         StringBuilder buffer = new StringBuilder(name);
         buffer.append("(");
 
-        for (int i = 0; i < size(parameterTypes); i++)
+        for (int i = 0; i < InternalUtils.size(parameterTypes); i++)
         {
             if (i > 0) buffer.append(",");
 
@@ -238,8 +237,8 @@
     @SuppressWarnings("unchecked")
     private boolean exceptionsEncompass(Class[] otherExceptions)
     {
-        int ourCount = size(exceptionTypes);
-        int otherCount = size(otherExceptions);
+        int ourCount = InternalUtils.size(exceptionTypes);
+        int otherCount = InternalUtils.size(otherExceptions);
 
         // If we have no exceptions, then ours encompass theirs only if they
         // have no exceptions, either.

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/InternalUtilsTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/InternalUtilsTest.java?rev=725884&r1=725883&r2=725884&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/InternalUtilsTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/InternalUtilsTest.java Thu Dec 11 17:46:11 2008
@@ -97,7 +97,9 @@
     @Test
     public void array_size_when_null()
     {
-        assertEquals(InternalUtils.size(null), 0);
+        Object[] array = null;
+
+        assertEquals(InternalUtils.size(array), 0);
     }
 
     @Test
@@ -560,4 +562,16 @@
 
         assertSame(InternalUtils.keys(map), map.keySet());
     }
+
+    @Test
+    public void collection_size()
+    {
+        Collection c = null;
+
+        assertEquals(InternalUtils.size(c), 0);
+
+        c = Arrays.asList("moe", "larry", "curly");
+
+        assertEquals(InternalUtils.size(c), 3);
+    }
 }