001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.tools;
003
004import java.util.AbstractCollection;
005import java.util.Collection;
006import java.util.Iterator;
007import java.util.NoSuchElementException;
008
009/**
010 * Filtered view of a collection.
011 * (read-only collection, but elements can be changed, of course)
012 * Lets you iterate through those elements of a given collection that satisfy a
013 * certain condition (imposed by a predicate).
014 * @param <S> element type of the underlying collection
015 * @param <T> element type of filtered collection (and subclass of S). The predicate
016 *      must accept only objects of type T.
017 * @since 3147
018 */
019public class SubclassFilteredCollection<S, T extends S> extends AbstractCollection<T> {
020
021    private final Collection<? extends S> collection;
022    private final Predicate<? super S> predicate;
023    private int size = -1;
024
025    private class FilterIterator implements Iterator<T> {
026
027        private final Iterator<? extends S> iterator;
028        private S current;
029
030        FilterIterator(Iterator<? extends S> iterator) {
031            this.iterator = iterator;
032        }
033
034        private void findNext() {
035            if (current == null) {
036                while (iterator.hasNext()) {
037                    current = iterator.next();
038                    if (predicate.evaluate(current))
039                        return;
040                }
041                current = null;
042            }
043        }
044
045        @Override
046        public boolean hasNext() {
047            findNext();
048            return current != null;
049        }
050
051        @SuppressWarnings("unchecked")
052        @Override
053        public T next() {
054            if (!hasNext())
055                throw new NoSuchElementException();
056            S old = current;
057            current = null;
058            // we are save because predicate only accepts objects of type T
059            return (T) old;
060        }
061
062        @Override
063        public void remove() {
064            throw new UnsupportedOperationException();
065        }
066    }
067
068    /**
069     * Constructs a new {@code SubclassFilteredCollection}.
070     * @param collection The base collection to filter
071     * @param predicate The predicate to use as filter
072     */
073    public SubclassFilteredCollection(Collection<? extends S> collection, Predicate<? super S> predicate) {
074        this.collection = collection;
075        this.predicate = predicate;
076    }
077
078    @Override
079    public Iterator<T> iterator() {
080        return new FilterIterator(collection.iterator());
081    }
082
083    @Override
084    public int size() {
085        if (size == -1) {
086            size = 0;
087            Iterator<T> it = iterator();
088            while (it.hasNext()) {
089                size++;
090                it.next();
091            }
092        }
093        return size;
094    }
095
096    @Override
097    public boolean isEmpty() {
098        return !iterator().hasNext();
099    }
100}