View Javadoc

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 }