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