1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.commons.betwixt.expression; 19 20 import java.lang.reflect.Array; 21 import java.util.Collection; 22 import java.util.Collections; 23 import java.util.Enumeration; 24 import java.util.Iterator; 25 import java.util.Map; 26 import java.util.NoSuchElementException; 27 28 29 /** <p><code>IteratorExpression</code> returns an iterator over the current context.</p> 30 * 31 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> 32 * @version $Revision: 438373 $ 33 */ 34 public class IteratorExpression implements Expression { 35 36 /** Use this <code>Expression</code> to perform initial evaluation*/ 37 private Expression expression; 38 39 /** 40 * Construct <code>IteratorExpression</code> using given expression for initial evaluation. 41 * @param expression this expression will be evaluated and the result converted to an 42 * iterator. 43 */ 44 public IteratorExpression(Expression expression) { 45 this.expression = expression; 46 } 47 48 /** 49 * Returns an interator over the current context 50 * @see org.apache.commons.betwixt.expression.Expression 51 */ 52 public Object evaluate(Context context) { 53 // evaluate wrapped expression against context 54 Object value = expression.evaluate( context ); 55 56 // based on the class of the result, 57 // return an appropriate iterator 58 if ( value instanceof Iterator ) { 59 // if the value is an iterator, we're done 60 return (Iterator) value; 61 62 } else if ( value instanceof Collection ) { 63 // if it's a collection, return an iterator for that collection 64 Collection collection = (Collection) value; 65 return collection.iterator(); 66 67 } else if ( value instanceof Map ) { 68 // if it's a map, return an iterator for the map entries 69 Map map = (Map) value; 70 return map.entrySet().iterator(); 71 72 } else if ( value instanceof Enumeration ) { 73 // if it's an enumeration, wrap it in an EnumerationIterator 74 return new EnumerationIterator( (Enumeration) value ); 75 76 } else if ( value != null ) { 77 // if we have an array return an ArrayIterator 78 Class type = value.getClass(); 79 if ( type.isArray() ) { 80 return new ArrayIterator( value ); 81 } 82 } 83 84 // we've got something we can't deal with 85 // so return an empty iterator 86 return Collections.EMPTY_LIST.iterator(); 87 } 88 89 /** 90 * Do nothing 91 * @see org.apache.commons.betwixt.expression.Expression 92 */ 93 public void update(Context context, String newValue) { 94 // do nothing 95 } 96 97 /** 98 * Returns something useful for logging 99 * @return string useful for logging 100 */ 101 public String toString() { 102 return "IteratorExpression [expression=" + expression + "]"; 103 } 104 105 106 /** 107 * <code>ArrayIterator</code> originated in commons-collections. Added 108 * as a private inner class to break dependency. 109 * 110 * @author James Strachan 111 * @author Mauricio S. Moura 112 * @author Michael A. Smith 113 * @author Neil O'Toole 114 * @author Stephen Colebourne 115 */ 116 private static final class ArrayIterator implements Iterator { 117 118 /** The array to iterate over */ 119 protected Object array; 120 121 /** The start index to loop from */ 122 protected int startIndex = 0; 123 124 /** The end index to loop to */ 125 protected int endIndex = 0; 126 127 /** The current iterator index */ 128 protected int index = 0; 129 130 // Constructors 131 // ---------------------------------------------------------------------- 132 /** 133 * Constructor for use with <code>setArray</code>. 134 * <p> 135 * Using this constructor, the iterator is equivalent to an empty 136 * iterator until {@link #setArray(Object)}is called to establish the 137 * array to iterate over. 138 */ 139 public ArrayIterator() { 140 super(); 141 } 142 143 /** 144 * Constructs an ArrayIterator that will iterate over the values in the 145 * specified array. 146 * 147 * @param array 148 * the array to iterate over. 149 * @throws IllegalArgumentException 150 * if <code>array</code> is not an array. 151 * @throws NullPointerException 152 * if <code>array</code> is <code>null</code> 153 */ 154 public ArrayIterator(final Object array) { 155 super(); 156 setArray(array); 157 } 158 159 /** 160 * Constructs an ArrayIterator that will iterate over the values in the 161 * specified array from a specific start index. 162 * 163 * @param array 164 * the array to iterate over. 165 * @param startIndex 166 * the index to start iterating at. 167 * @throws IllegalArgumentException 168 * if <code>array</code> is not an array. 169 * @throws NullPointerException 170 * if <code>array</code> is <code>null</code> 171 * @throws IndexOutOfBoundsException 172 * if the index is invalid 173 */ 174 public ArrayIterator(final Object array, final int startIndex) { 175 super(); 176 setArray(array); 177 checkBound(startIndex, "start"); 178 this.startIndex = startIndex; 179 this.index = startIndex; 180 } 181 182 /** 183 * Construct an ArrayIterator that will iterate over a range of values 184 * in the specified array. 185 * 186 * @param array 187 * the array to iterate over. 188 * @param startIndex 189 * the index to start iterating at. 190 * @param endIndex 191 * the index to finish iterating at. 192 * @throws IllegalArgumentException 193 * if <code>array</code> is not an array. 194 * @throws NullPointerException 195 * if <code>array</code> is <code>null</code> 196 * @throws IndexOutOfBoundsException 197 * if either index is invalid 198 */ 199 public ArrayIterator(final Object array, final int startIndex, 200 final int endIndex) { 201 super(); 202 setArray(array); 203 checkBound(startIndex, "start"); 204 checkBound(endIndex, "end"); 205 if (endIndex < startIndex) { 206 throw new IllegalArgumentException( 207 "End index must not be less than start index."); 208 } 209 this.startIndex = startIndex; 210 this.endIndex = endIndex; 211 this.index = startIndex; 212 } 213 214 /** 215 * Checks whether the index is valid or not. 216 * 217 * @param bound 218 * the index to check 219 * @param type 220 * the index type (for error messages) 221 * @throws IndexOutOfBoundsException 222 * if the index is invalid 223 */ 224 protected void checkBound(final int bound, final String type) { 225 if (bound > this.endIndex) { 226 throw new ArrayIndexOutOfBoundsException( 227 "Attempt to make an ArrayIterator that " + type 228 + "s beyond the end of the array. "); 229 } 230 if (bound < 0) { 231 throw new ArrayIndexOutOfBoundsException( 232 "Attempt to make an ArrayIterator that " + type 233 + "s before the start of the array. "); 234 } 235 } 236 237 // Iterator interface 238 //----------------------------------------------------------------------- 239 /** 240 * Returns true if there are more elements to return from the array. 241 * 242 * @return true if there is a next element to return 243 */ 244 public boolean hasNext() { 245 return (index < endIndex); 246 } 247 248 /** 249 * Returns the next element in the array. 250 * 251 * @return the next element in the array 252 * @throws NoSuchElementException 253 * if all the elements in the array have already been 254 * returned 255 */ 256 public Object next() { 257 if (hasNext() == false) { 258 throw new NoSuchElementException(); 259 } 260 return Array.get(array, index++); 261 } 262 263 /** 264 * Throws {@link UnsupportedOperationException}. 265 * 266 * @throws UnsupportedOperationException 267 * always 268 */ 269 public void remove() { 270 throw new UnsupportedOperationException( 271 "remove() method is not supported"); 272 } 273 274 // Properties 275 //----------------------------------------------------------------------- 276 /** 277 * Gets the array that this iterator is iterating over. 278 * 279 * @return the array this iterator iterates over, or <code>null</code> 280 * if the no-arg constructor was used and 281 * {@link #setArray(Object)}has never been called with a valid 282 * array. 283 */ 284 public Object getArray() { 285 return array; 286 } 287 288 /** 289 * Sets the array that the ArrayIterator should iterate over. 290 * <p> 291 * If an array has previously been set (using the single-arg constructor 292 * or this method) then that array is discarded in favour of this one. 293 * Iteration is restarted at the start of the new array. Although this 294 * can be used to reset iteration, the {@link #reset()}method is a more 295 * effective choice. 296 * 297 * @param array 298 * the array that the iterator should iterate over. 299 * @throws IllegalArgumentException 300 * if <code>array</code> is not an array. 301 * @throws NullPointerException 302 * if <code>array</code> is <code>null</code> 303 */ 304 public void setArray(final Object array) { 305 // Array.getLength throws IllegalArgumentException if the object is 306 // not 307 // an array or NullPointerException if the object is null. This call 308 // is made before saving the array and resetting the index so that 309 // the 310 // array iterator remains in a consistent state if the argument is 311 // not 312 // an array or is null. 313 this.endIndex = Array.getLength(array); 314 this.startIndex = 0; 315 this.array = array; 316 this.index = 0; 317 } 318 319 /** 320 * Resets the iterator back to the start index. 321 */ 322 public void reset() { 323 this.index = this.startIndex; 324 } 325 326 } 327 328 329 /** 330 * Adapter to make {@link Enumeration Enumeration}instances appear to be 331 * {@link Iterator Iterator}instances. Originated in commons-collections. 332 * Added as a private inner class to break dependency. 333 * 334 * @author <a href="mailto:jstrachan@apache.org">James Strachan </a> 335 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall </a> 336 */ 337 private static final class EnumerationIterator implements Iterator { 338 339 /** The collection to remove elements from */ 340 private Collection collection; 341 342 /** The enumeration being converted */ 343 private Enumeration enumeration; 344 345 /** The last object retrieved */ 346 private Object last; 347 348 // Constructors 349 //----------------------------------------------------------------------- 350 /** 351 * Constructs a new <code>EnumerationIterator</code> that will not 352 * function until {@link #setEnumeration(Enumeration)} is called. 353 */ 354 public EnumerationIterator() { 355 this(null, null); 356 } 357 358 /** 359 * Constructs a new <code>EnumerationIterator</code> that provides 360 * an iterator view of the given enumeration. 361 * 362 * @param enumeration the enumeration to use 363 */ 364 public EnumerationIterator(final Enumeration enumeration) { 365 this(enumeration, null); 366 } 367 368 /** 369 * Constructs a new <code>EnumerationIterator</code> that will remove 370 * elements from the specified collection. 371 * 372 * @param enumeration the enumeration to use 373 * @param collection the collection to remove elements form 374 */ 375 public EnumerationIterator(final Enumeration enumeration, 376 final Collection collection) { 377 super(); 378 this.enumeration = enumeration; 379 this.collection = collection; 380 this.last = null; 381 } 382 383 // Iterator interface 384 //----------------------------------------------------------------------- 385 /** 386 * Returns true if the underlying enumeration has more elements. 387 * 388 * @return true if the underlying enumeration has more elements 389 * @throws NullPointerException if the underlying enumeration is null 390 */ 391 public boolean hasNext() { 392 return enumeration.hasMoreElements(); 393 } 394 395 /** 396 * Returns the next object from the enumeration. 397 * 398 * @return the next object from the enumeration 399 * @throws NullPointerException if the enumeration is null 400 */ 401 public Object next() { 402 last = enumeration.nextElement(); 403 return last; 404 } 405 406 /** 407 * Removes the last retrieved element if a collection is attached. 408 * <p> 409 * Functions if an associated <code>Collection</code> is known. 410 * If so, the first occurrence of the last returned object from this 411 * iterator will be removed from the collection. 412 * 413 * @exception IllegalStateException <code>next()</code> not called. 414 * @exception UnsupportedOperationException if no associated collection 415 */ 416 public void remove() { 417 if (collection != null) { 418 if (last != null) { 419 collection.remove(last); 420 } else { 421 throw new IllegalStateException( 422 "next() must have been called for remove() to function"); 423 } 424 } else { 425 throw new UnsupportedOperationException( 426 "No Collection associated with this Iterator"); 427 } 428 } 429 430 // Properties 431 //----------------------------------------------------------------------- 432 /** 433 * Returns the underlying enumeration. 434 * 435 * @return the underlying enumeration 436 */ 437 public Enumeration getEnumeration() { 438 return enumeration; 439 } 440 441 /** 442 * Sets the underlying enumeration. 443 * 444 * @param enumeration the new underlying enumeration 445 */ 446 public void setEnumeration(final Enumeration enumeration) { 447 this.enumeration = enumeration; 448 } 449 } 450 451 }