/**
* 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 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();
}
@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 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
* @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 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;
}
private final 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 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
* @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 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();
}
@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 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
* @since v0.3.0
*/
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
* @since v0.3.0
*/
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
* @since v0.3.0
*/
public static final Set conditionalExistenceSet(final Set set,
final Predicate existenceCondition) {
return new ConditionalExistenceSet<>(set, existenceCondition);
}
}