001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     * 
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */ 
017    
018    package org.apache.commons.betwixt.expression;
019    
020    import java.lang.reflect.Array;
021    import java.util.Collection;
022    import java.util.Collections;
023    import java.util.Enumeration;
024    import java.util.Iterator;
025    import java.util.Map;
026    import java.util.NoSuchElementException;
027    
028    
029    /** <p><code>IteratorExpression</code> returns an iterator over the current context.</p>
030      *
031      * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
032      * @version $Revision: 438373 $
033      */
034    public class IteratorExpression implements Expression {
035        
036        /** Use this <code>Expression</code> to perform initial evaluation*/
037        private Expression expression;
038        
039        /** 
040         * Construct <code>IteratorExpression</code> using given expression for initial evaluation.
041         * @param expression this expression will be evaluated and the result converted to an 
042         *        iterator.
043         */
044        public IteratorExpression(Expression expression) {
045            this.expression = expression;
046        }
047        
048        /** 
049         * Returns an interator over the current context 
050         * @see org.apache.commons.betwixt.expression.Expression
051         */
052        public Object evaluate(Context context) {        
053            // evaluate wrapped expression against context
054            Object value = expression.evaluate( context );
055            
056            // based on the class of the result,
057            // return an appropriate iterator
058            if ( value instanceof Iterator ) {
059                // if the value is an iterator, we're done
060                return (Iterator) value;
061                
062            } else if ( value instanceof Collection ) {
063                // if it's a collection, return an iterator for that collection
064                Collection collection = (Collection) value;
065                return collection.iterator();
066                
067            } else if ( value instanceof Map ) {
068                // if it's a map, return an iterator for the map entries
069                Map map = (Map) value;
070                return map.entrySet().iterator();
071                
072            } else if ( value instanceof Enumeration ) {
073                // if it's an enumeration, wrap it in an EnumerationIterator
074                return new EnumerationIterator( (Enumeration) value );
075                
076            } else if ( value != null ) {
077                // if we have an array return an ArrayIterator
078                Class type = value.getClass();
079                if ( type.isArray() ) {
080                    return new ArrayIterator( value );
081                }
082            }
083            
084            // we've got something we can't deal with
085            // so return an empty iterator
086            return Collections.EMPTY_LIST.iterator();
087        }
088    
089        /** 
090         * Do nothing
091         * @see org.apache.commons.betwixt.expression.Expression
092         */
093        public void update(Context context, String newValue) {
094            // do nothing
095        }
096        
097        /**
098         * Returns something useful for logging
099         * @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    }