Skip to content

Commit

Permalink
implement AbstractMap methods
Browse files Browse the repository at this point in the history
Signed-off-by: Luciano Leggieri <[email protected]>
  • Loading branch information
lukiano committed Apr 16, 2024
1 parent 63382b5 commit 322442b
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 7 deletions.
82 changes: 82 additions & 0 deletions triemap/src/main/java/tech/pantheon/triemap/ImmutableTrieMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,6 +38,12 @@ public final class ImmutableTrieMap<K, V> extends TrieMap<K, V> {
@SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED", justification = "Handled through writeReplace")
private final transient INode<K, V> 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<K, V> root) {
this.root = requireNonNull(root);
}
Expand Down Expand Up @@ -112,6 +119,81 @@ public MutableTrieMap<K, V> mutableSnapshot() {
return new MutableTrieMap<>(new INode<>(new Gen(), root.gcasRead(this)));
}

@Override
public int hashCode() {
if (hashCode == -1) {
int hash = 0;
for (Entry<K, V> 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<K, V> 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<Entry<K,V>> it = entrySet().iterator();
for (;;) {
Entry<K,V> 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<K, V> immutableSnapshot() {
return this;
Expand Down
16 changes: 16 additions & 0 deletions triemap/src/main/java/tech/pantheon/triemap/MutableTrieMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,22 @@ public MutableTrieMap<K, V> 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<K, V> createEntrySet() {
// FIXME: it would be nice to have a ReadWriteTrieMap with read-only iterator
Expand Down
85 changes: 78 additions & 7 deletions triemap/src/main/java/tech/pantheon/triemap/TrieMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -34,15 +37,14 @@
* @param <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*/
public abstract sealed class TrieMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K,V>, Serializable
public abstract sealed class TrieMap<K, V> implements ConcurrentMap<K,V>, Serializable
permits ImmutableTrieMap, MutableTrieMap {
@java.io.Serial
private static final long serialVersionUID = 1L;

private transient AbstractEntrySet<K, V, ?> 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<K, ?> theKeySet;
private transient AbstractKeySet<K, ?> keySet;
private transient AbstractCollection<V> values;

TrieMap() {
// Hidden on purpose
Expand Down Expand Up @@ -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<Entry<K,V>> iterator = entrySet().iterator();
if (value == null) {
while (iterator.hasNext()) {
Entry<K,V> entry = iterator.next();
if (entry.getValue() == null) {
return true;
}
}
} else {
while (iterator.hasNext()) {
Entry<K,V> entry = iterator.next();
if (value.equals(entry.getValue())) {
return true;
}
}
}
return false;
}

@Override
Expand All @@ -106,7 +124,60 @@ public final Set<Entry<K, V>> entrySet() {
@Override
public final Set<K> keySet() {
final AbstractKeySet<K, ?> ret;
return (ret = theKeySet) != null ? ret : (theKeySet = createKeySet());
return (ret = keySet) != null ? ret : (keySet = createKeySet());
}

@Override
public Collection<V> values() {
final AbstractCollection<V> ret;
return (ret = values) != null ? ret : (values = createValues());
}

@Override
public void putAll(Map<? extends K, ? extends V> map) {
for (Map.Entry<? extends K, ? extends V> 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<V> createValues() {
return new AbstractCollection<V>() {
public Iterator<V> iterator() {
AbstractEntrySet<K, V, ?> abstractEntrySet = (AbstractEntrySet<K, V, ?>)TrieMap.this.entrySet();
return new ValuesIterator<V>((AbstractIterator<K, V>)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
Expand Down
35 changes: 35 additions & 0 deletions triemap/src/main/java/tech/pantheon/triemap/ValuesIterator.java
Original file line number Diff line number Diff line change
@@ -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<V> implements Iterator<V> {
private final AbstractIterator<?, V> delegate;

ValuesIterator(final AbstractIterator<?, V> 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();
}
}

0 comments on commit 322442b

Please sign in to comment.