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,
+   &lt;val>)</code> which works with arrays, otherwise it calls
+   <code>.put(&lt;val>, &lt;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


Reply via email to