View Javadoc

1   package org.apache.velocity.runtime.parser.node;
2   
3   import org.apache.velocity.context.InternalContextAdapter;
4   import org.apache.velocity.exception.MethodInvocationException;
5   import org.apache.velocity.exception.TemplateInitException;
6   import org.apache.velocity.exception.VelocityException;
7   import org.apache.velocity.runtime.RuntimeConstants;
8   import org.apache.velocity.runtime.log.Log;
9   import org.apache.velocity.runtime.parser.Parser;
10  import org.apache.velocity.util.ClassUtils;
11  import org.apache.velocity.util.introspection.VelMethod;
12  
13  /*
14   * Licensed to the Apache Software Foundation (ASF) under one
15   * or more contributor license agreements.  See the NOTICE file
16   * distributed with this work for additional information
17   * regarding copyright ownership.  The ASF licenses this file
18   * to you under the Apache License, Version 2.0 (the
19   * "License"); you may not use this file except in compliance
20   * with the License.  You may obtain a copy of the License at
21   *
22   *   http://www.apache.org/licenses/LICENSE-2.0
23   *
24   * Unless required by applicable law or agreed to in writing,
25   * software distributed under the License is distributed on an
26   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
27   * KIND, either express or implied.  See the License for the
28   * specific language governing permissions and limitations
29   * under the License.    
30   */
31  
32  /**
33   *  This node is responsible for the bracket notation at the end of
34   *  a reference, e.g., $foo[1]
35   */
36  
37  public class ASTIndex extends SimpleNode
38  {
39      private final String methodName = "get";
40  
41      /**
42       * Indicates if we are running in strict reference mode.
43       */
44      protected boolean strictRef = false;
45  
46      public ASTIndex(int i)
47      {
48          super(i);
49      }
50  
51      public ASTIndex(Parser p, int i)
52      {
53          super(p, i);
54      }
55    
56      public Object init(InternalContextAdapter context, Object data)
57          throws TemplateInitException
58      {
59          super.init(context, data);    
60          strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false);
61          return data;
62      }  
63  
64  
65      
66      private final static Object[] noParams = {};
67      private final static Class[] noTypes = {};      
68      /**
69       * If argument is an Integer and negative, then return (o.size() - argument). 
70       * Otherwise return the original argument.  We use this to calculate the true
71       * index of a negative index e.g., $foo[-1]. If no size() method is found on the
72       * 'o' object, then we throw an VelocityException.
73       * @param context Used to access the method cache.
74       * @param node  ASTNode used for error reporting.
75       */
76      public static Object adjMinusIndexArg(Object argument, Object o, 
77                                 InternalContextAdapter context, SimpleNode node)
78      {
79        if (argument instanceof Integer && ((Integer)argument).intValue() < 0)
80        {
81            // The index value is a negative number, $foo[-1], so we want to actually
82            // Index [size - value], so try and call the size method.
83            VelMethod method = ClassUtils.getMethod("size", noParams, noTypes, 
84                               o, context, node, false);
85            if (method == null)
86            {
87                // The object doesn't have a size method, so there is no notion of "at the end"
88                throw new VelocityException(
89                  "A 'size()' method required for negative value "
90                   + ((Integer)argument).intValue() + " does not exist for class '" 
91                   + o.getClass().getName() + "' at " + Log.formatFileString(node));
92            }             
93  
94            Object size = null;
95            try
96            {
97                size = method.invoke(o, noParams);
98            }
99            catch (Exception e)
100           {
101               throw new VelocityException("Error trying to calls the 'size()' method on '"
102                 + o.getClass().getName() + "' at " + Log.formatFileString(node), e);
103           }
104           
105           int sizeint = 0;          
106           try
107           {
108               sizeint = ((Integer)size).intValue();
109           }
110           catch (ClassCastException e)
111           {
112               // If size() doesn't return an Integer we want to report a pretty error
113               throw new VelocityException("Method 'size()' on class '" 
114                   + o.getClass().getName() + "' returned '" + size.getClass().getName()
115                   + "' when Integer was expected at " + Log.formatFileString(node));
116           }
117           
118           argument = new Integer(sizeint + ((Integer)argument).intValue());
119       }
120       
121       // Nothing to do, return the original argument
122       return argument;
123     }
124     
125     public Object execute(Object o, InternalContextAdapter context)
126         throws MethodInvocationException
127     {
128         Object argument = jjtGetChild(0).value(context);
129         // If negative, turn -1 into size - 1
130         argument = adjMinusIndexArg(argument, o, context, this);
131         Object [] params = {argument};
132         Class[] paramClasses = {argument == null ? null : argument.getClass()};
133 
134         VelMethod method = ClassUtils.getMethod(methodName, params, paramClasses, 
135                                                 o, context, this, strictRef);
136 
137         if (method == null) return null;
138     
139         try
140         {
141             /*
142              *  get the returned object.  It may be null, and that is
143              *  valid for something declared with a void return type.
144              *  Since the caller is expecting something to be returned,
145              *  as long as things are peachy, we can return an empty
146              *  String so ASTReference() correctly figures out that
147              *  all is well.
148              */
149             Object obj = method.invoke(o, params);
150 
151             if (obj == null)
152             {
153                 if( method.getReturnType() == Void.TYPE)
154                 {
155                     return "";
156                 }
157             }
158 
159             return obj;
160         }
161         /**
162          * pass through application level runtime exceptions
163          */
164         catch( RuntimeException e )
165         {
166             throw e;
167         }
168         catch( Exception e )
169         {
170             String msg = "Error invoking method 'get("
171               + (argument == null ? "null" : argument.getClass().getName()) 
172               + ")' in " + o.getClass().getName()
173               + " at " + Log.formatFileString(this);
174             log.error(msg, e);
175             throw new VelocityException(msg, e);
176         }
177     }  
178 }