This is an automated email from the ASF dual-hosted git repository. sunlan pushed a commit to branch danielsun/tweak-ManagedIdentityConcurrentMap in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 9923bced18b32b66954c63667cab1f309a5f5991 Author: Daniel Sun <sun...@apache.org> AuthorDate: Fri Jul 31 07:41:24 2020 +0800 Tweak `ConcurrentIdentityHashMap` --- .../groovy/util/ConcurrentReferenceHashMap.java | 107 ++++++++------- .../groovy/util/ManagedIdentityConcurrentMap.java | 48 +++++++ .../java/org/codehaus/groovy/ast/ClassHelper.java | 5 +- .../org/codehaus/groovy/reflection/ClassInfo.java | 4 +- .../groovy/reflection/MixinInMetaClass.java | 5 +- .../metaclass/ThreadManagedMetaBeanProperty.java | 4 +- .../groovy/util/ManagedIdentityConcurrentMap.java | 149 --------------------- .../util/ManagedIdentityConcurrentMapTest.groovy | 16 +-- 8 files changed, 120 insertions(+), 218 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/util/ConcurrentReferenceHashMap.java b/src/main/java/org/apache/groovy/util/ConcurrentReferenceHashMap.java similarity index 95% rename from src/main/java/org/codehaus/groovy/util/ConcurrentReferenceHashMap.java rename to src/main/java/org/apache/groovy/util/ConcurrentReferenceHashMap.java index 171e4f1..9c4395a 100644 --- a/src/main/java/org/codehaus/groovy/util/ConcurrentReferenceHashMap.java +++ b/src/main/java/org/apache/groovy/util/ConcurrentReferenceHashMap.java @@ -1,4 +1,22 @@ /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,7 +32,7 @@ * limitations under the License. */ -package org.codehaus.groovy.util; +package org.apache.groovy.util; /* * Written by Doug Lea with assistance from members of JCP JSR-166 @@ -22,7 +40,6 @@ package org.codehaus.groovy.util; * http://creativecommons.org/licenses/publicdomain */ -import com.hazelcast.internal.serialization.SerializableByConvention; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; @@ -44,13 +61,12 @@ import java.util.IdentityHashMap; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; import java.util.function.BiFunction; import java.util.function.Function; -import static com.hazelcast.internal.util.Preconditions.checkNotNull; - /** * An advanced hash table supporting configurable garbage collection semantics * of keys and values, optional referential-equality, full concurrency of @@ -149,10 +165,7 @@ import static com.hazelcast.internal.util.Preconditions.checkNotNull; * @author Jason T. Greene */ @SuppressWarnings("all") -@SerializableByConvention -public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> - implements com.hazelcast.internal.util.IConcurrentMap<K, V>, Serializable { - +public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implements Serializable { /* * The basic strategy is to subdivide the table among Segments, * each of which itself is a concurrently readable hash table. @@ -194,28 +207,28 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> /* ---------------- Constants -------------- */ - static final ReferenceType DEFAULT_KEY_TYPE = ReferenceType.WEAK; + private static final ReferenceType DEFAULT_KEY_TYPE = ReferenceType.WEAK; - static final ReferenceType DEFAULT_VALUE_TYPE = ReferenceType.STRONG; + private static final ReferenceType DEFAULT_VALUE_TYPE = ReferenceType.STRONG; /** * The default initial capacity for this table, * used when not otherwise specified in a constructor. */ - static final int DEFAULT_INITIAL_CAPACITY = 16; + private static final int DEFAULT_INITIAL_CAPACITY = 16; /** * The default load factor for this table, used when not * otherwise specified in a constructor. */ - static final float DEFAULT_LOAD_FACTOR = 0.75f; + private static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * The default concurrency level for this table, used when not * otherwise specified in a constructor. */ - static final int DEFAULT_CONCURRENCY_LEVEL = 16; + private static final int DEFAULT_CONCURRENCY_LEVEL = 16; /** * The maximum capacity, used if a higher value is implicitly @@ -223,13 +236,13 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> * be a power of two <= 1<<30 to ensure that entries are indexable * using ints. */ - static final int MAXIMUM_CAPACITY = 1 << 30; + private static final int MAXIMUM_CAPACITY = 1 << 30; /** * The maximum number of segments to allow; used to bound * constructor arguments. */ - static final int MAX_SEGMENTS = 1 << 16; + private static final int MAX_SEGMENTS = 1 << 16; /** * Number of unsynchronized retries in size and containsValue @@ -237,7 +250,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> * unbounded retries if tables undergo continuous modification * which would make it impossible to obtain an accurate result. */ - static final int RETRIES_BEFORE_LOCK = 2; + private static final int RETRIES_BEFORE_LOCK = 2; private static final long serialVersionUID = 7249069246763182397L; @@ -247,23 +260,23 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> * Mask value for indexing into segments. The upper bits of a * key's hash code are used to choose the segment. */ - final int segmentMask; + private final int segmentMask; /** * Shift value for indexing within segments. */ - final int segmentShift; + private final int segmentShift; /** * The segments, each of which is a specialized hash table */ - final Segment<K, V>[] segments; + private final Segment<K, V>[] segments; - boolean identityComparisons; + private boolean identityComparisons; - transient Set<K> keySet; - transient Set<Entry<K, V>> entrySet; - transient Collection<V> values; + private transient Set<K> keySet; + private transient Set<Entry<K, V>> entrySet; + private transient Collection<V> values; /* ---------------- Small Utilities -------------- */ @@ -291,7 +304,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> * @param hash the hash code for the key * @return the segment */ - final Segment<K, V> segmentFor(int hash) { + private final Segment<K, V> segmentFor(int hash) { return segments[(hash >>> segmentShift) & segmentMask]; } @@ -301,7 +314,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> /* ---------------- Inner Classes -------------- */ - interface KeyReference { + private interface KeyReference { int keyHash(); Object keyRef(); @@ -310,7 +323,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> /** * A weak-key reference which stores the key hash needed for reclamation. */ - static final class WeakKeyReference<K> extends WeakReference<K> implements KeyReference { + private static final class WeakKeyReference<K> extends WeakReference<K> implements KeyReference { final int hash; WeakKeyReference(K key, int hash, ReferenceQueue<Object> refQueue) { @@ -330,7 +343,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> /** * A soft-key reference which stores the key hash needed for reclamation. */ - static final class SoftKeyReference<K> extends SoftReference<K> implements KeyReference { + private static final class SoftKeyReference<K> extends SoftReference<K> implements KeyReference { final int hash; SoftKeyReference(K key, int hash, ReferenceQueue<Object> refQueue) { @@ -347,7 +360,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> } } - static final class WeakValueReference<V> extends WeakReference<V> implements KeyReference { + private static final class WeakValueReference<V> extends WeakReference<V> implements KeyReference { final Object keyRef; final int hash; @@ -366,7 +379,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> } } - static final class SoftValueReference<V> extends SoftReference<V> implements KeyReference { + private static final class SoftValueReference<V> extends SoftReference<V> implements KeyReference { final Object keyRef; final int hash; @@ -397,7 +410,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> * backup in case a null (pre-initialized) value is ever seen in * an unsynchronized access method. */ - static final class HashEntry<K, V> { + private static final class HashEntry<K, V> { final Object keyRef; final int hash; volatile Object valueRef; @@ -471,8 +484,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> * subclasses from ReentrantLock opportunistically, just to * simplify some locking and avoid separate construction. */ - @SerializableByConvention - static final class Segment<K, V> extends ReentrantLock implements Serializable { + private static final class Segment<K, V> extends ReentrantLock implements Serializable { /* * Segments maintain a table of entry lists that are ALWAYS * kept in a consistent state, so they can be read without locking. @@ -1447,10 +1459,9 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> * absent. Implementations which support null values <strong>must</strong> * override this default implementation. */ - @Override public V applyIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) { - checkNotNull(key); - checkNotNull(mappingFunction); + Objects.requireNonNull(key); + Objects.requireNonNull(mappingFunction); int hash = hashOf(key); Segment<K, V> segment = segmentFor(hash); @@ -1458,10 +1469,9 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> return v == null ? segment.put(key, hash, null, mappingFunction, true) : v; } - @Override public V applyIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) { - checkNotNull(key); - checkNotNull(remappingFunction); + Objects.requireNonNull(key); + Objects.requireNonNull(remappingFunction); int hash = hashOf(key); Segment<K, V> segment = segmentFor(hash); @@ -1473,10 +1483,9 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> return segmentFor(hash).applyIfPresent(key, hash, remappingFunction); } - @Override public V apply(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) { - checkNotNull(key); - checkNotNull(remappingFunction); + Objects.requireNonNull(key); + Objects.requireNonNull(remappingFunction); int hash = hashOf(key); Segment<K, V> segment = segmentFor(hash); @@ -1741,7 +1750,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> } } - final class KeyIterator extends HashIterator implements Iterator<K>, Enumeration<K> { + private final class KeyIterator extends HashIterator implements Iterator<K>, Enumeration<K> { public K next() { return super.nextEntry().key(); } @@ -1751,7 +1760,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> } } - final class ValueIterator extends HashIterator implements Iterator<V>, Enumeration<V> { + private final class ValueIterator extends HashIterator implements Iterator<V>, Enumeration<V> { public V next() { return super.nextEntry().value(); } @@ -1764,7 +1773,6 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> /* * This class is needed for JDK5 compatibility. */ - @SerializableByConvention protected static class SimpleEntry<K, V> implements Entry<K, V>, Serializable { private static final long serialVersionUID = -8499721149061103585L; @@ -1822,7 +1830,6 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> * Custom Entry class used by EntryIterator.next(), that relays setValue * changes to the underlying map. */ - @SerializableByConvention protected class WriteThroughEntry extends SimpleEntry<K, V> { private static final long serialVersionUID = -7900634345345313646L; @@ -1849,14 +1856,14 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> } } - final class EntryIterator extends HashIterator implements Iterator<Entry<K, V>> { + private final class EntryIterator extends HashIterator implements Iterator<Entry<K, V>> { public Entry<K, V> next() { HashEntry<K, V> e = super.nextEntry(); return new WriteThroughEntry(e.key(), e.value()); } } - final class CachedEntryIterator extends HashIterator implements Iterator<Entry<K, V>> { + private final class CachedEntryIterator extends HashIterator implements Iterator<Entry<K, V>> { private InitializableEntry entry = new InitializableEntry(); public Entry<K, V> next() { @@ -1891,7 +1898,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> } } - final class KeySet extends AbstractSet<K> { + private final class KeySet extends AbstractSet<K> { public Iterator<K> iterator() { return new KeyIterator(); } @@ -1917,7 +1924,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> } } - final class Values extends AbstractCollection<V> { + private final class Values extends AbstractCollection<V> { public Iterator<V> iterator() { return new ValueIterator(); } @@ -1939,7 +1946,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> } } - final class EntrySet extends AbstractSet<Entry<K, V>> { + private final class EntrySet extends AbstractSet<Entry<K, V>> { private final boolean cached; public EntrySet(boolean cached) { diff --git a/src/main/java/org/apache/groovy/util/ManagedIdentityConcurrentMap.java b/src/main/java/org/apache/groovy/util/ManagedIdentityConcurrentMap.java new file mode 100644 index 0000000..0615290 --- /dev/null +++ b/src/main/java/org/apache/groovy/util/ManagedIdentityConcurrentMap.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.groovy.util; + +import java.util.EnumSet; + +/** + * This is a basic implementation of a map able to forget its keys. This + * map uses internally a ConcurrentHashMap, thus should be safe for concurrency. + * hashcode and equals are used to find the entries and should thus be implemented + * properly for the keys. This map does not support null keys. + * + * @param <K> the key type + * @param <V> the value type + * @since 4.0.0 + */ +public class ManagedIdentityConcurrentMap<K, V> extends ConcurrentReferenceHashMap<K, V> { + public ManagedIdentityConcurrentMap(ReferenceType keyType) { + super(keyType, ReferenceType.STRONG, EnumSet.of(Option.IDENTITY_COMPARISONS)); + } + + /** + * Get the key specified value, or put the default value and return it if the key is absent + * + * @param key the key to look up + * @param value the default value if the key is absent + * @return the value + */ + public V getOrPut(K key, V value) { + return this.applyIfAbsent(key, k -> value); + } +} diff --git a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java index 54b320f..50ab79a 100644 --- a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java +++ b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java @@ -46,14 +46,13 @@ import groovy.lang.Tuple6; import groovy.lang.Tuple7; import groovy.lang.Tuple8; import groovy.lang.Tuple9; +import org.apache.groovy.util.ManagedIdentityConcurrentMap; import org.apache.groovy.util.Maps; import org.codehaus.groovy.classgen.asm.util.TypeUtil; import org.codehaus.groovy.runtime.GeneratedClosure; import org.codehaus.groovy.runtime.GeneratedLambda; import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport; import org.codehaus.groovy.transform.trait.Traits; -import org.codehaus.groovy.util.ManagedIdentityConcurrentMap; -import org.codehaus.groovy.util.ReferenceBundle; import org.codehaus.groovy.vmplugin.VMPluginFactory; import org.objectweb.asm.Opcodes; @@ -408,7 +407,7 @@ public class ClassHelper { } static class ClassHelperCache { - static ManagedIdentityConcurrentMap<Class, SoftReference<ClassNode>> classCache = new ManagedIdentityConcurrentMap<Class, SoftReference<ClassNode>>(ReferenceBundle.getWeakBundle()); + static ManagedIdentityConcurrentMap<Class, SoftReference<ClassNode>> classCache = new ManagedIdentityConcurrentMap<>(ManagedIdentityConcurrentMap.ReferenceType.WEAK); } public static boolean isSAMType(final ClassNode type) { diff --git a/src/main/java/org/codehaus/groovy/reflection/ClassInfo.java b/src/main/java/org/codehaus/groovy/reflection/ClassInfo.java index b0c00e1..42cc3e4 100644 --- a/src/main/java/org/codehaus/groovy/reflection/ClassInfo.java +++ b/src/main/java/org/codehaus/groovy/reflection/ClassInfo.java @@ -25,6 +25,7 @@ import groovy.lang.GroovySystem; import groovy.lang.MetaClass; import groovy.lang.MetaClassRegistry; import groovy.lang.MetaMethod; +import org.apache.groovy.util.ManagedIdentityConcurrentMap; import org.codehaus.groovy.reflection.GroovyClassValue.ComputeValue; import org.codehaus.groovy.reflection.stdclasses.ArrayCachedClass; import org.codehaus.groovy.reflection.stdclasses.BigDecimalCachedClass; @@ -46,7 +47,6 @@ import org.codehaus.groovy.util.Finalizable; import org.codehaus.groovy.util.LazyReference; import org.codehaus.groovy.util.LockableObject; import org.codehaus.groovy.util.ManagedConcurrentLinkedQueue; -import org.codehaus.groovy.util.ManagedIdentityConcurrentMap; import org.codehaus.groovy.util.ManagedReference; import org.codehaus.groovy.util.ReferenceBundle; import org.codehaus.groovy.vmplugin.VMPluginFactory; @@ -409,7 +409,7 @@ public class ClassInfo implements Finalizable { if (metaClass != null) { if (perInstanceMetaClassMap == null) - perInstanceMetaClassMap = new ManagedIdentityConcurrentMap<Object, MetaClass>(ReferenceBundle.getWeakBundle()); + perInstanceMetaClassMap = new ManagedIdentityConcurrentMap<>(ManagedIdentityConcurrentMap.ReferenceType.WEAK); perInstanceMetaClassMap.put(obj, metaClass); } diff --git a/src/main/java/org/codehaus/groovy/reflection/MixinInMetaClass.java b/src/main/java/org/codehaus/groovy/reflection/MixinInMetaClass.java index f9763e1..77477c1 100644 --- a/src/main/java/org/codehaus/groovy/reflection/MixinInMetaClass.java +++ b/src/main/java/org/codehaus/groovy/reflection/MixinInMetaClass.java @@ -26,14 +26,13 @@ import groovy.lang.MetaClass; import groovy.lang.MetaMethod; import groovy.lang.MetaProperty; import groovy.transform.Internal; +import org.apache.groovy.util.ManagedIdentityConcurrentMap; import org.codehaus.groovy.runtime.HandleMetaClass; import org.codehaus.groovy.runtime.MetaClassHelper; import org.codehaus.groovy.runtime.metaclass.MixedInMetaClass; import org.codehaus.groovy.runtime.metaclass.MixinInstanceMetaMethod; import org.codehaus.groovy.runtime.metaclass.MixinInstanceMetaProperty; import org.codehaus.groovy.runtime.metaclass.NewInstanceMetaMethod; -import org.codehaus.groovy.util.ManagedIdentityConcurrentMap; -import org.codehaus.groovy.util.ReferenceBundle; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -45,7 +44,7 @@ public class MixinInMetaClass { final CachedClass mixinClass; final CachedConstructor constructor; private final ManagedIdentityConcurrentMap managedIdentityConcurrentMap = - new ManagedIdentityConcurrentMap(ReferenceBundle.getSoftBundle()); + new ManagedIdentityConcurrentMap(ManagedIdentityConcurrentMap.ReferenceType.SOFT); public MixinInMetaClass(ExpandoMetaClass emc, CachedClass mixinClass) { this.emc = emc; diff --git a/src/main/java/org/codehaus/groovy/runtime/metaclass/ThreadManagedMetaBeanProperty.java b/src/main/java/org/codehaus/groovy/runtime/metaclass/ThreadManagedMetaBeanProperty.java index 8cae32f..d75d934 100644 --- a/src/main/java/org/codehaus/groovy/runtime/metaclass/ThreadManagedMetaBeanProperty.java +++ b/src/main/java/org/codehaus/groovy/runtime/metaclass/ThreadManagedMetaBeanProperty.java @@ -21,9 +21,9 @@ package org.codehaus.groovy.runtime.metaclass; import groovy.lang.Closure; import groovy.lang.MetaBeanProperty; import groovy.lang.MetaMethod; +import org.apache.groovy.util.ManagedIdentityConcurrentMap; import org.codehaus.groovy.reflection.CachedClass; import org.codehaus.groovy.reflection.ReflectionCache; -import org.codehaus.groovy.util.ManagedIdentityConcurrentMap; import org.codehaus.groovy.util.ReferenceBundle; import java.lang.reflect.Modifier; @@ -120,7 +120,7 @@ public class ThreadManagedMetaBeanProperty extends MetaBeanProperty { private static ManagedIdentityConcurrentMap getInstance2PropName(String name) { ManagedIdentityConcurrentMap res = PROPNAME_TO_MAP.get(name); if (res == null) { - res = new ManagedIdentityConcurrentMap(SOFT_BUNDLE); + res = new ManagedIdentityConcurrentMap(ManagedIdentityConcurrentMap.ReferenceType.SOFT); ManagedIdentityConcurrentMap ores = PROPNAME_TO_MAP.putIfAbsent(name, res); if (ores != null) return ores; diff --git a/src/main/java/org/codehaus/groovy/util/ManagedIdentityConcurrentMap.java b/src/main/java/org/codehaus/groovy/util/ManagedIdentityConcurrentMap.java deleted file mode 100644 index 2736244..0000000 --- a/src/main/java/org/codehaus/groovy/util/ManagedIdentityConcurrentMap.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.codehaus.groovy.util; - -import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; - -/** - * This is a basic implementation of a map able to forget its keys. This - * map uses internally a ConcurrentHashMap, thus should be safe for concurrency. - * hashcode and equals are used to find the entries and should thus be implemented - * properly for the keys. This map does not support null keys. - * - * @param <K> the key type - * @param <V> the value type - * @since 4.0.0 - */ -public class ManagedIdentityConcurrentMap<K, V> { - private final ConcurrentHashMap<ManagedReference<K>, V> internalMap; - private final ReferenceBundle bundle; - - public ManagedIdentityConcurrentMap(ReferenceBundle bundle) { - this.internalMap = new ConcurrentHashMap<>(); - this.bundle = bundle; - } - - /** - * Returns the value stored for the given key at the point of call. - * - * @param key a non null key - * @return the value stored in the map for the given key - */ - public V get(K key) { - return internalMap.get(new ManagedIdentityKey<K>(key)); - } - - /** - * Sets a new value for a given key. an older value is overwritten. - * - * @param key a non null key - * @param value the new value - */ - public V put(final K key, V value) { - return internalMap.put(new ManagedIdentityKey<K>(key), value); - } - - /** - * Returns the values of the map - */ - public Collection<V> values() { - return internalMap.values(); - } - - /** - * Remove the key specified entry - * - * @param key the key to look up - * @return the removed value - */ - public V remove(K key) { - return internalMap.remove(new ManagedIdentityKey<K>(key)); - } - - /** - * Get the key specified value, or put the default value and return it if the key is absent - * - * @param key the key to look up - * @param value the default value if the key is absent - * @return the value - */ - public V getOrPut(K key, V value) { - return internalMap.computeIfAbsent(new ManagedIdentityKey<K>(key), k -> value); - } - - /** - * Returns the map size - */ - public Object size() { - return internalMap.size(); - } - - /** - * Check if the map is empty or not - * - * @return {@code true} when the map is empty, otherwise {@code false} - */ - public boolean isEmpty() { - return internalMap.isEmpty(); - } - - /** - * Represents identity key of {@link ManagedIdentityConcurrentMap} - * - * @param <K> the key type - */ - private class ManagedIdentityKey<K> extends ManagedReference<K> { - private final int hashCode; - - private ManagedIdentityKey(K key) { - super(bundle, key); - this.hashCode = hash(key); - } - - @Override - public void finalizeReference() { - internalMap.remove(this); - super.finalizeReference(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ManagedIdentityKey)) return false; - return get() == ((ManagedIdentityKey<?>) o).get(); - } - - @Override - public int hashCode() { - return this.hashCode; - } - - private int hash(K key) { - int h = (null == key) ? 0 : System.identityHashCode(key); - h += (h << 15) ^ 0xffffcd7d; - h ^= (h >>> 10); - h += (h << 3); - h ^= (h >>> 6); - h += (h << 2) + (h << 14); - h ^= (h >>> 16); - return h; - } - } -} diff --git a/src/test/org/codehaus/groovy/util/ManagedIdentityConcurrentMapTest.groovy b/src/test/org/apache/groovy/util/ManagedIdentityConcurrentMapTest.groovy similarity index 84% rename from src/test/org/codehaus/groovy/util/ManagedIdentityConcurrentMapTest.groovy rename to src/test/org/apache/groovy/util/ManagedIdentityConcurrentMapTest.groovy index d454b3f..3c76b1c 100644 --- a/src/test/org/codehaus/groovy/util/ManagedIdentityConcurrentMapTest.groovy +++ b/src/test/org/apache/groovy/util/ManagedIdentityConcurrentMapTest.groovy @@ -16,15 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -package org.codehaus.groovy.util - +package org.apache.groovy.util import org.junit.Test class ManagedIdentityConcurrentMapTest { @Test void testRemovingEntriesFromMapAfterGC() { - def m = new ManagedIdentityConcurrentMap<Object, String>(ReferenceBundle.getWeakBundle()) + def m = new ManagedIdentityConcurrentMap<Object, String>(ManagedIdentityConcurrentMap.ReferenceType.WEAK) def k1 = new Object() m.put(k1, "a") def k2 = new Object() @@ -50,11 +49,10 @@ class ManagedIdentityConcurrentMapTest { Thread.sleep(100) } - // finalize manually - if (!m.isEmpty()) { - m.internalMap.keySet().stream().forEach(e -> e.finalizeReference()) - } - - assert m.isEmpty() + def k4 = new Object() + assert "d" == m.getOrPut(k4, "d") + assert "d" == m.getOrPut(k4, "e") + assert [k4] == (m.keySet() as List) + assert ["d"] == (m.values() as List) } }