/** * Copyright (C) 2019, 2021, 2024, 2025 Adrien Hopkins * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package sevenUnits.utils; import java.util.AbstractCollection; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Set; import java.util.function.Predicate; /** * Elements in these wrapper collections only exist if they pass a condition. *

* All of the collections in this class are "views" of the provided collections. * They are mutable if the provided collections are mutable, they allow null if * the provided collections allow null, they will reflect changes in the * provided collection, etc. *

* The modification operations will always run the corresponding operations, * even if the conditional existence collection doesn't change. For example, if * you have a set that ignores even numbers, add(2) will still add a 2 to the * backing set (but the conditional existence set will say it doesn't exist). *

* The returned collections do not pass the hashCode and equals * operations through to the backing collections, but rely on {@code Object}'s * {@code equals} and {@code hashCode} methods. This is necessary to preserve * the contracts of these operations in the case that the backing collections * are sets or lists. *

* Other than that, the only difference between the provided collections and * the returned collections are that elements don't exist if they don't pass the * provided condition. * * * @author Adrien Hopkins * @since 2019-10-17 * @since v0.3.0 */ public final class ConditionalExistenceCollections { /** * Elements in this collection only exist if they meet a condition. * * @author Adrien Hopkins * @since 2019-10-17 * @since v0.3.0 * @param type of element in collection */ static final class ConditionalExistenceCollection extends AbstractCollection { final Collection collection; final Predicate existenceCondition; /** * Creates the {@code ConditionalExistenceCollection}. * * @param collection * @param existenceCondition * @since 2019-10-17 * @since v0.3.0 */ private ConditionalExistenceCollection(final Collection collection, final Predicate existenceCondition) { this.collection = collection; this.existenceCondition = existenceCondition; } @Override public boolean add(final E e) { return this.collection.add(e) && this.existenceCondition.test(e); } @Override public void clear() { this.collection.clear(); } @Override public boolean contains(final Object o) { if (!this.collection.contains(o)) return false; // this collection can only contain instances of E // since the object is in the collection, we know that it must be an // instance of E // therefore this cast will always work @SuppressWarnings("unchecked") final var e = (E) o; return this.existenceCondition.test(e); } @Override public Iterator iterator() { return conditionalExistenceIterator(this.collection.iterator(), this.existenceCondition); } @Override public boolean remove(final Object o) { // remove() must be first in the && statement, otherwise it may not // execute final var containedObject = this.contains(o); return this.collection.remove(o) && containedObject; } @Override public int size() { return (int) this.collection.stream().filter(this.existenceCondition) .count(); } @Override public Object[] toArray() { // ensure the toArray operation is supported this.collection.toArray(); // if it works, do it for real return super.toArray(); } @Override public T[] toArray(T[] a) { // ensure the toArray operation is supported this.collection.toArray(); // if it works, do it for real return super.toArray(a); } } /** * Elements in this wrapper iterator only exist if they pass a condition. * * @author Adrien Hopkins * @since 2019-10-17 * @since v0.3.0 * @param type of elements in iterator */ static final class ConditionalExistenceIterator implements Iterator { final Iterator iterator; final Predicate existenceCondition; E nextElement; boolean hasNext; /** * Creates the {@code ConditionalExistenceIterator}. * * @param iterator * @param condition * @since 2019-10-17 * @since v0.3.0 */ private ConditionalExistenceIterator(final Iterator iterator, final Predicate condition) { this.iterator = iterator; this.existenceCondition = condition; this.getAndSetNextElement(); } /** * Gets the next element, and sets nextElement and hasNext accordingly. * * @since 2019-10-17 * @since v0.3.0 */ private void getAndSetNextElement() { do { if (!this.iterator.hasNext()) { this.nextElement = null; this.hasNext = false; return; } this.nextElement = this.iterator.next(); } while (!this.existenceCondition.test(this.nextElement)); this.hasNext = true; } @Override public boolean hasNext() { return this.hasNext; } @Override public E next() { if (this.hasNext()) { final var next = this.nextElement; this.getAndSetNextElement(); return next; } throw new NoSuchElementException(); } @Override public void remove() { this.iterator.remove(); } } /** * Mappings in this map only exist if the entry passes some condition. * * @author Adrien Hopkins * @since 2019-10-17 * @since v0.3.0 * @param key type * @param value type */ static final class ConditionalExistenceMap extends AbstractMap { Map map; Predicate> entryExistenceCondition; /** * Creates the {@code ConditionalExistenceMap}. * * @param map * @param entryExistenceCondition * @since 2019-10-17 * @since v0.3.0 */ private ConditionalExistenceMap(final Map map, final Predicate> entryExistenceCondition) { this.map = map; this.entryExistenceCondition = entryExistenceCondition; } @Override public boolean containsKey(final Object key) { if (!this.map.containsKey(key)) return false; // only instances of K have mappings in the backing map // since we know that key is a valid key, it must be an instance of K @SuppressWarnings("unchecked") final var keyAsK = (K) key; // get and test entry final var value = this.map.get(key); final Entry entry = new SimpleEntry<>(keyAsK, value); return this.entryExistenceCondition.test(entry); } @Override public Set> entrySet() { return conditionalExistenceSet(this.map.entrySet(), this.entryExistenceCondition); } @Override public V get(final Object key) { return this.containsKey(key) ? this.map.get(key) : null; } private Entry getEntry(K key) { return new Entry<>() { @Override public K getKey() { return key; } @Override public V getValue() { return ConditionalExistenceMap.this.map.get(key); } @Override public V setValue(V value) { return ConditionalExistenceMap.this.map.put(key, value); } }; } @Override public Set keySet() { return conditionalExistenceSet(this.map.keySet(), k -> this.entryExistenceCondition.test(this.getEntry(k))); } @Override public V put(final K key, final V value) { final var oldValue = this.map.put(key, value); // get and test entry final Entry entry = new SimpleEntry<>(key, oldValue); return this.entryExistenceCondition.test(entry) ? oldValue : null; } @Override public V remove(final Object key) { final var oldValue = this.map.remove(key); return this.containsKey(key) ? oldValue : null; } @Override public Collection values() { // maybe change this to use ConditionalExistenceCollection return super.values(); } } /** * Elements in this set only exist if a certain condition is true. * * @author Adrien Hopkins * @since 2019-10-17 * @since v0.3.0 * @param type of element in set */ static final class ConditionalExistenceSet extends AbstractSet { private final Set set; private final Predicate existenceCondition; /** * Creates the {@code ConditionalNonexistenceSet}. * * @param set set to use * @param existenceCondition condition where element exists * @since 2019-10-17 * @since v0.3.0 */ private ConditionalExistenceSet(final Set set, final Predicate existenceCondition) { this.set = set; this.existenceCondition = existenceCondition; } /** * {@inheritDoc} *

* Note that this method returns {@code false} if {@code e} does not pass * the existence condition. */ @Override public boolean add(final E e) { return this.set.add(e) && this.existenceCondition.test(e); } @Override public void clear() { this.set.clear(); } @Override public boolean contains(final Object o) { if (!this.set.contains(o)) return false; // this set can only contain instances of E // since the object is in the set, we know that it must be an instance // of E // therefore this cast will always work @SuppressWarnings("unchecked") final var e = (E) o; return this.existenceCondition.test(e); } @Override public Iterator iterator() { return conditionalExistenceIterator(this.set.iterator(), this.existenceCondition); } @Override public boolean remove(final Object o) { // remove() must be first in the && statement, otherwise it may not // execute final var containedObject = this.contains(o); return this.set.remove(o) && containedObject; } @Override public int size() { return (int) this.set.stream().filter(this.existenceCondition).count(); } @Override public Object[] toArray() { // ensure the toArray operation is supported this.set.toArray(); // if it works, do it for real return super.toArray(); } @Override public T[] toArray(T[] a) { // ensure the toArray operation is supported this.set.toArray(); // if it works, do it for real return super.toArray(a); } } /** * Elements in the returned wrapper collection are ignored if they don't pass * a condition. * * @param type of elements in collection * @param collection collection to wrap * @param existenceCondition elements only exist if this returns true * @return wrapper collection * @since 2019-10-17 * @since v0.3.0 */ public static Collection conditionalExistenceCollection( final Collection collection, final Predicate existenceCondition) { return new ConditionalExistenceCollection<>(collection, existenceCondition); } /** * Elements in the returned wrapper iterator are ignored if they don't pass a * condition. * * @param type of elements in iterator * @param iterator iterator to wrap * @param existenceCondition elements only exist if this returns true * @return wrapper iterator * @since 2019-10-17 * @since v0.3.0 */ public static Iterator conditionalExistenceIterator( final Iterator iterator, final Predicate existenceCondition) { return new ConditionalExistenceIterator<>(iterator, existenceCondition); } /** * Mappings in the returned wrapper map are ignored if the corresponding * entry doesn't pass a condition * * @param type of key in map * @param type of value in map * @param map map to wrap * @param entryExistenceCondition mappings only exist if this returns true * @return wrapper map * @since 2019-10-17 * @since v0.3.0 */ public static Map conditionalExistenceMap(final Map map, final Predicate> entryExistenceCondition) { return new ConditionalExistenceMap<>(map, entryExistenceCondition); } /** * Elements in the returned wrapper set are ignored if they don't pass a * condition. * * @param type of elements in set * @param set set to wrap * @param existenceCondition elements only exist if this returns true * @return wrapper set * @since 2019-10-17 * @since v0.3.0 */ public static Set conditionalExistenceSet(final Set set, final Predicate existenceCondition) { return new ConditionalExistenceSet<>(set, existenceCondition); } }