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