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 * <tools> 65 * <toolbox scope="request"> 66 * <tool class="org.apache.velocity.tools.generic.IteratorTool"/> 67 * </toolbox> 68 * </tools> 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 }