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