summaryrefslogtreecommitdiff
path: root/src/org/unitConverter/math
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/unitConverter/math')
-rw-r--r--src/org/unitConverter/math/ConditionalExistenceCollections.java322
-rw-r--r--src/org/unitConverter/math/ConditionalExistenceCollectionsTest.java (renamed from src/org/unitConverter/math/ZeroIsNullMapTest.java)50
-rw-r--r--src/org/unitConverter/math/ObjectProduct.java3
-rw-r--r--src/org/unitConverter/math/ZeroIsNullMap.java129
4 files changed, 372 insertions, 132 deletions
diff --git a/src/org/unitConverter/math/ConditionalExistenceCollections.java b/src/org/unitConverter/math/ConditionalExistenceCollections.java
new file mode 100644
index 0000000..c0a69f0
--- /dev/null
+++ b/src/org/unitConverter/math/ConditionalExistenceCollections.java
@@ -0,0 +1,322 @@
+/**
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+package org.unitConverter.math;
+
+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.
+ * <p>
+ * 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.
+ * <p>
+ * 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).
+ * <p>
+ * The returned collections do <i>not</i> 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.
+ * <p>
+ * Other than that, <i>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</i>.
+ *
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-17
+ */
+// TODO add conditional existence Collections, Lists and Sorted/Navigable Sets/Maps
+public final class ConditionalExistenceCollections {
+ /**
+ * Elements in this wrapper iterator only exist if they pass a condition.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-10-17
+ * @param <E>
+ * type of elements in iterator
+ */
+ static final class ConditionalExistenceIterator<E> implements Iterator<E> {
+ final Iterator<E> iterator;
+ final Predicate<E> existenceCondition;
+ E nextElement;
+ boolean hasNext;
+
+ /**
+ * Creates the {@code ConditionalExistenceIterator}.
+ *
+ * @param iterator
+ * @param condition
+ * @since 2019-10-17
+ */
+ private ConditionalExistenceIterator(final Iterator<E> iterator, final Predicate<E> 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 <K>
+ * key type
+ * @param <V>
+ * value type
+ */
+ static final class ConditionalExistenceMap<K, V> extends AbstractMap<K, V> {
+ Map<K, V> map;
+ Predicate<Entry<K, V>> entryExistenceCondition;
+
+ /**
+ * Creates the {@code ConditionalExistenceMap}.
+ *
+ * @param map
+ * @param entryExistenceCondition
+ * @since 2019-10-17
+ */
+ private ConditionalExistenceMap(final Map<K, V> map, final Predicate<Entry<K, V>> 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<K, V> entry = new SimpleEntry<>(keyAsK, value);
+ return this.entryExistenceCondition.test(entry);
+ }
+
+ @Override
+ public Set<Entry<K, V>> 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<K> 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<K, V> 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<V> 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 <E>
+ * type of element in set
+ */
+ static final class ConditionalExistenceSet<E> extends AbstractSet<E> {
+ private final Set<E> set;
+ private final Predicate<E> existenceCondition;
+
+ /**
+ * Creates the {@code ConditionalNonexistenceSet}.
+ *
+ * @param set
+ * set to use
+ * @param existenceCondition
+ * condition where element exists
+ * @since 2019-10-17
+ */
+ private ConditionalExistenceSet(final Set<E> set, final Predicate<E> existenceCondition) {
+ this.set = set;
+ this.existenceCondition = existenceCondition;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * 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<E> iterator() {
+ return conditionalExistenceIterator(this.set.iterator(), this.existenceCondition);
+ }
+
+ @Override
+ public boolean remove(final Object o) {
+ 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 iterator are ignored if they don't pass a condition.
+ *
+ * @param <E>
+ * 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 <E> Iterator<E> conditionalExistenceIterator(final Iterator<E> iterator,
+ final Predicate<E> existenceCondition) {
+ return new ConditionalExistenceIterator<>(iterator, existenceCondition);
+ }
+
+ /**
+ * Mappings in the returned wrapper map are ignored if the corresponding entry doesn't pass a condition
+ *
+ * @param <K>
+ * type of key in map
+ * @param <V>
+ * 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 <K, V> Map<K, V> conditionalExistenceMap(final Map<K, V> map,
+ final Predicate<Entry<K, V>> entryExistenceCondition) {
+ return new ConditionalExistenceMap<>(map, entryExistenceCondition);
+ }
+
+ /**
+ * Elements in the returned wrapper set are ignored if they don't pass a condition.
+ *
+ * @param <E>
+ * 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 <E> Set<E> conditionalExistenceSet(final Set<E> set, final Predicate<E> existenceCondition) {
+ return new ConditionalExistenceSet<>(set, existenceCondition);
+ }
+}
diff --git a/src/org/unitConverter/math/ZeroIsNullMapTest.java b/src/org/unitConverter/math/ConditionalExistenceCollectionsTest.java
index c3db951..311ace5 100644
--- a/src/org/unitConverter/math/ZeroIsNullMapTest.java
+++ b/src/org/unitConverter/math/ConditionalExistenceCollectionsTest.java
@@ -18,21 +18,45 @@ package org.unitConverter.math;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.NoSuchElementException;
import org.junit.jupiter.api.Test;
+import org.unitConverter.math.ConditionalExistenceCollections.ConditionalExistenceIterator;
/**
+ * Tests the {@link #ConditionalExistenceCollections}.
+ *
* @author Adrien Hopkins
* @since 2019-10-16
*/
-class ZeroIsNullMapTest {
+class ConditionalExistenceCollectionsTest {
/**
+ * The returned iterator ignores elements that don't start with "a".
+ *
+ * @return test iterator
+ * @since 2019-10-17
+ */
+ ConditionalExistenceIterator<String> getTestIterator() {
+ final List<String> items = Arrays.asList("aa", "ab", "ba");
+ final Iterator<String> it = items.iterator();
+ final ConditionalExistenceIterator<String> cit = (ConditionalExistenceIterator<String>) ConditionalExistenceCollections
+ .conditionalExistenceIterator(it, s -> s.startsWith("a"));
+ return cit;
+ }
+
+ /**
+ * The returned map ignores mappings where the value is zero.
+ *
* @return map to be used for test data
* @since 2019-10-16
*/
@@ -42,7 +66,9 @@ class ZeroIsNullMapTest {
map.put("two", 2);
map.put("zero", 0);
map.put("ten", 10);
- return ZeroIsNullMap.create(map);
+ final Map<String, Integer> conditionalMap = ConditionalExistenceCollections.conditionalExistenceMap(map,
+ e -> !Integer.valueOf(0).equals(e.getValue()));
+ return conditionalMap;
}
/**
@@ -92,6 +118,26 @@ class ZeroIsNullMapTest {
assertEquals(null, map.get("zero"));
}
+ @Test
+ void testIterator() {
+ final ConditionalExistenceIterator<String> testIterator = this.getTestIterator();
+
+ assertTrue(testIterator.hasNext);
+ assertTrue(testIterator.hasNext());
+ assertEquals("aa", testIterator.nextElement);
+ assertEquals("aa", testIterator.next());
+
+ assertTrue(testIterator.hasNext);
+ assertTrue(testIterator.hasNext());
+ assertEquals("ab", testIterator.nextElement);
+ assertEquals("ab", testIterator.next());
+
+ assertFalse(testIterator.hasNext);
+ assertFalse(testIterator.hasNext());
+ assertEquals(null, testIterator.nextElement);
+ assertThrows(NoSuchElementException.class, testIterator::next);
+ }
+
/**
* Test method for {@link org.unitConverter.math.ZeroIsNullMap#keySet()}.
*/
diff --git a/src/org/unitConverter/math/ObjectProduct.java b/src/org/unitConverter/math/ObjectProduct.java
index 21ab207..0cf89ec 100644
--- a/src/org/unitConverter/math/ObjectProduct.java
+++ b/src/org/unitConverter/math/ObjectProduct.java
@@ -92,7 +92,8 @@ public final class ObjectProduct<T> {
* @since 2019-10-16
*/
private ObjectProduct(final Map<T, Integer> exponents) {
- this.exponents = Collections.unmodifiableMap(ZeroIsNullMap.create(new HashMap<>(exponents)));
+ this.exponents = Collections.unmodifiableMap(ConditionalExistenceCollections
+ .conditionalExistenceMap(new HashMap<>(exponents), e -> !Integer.valueOf(0).equals(e.getValue())));
}
/**
diff --git a/src/org/unitConverter/math/ZeroIsNullMap.java b/src/org/unitConverter/math/ZeroIsNullMap.java
deleted file mode 100644
index 10e4d52..0000000
--- a/src/org/unitConverter/math/ZeroIsNullMap.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/**
- * 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 <https://www.gnu.org/licenses/>.
- */
-package org.unitConverter.math;
-
-import java.util.AbstractMap;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * A "wrapper" for an existing map that treats a zero value as equivalent to null.
- *
- * @author Adrien Hopkins
- * @since 2019-10-16
- * @param <T>
- * type of key
- */
-public final class ZeroIsNullMap<T> extends AbstractMap<T, Integer> {
- /**
- * Create a ZeroIsNullMap. This method always creates a new map.
- *
- * @param <T>
- * type of key
- * @param map
- * map to input
- * @return map that treats zero as null
- * @throws NullPointerException
- * if map is null
- * @since 2019-10-16
- */
- public static <T> Map<T, Integer> create(final Map<T, Integer> map) {
- return new ZeroIsNullMap<>(Objects.requireNonNull(map, "map must not be null."));
- }
-
- private final Map<T, Integer> map;
-
- /**
- * Creates the {@code ObjectProductMap}.
- *
- * @param map
- * @since 2019-10-16
- */
- private ZeroIsNullMap(final Map<T, Integer> map) {
- this.map = map;
- }
-
- @Override
- public void clear() {
- this.map.clear();
- }
-
- @Override
- public boolean containsKey(final Object key) {
- return this.map.containsKey(key) && this.map.get(key) != 0;
- }
-
- @Override
- public boolean containsValue(final Object value) {
- return this.values().contains(value);
- }
-
- @Override
- public Set<Entry<T, Integer>> entrySet() {
- final Set<Entry<T, Integer>> entrySet = new HashSet<>(this.map.entrySet());
- entrySet.removeIf(e -> e.getValue() == 0);
- return entrySet;
- }
-
- @Override
- public Integer get(final Object key) {
- final Integer i = this.map.get(key);
- if (Objects.equals(i, 0))
- return null;
- else
- return i;
- }
-
- @Override
- public Set<T> keySet() {
- final Set<T> keySet = new HashSet<>(this.map.keySet());
- keySet.removeIf(k -> this.map.get(k) == 0);
- return keySet;
- }
-
- @Override
- public Integer put(final T key, final Integer value) {
- if (value != 0)
- return this.map.put(key, value);
- else
- return null;
- }
-
- @Override
- public void putAll(final Map<? extends T, ? extends Integer> m) {
- for (final T key : m.keySet()) {
- this.put(key, m.get(key));
- }
- }
-
- @Override
- public Integer remove(final Object key) {
- return this.map.remove(key);
- }
-
- @Override
- public Collection<Integer> values() {
- final List<Integer> values = new ArrayList<>(this.map.values());
- values.removeIf(i -> i == 0);
- return values;
- }
-} \ No newline at end of file