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