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.Iterator;
25  
26  import org.apache.velocity.app.event.EventCartridge;
27  import org.apache.velocity.context.Context;
28  import org.apache.velocity.context.InternalContextAdapter;
29  import org.apache.velocity.exception.MethodInvocationException;
30  import org.apache.velocity.exception.ParseErrorException;
31  import org.apache.velocity.exception.ResourceNotFoundException;
32  import org.apache.velocity.exception.TemplateInitException;
33  import org.apache.velocity.runtime.RuntimeConstants;
34  import org.apache.velocity.runtime.RuntimeServices;
35  import org.apache.velocity.runtime.parser.node.ASTReference;
36  import org.apache.velocity.runtime.parser.node.Node;
37  import org.apache.velocity.runtime.parser.node.SimpleNode;
38  import org.apache.velocity.runtime.resource.Resource;
39  import org.apache.velocity.util.introspection.Info;
40  import org.apache.velocity.util.introspection.IntrospectionCacheData;
41  
42  /**
43   * Foreach directive used for moving through arrays,
44   * or objects that provide an Iterator.
45   *
46   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
47   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
48   * @author Daniel Rall
49   * @version $Id: Foreach.java 479060 2006-11-25 00:30:19Z henning $
50   */
51  public class Foreach extends Directive
52  {
53      /**
54       * A special context to use when the foreach iterator returns a null.  This
55       * is required since the standard context may not support nulls.
56       * All puts and gets are passed through, except for the foreach iterator key.
57       */
58      protected static class NullHolderContext implements InternalContextAdapter
59      {
60          private InternalContextAdapter  innerContext = null;
61          private String   loopVariableKey = "";
62          private boolean  active = true;
63  
64          /**
65           * Create the context as a wrapper to be used within the foreach
66           * @param key the reference used in the foreach
67           * @param context the parent context
68           */
69          private NullHolderContext( String key, InternalContextAdapter context )
70          {
71             innerContext = context;
72             if( key != null )
73                 loopVariableKey = key;
74          }
75  
76          /**
77           * Get an object from the context, or null if the key is equal to the loop variable
78           * @see org.apache.velocity.context.InternalContextAdapter#get(java.lang.String)
79           * @exception MethodInvocationException passes on potential exception from reference method call
80           */
81          public Object get( String key ) throws MethodInvocationException
82          {
83              return ( active && loopVariableKey.equals(key) )
84                  ? null
85                  : innerContext.get(key);
86          }
87  
88          /**
89           * @see org.apache.velocity.context.InternalContextAdapter#put(java.lang.String key, java.lang.Object value)
90           */
91          public Object put( String key, Object value )
92          {
93              if( loopVariableKey.equals(key) && (value == null) )
94              {
95                  active = true;
96              }
97  
98              return innerContext.put( key, value );
99          }
100 
101         /**
102          * Allows callers to explicitly put objects in the local context.
103          * Objects added to the context through this method always end up
104          * in the top-level context of possible wrapped contexts.
105          *
106          * @param key name of item to set.
107          * @param value object to set to key.
108          * @see org.apache.velocity.context.InternalWrapperContext#localPut(String, Object)
109          */        
110         public Object localPut(final String key, final Object value)
111         {
112             return put(key, value);
113         }
114 
115        /**
116          * Does the context contain the key
117          * @see org.apache.velocity.context.InternalContextAdapter#containsKey(java.lang.Object key)
118          */
119         public boolean containsKey( Object key )
120         {
121             return innerContext.containsKey(key);
122         }
123 
124         /**
125          * @see org.apache.velocity.context.InternalContextAdapter#getKeys()
126          */
127         public Object[] getKeys()
128         {
129            return innerContext.getKeys();
130         }
131 
132         /**
133          * Remove an object from the context
134          * @see org.apache.velocity.context.InternalContextAdapter#remove(java.lang.Object key)
135          */
136         public Object remove(Object key)
137         {
138            if( loopVariableKey.equals(key) )
139            {
140              active = false;
141            }
142            return innerContext.remove(key);
143         }
144 
145         /**
146          * @see org.apache.velocity.context.InternalContextAdapter#pushCurrentTemplateName(java.lang.String s)
147          */
148         public void pushCurrentTemplateName(String s)
149         {
150             innerContext.pushCurrentTemplateName(s);
151         }
152 
153         /**
154          * @see org.apache.velocity.context.InternalContextAdapter#popCurrentTemplateName()
155          */
156         public void popCurrentTemplateName()
157         {
158             innerContext.popCurrentTemplateName();
159         }
160 
161         /**
162          * @see org.apache.velocity.context.InternalContextAdapter#getCurrentTemplateName()
163          */
164         public String getCurrentTemplateName()
165         {
166             return innerContext.getCurrentTemplateName();
167         }
168 
169         /**
170          * @see org.apache.velocity.context.InternalContextAdapter#getTemplateNameStack()
171          */
172         public Object[] getTemplateNameStack()
173         {
174             return innerContext.getTemplateNameStack();
175         }
176 
177         /**
178          * @see org.apache.velocity.context.InternalContextAdapter#icacheGet(java.lang.Object key)
179          */
180         public IntrospectionCacheData icacheGet(Object key)
181         {
182             return innerContext.icacheGet(key);
183         }
184 
185         /**
186          * @see org.apache.velocity.context.InternalContextAdapter#icachePut(java.lang.Object key, org.apache.velocity.util.introspection.IntrospectionCacheData o)
187          */
188         public void icachePut(Object key, IntrospectionCacheData o)
189         {
190             innerContext.icachePut(key,o);
191         }
192 
193         /**
194          * @see org.apache.velocity.context.InternalContextAdapter#setCurrentResource(org.apache.velocity.runtime.resource.Resource r)
195          */
196         public void setCurrentResource( Resource r )
197         {
198             innerContext.setCurrentResource(r);
199         }
200 
201         /**
202          * @see org.apache.velocity.context.InternalContextAdapter#getCurrentResource()
203          */
204         public Resource getCurrentResource()
205         {
206             return innerContext.getCurrentResource();
207         }
208 
209         /**
210          * @see org.apache.velocity.context.InternalContextAdapter#getBaseContext()
211          */
212         public InternalContextAdapter getBaseContext()
213         {
214             return innerContext.getBaseContext();
215         }
216 
217         /**
218          * @see org.apache.velocity.context.InternalContextAdapter#getInternalUserContext()
219          */
220         public Context getInternalUserContext()
221         {
222             return innerContext.getInternalUserContext();
223         }
224 
225         /**
226          * @see org.apache.velocity.context.InternalContextAdapter#attachEventCartridge(org.apache.velocity.app.event.EventCartridge ec)
227          */
228         public EventCartridge attachEventCartridge(EventCartridge ec)
229         {
230             EventCartridge cartridge = innerContext.attachEventCartridge( ec );
231 
232             return cartridge;
233         }
234 
235         /**
236          * @see org.apache.velocity.context.InternalContextAdapter#getEventCartridge()
237          */
238         public EventCartridge getEventCartridge()
239         {
240             return innerContext.getEventCartridge();
241         }
242 
243         /**
244          * @see org.apache.velocity.context.InternalContextAdapter#getAllowRendering()
245          */
246         public boolean getAllowRendering()
247         {
248             return innerContext.getAllowRendering();
249         }
250 
251         /**
252          * @see org.apache.velocity.context.InternalContextAdapter#setAllowRendering(boolean v)
253          */
254         public void setAllowRendering(boolean v)
255         {
256             innerContext.setAllowRendering(v);
257         }
258 
259     }
260 
261     /**
262      * Return name of this directive.
263      * @return The name of this directive.
264      */
265     public String getName()
266     {
267         return "foreach";
268     }
269 
270     /**
271      * Return type of this directive.
272      * @return The type of this directive.
273      */
274     public int getType()
275     {
276         return BLOCK;
277     }
278 
279     /**
280      * The name of the variable to use when placing
281      * the counter value into the context. Right
282      * now the default is $velocityCount.
283      */
284     private String counterName;
285 
286     /**
287      * What value to start the loop counter at.
288      */
289     private int counterInitialValue;
290 
291     /**
292      * The maximum number of times we're allowed to loop.
293      */
294     private int maxNbrLoops;
295 
296     /**
297      * The reference name used to access each
298      * of the elements in the list object. It
299      * is the $item in the following:
300      *
301      * #foreach ($item in $list)
302      *
303      * This can be used class wide because
304      * it is immutable.
305      */
306     private String elementKey;
307 
308     /**
309      *  immutable, so create in init
310      */
311     protected Info uberInfo;
312 
313     /**
314      *  simple init - init the tree and get the elementKey from
315      *  the AST
316      * @param rs
317      * @param context
318      * @param node
319      * @throws TemplateInitException
320      */
321     public void init(RuntimeServices rs, InternalContextAdapter context, Node node)
322         throws TemplateInitException
323     {
324         super.init(rs, context, node);
325 
326         counterName = rsvc.getString(RuntimeConstants.COUNTER_NAME);
327         counterInitialValue = rsvc.getInt(RuntimeConstants.COUNTER_INITIAL_VALUE);
328         maxNbrLoops = rsvc.getInt(RuntimeConstants.MAX_NUMBER_LOOPS,
329                                   Integer.MAX_VALUE);
330         if (maxNbrLoops < 1)
331         {
332             maxNbrLoops = Integer.MAX_VALUE;
333         }
334 
335         /*
336          *  this is really the only thing we can do here as everything
337          *  else is context sensitive
338          */
339 
340         SimpleNode sn = (SimpleNode) node.jjtGetChild(0);
341 
342         if (sn instanceof ASTReference)
343         {
344             elementKey = ((ASTReference) sn).getRootString();
345         }
346         else
347         {
348             /*
349              * the default, error-prone way which we'll remove
350              *  TODO : remove if all goes well
351              */
352             elementKey = sn.getFirstToken().image.substring(1);
353         }
354 
355         /*
356          * make an uberinfo - saves new's later on
357          */
358 
359         uberInfo = new Info(context.getCurrentTemplateName(),
360                 getLine(),getColumn());
361     }
362 
363     /**
364      *  renders the #foreach() block
365      * @param context
366      * @param writer
367      * @param node
368      * @return True if the directive rendered successfully.
369      * @throws IOException
370      * @throws MethodInvocationException
371      * @throws ResourceNotFoundException
372      * @throws ParseErrorException
373      */
374     public boolean render(InternalContextAdapter context,
375                            Writer writer, Node node)
376         throws IOException,  MethodInvocationException, ResourceNotFoundException,
377         	ParseErrorException
378     {
379         /*
380          *  do our introspection to see what our collection is
381          */
382 
383         Object listObject = node.jjtGetChild(2).value(context);
384 
385         if (listObject == null)
386              return false;
387 
388         Iterator i = null;
389 
390         try
391         {
392             i = rsvc.getUberspect().getIterator(listObject, uberInfo);
393         }
394         /**
395          * pass through application level runtime exceptions
396          */
397         catch( RuntimeException e )
398         {
399             throw e;
400         }
401         catch(Exception ee)
402         {
403             rsvc.getLog().error("Error getting iterator for #foreach", ee);
404         }
405 
406         if (i == null)
407         {
408             return false;
409         }
410 
411         int counter = counterInitialValue;
412         boolean maxNbrLoopsExceeded = false;
413 
414         /*
415          *  save the element key if there is one, and the loop counter
416          */
417         Object o = context.get(elementKey);
418         Object savedCounter = context.get(counterName);
419 
420         /*
421          * Instantiate the null holder context if a null value
422          * is returned by the foreach iterator.  Only one instance is
423          * created - it's reused for every null value.
424          */
425         NullHolderContext nullHolderContext = null;
426 
427         while (!maxNbrLoopsExceeded && i.hasNext())
428         {
429             // TODO: JDK 1.4+ -> valueOf()
430             context.localPut(counterName , new Integer(counter));
431             Object value = i.next();
432             context.localPut(elementKey, value);
433 
434             /*
435              * If the value is null, use the special null holder context
436              */
437             if( value == null )
438             {
439                 if( nullHolderContext == null )
440                 {
441                     // lazy instantiation
442                     nullHolderContext = new NullHolderContext(elementKey, context);
443                 }
444                 node.jjtGetChild(3).render(nullHolderContext, writer);
445             }
446             else
447             {
448                 node.jjtGetChild(3).render(context, writer);
449             }
450             counter++;
451 
452             // Determine whether we're allowed to continue looping.
453             // ASSUMPTION: counterInitialValue is not negative!
454             maxNbrLoopsExceeded = (counter - counterInitialValue) >= maxNbrLoops;
455         }
456 
457         /*
458          * restores the loop counter (if we were nested)
459          * if we have one, else just removes
460          */
461 
462         if (savedCounter != null)
463         {
464             context.put(counterName, savedCounter);
465         }
466         else
467         {
468             context.remove(counterName);
469         }
470 
471 
472         /*
473          *  restores element key if exists
474          *  otherwise just removes
475          */
476 
477         if (o != null)
478         {
479             context.put(elementKey, o);
480         }
481         else
482         {
483             context.remove(elementKey);
484         }
485 
486         return true;
487     }
488 }