Modified: velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/ParserTreeConstants.java URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/ParserTreeConstants.java?rev=730566&r1=730565&r2=730566&view=diff ============================================================================== --- velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/ParserTreeConstants.java (original) +++ velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/ParserTreeConstants.java Thu Jan 1 08:43:22 2009 @@ -1,4 +1,5 @@ -/* Generated By:JavaCC: Do not edit this line. ParserTreeConstants.java Version 4.1 */ +/* Generated By:JJTree: Do not edit this line. ParserTreeConstants.java */ + package org.apache.velocity.runtime.parser; public interface ParserTreeConstants @@ -19,31 +20,32 @@ public int JJTOBJECTARRAY = 13; public int JJTINTEGERRANGE = 14; public int JJTMETHOD = 15; - public int JJTREFERENCE = 16; - public int JJTTRUE = 17; - public int JJTFALSE = 18; - public int JJTTEXT = 19; - public int JJTIFSTATEMENT = 20; - public int JJTELSESTATEMENT = 21; - public int JJTELSEIFSTATEMENT = 22; - public int JJTSETDIRECTIVE = 23; - public int JJTSTOP = 24; - public int JJTEXPRESSION = 25; - public int JJTASSIGNMENT = 26; - public int JJTORNODE = 27; - public int JJTANDNODE = 28; - public int JJTEQNODE = 29; - public int JJTNENODE = 30; - public int JJTLTNODE = 31; - public int JJTGTNODE = 32; - public int JJTLENODE = 33; - public int JJTGENODE = 34; - public int JJTADDNODE = 35; - public int JJTSUBTRACTNODE = 36; - public int JJTMULNODE = 37; - public int JJTDIVNODE = 38; - public int JJTMODNODE = 39; - public int JJTNOTNODE = 40; + public int JJTINDEX = 16; + public int JJTREFERENCE = 17; + public int JJTTRUE = 18; + public int JJTFALSE = 19; + public int JJTTEXT = 20; + public int JJTIFSTATEMENT = 21; + public int JJTELSESTATEMENT = 22; + public int JJTELSEIFSTATEMENT = 23; + public int JJTSETDIRECTIVE = 24; + public int JJTSTOP = 25; + public int JJTEXPRESSION = 26; + public int JJTASSIGNMENT = 27; + public int JJTORNODE = 28; + public int JJTANDNODE = 29; + public int JJTEQNODE = 30; + public int JJTNENODE = 31; + public int JJTLTNODE = 32; + public int JJTGTNODE = 33; + public int JJTLENODE = 34; + public int JJTGENODE = 35; + public int JJTADDNODE = 36; + public int JJTSUBTRACTNODE = 37; + public int JJTMULNODE = 38; + public int JJTDIVNODE = 39; + public int JJTMODNODE = 40; + public int JJTNOTNODE = 41; public String[] jjtNodeName = { @@ -63,6 +65,7 @@ "ObjectArray", "IntegerRange", "Method", + "Index", "Reference", "True", "False", @@ -90,4 +93,3 @@ "NotNode", }; } -/* JavaCC - OriginalChecksum=3c1632cd7e231249279dbb78c45031b8 (do not edit this line) */
Added: velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/node/ASTIndex.java URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/node/ASTIndex.java?rev=730566&view=auto ============================================================================== --- velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/node/ASTIndex.java (added) +++ velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/node/ASTIndex.java Thu Jan 1 08:43:22 2009 @@ -0,0 +1,118 @@ +package org.apache.velocity.runtime.parser.node; + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.log.Log; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.runtime.parser.node.ASTMethod.MethodCacheKey; +import org.apache.velocity.util.ClassUtils; +import org.apache.velocity.util.introspection.Info; +import org.apache.velocity.util.introspection.IntrospectionCacheData; +import org.apache.velocity.util.introspection.VelMethod; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * This node is responsible for the bracket notation at the end of + * a reference, e.g., $foo[1] + */ + +public class ASTIndex extends SimpleNode +{ + private final String methodName = "get"; + + /** + * Indicates if we are running in strict reference mode. + */ + protected boolean strictRef = false; + + public ASTIndex(int i) + { + super(i); + } + + public ASTIndex(Parser p, int i) + { + super(p, i); + } + + public Object init(InternalContextAdapter context, Object data) + throws TemplateInitException + { + super.init(context, data); + strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false); + return data; + } + + public Object execute(Object o, InternalContextAdapter context) + throws MethodInvocationException + { + Object argument = jjtGetChild(0).value(context); + Object [] params = {argument}; + Class[] paramClasses = {argument == null ? null : argument.getClass()}; + + VelMethod method = ClassUtils.getMethod(methodName, params, paramClasses, + o, context, this, rsvc, strictRef); + + if (method == null) return null; + + try + { + /* + * get the returned object. It may be null, and that is + * valid for something declared with a void return type. + * Since the caller is expecting something to be returned, + * as long as things are peachy, we can return an empty + * String so ASTReference() correctly figures out that + * all is well. + */ + Object obj = method.invoke(o, params); + + if (obj == null) + { + if( method.getReturnType() == Void.TYPE) + { + return ""; + } + } + + return obj; + } + /** + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + throw e; + } + catch( Exception e ) + { + String msg = "Error invoking method 'get(" + + (argument == null ? "null" : argument.getClass().getName()) + + ")' in " + o.getClass() + + " at " + Log.formatFileString(this); + log.error(msg, e); + throw new VelocityException(msg, e); + } + } +} Modified: velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/node/ASTMethod.java URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/node/ASTMethod.java?rev=730566&r1=730565&r2=730566&view=diff ============================================================================== --- velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/node/ASTMethod.java (original) +++ velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/node/ASTMethod.java Thu Jan 1 08:43:22 2009 @@ -30,6 +30,7 @@ import org.apache.velocity.exception.VelocityException; import org.apache.velocity.runtime.RuntimeConstants; import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.util.ClassUtils; import org.apache.velocity.util.introspection.Info; import org.apache.velocity.util.introspection.IntrospectionCacheData; import org.apache.velocity.util.introspection.VelMethod; @@ -136,125 +137,27 @@ * at execution time. There can be no in-node caching, * but if we are careful, we can do it in the context. */ - - VelMethod method = null; - Object [] params = new Object[paramCount]; - try - { - /* - * sadly, we do need recalc the values of the args, as this can - * change from visit to visit - */ - - final Class[] paramClasses = paramCount > 0 ? new Class[paramCount] : ArrayUtils.EMPTY_CLASS_ARRAY; - - for (int j = 0; j < paramCount; j++) - { - params[j] = jjtGetChild(j + 1).value(context); - - if (params[j] != null) - { - paramClasses[j] = params[j].getClass(); - } - } - - /* - * check the cache - */ - - MethodCacheKey mck = new MethodCacheKey(methodName, paramClasses); - IntrospectionCacheData icd = context.icacheGet( mck ); - - /* - * like ASTIdentifier, if we have cache information, and the - * Class of Object o is the same as that in the cache, we are - * safe. - */ - - if ( icd != null && (o != null && icd.contextData == o.getClass()) ) - { - - /* - * get the method from the cache - */ - - method = (VelMethod) icd.thingy; - } - else - { - /* - * otherwise, do the introspection, and then - * cache it - */ - - method = rsvc.getUberspect().getMethod(o, methodName, params, new Info(getTemplateName(), getLine(), getColumn())); - - if ((method != null) && (o != null)) - { - icd = new IntrospectionCacheData(); - icd.contextData = o.getClass(); - icd.thingy = method; - - context.icachePut( mck, icd ); - } - } - - /* - * if we still haven't gotten the method, either we are calling - * a method that doesn't exist (which is fine...) or I screwed - * it up. - */ + /* + * sadly, we do need recalc the values of the args, as this can + * change from visit to visit + */ + final Class[] paramClasses = + paramCount > 0 ? new Class[paramCount] : ArrayUtils.EMPTY_CLASS_ARRAY; - if (method == null) + for (int j = 0; j < paramCount; j++) + { + params[j] = jjtGetChild(j + 1).value(context); + if (params[j] != null) { - if (strictRef) - { - // Create a parameter list for the exception error message - StringBuffer plist = new StringBuffer(); - for (int i=0; i<params.length; i++) - { - Class param = paramClasses[i]; - plist.append(param == null ? "null" : param.getName()); - if (i < params.length -1) plist.append(", "); - } - throw new MethodInvocationException("Object '" + o.getClass().getName() + - "' does not contain method " + methodName + "(" + plist + ")", - null, methodName, uberInfo.getTemplateName(), uberInfo.getLine(), uberInfo.getColumn()); - } - else - { - return null; - } + paramClasses[j] = params[j].getClass(); } } - catch( MethodInvocationException mie ) - { - /* - * this can come from the doIntrospection(), as the arg values - * are evaluated to find the right method signature. We just - * want to propogate it here, not do anything fancy - */ - - throw mie; - } - /** - * pass through application level runtime exceptions - */ - catch( RuntimeException e ) - { - throw e; - } - catch( Exception e ) - { - /* - * can come from the doIntropection() also, from Introspector - */ - String msg = "ASTMethod.execute() : exception from introspection"; - log.error(msg, e); - throw new VelocityException(msg, e); - } + + VelMethod method = ClassUtils.getMethod(methodName, params, paramClasses, + o, context, this, rsvc, strictRef); + if (method == null) return null; try { Modified: velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/node/ASTReference.java URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/node/ASTReference.java?rev=730566&r1=730565&r2=730566&view=diff ============================================================================== --- velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/node/ASTReference.java (original) +++ velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/node/ASTReference.java Thu Jan 1 08:43:22 2009 @@ -34,7 +34,9 @@ import org.apache.velocity.runtime.log.Log; import org.apache.velocity.runtime.parser.Parser; import org.apache.velocity.runtime.parser.Token; +import org.apache.velocity.util.ClassUtils; import org.apache.velocity.util.introspection.Info; +import org.apache.velocity.util.introspection.VelMethod; import org.apache.velocity.util.introspection.VelPropertySet; /** @@ -75,6 +77,12 @@ */ public boolean strictRef = false; + /** + * non null Indicates if we are setting an index reference e.g, $foo[2], which basically + * means that the last syntax of the reference are brackets. + */ + private ASTIndex astIndex = null; + private int numChildren = 0; protected Info uberInfo; @@ -125,14 +133,18 @@ rootString = getRoot(); numChildren = jjtGetNumChildren(); - + /* * and if appropriate... */ if (numChildren > 0 ) { - identifier = jjtGetChild(numChildren - 1).getFirstToken().image; + Node lastNode = jjtGetChild(numChildren-1); + if (lastNode instanceof ASTIndex) + astIndex = (ASTIndex)lastNode; + else + identifier = lastNode.getFirstToken().image; } /* @@ -549,6 +561,61 @@ } } + if (astIndex != null) + { + // If astIndex is not null then we are actually setting an index reference, + // something of the form $foo[1] =, or in general any reference that ends with + // the brackets. This means that we need to call a more general method + // of the form set(Integer, <something>), or put(Object, <something), where + // the first parameter is the index value and the second is the LHS of the set. + + String methodName = "put"; + Object [] params = {astIndex.jjtGetChild(0).value(context), value}; + Class[] paramClasses = {params[0] == null ? null : params[0].getClass(), + params[1] == null ? null : params[1].getClass()}; + if (params[0] instanceof Integer) + { + // If the index parameter is of type Integer, then it is more approiate + // to call set(Integer, <whatever>) since the user is probabbly attempting + // to set an array. + methodName = "set"; + } + + VelMethod method = ClassUtils.getMethod(methodName, params, paramClasses, + result, context, astIndex, rsvc, strictRef); + + if (method == null) return false; + try + { + method.invoke(result, params); + } + catch(RuntimeException e) + { + // Kludge since invoke throws Exception, pass up Runtimes + throw e; + } + catch(Exception e) + { + // Create a parameter list for the exception error message + StringBuffer plist = new StringBuffer(); + for (int i = 0; i < params.length; i++) + { + Class param = paramClasses[i]; + plist.append(param == null ? "null" : param.getName()); + if (i < params.length - 1) + plist.append(", "); + } + throw new MethodInvocationException( + "Exception calling of method '" + + methodName + "(" + plist + ")' in " + result.getClass(), + e.getCause(), identifier, astIndex.getTemplateName(), astIndex.getLine(), + astIndex.getColumn()); + } + + return true; + } + + /* * We support two ways of setting the value in a #set($ref.foo = $value ) : * 1) ref.setFoo( value ) Modified: velocity/engine/trunk/src/java/org/apache/velocity/util/ClassUtils.java URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/util/ClassUtils.java?rev=730566&r1=730565&r2=730566&view=diff ============================================================================== --- velocity/engine/trunk/src/java/org/apache/velocity/util/ClassUtils.java (original) +++ velocity/engine/trunk/src/java/org/apache/velocity/util/ClassUtils.java Thu Jan 1 08:43:22 2009 @@ -21,6 +21,16 @@ import java.io.InputStream; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.parser.node.SimpleNode; +import org.apache.velocity.runtime.parser.node.ASTMethod.MethodCacheKey; +import org.apache.velocity.util.introspection.Info; +import org.apache.velocity.util.introspection.IntrospectionCacheData; +import org.apache.velocity.util.introspection.VelMethod; + /** @@ -147,5 +157,120 @@ } + /** + * Lookup a VelMethod object given the method signature that is specified in + * the passed in parameters. This method first searches the cache, if not found in + * the cache then uses reflections to inspect Object o, for the given method. + * @param methodName Name of method + * @param params Array of objects that are parameters to the method + * @param paramClasses Array of Classes coresponding to the types in params. + * @param o Object to introspect for the given method. + * @param context Context from which the method cache is aquirred + * @param node ASTNode, used for error reporting. + * @param rsvc RuntimeServices used to retrieve the current Uberspector. + * @param strictRef If no method is found, throw an exception, never return null in this case + * @return VelMethod object if the object is found, null if not matching method is found + */ + public static VelMethod getMethod(String methodName, Object[] params, + Class[] paramClasses, Object o, InternalContextAdapter context, + SimpleNode node, RuntimeServices rsvc, boolean strictRef) + { + VelMethod method = null; + + try + { + /* + * check the cache + */ + MethodCacheKey mck = new MethodCacheKey(methodName, paramClasses); + IntrospectionCacheData icd = context.icacheGet(mck); + + /* + * like ASTIdentifier, if we have cache information, and the Class of + * Object o is the same as that in the cache, we are safe. + */ + if (icd != null && (o != null && icd.contextData == o.getClass())) + { + + /* + * get the method from the cache + */ + method = (VelMethod) icd.thingy; + } + else + { + /* + * otherwise, do the introspection, and then cache it + */ + method = rsvc.getUberspect().getMethod(o, methodName, params, + new Info(node.getTemplateName(), node.getLine(), node.getColumn())); + + if ((method != null) && (o != null)) + { + icd = new IntrospectionCacheData(); + icd.contextData = o.getClass(); + icd.thingy = method; + + context.icachePut(mck, icd); + } + } + + /* + * if we still haven't gotten the method, either we are calling a method + * that doesn't exist (which is fine...) or I screwed it up. + */ + if (method == null) + { + if (strictRef) + { + // Create a parameter list for the exception error message + StringBuffer plist = new StringBuffer(); + for (int i = 0; i < params.length; i++) + { + Class param = paramClasses[i]; + plist.append(param == null ? "null" : param.getName()); + if (i < params.length - 1) + plist.append(", "); + } + throw new MethodInvocationException("Object '" + + o.getClass().getName() + "' does not contain method " + + methodName + "(" + plist + ")", null, methodName, node + .getTemplateName(), node.getLine(), node.getColumn()); + } + else + { + return null; + } + } + + } + catch (MethodInvocationException mie) + { + /* + * this can come from the doIntrospection(), as the arg values are + * evaluated to find the right method signature. We just want to propogate + * it here, not do anything fancy + */ + + throw mie; + } + catch (RuntimeException e) + { + /** + * pass through application level runtime exceptions + */ + throw e; + } + catch (Exception e) + { + /* + * can come from the doIntropection() also, from Introspector + */ + String msg = "ASTMethod.execute() : exception from introspection"; + throw new VelocityException(msg, e); + } + return method; + } + } Modified: velocity/engine/trunk/src/parser/Parser.jjt URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/parser/Parser.jjt?rev=730566&r1=730565&r2=730566&view=diff ============================================================================== --- velocity/engine/trunk/src/parser/Parser.jjt (original) +++ velocity/engine/trunk/src/parser/Parser.jjt Thu Jan 1 08:43:22 2009 @@ -504,6 +504,27 @@ * * ------------------------------------------------------------------------- */ +<REFERENCE, REFMODIFIER> +TOKEN: +{ + <INDEX_LBRACKET: "["> + { + stateStackPush(); + SwitchTo(REFINDEX); + } + +} + +<REFINDEX> +TOKEN: +{ + <INDEX_RBRACKET: "]"> + { + stateStackPop(); + } +} + + <DIRECTIVE,REFMOD2> TOKEN: { @@ -845,13 +866,13 @@ * * ---------------------------------------------------------------------- */ -<DIRECTIVE,REFMOD2> +<DIRECTIVE,REFMOD2,REFINDEX> TOKEN: { <WHITESPACE : ([" ","\t", "\n", "\r"])+ > } -<DIRECTIVE,REFMOD2> +<DIRECTIVE,REFMOD2,REFINDEX> TOKEN : { // <STRING_LITERAL: ( "\"" ( ~["\"","\n","\r"] )* "\"" ) | ( "'" ( ~["'","\n","\r"] )* "'" ) > @@ -890,7 +911,7 @@ } } -<REFERENCE,DIRECTIVE,REFMODIFIER,REFMOD2> +<REFERENCE,DIRECTIVE,REFMODIFIER,REFMOD2,REFINDEX> TOKEN: { <TRUE: "true"> @@ -971,7 +992,7 @@ } } -<PRE_DIRECTIVE,DIRECTIVE,REFMOD2> +<PRE_DIRECTIVE,DIRECTIVE,REFMOD2,REFINDEX> TOKEN: { <#DIGIT: [ "0"-"9" ] > @@ -1001,7 +1022,7 @@ * because we want to handle the \n after */ - if ( lparen == 0 && !inSet && curLexState != REFMOD2) + if ( lparen == 0 && !inSet && curLexState != REFMOD2 && curLexState != REFINDEX) { stateStackPop(); } @@ -1060,7 +1081,10 @@ * | re stateStack. * |-- > REFMOD2 : state switch to when the LPAREN is encountered. * again, this is a switch, not a push. - * + * + * During the REFERENCE or REFMODIFIER lex states we will switch to + * REFINDEX if a bracket is encountered '['. for example: $foo[1] + * or $foo.bar[1], $foo.bar( "arg" )[1] * ---------------------------------------------------------------------------- */ <REFERENCE,REFMODIFIER,REFMOD2> @@ -1555,6 +1579,23 @@ /** + * A Simplified parameter more suitable for an index position: $foo[$index] + */ +void IndexParameter() #void: {} +{ + [<WHITESPACE>] + ( + StringLiteral() + | IntegerLiteral() + | True() + | False() + | Reference() + ) + [ <WHITESPACE>] +} + + +/** * This method has yet to be fully implemented * but will allow arbitrarily nested method * calls @@ -1586,6 +1627,12 @@ Identifier() <LPAREN> [ Parameter() ( <COMMA> Parameter() )* ] <REFMOD2_RPAREN> } + +void Index() : {} +{ + <INDEX_LBRACKET> IndexParameter() <INDEX_RBRACKET> +} + void Reference() : {} { /* @@ -1593,14 +1640,14 @@ */ ( - <IDENTIFIER> - (LOOKAHEAD(2) <DOT> (LOOKAHEAD(3) Method() | Identifier() ))* + <IDENTIFIER> (Index())* + (LOOKAHEAD(2) <DOT> (LOOKAHEAD(3) Method() | Identifier() ) (Index())* )* ) | ( <LCURLY> - <IDENTIFIER> - (LOOKAHEAD(2) <DOT> (LOOKAHEAD(3) Method() | Identifier() ))* + <IDENTIFIER> (Index())* + (LOOKAHEAD(2) <DOT> (LOOKAHEAD(3) Method() | Identifier() ) (Index())* )* <RCURLY> ) } Modified: velocity/engine/trunk/src/test/org/apache/velocity/test/BaseEvalTestCase.java URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/test/org/apache/velocity/test/BaseEvalTestCase.java?rev=730566&r1=730565&r2=730566&view=diff ============================================================================== --- velocity/engine/trunk/src/test/org/apache/velocity/test/BaseEvalTestCase.java (original) +++ velocity/engine/trunk/src/test/org/apache/velocity/test/BaseEvalTestCase.java Thu Jan 1 08:43:22 2009 @@ -147,6 +147,7 @@ engine.getLog().info("Expectation: Exception at "+loc); } Exception e = assertEvalException(evil, null); + if (e.getMessage().indexOf(loc) < 1) { fail("Was expecting exception at "+loc+" instead of "+e.getMessage()); Added: velocity/engine/trunk/src/test/org/apache/velocity/test/IndexTestCase.java URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/test/org/apache/velocity/test/IndexTestCase.java?rev=730566&view=auto ============================================================================== --- velocity/engine/trunk/src/test/org/apache/velocity/test/IndexTestCase.java (added) +++ velocity/engine/trunk/src/test/org/apache/velocity/test/IndexTestCase.java Thu Jan 1 08:43:22 2009 @@ -0,0 +1,147 @@ +package org.apache.velocity.test; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.util.ArrayList; +import org.apache.velocity.runtime.RuntimeConstants; +/** + * Test index syntax e.g, $foo[1] + */ +public class IndexTestCase extends BaseEvalTestCase +{ + public IndexTestCase(String name) + { + super(name); + } + + public void setUp() throws Exception + { + super.setUp(); + engine.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT, Boolean.TRUE); + engine.setProperty(RuntimeConstants.COUNTER_INITIAL_VALUE, 0); + + context.put("NULL", null); + Integer[] a = {1, 2, 3}; + context.put("a", a); + String[] str = {"a", "ab", "abc"}; + context.put("str", str); + + ArrayList alist = new ArrayList(); + alist.add(Integer.valueOf(1)); + alist.add(Integer.valueOf(2)); + alist.add(Integer.valueOf(3)); + alist.add(a); + alist.add(null); + context.put("alist", alist); + + Foo foo = new Foo(); + foo.bar = alist; + context.put("foo", foo); + + Boo boo = new Boo(); + context.put("boo", boo); + } + + public void testCallingIndex() + { + assertEvalEquals("1 -3-", "$a[0] -$a[ 2 ]-"); + assertEvalEquals("x21", "#set($i = 1)x$a[$i]1"); + assertEvalEquals("3", "$a[ $a[ $a[0] ] ]"); + assertEvalEquals("ab", "$str[1]"); + assertEvalEquals("123", "$alist[0]$alist[1]$alist[2]"); + assertEvalEquals("1][2-[3]", "$alist[0]][$alist[$a[0]]-[$alist[2]]"); + + assertEvalEquals("2", "$alist[3][1]"); + assertEvalEquals("3 [1]", "$alist[2] [1]"); + + assertEvalEquals("4", "#set($bar = [3, 4, 5])$bar[1]"); + assertEvalEquals("21", "#set($i = 1)#set($bar = [3, $a[$i], $a[0]])$bar[1]$bar[2]"); + + assertEvalEquals("2", "$foo.bar[1]"); + assertEvalEquals("2", "$foo.getBar()[1]"); + assertEvalEquals("2", "$foo.getBar()[3][1]"); + + assertEvalEquals(" a ab abc ", "#foreach($i in $foo.bar[3]) $str[$velocityCount] #end"); + + assertEvalEquals("apple", "#set($hash = {'a':'apple', 'b':'orange'})$hash['a']"); + + assertEvalEquals("xx ", "#if($alist[4] == $NULL)xx#end #if($alist[4])yy#end"); + + assertEvalEquals("BIG TRUE BIG FALSE", "$foo[true] $foo[false]"); + assertEvalEquals("junk foobar ", "$foo[\"junk\"]"); + assertEvalEquals("GOT NULL", "#set($i=$NULL)$boo[$i]"); + } + + public void testIndexSetting() + { + assertEvalEquals("foo", "#set($str[1] = \"foo\")$str[1]"); + assertEvalEquals("5150", "#set($alist[1] = 5150)$alist[1]"); + assertEvalEquals("15", "$alist[3][0]#set($alist[3][0] = 5)$alist[3][0]"); + assertEvalEquals("orange","#set($blaa = {\"apple\":\"grape\"})#set($blaa[\"apple\"] = \"orange\")$blaa[\"apple\"]"); + assertEvalEquals("null","#set($str[0] = $NULL)#if($str[0] == $NULL)null#end"); + assertEvalEquals("null","#set($blaa = {\"apple\":\"grape\"})#set($blaa[\"apple\"] = $NULL)#if($blaa[\"apple\"] == $NULL)null#end"); + } + + + public void testErrorHandling() + { + assertEvalExceptionAt("$boo['throwex']", 1, 5); + assertEvalExceptionAt("$boo[]", 1, 6); + // Need to fix parse error reporting + // assertEvalExceptionAt("$boo[blaa]", 1, 6); + assertEvalExceptionAt("#set($foo[1] = 3)", 1, 10); + } + + + public static class Foo + { + public Object bar = null; + public Object getBar() + { + return bar; + } + + public String get(Boolean bool) + { + if (bool.booleanValue()) + return "BIG TRUE"; + else + return "BIG FALSE"; + } + + public String get(String str) + { + return str + " foobar "; + } + } + + public static class Boo + { + public Object get(Object obj) + { + if (obj == null) + return "GOT NULL"; + else if (obj.equals("throwex")) + throw new RuntimeException("Generated Exception"); + + return obj; + } + } +} Modified: velocity/engine/trunk/src/test/org/apache/velocity/test/StrictCompareTestCase.java URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/test/org/apache/velocity/test/StrictCompareTestCase.java?rev=730566&r1=730565&r2=730566&view=diff ============================================================================== --- velocity/engine/trunk/src/test/org/apache/velocity/test/StrictCompareTestCase.java (original) +++ velocity/engine/trunk/src/test/org/apache/velocity/test/StrictCompareTestCase.java Thu Jan 1 08:43:22 2009 @@ -72,7 +72,6 @@ */ public void assertVelocityEx(String template) { - System.out.println(template); assertEvalException(template, VelocityException.class); } } Modified: velocity/engine/trunk/xdocs/docs/user-guide.xml URL: http://svn.apache.org/viewvc/velocity/engine/trunk/xdocs/docs/user-guide.xml?rev=730566&r1=730565&r2=730566&view=diff ============================================================================== --- velocity/engine/trunk/xdocs/docs/user-guide.xml (original) +++ velocity/engine/trunk/xdocs/docs/user-guide.xml Thu Jan 1 08:43:22 2009 @@ -47,6 +47,7 @@ <li><a href="#methods">Methods</a></li> <li><a href="#propertylookuprules">Property Lookup Rules</a></li> <li><a href="#rendering">Rendering</a></li> + <li><a href="#index">Index Notation</a></li> </ol> </li> <li><a href="#formalreferencenotation">Formal Reference Notation</a></li> @@ -609,6 +610,45 @@ </p> <p> + <a name="index"><strong>Index Notation</strong></a> + <br/> + Using the notation of the form <code>$foo[0]</code> can be used to access a + given index of an object. This form is synonymous with calling + the get(Object) method on a given object i.e, <code>$foo.get(0)</code>, and + provides essentially a syntactic shorthand for such + operations. Since this simply calls the get method all of the + following are valid uses: + </p> + <source>$foo[0] ## $foo takes in an Integer look up +$foo[$i] ## Using another reference as the index +$foo["bar"] ## Passing a string where $foo may be a Map</source> + <p> + The bracketed syntax also works with Java arrays since Velocity + wraps arrays in an access object that provides a + <code>get(Integer)</code> method which returns the specified + element. + </p> + <p> + The bracketed syntax is valid anywhere <code>.get</code> is valid, + for example: + </p> + <source>$foo.bar[1].junk +$foo.callMethod()[1] +$foo["apple"][4]</source> + <p> + A reference can also be set using index notation, for example: + </p> + <source>#set($foo[0] = 1) +#set($foo.bar[1] = 3) +#set($map["apple"] = "orange")</source> + <p> + The specified element is set with the given value. If the index + value is of type Integer then Velocity calls <code>.set(Integer, + <val>)</code> which works with arrays, otherwise it calls + <code>.put(<val>, <val>)</code> which works with Maps. + </p> + + <p> <a name="formalreferencenotation"><strong>Formal Reference Notation</strong></a> <br/> Shorthand notation for references was used for the examples listed
