View Javadoc

1   package org.apache.velocity.tools.generic;
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.util.Collection;
23  import java.util.Enumeration;
24  import java.util.Iterator;
25  import java.util.Map;
26  import org.apache.velocity.util.ArrayIterator;
27  import org.apache.velocity.util.EnumerationIterator;
28  import org.apache.velocity.tools.config.DefaultKey;
29  
30  /**
31   * <p>
32   * A convenience tool to use with #foreach loops. It wraps a list
33   * to let the designer specify a condition to terminate the loop,
34   * and reuse the same list in different loops.
35   * </p>
36   * <p>
37   * Example of use:
38   * <pre>
39   *  Java
40   *  ----
41   *  context.put("mill", new IteratorTool());
42   *
43   *
44   *  VTL
45   *  ---
46   *
47   *  #set ($list = [1, 2, 3, 5, 8, 13])
48   *  #set ($numbers = $mill.wrap($list))
49   *
50   *  #foreach ($item in $numbers)
51   *  #if ($item < 8) $numbers.more()#end
52   *  #end
53   *
54   *  $numbers.more()
55   *
56   *
57   *  Output
58   *  ------
59   *
60   *   1 2 3 5
61   *  8
62   *
63   * Example tools.xml config (if you want to use this with VelocityView):
64   * &lt;tools&gt;
65   *   &lt;toolbox scope="request"&gt;
66   *     &lt;tool class="org.apache.velocity.tools.generic.IteratorTool"/&gt;
67   *   &lt;/toolbox&gt;
68   * &lt;/tools&gt;
69   * </pre>
70   * </p>
71   * <p>
72   * <b>Warning:</b> It is not recommended to use hasNext() with this
73   * tool as it is used to control the #foreach. Use hasMore() instead.
74   * </p>
75   *
76   * @author <a href="mailto:jido@respublica.fr">Denis Bredelet</a>
77   * @version $Id: IteratorTool.java 598471 2007-11-27 00:26:10Z nbubna $
78   * @deprecated Use {@link LoopTool} instead
79   */
80  @DefaultKey("mill")
81  @Deprecated
82  public class IteratorTool implements Iterator {
83  
84  
85      private Object wrapped;
86      private Iterator iterator;
87      private boolean wantMore;
88      private boolean cachedNext;
89      protected Object next;
90  
91  
92      /**
93       * Create a IteratorTool instance to use as tool.
94       * When it is created this way, the tool returns a new
95       * instance each time wrap() is called. This is
96       * useful when you want to allow the designers to create instances.
97       */
98      public IteratorTool()
99      {
100         this(null);
101     }
102 
103 
104     /**
105      * Create a IteratorTool instance to use in #foreach.
106      *
107      * @param wrapped The list to wrap.
108      */
109     public IteratorTool(Object wrapped)
110     {
111         internalWrap(wrapped);
112     }
113 
114 
115     /**
116      * Wraps a list with the tool.
117      * <br>The list can be an array, a Collection, a Map, an Iterator
118      * or an Enumeration.
119      * <br>If the list is a Map, the tool iterates over the values.
120      * <br>If the list is an Iterator or an Enumeration, the tool can
121      * be used only once.
122      *
123      * @param list The list to wrap.
124      * @return A new wrapper if this object is used as a tool, or
125      *         itself if it is a wrapper.
126      */
127     public IteratorTool wrap(Object list)
128     {
129         if (this.wrapped == null)
130         {
131             return new IteratorTool(list);
132         }
133         else if (list != null)
134         {
135             internalWrap(list);
136             return this;
137         }
138         else
139         {
140             throw new IllegalArgumentException("Need a valid list to wrap");
141         }
142     }
143 
144 
145     /**
146      * Wraps a list with the tool. This object can therefore
147      * be used instead of the list itself in a #foreach.
148      * The list can be an array, a Collection, a Map, an
149      * Iterator or an Enumeration.
150      * <br>- If the list is a Map, the tool iterates over the values.
151      * <br>- If the list is an Iterator or an Enumeration, the tool
152      * can be used only once.
153      *
154      * @param wrapped The list to wrap.
155      */
156     private void internalWrap(Object wrapped)
157     {
158         if (wrapped != null)
159         {
160             /* rip-off from org/apache/velocity/runtime/directive/ForEach.java */
161             if (wrapped.getClass().isArray())
162             {
163                 this.iterator = new ArrayIterator((Object[])wrapped);
164             }
165             else if (wrapped instanceof Collection)
166             {
167                 this.iterator = ((Collection)wrapped).iterator();
168             }
169             else if (wrapped instanceof Map)
170             {
171                 this.iterator = ((Map)wrapped).values().iterator();
172             }
173             else if (wrapped instanceof Iterator)
174             {
175                 this.iterator = (Iterator)wrapped;
176             }
177             else if (wrapped instanceof Enumeration)
178             {
179                 this.iterator = new EnumerationIterator((Enumeration)wrapped);
180             }
181             else
182             {
183                 /* Don't know what is the object.
184                  * Should we put it in a one-item array? */
185                 throw new IllegalArgumentException("Don't know how to wrap: "+wrapped);
186             }
187 
188             this.wrapped = wrapped;
189             this.wantMore = true;
190             this.cachedNext = false;
191         }
192         else
193         {
194             this.iterator = null;
195             this.wrapped = null;
196             this.wantMore = false;
197             this.cachedNext = false;
198         }
199     }
200 
201 
202     /**
203      * <p>
204      * Resets the wrapper so that it starts over at the beginning of the list.
205      * </p>
206      * <p>
207      * <b>Note to programmers:</b> This method has no effect if the wrapped
208      * object is an enumeration or an iterator.
209      */
210     public void reset()
211     {
212         if (this.wrapped != null)
213         {
214             internalWrap(this.wrapped);
215         }
216     }
217 
218 
219     /**
220      * <p>
221      * Gets the next object in the list. This method is called
222      * by #foreach to define $item in:
223      * <pre>
224      * #foreach( $item in $list )
225      * </pre>
226      * </p>
227      * <p>
228      * This method is not intended for template designers, but they can use
229      * them if they want to read the value of the next item without doing
230      * more().
231      * </p>
232      *
233      * @return The next item in the list.
234      * @throws NoSuchElementException if there are no more
235      *         elements in the list.
236      */
237     public Object next()
238     {
239         if (this.wrapped == null)
240         {
241             throw new IllegalStateException("Use wrap() before calling next()");
242         }
243 
244         if (!this.cachedNext)
245         {
246             this.cachedNext = true;
247             this.next = this.iterator.next();
248             return this.next;
249         }
250         else
251         {
252             return this.next;
253         }
254     }
255 
256     /**
257      * Returns true if there are more elements in the
258      * list and more() was called.
259      * <br>This code always return false:
260      * <pre>
261      * tool.hasNext()? tool.hasNext(): false;
262      * </pre>
263      *
264      * @return true if there are more elements, and either more()
265      *         or hasNext() was called since last call.
266      */
267     public boolean hasNext()
268     {
269         if (this.wantMore)
270         {
271             /* don't want more unless more is called */
272             this.wantMore = false;
273             return hasMore();
274         }
275         else
276         {
277             /* prepare for next #foreach */
278             this.wantMore = true;
279             return false;
280         }
281     }
282 
283     /**
284      * Removes the current element from the list.
285      * The current element is defined as the last element that was read
286      * from the list, either with next() or with more().
287      *
288      * @throws UnsupportedOperationException if the wrapped list
289      *  iterator doesn't support this operation.
290      */
291     public void remove() throws UnsupportedOperationException
292     {
293         if (this.wrapped == null)
294         {
295             throw new IllegalStateException("Use wrap() before calling remove()");
296         }
297 
298         /* Let the iterator decide whether to implement this or not */
299         this.iterator.remove();
300     }
301 
302 
303     /**
304      * <p>
305      * Asks for the next element in the list. This method is to be used
306      * by the template designer in #foreach loops.
307      * </p>
308      * <p>
309      * If this method is called in the body of #foreach, the loop
310      * continues as long as there are elements in the list.
311      * <br>If this method is not called the loop terminates after the
312      * current iteration.
313      * </p>
314      *
315      * @return The next element in the list, or null if there are no
316      *         more elements.
317      */
318     public Object more()
319     {
320         this.wantMore = true;
321         if (hasMore())
322         {
323             Object next = next();
324             this.cachedNext = false;
325             return next;
326         }
327         else
328         {
329             return null;
330         }
331     }
332 
333     /**
334      * Returns true if there are more elements in the wrapped list.
335      * <br>If this object doesn't wrap a list, the method always returns false.
336      *
337      * @return true if there are more elements in the list.
338      */
339     public boolean hasMore()
340     {
341         if (this.wrapped == null)
342         {
343             return false;
344         }
345         return cachedNext || this.iterator.hasNext();
346     }
347 
348 
349     /**
350      * Puts a condition to break out of the loop.
351      * The #foreach loop will terminate after this iteration, unless more()
352      * is called after stop().
353      */
354     public void stop()
355     {
356         this.wantMore = false;
357     }
358 
359 
360     /**
361      * Returns this object as a String.
362      * <br>If this object is used as a tool, it just gives the class name.
363      * <br>Otherwise it appends the wrapped list to the class name.
364      *
365      * @return A string representation of this object.
366      */
367     public String toString()
368     {
369         StringBuilder out = new StringBuilder(this.getClass().getName());
370         if (this.wrapped != null)
371         {
372             out.append('(');
373             out.append(this.wrapped);
374             out.append(')');
375         }
376         return out.toString();
377     }
378 
379 
380 }