View Javadoc

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