/** * Copyright (C) 2019 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 org.unitConverter.math; 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 */ // TODO add conditional existence Lists and Sorted/Navigable Sets/Maps public final class ConditionalExistenceCollections { /** * Elements in this collection only exist if they meet a condition. * * @author Adrien Hopkins * @since 2019-10-17 * @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 */ 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 E 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 boolean containedObject = this.contains(o); return this.collection.remove(o) && containedObject; } @Override public int size() { return (int) this.collection.stream().filter(this.existenceCondition).count(); } } /** * Elements in this wrapper iterator only exist if they pass a condition. * * @author Adrien Hopkins * @since 2019-10-17 * @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 */ 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 */ 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 E next = this.nextElement; this.getAndSetNextElement(); return next; } else 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 * @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 */ 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 K keyAsK = (K) key; // get and test entry final V 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; } @Override public Set keySet() { // maybe change this to use ConditionalExistenceSet return super.keySet(); } @Override public V put(final K key, final V value) { final V 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 V 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 * @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 */ 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 E 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 boolean containedObject = this.contains(o); return this.set.remove(o) && containedObject; } @Override public int size() { return (int) this.set.stream().filter(this.existenceCondition).count(); } } /** * 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 */ public static final 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 */ public static final 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 */ public static final 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 */ public static final Set conditionalExistenceSet(final Set set, final Predicate existenceCondition) { return new ConditionalExistenceSet<>(set, existenceCondition); } }