ObjectGraphIterator.java

  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. package org.apache.commons.collections4.iterators;

  18. import java.util.ArrayDeque;
  19. import java.util.Deque;
  20. import java.util.Iterator;
  21. import java.util.NoSuchElementException;

  22. import org.apache.commons.collections4.Transformer;

  23. /**
  24.  * An Iterator that can traverse multiple iterators down an object graph.
  25.  * <p>
  26.  * This iterator can extract multiple objects from a complex tree-like object graph.
  27.  * The iteration starts from a single root object.
  28.  * It uses a {@code Transformer} to extract the iterators and elements.
  29.  * Its main benefit is that no intermediate {@code List} is created.
  30.  * </p>
  31.  * <p>
  32.  * For example, consider an object graph:
  33.  * </p>
  34.  * <pre>
  35.  *                 |- Branch -- Leaf
  36.  *                 |         \- Leaf
  37.  *         |- Tree |         /- Leaf
  38.  *         |       |- Branch -- Leaf
  39.  *  Forest |                 \- Leaf
  40.  *         |       |- Branch -- Leaf
  41.  *         |       |         \- Leaf
  42.  *         |- Tree |         /- Leaf
  43.  *                 |- Branch -- Leaf
  44.  *                 |- Branch -- Leaf</pre>
  45.  * <p>
  46.  * The following {@code Transformer}, used in this class, will extract all
  47.  * the Leaf objects without creating a combined intermediate list:
  48.  * </p>
  49.  * <pre>
  50.  * public Object transform(Object input) {
  51.  *   if (input instanceof Forest) {
  52.  *     return ((Forest) input).treeIterator();
  53.  *   }
  54.  *   if (input instanceof Tree) {
  55.  *     return ((Tree) input).branchIterator();
  56.  *   }
  57.  *   if (input instanceof Branch) {
  58.  *     return ((Branch) input).leafIterator();
  59.  *   }
  60.  *   if (input instanceof Leaf) {
  61.  *     return input;
  62.  *   }
  63.  *   throw new ClassCastException();
  64.  * }</pre>
  65.  * <p>
  66.  * Internally, iteration starts from the root object. When next is called,
  67.  * the transformer is called to examine the object. The transformer will return
  68.  * either an iterator or an object. If the object is an Iterator, the next element
  69.  * from that iterator is obtained and the process repeats. If the element is an object
  70.  * it is returned.
  71.  * </p>
  72.  * <p>
  73.  * Under many circumstances, linking Iterators together in this manner is
  74.  * more efficient (and convenient) than using nested for loops to extract a list.
  75.  * </p>
  76.  *
  77.  * @param <E> the type of elements returned by this iterator.
  78.  * @since 3.1
  79.  */
  80. public class ObjectGraphIterator<E> implements Iterator<E> {

  81.     /** The stack of iterators */
  82.     private final Deque<Iterator<? extends E>> stack = new ArrayDeque<>(8);
  83.     /** The root object in the tree */
  84.     private E root;
  85.     /** The transformer to use */
  86.     private final Transformer<? super E, ? extends E> transformer;

  87.     /** Whether there is another element in the iteration */
  88.     private boolean hasNext;
  89.     /** The current iterator */
  90.     private Iterator<? extends E> currentIterator;
  91.     /** The current value */
  92.     private E currentValue;
  93.     /** The last used iterator, needed for remove() */
  94.     private Iterator<? extends E> lastUsedIterator;

  95.     /**
  96.      * Constructs an ObjectGraphIterator using a root object and transformer.
  97.      * <p>
  98.      * The root object can be an iterator, in which case it will be immediately
  99.      * looped around.
  100.      *
  101.      * @param root  the root object, null will result in an empty iterator
  102.      * @param transformer  the transformer to use, null will use a no effect transformer
  103.      */
  104.     @SuppressWarnings("unchecked")
  105.     public ObjectGraphIterator(final E root, final Transformer<? super E, ? extends E> transformer) {
  106.         if (root instanceof Iterator) {
  107.             this.currentIterator = (Iterator<? extends E>) root;
  108.         } else {
  109.             this.root = root;
  110.         }
  111.         this.transformer = transformer;
  112.     }

  113.     /**
  114.      * Constructs a ObjectGraphIterator that will handle an iterator of iterators.
  115.      * <p>
  116.      * This constructor exists for convenience to emphasise that this class can
  117.      * be used to iterate over nested iterators. That is to say that the iterator
  118.      * passed in here contains other iterators, which may in turn contain further
  119.      * iterators.
  120.      * </p>
  121.      *
  122.      * @param rootIterator  the root iterator, null will result in an empty iterator
  123.      */
  124.     public ObjectGraphIterator(final Iterator<? extends E> rootIterator) {
  125.         this.currentIterator = rootIterator;
  126.         this.transformer = null;
  127.     }

  128.     /**
  129.      * Finds the next object in the iteration given any start object.
  130.      *
  131.      * @param value  the value to start from
  132.      */
  133.     @SuppressWarnings("unchecked")
  134.     protected void findNext(final E value) {
  135.         if (value instanceof Iterator) {
  136.             // need to examine this iterator
  137.             findNextByIterator((Iterator<? extends E>) value);
  138.         } else {
  139.             // next value found
  140.             currentValue = value;
  141.             hasNext = true;
  142.         }
  143.     }

  144.     /**
  145.      * Finds the next object in the iteration given an iterator.
  146.      *
  147.      * @param iterator  the iterator to start from
  148.      */
  149.     protected void findNextByIterator(final Iterator<? extends E> iterator) {
  150.         if (iterator != currentIterator) {
  151.             // recurse a level
  152.             if (currentIterator != null) {
  153.                 stack.push(currentIterator);
  154.             }
  155.             currentIterator = iterator;
  156.         }

  157.         while (currentIterator.hasNext() && !hasNext) {
  158.             E next = currentIterator.next();
  159.             if (transformer != null) {
  160.                 next = transformer.apply(next);
  161.             }
  162.             findNext(next);
  163.         }
  164.         // if we haven't found the next value and iterators are not yet exhausted
  165.         if (!hasNext && !stack.isEmpty()) {
  166.             // current iterator exhausted, go up a level
  167.             currentIterator = stack.pop();
  168.             findNextByIterator(currentIterator);
  169.         }
  170.     }

  171.     /**
  172.      * Checks whether there are any more elements in the iteration to obtain.
  173.      *
  174.      * @return true if elements remain in the iteration
  175.      */
  176.     @Override
  177.     public boolean hasNext() {
  178.         updateCurrentIterator();
  179.         return hasNext;
  180.     }

  181.     /**
  182.      * Gets the next element of the iteration.
  183.      *
  184.      * @return the next element from the iteration
  185.      * @throws NoSuchElementException if all the Iterators are exhausted
  186.      */
  187.     @Override
  188.     public E next() {
  189.         updateCurrentIterator();
  190.         if (!hasNext) {
  191.             throw new NoSuchElementException("No more elements in the iteration");
  192.         }
  193.         lastUsedIterator = currentIterator;
  194.         final E result = currentValue;
  195.         currentValue = null;
  196.         hasNext = false;
  197.         return result;
  198.     }

  199.     /**
  200.      * Removes from the underlying collection the last element returned.
  201.      * <p>
  202.      * This method calls remove() on the underlying Iterator, and it may
  203.      * throw an UnsupportedOperationException if the underlying Iterator
  204.      * does not support this method.
  205.      * </p>
  206.      *
  207.      * @throws UnsupportedOperationException
  208.      *   if the remove operator is not supported by the underlying Iterator
  209.      * @throws IllegalStateException
  210.      *   if the next method has not yet been called, or the remove method has
  211.      *   already been called after the last call to the next method.
  212.      */
  213.     @Override
  214.     public void remove() {
  215.         if (lastUsedIterator == null) {
  216.             throw new IllegalStateException("Iterator remove() cannot be called at this time");
  217.         }
  218.         lastUsedIterator.remove();
  219.         lastUsedIterator = null;
  220.     }

  221.     /**
  222.      * Loops around the iterators to find the next value to return.
  223.      */
  224.     protected void updateCurrentIterator() {
  225.         if (hasNext) {
  226.             return;
  227.         }
  228.         if (currentIterator == null) {
  229.             if (root == null) { // NOPMD
  230.                 // do nothing, hasNext will be false
  231.             } else {
  232.                 if (transformer == null) {
  233.                     findNext(root);
  234.                 } else {
  235.                     findNext(transformer.apply(root));
  236.                 }
  237.                 root = null;
  238.             }
  239.         } else {
  240.             findNextByIterator(currentIterator);
  241.         }
  242.     }

  243. }