1 package org.apache.velocity.runtime.directive; 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.TemplateInitException; 24 import org.apache.velocity.exception.VelocityException; 25 import org.apache.velocity.runtime.RuntimeConstants; 26 import org.apache.velocity.runtime.RuntimeServices; 27 import org.apache.velocity.runtime.parser.ParseException; 28 import org.apache.velocity.runtime.parser.Token; 29 import org.apache.velocity.runtime.parser.node.*; 30 import org.apache.velocity.util.StringUtils; 31 import org.apache.velocity.util.introspection.Info; 32 33 import java.io.Closeable; 34 import java.io.IOException; 35 import java.io.Writer; 36 import java.util.ArrayList; 37 import java.util.Iterator; 38 39 /** 40 * Foreach directive used for moving through arrays, 41 * or objects that provide an Iterator. 42 * 43 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> 44 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> 45 * @author Daniel Rall 46 * @version $Id$ 47 */ 48 public class Foreach extends Directive 49 { 50 /** 51 * Return name of this directive. 52 * @return The name of this directive. 53 */ 54 @Override getName()55 public String getName() 56 { 57 return "foreach"; 58 } 59 60 /** 61 * Return type of this directive. 62 * @return The type of this directive. 63 */ 64 @Override getType()65 public int getType() 66 { 67 return BLOCK; 68 } 69 70 /** 71 * The maximum number of times we're allowed to loop. 72 */ 73 private int maxNbrLoops; 74 75 /** 76 * Whether or not to throw an Exception if the iterator is null. 77 */ 78 private boolean skipInvalidIterator; 79 80 /** 81 * The reference name used to access each 82 * of the elements in the list object. It 83 * is the $item in the following: 84 * 85 * #foreach ($item in $list) 86 * 87 * This can be used class wide because 88 * it is immutable. 89 */ 90 private String elementKey; 91 92 /** 93 * immutable, so create in init 94 */ 95 protected Info uberInfo; 96 97 /** 98 * simple init - init the tree and get the elementKey from 99 * the AST 100 * @param rs 101 * @param context 102 * @param node 103 * @throws TemplateInitException 104 */ 105 @Override init(RuntimeServices rs, InternalContextAdapter context, Node node)106 public void init(RuntimeServices rs, InternalContextAdapter context, Node node) 107 throws TemplateInitException 108 { 109 super.init(rs, context, node); 110 111 maxNbrLoops = rsvc.getInt(RuntimeConstants.MAX_NUMBER_LOOPS, 112 Integer.MAX_VALUE); 113 if (maxNbrLoops < 1) 114 { 115 maxNbrLoops = Integer.MAX_VALUE; 116 } 117 skipInvalidIterator = 118 rsvc.getBoolean(RuntimeConstants.SKIP_INVALID_ITERATOR, true); 119 120 if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false)) 121 { 122 // If we are in strict mode then the default for skipInvalidItarator 123 // is true. However, if the property is explicitly set, then honor the setting. 124 skipInvalidIterator = rsvc.getBoolean(RuntimeConstants.SKIP_INVALID_ITERATOR, false); 125 } 126 127 /* 128 * this is really the only thing we can do here as everything 129 * else is context sensitive 130 */ 131 SimpleNode sn = (SimpleNode) node.jjtGetChild(0); 132 133 if (sn instanceof ASTReference) 134 { 135 elementKey = ((ASTReference) sn).getRootString(); 136 } 137 else 138 { 139 /* 140 * the default, error-prone way which we'll remove 141 */ 142 elementKey = sn.getFirstTokenImage().substring(1); 143 } 144 145 /* 146 * make an uberinfo - saves new's later on 147 */ 148 149 uberInfo = new Info(this.getTemplateName(), 150 getLine(),getColumn()); 151 } 152 153 /** 154 * Extension hook to allow subclasses to control whether loop vars 155 * are set locally or not. So, those in favor of VELOCITY-285, can 156 * make that happen easily by overriding this and having it use 157 * context.localPut(k,v). See VELOCITY-630 for more on this. 158 * @param context 159 * @param key 160 * @param value 161 */ put(InternalContextAdapter context, String key, Object value)162 protected void put(InternalContextAdapter context, String key, Object value) 163 { 164 context.put(key, value); 165 } 166 167 /** 168 * Retrieve the contextual iterator. 169 * @param iterable 170 * @param node 171 * @return iterator 172 */ getIterator(Object iterable, Node node)173 protected Iterator getIterator(Object iterable, Node node) 174 { 175 Iterator i = null; 176 /* 177 * do our introspection to see what our collection is 178 */ 179 if (iterable != null) 180 { 181 try 182 { 183 i = rsvc.getUberspect().getIterator(iterable, uberInfo); 184 } 185 /* 186 * pass through application level runtime exceptions 187 */ 188 catch (RuntimeException e) 189 { 190 throw e; 191 } 192 catch (Exception ee) 193 { 194 String msg = "Error getting iterator for #foreach parameter " 195 + node.literal() + " at " + StringUtils.formatFileString(node); 196 log.error(msg, ee); 197 throw new VelocityException(msg, ee, rsvc.getLogContext().getStackTrace()); 198 } 199 200 if (i == null && !skipInvalidIterator) 201 { 202 String msg = "#foreach parameter " + node.literal() + " at " 203 + StringUtils.formatFileString(node) + " is of type " + iterable.getClass().getName() 204 + " and cannot be iterated by " + rsvc.getUberspect().getClass().getName(); 205 log.error(msg); 206 throw new VelocityException(msg, null, rsvc.getLogContext().getStackTrace()); 207 } 208 } 209 return i; 210 } 211 212 /** 213 * renders the #foreach() block 214 * @param context 215 * @param writer 216 * @param node 217 * @return True if the directive rendered successfully. 218 * @throws IOException 219 */ 220 @Override render(InternalContextAdapter context, Writer writer, Node node)221 public boolean render(InternalContextAdapter context, Writer writer, Node node) 222 throws IOException 223 { 224 // Get the block ast tree which is always the last child ... 225 Node block = node.jjtGetChild(node.jjtGetNumChildren()-1); 226 227 // ... except if there is an #else clause 228 Node elseBlock = null; 229 Node previous = node.jjtGetChild(node.jjtGetNumChildren()-2); 230 if (previous instanceof ASTBlock) 231 { 232 elseBlock = block; 233 block = previous; 234 } 235 236 Node iterableNode = node.jjtGetChild(2); 237 Object iterable = iterableNode.value(context); 238 Iterator i = getIterator(iterable, iterableNode); 239 if (i == null || !i.hasNext()) 240 { 241 if (elseBlock != null) 242 { 243 renderBlock(context, writer, elseBlock); 244 } 245 return false; 246 } 247 248 /* 249 * save the element key if there is one 250 */ 251 Object o = context.get(elementKey); 252 253 /* 254 * roll our own scope class instead of using preRender(ctx)'s 255 */ 256 ForeachScope foreach = null; 257 if (isScopeProvided()) 258 { 259 String name = getScopeName(); 260 foreach = new ForeachScope(this, context.get(name)); 261 context.put(name, foreach); 262 } 263 264 int count = 1; 265 while (count <= maxNbrLoops && i.hasNext()) 266 { 267 count++; 268 269 put(context, elementKey, i.next()); 270 if (isScopeProvided()) 271 { 272 // update the scope control 273 foreach.index++; 274 foreach.hasNext = i.hasNext(); 275 } 276 277 try 278 { 279 renderBlock(context, writer, block); 280 } 281 catch (StopCommand stop) 282 { 283 if (stop.isFor(this)) 284 { 285 break; 286 } 287 else 288 { 289 // clean up first 290 clean(context, o); 291 throw stop; 292 } 293 } 294 } 295 clean(context, o); 296 /* 297 * closes the iterator if it implements the Closeable interface 298 */ 299 if (i instanceof Closeable && i != iterable) /* except if the iterable is the iterator itself */ 300 { 301 ((Closeable)i).close(); 302 } 303 return true; 304 } 305 renderBlock(InternalContextAdapter context, Writer writer, Node block)306 protected void renderBlock(InternalContextAdapter context, Writer writer, Node block) 307 throws IOException 308 { 309 block.render(context, writer); 310 } 311 clean(InternalContextAdapter context, Object o)312 protected void clean(InternalContextAdapter context, Object o) 313 { 314 /* 315 * restores element key if exists 316 * otherwise just removes 317 */ 318 if (o != null) 319 { 320 context.put(elementKey, o); 321 } 322 else 323 { 324 context.remove(elementKey); 325 } 326 327 // clean up after the ForeachScope 328 postRender(context); 329 } 330 331 /** 332 * We do not allow a word token in any other arg position except for the 2nd since 333 * we are looking for the pattern #foreach($foo in $bar). 334 */ 335 @Override checkArgs(ArrayList<Integer> argtypes, Token t, String templateName)336 public void checkArgs(ArrayList<Integer> argtypes, Token t, String templateName) 337 throws ParseException 338 { 339 if (argtypes.size() < 3) 340 { 341 throw new MacroParseException("Too few arguments to the #foreach directive", 342 templateName, t); 343 } 344 else if (argtypes.get(0) != ParserTreeConstants.JJTREFERENCE) 345 { 346 throw new MacroParseException("Expected argument 1 of #foreach to be a reference", 347 templateName, t); 348 } 349 else if (argtypes.get(1) != ParserTreeConstants.JJTWORD) 350 { 351 throw new MacroParseException("Expected word 'in' at argument position 2 in #foreach", 352 templateName, t); 353 } 354 else if (argtypes.get(2) == ParserTreeConstants.JJTWORD) 355 { 356 throw new MacroParseException("Argument 3 of #foreach is of the wrong type", 357 templateName, t); 358 } 359 } 360 } 361