From 322442b779c04f958940887af5c4975c29ff6064 Mon Sep 17 00:00:00 2001 From: Luciano Leggieri <230980@gmail.com> Date: Mon, 15 Apr 2024 18:41:21 +1000 Subject: [PATCH] implement AbstractMap methods Signed-off-by: Luciano Leggieri <230980@gmail.com> --- .../pantheon/triemap/ImmutableTrieMap.java | 82 ++++++++++++++++++ .../tech/pantheon/triemap/MutableTrieMap.java | 16 ++++ .../java/tech/pantheon/triemap/TrieMap.java | 85 +++++++++++++++++-- .../tech/pantheon/triemap/ValuesIterator.java | 35 ++++++++ 4 files changed, 211 insertions(+), 7 deletions(-) create mode 100644 triemap/src/main/java/tech/pantheon/triemap/ValuesIterator.java diff --git a/triemap/src/main/java/tech/pantheon/triemap/ImmutableTrieMap.java b/triemap/src/main/java/tech/pantheon/triemap/ImmutableTrieMap.java index d58e799..92def8b 100644 --- a/triemap/src/main/java/tech/pantheon/triemap/ImmutableTrieMap.java +++ b/triemap/src/main/java/tech/pantheon/triemap/ImmutableTrieMap.java @@ -18,6 +18,7 @@ import static java.util.Objects.requireNonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.util.Iterator; import java.util.Map; import java.util.function.BiFunction; import java.util.function.Function; @@ -37,6 +38,12 @@ public final class ImmutableTrieMap extends TrieMap { @SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED", justification = "Handled through writeReplace") private final transient INode root; + @SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED", justification = "Handled through writeReplace") + private transient int hashCode = -1; + + @SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED", justification = "Handled through writeReplace") + private transient String stringRepresentation; + ImmutableTrieMap(final INode root) { this.root = requireNonNull(root); } @@ -112,6 +119,81 @@ public MutableTrieMap mutableSnapshot() { return new MutableTrieMap<>(new INode<>(new Gen(), root.gcasRead(this))); } + @Override + public int hashCode() { + if (hashCode == -1) { + int hash = 0; + for (Entry entry : entrySet()) { + hash += entry.hashCode(); + } + hashCode = hash; + } + return hashCode; + } + + @Override + public boolean equals(Object anObject) { + if (anObject == this) { + return true; + } + + if (!(anObject instanceof Map aMap)) { + return false; + } + if (aMap.size() != size()) { + return false; + } + + try { + for (Entry entry : entrySet()) { + K key = entry.getKey(); + V value = entry.getValue(); + if (value == null) { + if (!(aMap.get(key) == null && aMap.containsKey(key))) { + return false; + } + } else { + if (!value.equals(aMap.get(key))) { + return false; + } + } + } + } catch (ClassCastException unused) { + return false; + } + + return true; + } + + @Override + public String toString() { + if (stringRepresentation == null) { + if (isEmpty()) { + stringRepresentation = "{}"; + } else { + StringBuilder sb = new StringBuilder(); + sb.append('{'); + Iterator> it = entrySet().iterator(); + for (;;) { + Entry entry = it.next(); + K key = entry.getKey(); + V value = entry.getValue(); + sb.append(key == this ? "(this Map)" : key); + sb.append('='); + sb.append(value == this ? "(this Map)" : value); + if (it.hasNext()) { + sb.append(',').append(' '); + } else { + sb.append('}'); + break; + } + } + stringRepresentation = sb.toString(); + } + } + return stringRepresentation; + } + @Override public ImmutableTrieMap immutableSnapshot() { return this; diff --git a/triemap/src/main/java/tech/pantheon/triemap/MutableTrieMap.java b/triemap/src/main/java/tech/pantheon/triemap/MutableTrieMap.java index e301310..18c3eca 100644 --- a/triemap/src/main/java/tech/pantheon/triemap/MutableTrieMap.java +++ b/triemap/src/main/java/tech/pantheon/triemap/MutableTrieMap.java @@ -130,6 +130,22 @@ public MutableTrieMap mutableSnapshot() { return new MutableTrieMap<>(snapshot().copyToGen(new Gen(), this)); } + @Override + public int hashCode() { + return immutableSnapshot().hashCode(); + } + + @Override + public boolean equals(Object anObject) { + return immutableSnapshot().equals(anObject); + } + + @Override + public String toString() { + return immutableSnapshot().toString(); + } + + @Override MutableEntrySet createEntrySet() { // FIXME: it would be nice to have a ReadWriteTrieMap with read-only iterator diff --git a/triemap/src/main/java/tech/pantheon/triemap/TrieMap.java b/triemap/src/main/java/tech/pantheon/triemap/TrieMap.java index ea2c10d..a384908 100644 --- a/triemap/src/main/java/tech/pantheon/triemap/TrieMap.java +++ b/triemap/src/main/java/tech/pantheon/triemap/TrieMap.java @@ -19,7 +19,10 @@ import static tech.pantheon.triemap.LookupResult.RESTART; import java.io.Serializable; -import java.util.AbstractMap; +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; @@ -34,15 +37,14 @@ * @param the type of keys maintained by this map * @param the type of mapped values */ -public abstract sealed class TrieMap extends AbstractMap implements ConcurrentMap, Serializable +public abstract sealed class TrieMap implements ConcurrentMap, Serializable permits ImmutableTrieMap, MutableTrieMap { @java.io.Serial private static final long serialVersionUID = 1L; private transient AbstractEntrySet entrySet; - // Note: AbstractMap.keySet is something we do not have access to. At some point we should just not subclass - // AbstractMap and lower our memory footprint. - private transient AbstractKeySet theKeySet; + private transient AbstractKeySet keySet; + private transient AbstractCollection values; TrieMap() { // Hidden on purpose @@ -94,7 +96,23 @@ public final boolean containsKey(final Object key) { @Override public final boolean containsValue(final Object value) { - return super.containsValue(requireNonNull(value)); + Iterator> iterator = entrySet().iterator(); + if (value == null) { + while (iterator.hasNext()) { + Entry entry = iterator.next(); + if (entry.getValue() == null) { + return true; + } + } + } else { + while (iterator.hasNext()) { + Entry entry = iterator.next(); + if (value.equals(entry.getValue())) { + return true; + } + } + } + return false; } @Override @@ -106,7 +124,60 @@ public final Set> entrySet() { @Override public final Set keySet() { final AbstractKeySet ret; - return (ret = theKeySet) != null ? ret : (theKeySet = createKeySet()); + return (ret = keySet) != null ? ret : (keySet = createKeySet()); + } + + @Override + public Collection values() { + final AbstractCollection ret; + return (ret = values) != null ? ret : (values = createValues()); + } + + @Override + public void putAll(Map map) { + for (Map.Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public abstract int hashCode(); + + @Override + public abstract boolean equals(Object anObject); + + @Override + public abstract String toString(); + + private AbstractCollection createValues() { + return new AbstractCollection() { + public Iterator iterator() { + AbstractEntrySet abstractEntrySet = (AbstractEntrySet)TrieMap.this.entrySet(); + return new ValuesIterator((AbstractIterator)abstractEntrySet.iterator()); + } + + public int size() { + return TrieMap.this.size(); + } + + public boolean isEmpty() { + return TrieMap.this.isEmpty(); + } + + public void clear() { + TrieMap.this.clear(); + } + + public boolean contains(Object object) { + return TrieMap.this.containsValue(object); + } + }; + } @Override diff --git a/triemap/src/main/java/tech/pantheon/triemap/ValuesIterator.java b/triemap/src/main/java/tech/pantheon/triemap/ValuesIterator.java new file mode 100644 index 0000000..7099085 --- /dev/null +++ b/triemap/src/main/java/tech/pantheon/triemap/ValuesIterator.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package tech.pantheon.triemap; + +import static java.util.Objects.requireNonNull; + +import java.util.Iterator; + +final class ValuesIterator implements Iterator { + private final AbstractIterator delegate; + + ValuesIterator(final AbstractIterator delegate) { + this.delegate = requireNonNull(delegate); + } + + @Override + public boolean hasNext() { + return delegate.hasNext(); + } + + @Override + public V next() { + return delegate.next().getValue(); + } + + @Override + public void remove() { + delegate.remove(); + } +}