View Javadoc

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 java.io.IOException;
23  import java.io.Writer;
24  import java.util.ArrayList;
25  import java.util.Iterator;
26  
27  import org.apache.velocity.context.InternalContextAdapter;
28  import org.apache.velocity.exception.TemplateInitException;
29  import org.apache.velocity.exception.VelocityException;
30  import org.apache.velocity.runtime.RuntimeConstants;
31  import org.apache.velocity.runtime.RuntimeServices;
32  import org.apache.velocity.runtime.log.Log;
33  import org.apache.velocity.runtime.parser.ParseException;
34  import org.apache.velocity.runtime.parser.ParserTreeConstants;
35  import org.apache.velocity.runtime.parser.Token;
36  import org.apache.velocity.runtime.parser.node.ASTReference;
37  import org.apache.velocity.runtime.parser.node.Node;
38  import org.apache.velocity.runtime.parser.node.SimpleNode;
39  import org.apache.velocity.util.introspection.Info;
40  
41  /**
42   * Foreach directive used for moving through arrays,
43   * or objects that provide an Iterator.
44   *
45   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
46   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
47   * @author Daniel Rall
48   * @version $Id: Foreach.java 752661 2009-03-11 22:34:40Z nbubna $
49   */
50  public class Foreach extends Directive
51  {
52      /**
53       * Return name of this directive.
54       * @return The name of this directive.
55       */
56      public String getName()
57      {
58          return "foreach";
59      }
60  
61      /**
62       * Return type of this directive.
63       * @return The type of this directive.
64       */
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     public void init(RuntimeServices rs, InternalContextAdapter context, Node node)
106         throws TemplateInitException
107     {
108         super.init(rs, context, node);
109 
110         maxNbrLoops = rsvc.getInt(RuntimeConstants.MAX_NUMBER_LOOPS,
111                                   Integer.MAX_VALUE);
112         if (maxNbrLoops < 1)
113         {
114             maxNbrLoops = Integer.MAX_VALUE;
115         }
116         skipInvalidIterator =
117             rsvc.getBoolean(RuntimeConstants.SKIP_INVALID_ITERATOR, true);
118         
119         if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false))
120         {
121           // If we are in strict mode then the default for skipInvalidItarator
122           // is true.  However, if the property is explicitly set, then honor the setting.
123           skipInvalidIterator = rsvc.getBoolean(RuntimeConstants.SKIP_INVALID_ITERATOR, false);
124         }
125                 
126         /*
127          *  this is really the only thing we can do here as everything
128          *  else is context sensitive
129          */
130         SimpleNode sn = (SimpleNode) node.jjtGetChild(0);
131 
132         if (sn instanceof ASTReference)
133         {
134             elementKey = ((ASTReference) sn).getRootString();
135         }
136         else
137         {
138             /*
139              * the default, error-prone way which we'll remove
140              *  TODO : remove if all goes well
141              */
142             elementKey = sn.getFirstToken().image.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      */
159     protected void put(InternalContextAdapter context, String key, Object value)
160     {
161         context.put(key, value);
162     }
163 
164     /**
165      * Retrieve the contextual iterator.
166      */
167     protected Iterator getIterator(InternalContextAdapter context, Node node)
168     {
169         Iterator i = null;
170         /*
171          * do our introspection to see what our collection is
172          */
173         Object iterable = node.value(context);
174         if (iterable != null)
175         {
176             try
177             {
178                 i = rsvc.getUberspect().getIterator(iterable, uberInfo);
179             }
180             /*
181              * pass through application level runtime exceptions
182              */
183             catch (RuntimeException e)
184             {
185                 throw e;
186             }
187             catch (Exception ee)
188             {
189                 String msg = "Error getting iterator for #foreach parameter "
190                     + node.literal() + " at " + Log.formatFileString(node);
191                 rsvc.getLog().error(msg, ee);
192                 throw new VelocityException(msg, ee);
193             }
194 
195             if (i == null && !skipInvalidIterator)
196             {
197                 String msg = "#foreach parameter " + node.literal() + " at "
198                     + Log.formatFileString(node) + " is of type " + iterable.getClass().getName()
199                     + " and cannot be iterated by " + rsvc.getUberspect().getClass().getName();
200                 rsvc.getLog().error(msg);
201                 throw new VelocityException(msg);
202             }
203         }
204         return i;
205     }
206 
207     /**
208      *  renders the #foreach() block
209      * @param context
210      * @param writer
211      * @param node
212      * @return True if the directive rendered successfully.
213      * @throws IOException
214      */
215     public boolean render(InternalContextAdapter context, Writer writer, Node node)
216         throws IOException
217     {
218         Iterator i = getIterator(context, node.jjtGetChild(2));
219         if (i == null)
220         {
221             return false;
222         }
223 
224         // Get the block ast tree which is always the last child
225         Node block = node.jjtGetChild(node.jjtGetNumChildren()-1);
226         
227         /*
228          * save the element key if there is one
229          */
230         Object o = context.get(elementKey);
231 
232         /*
233          * roll our own scope class instead of using preRender(ctx)'s
234          */
235         ForeachScope foreach = null;
236         if (isScopeProvided())
237         {
238             String name = getScopeName();
239             foreach = new ForeachScope(this, context.get(name));
240             context.put(name, foreach);
241         }
242 
243         int count = 1;
244         while (count <= maxNbrLoops && i.hasNext())
245         {
246             count++;
247 
248             put(context, elementKey, i.next());
249             if (isScopeProvided())
250             {
251                 // update the scope control
252                 foreach.index++;
253                 foreach.hasNext = i.hasNext();
254             }
255 
256             try
257             {
258                 renderBlock(context, writer, block);
259             }
260             catch (StopCommand stop)
261             {
262                 if (stop.isFor(this))
263                 {
264                     break;
265                 }
266                 else
267                 {
268                     // clean up first
269                     clean(context, o);
270                     throw stop;
271                 }
272             }
273         }
274         clean(context, o);
275         return true;
276     }
277 
278     protected void renderBlock(InternalContextAdapter context, Writer writer, Node block)
279         throws IOException
280     {
281         block.render(context, writer);
282     }
283 
284     protected void clean(InternalContextAdapter context, Object o)
285     {
286         /*
287          *  restores element key if exists
288          *  otherwise just removes
289          */
290         if (o != null)
291         {
292             context.put(elementKey, o);
293         }
294         else
295         {
296             context.remove(elementKey);
297         }
298 
299         // clean up after the ForeachScope
300         postRender(context);
301     }
302     
303     /**
304      * We do not allow a word token in any other arg position except for the 2nd since
305      * we are looking for the pattern #foreach($foo in $bar).
306      */
307     public void checkArgs(ArrayList<Integer> argtypes,  Token t, String templateName)
308       throws ParseException
309     {
310         if (argtypes.size() < 3)
311         {
312             throw new MacroParseException("Too few arguments to the #foreach directive", 
313               templateName, t);
314         }        
315         else if (argtypes.get(0) != ParserTreeConstants.JJTREFERENCE)
316         {
317             throw new MacroParseException("Expected argument 1 of #foreach to be a reference",
318                 templateName, t);
319         }      
320         else if (argtypes.get(1) != ParserTreeConstants.JJTWORD)
321         {
322             throw new MacroParseException("Expected word 'in' at argument position 2 in #foreach",
323                 templateName, t);
324         }        
325         else if (argtypes.get(2) == ParserTreeConstants.JJTWORD)
326         {
327             throw new MacroParseException("Argument 3 of #foreach is of the wrong type",
328                 templateName, t);
329         }
330     }        
331 }