001: package org.apache.velocity.tools.generic;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.util.Collection;
023: import java.util.Enumeration;
024: import java.util.Iterator;
025: import java.util.Map;
026:
027: import org.apache.velocity.util.ArrayIterator;
028: import org.apache.velocity.util.EnumerationIterator;
029:
030: /**
031: * <p>
032: * A convenience tool to use with #foreach loops. It wraps a list
033: * to let the designer specify a condition to terminate the loop,
034: * and reuse the same list in different loops.
035: * </p>
036: * <p>
037: * Example of use:
038: * <pre>
039: * Java
040: * ----
041: * context.put("mill", new IteratorTool());
042: *
043: *
044: * VTL
045: * ---
046: *
047: * #set ($list = [1, 2, 3, 5, 8, 13])
048: * #set ($numbers = $mill.wrap($list))
049: *
050: * #foreach ($item in $numbers)
051: * #if ($item < 8) $numbers.more()#end
052: * #end
053: *
054: * $numbers.more()
055: *
056: *
057: * Output
058: * ------
059: *
060: * 1 2 3 5
061: * 8
062: *
063: * Example toolbox.xml config (if you want to use this with VelocityView):
064: * <tool>
065: * <key>mill</key>
066: * <scope>request</scope>
067: * <class>org.apache.velocity.tools.generic.IteratorTool</class>
068: * </tool>
069: * </pre>
070: * </p>
071: * <p>
072: * <b>Warning:</b> It is not recommended to use hasNext() with this
073: * tool as it is used to control the #foreach. Use hasMore() instead.
074: * </p>
075: *
076: * @author <a href="mailto:jido@respublica.fr">Denis Bredelet</a>
077: * @version $Id: IteratorTool.java 477914 2006-11-21 21:52:11Z henning $
078: */
079:
080: public class IteratorTool implements Iterator {
081:
082: private Object wrapped;
083: private Iterator iterator;
084: private boolean wantMore;
085: private boolean cachedNext;
086: protected Object next;
087:
088: /**
089: * Create a IteratorTool instance to use as tool.
090: * When it is created this way, the tool returns a new
091: * instance each time wrap() is called. This is
092: * useful when you want to allow the designers to create instances.
093: */
094: public IteratorTool() {
095: this (null);
096: }
097:
098: /**
099: * Create a IteratorTool instance to use in #foreach.
100: *
101: * @param wrapped The list to wrap.
102: */
103: public IteratorTool(Object wrapped) {
104: internalWrap(wrapped);
105: }
106:
107: /**
108: * Wraps a list with the tool.
109: * <br>The list can be an array, a Collection, a Map, an Iterator
110: * or an Enumeration.
111: * <br>If the list is a Map, the tool iterates over the values.
112: * <br>If the list is an Iterator or an Enumeration, the tool can
113: * be used only once.
114: *
115: * @param list The list to wrap.
116: * @return A new wrapper if this object is used as a tool, or
117: * itself if it is a wrapper.
118: */
119: public IteratorTool wrap(Object list) {
120: if (this .wrapped == null) {
121: return new IteratorTool(list);
122: } else if (list != null) {
123: internalWrap(list);
124: return this ;
125: } else {
126: throw new IllegalArgumentException(
127: "Need a valid list to wrap");
128: }
129: }
130:
131: /**
132: * Wraps a list with the tool. This object can therefore
133: * be used instead of the list itself in a #foreach.
134: * The list can be an array, a Collection, a Map, an
135: * Iterator or an Enumeration.
136: * <br>- If the list is a Map, the tool iterates over the values.
137: * <br>- If the list is an Iterator or an Enumeration, the tool
138: * can be used only once.
139: *
140: * @param wrapped The list to wrap.
141: */
142: private void internalWrap(Object wrapped) {
143: if (wrapped != null) {
144: /* rip-off from org/apache/velocity/runtime/directive/ForEach.java */
145: if (wrapped.getClass().isArray()) {
146: this .iterator = new ArrayIterator((Object[]) wrapped);
147: } else if (wrapped instanceof Collection) {
148: this .iterator = ((Collection) wrapped).iterator();
149: } else if (wrapped instanceof Map) {
150: this .iterator = ((Map) wrapped).values().iterator();
151: } else if (wrapped instanceof Iterator) {
152: this .iterator = (Iterator) wrapped;
153: } else if (wrapped instanceof Enumeration) {
154: this .iterator = new EnumerationIterator(
155: (Enumeration) wrapped);
156: } else {
157: /* Don't know what is the object.
158: * Should we put it in a one-item array? */
159: throw new IllegalArgumentException(
160: "Don't know how to wrap this list");
161: }
162:
163: this .wrapped = wrapped;
164: this .wantMore = true;
165: this .cachedNext = false;
166: } else {
167: this .iterator = null;
168: this .wrapped = null;
169: this .wantMore = false;
170: this .cachedNext = false;
171: }
172: }
173:
174: /**
175: * <p>
176: * Resets the wrapper so that it starts over at the beginning of the list.
177: * </p>
178: * <p>
179: * <b>Note to programmers:</b> This method has no effect if the wrapped
180: * object is an enumeration or an iterator.
181: */
182: public void reset() {
183: if (this .wrapped != null) {
184: internalWrap(this .wrapped);
185: }
186: }
187:
188: /**
189: * <p>
190: * Gets the next object in the list. This method is called
191: * by #foreach to define $item in:
192: * <pre>
193: * #foreach( $item in $list )
194: * </pre>
195: * </p>
196: * <p>
197: * This method is not intended for template designers, but they can use
198: * them if they want to read the value of the next item without doing
199: * more().
200: * </p>
201: *
202: * @return The next item in the list.
203: * @throws NoSuchElementException if there are no more
204: * elements in the list.
205: */
206: public Object next() {
207: if (this .wrapped == null) {
208: throw new IllegalStateException(
209: "Use wrap() before calling next()");
210: }
211:
212: if (!this .cachedNext) {
213: this .cachedNext = true;
214: this .next = this .iterator.next();
215: return this .next;
216: } else {
217: return this .next;
218: }
219: }
220:
221: /**
222: * Returns true if there are more elements in the
223: * list and more() was called.
224: * <br>This code always return false:
225: * <pre>
226: * tool.hasNext()? tool.hasNext(): false;
227: * </pre>
228: *
229: * @return true if there are more elements, and either more()
230: * or hasNext() was called since last call.
231: */
232: public boolean hasNext() {
233: if (this .wantMore) {
234: /* don't want more unless more is called */
235: this .wantMore = false;
236: return hasMore();
237: } else {
238: /* prepare for next #foreach */
239: this .wantMore = true;
240: return false;
241: }
242: }
243:
244: /**
245: * Removes the current element from the list.
246: * The current element is defined as the last element that was read
247: * from the list, either with next() or with more().
248: *
249: * @throws UnsupportedOperationException if the wrapped list
250: * iterator doesn't support this operation.
251: */
252: public void remove() throws UnsupportedOperationException {
253: if (this .wrapped == null) {
254: throw new IllegalStateException(
255: "Use wrap() before calling remove()");
256: }
257:
258: /* Let the iterator decide whether to implement this or not */
259: this .iterator.remove();
260: }
261:
262: /**
263: * <p>
264: * Asks for the next element in the list. This method is to be used
265: * by the template designer in #foreach loops.
266: * </p>
267: * <p>
268: * If this method is called in the body of #foreach, the loop
269: * continues as long as there are elements in the list.
270: * <br>If this method is not called the loop terminates after the
271: * current iteration.
272: * </p>
273: *
274: * @return The next element in the list, or null if there are no
275: * more elements.
276: */
277: public Object more() {
278: this .wantMore = true;
279: if (hasMore()) {
280: Object next = next();
281: this .cachedNext = false;
282: return next;
283: } else {
284: return null;
285: }
286: }
287:
288: /**
289: * Returns true if there are more elements in the wrapped list.
290: * <br>If this object doesn't wrap a list, the method always returns false.
291: *
292: * @return true if there are more elements in the list.
293: */
294: public boolean hasMore() {
295: if (this .wrapped == null) {
296: return false;
297: }
298: return cachedNext || this .iterator.hasNext();
299: }
300:
301: /**
302: * Puts a condition to break out of the loop.
303: * The #foreach loop will terminate after this iteration, unless more()
304: * is called after stop().
305: */
306: public void stop() {
307: this .wantMore = false;
308: }
309:
310: /**
311: * Returns this object as a String.
312: * <br>If this object is used as a tool, it just gives the class name.
313: * <br>Otherwise it appends the wrapped list to the class name.
314: *
315: * @return A string representation of this object.
316: */
317: public String toString() {
318: StringBuffer out = new StringBuffer(this .getClass().getName());
319: if (this .wrapped != null) {
320: out.append('(');
321: out.append(this .wrapped);
322: out.append(')');
323: }
324: return out.toString();
325: }
326:
327: }
|