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; 008import java.util.Objects; 009import java.util.Spliterator; 010import java.util.Spliterators; 011import java.util.function.Predicate; 012 013/** 014 * Filtered view of a collection. 015 * (read-only collection, but elements can be changed, of course) 016 * Lets you iterate through those elements of a given collection that satisfy a 017 * certain condition (imposed by a predicate). 018 * <p> 019 * The behaviour of this class is undefined if the underlying collection is changed. 020 * @param <S> element type of the underlying collection 021 * @param <T> element type of filtered collection (and subclass of S). The predicate 022 * must accept only objects of type T. 023 * @since 3147 024 */ 025public class SubclassFilteredCollection<S, T extends S> extends AbstractCollection<T> { 026 027 private final Collection<? extends S> collection; 028 private final Predicate<? super S> predicate; 029 private int size = -1; 030 031 private class FilterIterator implements Iterator<T> { 032 033 private final Iterator<? extends S> iterator; 034 private S current; 035 036 FilterIterator(Iterator<? extends S> iterator) { 037 this.iterator = iterator; 038 } 039 040 private void findNext() { 041 if (current == null) { 042 while (iterator.hasNext()) { 043 current = iterator.next(); 044 if (predicate.test(current)) 045 return; 046 } 047 current = null; 048 } 049 } 050 051 @Override 052 public boolean hasNext() { 053 findNext(); 054 return current != null; 055 } 056 057 @SuppressWarnings("unchecked") 058 @Override 059 public T next() { 060 if (!hasNext()) 061 throw new NoSuchElementException(); 062 S old = current; 063 current = null; 064 // we are save because predicate only accepts objects of type T 065 return (T) old; 066 } 067 068 @Override 069 public void remove() { 070 throw new UnsupportedOperationException(); 071 } 072 } 073 074 /** 075 * Constructs a new {@code SubclassFilteredCollection}. 076 * @param collection The base collection to filter 077 * @param predicate The predicate to use as filter 078 * @see #filter(Collection, Predicate) for an alternative way to construct this. 079 */ 080 public SubclassFilteredCollection(Collection<? extends S> collection, Predicate<? super S> predicate) { 081 this.collection = Objects.requireNonNull(collection); 082 this.predicate = Objects.requireNonNull(predicate); 083 } 084 085 @Override 086 public Iterator<T> iterator() { 087 return new FilterIterator(collection.iterator()); 088 } 089 090 @Override 091 public Spliterator<T> spliterator() { 092 return Spliterators.spliteratorUnknownSize(iterator(), 0); 093 } 094 095 @Override 096 public int size() { 097 if (size == -1) { 098 size = 0; 099 forEach(t -> size++); 100 } 101 return size; 102 } 103 104 @Override 105 public boolean isEmpty() { 106 return !iterator().hasNext(); 107 } 108 109 /** 110 * Create a new filtered collection without any constraints on the predicate type. 111 * @param <T> The collection type. 112 * @param collection The collection to filter. 113 * @param predicate The predicate to filter for. 114 * @return The filtered collection. It is a {@code Collection<T>}. 115 */ 116 public static <T> SubclassFilteredCollection<T, T> filter(Collection<? extends T> collection, Predicate<T> predicate) { 117 return new SubclassFilteredCollection<>(collection, predicate); 118 } 119}