diff --git a/platform/platform-tests/testSrc/com/intellij/util/containers/ConcurrentMapsTest.java b/platform/platform-tests/testSrc/com/intellij/util/containers/ConcurrentMapsTest.java index 918d2a607a00..c4f1760c1ddb 100644 --- a/platform/platform-tests/testSrc/com/intellij/util/containers/ConcurrentMapsTest.java +++ b/platform/platform-tests/testSrc/com/intellij/util/containers/ConcurrentMapsTest.java @@ -298,6 +298,10 @@ public class ConcurrentMapsTest { @Test(timeout = TIMEOUT) public void testWeakKeyWeakValueTossedWeakKeyAndValue() { WeakKeyWeakValueHashMap map = new WeakKeyWeakValueHashMap<>(); + checkTossed(map); + } + + private static void checkTossed(RefKeyRefValueHashMap map) { map.put(new Object(), new Object()); do { @@ -306,6 +310,32 @@ public class ConcurrentMapsTest { } while (!map.processQueue()); assertTrue(map.isEmpty()); + + Object hardKey = map; + map.put(hardKey, new Object()); + + do { + tryGcSoftlyReachableObjects(); + System.gc(); + } + while (!map.processQueue()); + assertTrue(map.isEmpty()); + + Object hardValue = map; + map.put(new Object(), hardValue); + + do { + tryGcSoftlyReachableObjects(); + System.gc(); + } + while (!map.processQueue()); + assertTrue(map.isEmpty()); + } + + @Test(timeout = TIMEOUT) + public void testWeakKeySoftValueTossedWeakKeyAndValue() { + WeakKeySoftValueHashMap map = new WeakKeySoftValueHashMap<>(); + checkTossed(map); } @Test(timeout = TIMEOUT) diff --git a/platform/util/src/com/intellij/util/containers/ContainerUtil.java b/platform/util/src/com/intellij/util/containers/ContainerUtil.java index 6c156c0136d5..2eeb244565af 100644 --- a/platform/util/src/com/intellij/util/containers/ContainerUtil.java +++ b/platform/util/src/com/intellij/util/containers/ContainerUtil.java @@ -2821,5 +2821,10 @@ public class ContainerUtil extends ContainerUtilRt { return new TreeMap>(); } } + + @NotNull + public static Map createWeakKeySoftValueMap() { + return new WeakKeySoftValueHashMap(); + } } diff --git a/platform/util/src/com/intellij/util/containers/RefKeyRefValueHashMap.java b/platform/util/src/com/intellij/util/containers/RefKeyRefValueHashMap.java new file mode 100644 index 000000000000..4ca3eef69855 --- /dev/null +++ b/platform/util/src/com/intellij/util/containers/RefKeyRefValueHashMap.java @@ -0,0 +1,134 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed 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 com.intellij.util.containers; + +import org.jetbrains.annotations.NotNull; + +import java.lang.ref.ReferenceQueue; +import java.util.*; + +abstract class RefKeyRefValueHashMap implements Map{ + private final RefHashMap> myWeakKeyMap; + private final ReferenceQueue myQueue = new ReferenceQueue(); + + public RefKeyRefValueHashMap(@NotNull RefHashMap> weakKeyMap) { + myWeakKeyMap = weakKeyMap; + } + + protected interface ValueReference { + @NotNull + RefHashMap.Key getKey(); + + V get(); + } + protected V dereference(ValueReference reference) { + return reference == null ? null : reference.get(); + } + + protected abstract ValueReference createValueReference(@NotNull RefHashMap.Key key, V referent, ReferenceQueue q); + + // returns true if some refs were tossed + boolean processQueue() { + boolean processed = myWeakKeyMap.processQueue(); + while(true) { + ValueReference ref = (ValueReference)myQueue.poll(); + if (ref == null) break; + RefHashMap.Key weakKey = ref.getKey(); + myWeakKeyMap.removeKey(weakKey); + processed = true; + } + return processed; + } + + @Override + public V get(Object key) { + ValueReference ref = myWeakKeyMap.get(key); + return dereference(ref); + } + + @Override + public V put(K key, V value) { + processQueue(); + RefHashMap.Key weakKey = myWeakKeyMap.createKey(key); + ValueReference reference = createValueReference(weakKey, value, myQueue); + ValueReference oldRef = myWeakKeyMap.putKey(weakKey, reference); + return dereference(oldRef); + } + + @Override + public V remove(Object key) { + processQueue(); + ValueReference ref = myWeakKeyMap.remove(key); + return dereference(ref); + } + + @Override + public void putAll(@NotNull Map t) { + throw new RuntimeException("method not implemented"); + } + + @Override + public void clear() { + myWeakKeyMap.clear(); + processQueue(); + } + + @Override + public int size() { + return myWeakKeyMap.size(); //? + } + + @Override + public boolean isEmpty() { + return myWeakKeyMap.isEmpty(); //? + } + + @Override + public boolean containsKey(Object key) { + return get(key) != null; + } + + @Override + public boolean containsValue(Object value) { + throw new RuntimeException("method not implemented"); + } + + @NotNull + @Override + public Set keySet() { + return myWeakKeyMap.keySet(); + } + + @NotNull + @Override + public Collection values() { + List result = new ArrayList(); + final Collection> refs = myWeakKeyMap.values(); + for (ValueReference ref : refs) { + final V value = ref.get(); + if (value != null) { + result.add(value); + } + } + return result; + } + + @NotNull + @Override + public Set> entrySet() { + throw new RuntimeException("method not implemented"); + } +} diff --git a/platform/util/src/com/intellij/util/containers/WeakKeySoftValueHashMap.java b/platform/util/src/com/intellij/util/containers/WeakKeySoftValueHashMap.java new file mode 100644 index 000000000000..a0a1372812bd --- /dev/null +++ b/platform/util/src/com/intellij/util/containers/WeakKeySoftValueHashMap.java @@ -0,0 +1,50 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed 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 com.intellij.util.containers; + +import org.jetbrains.annotations.NotNull; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.util.Map; + +final class WeakKeySoftValueHashMap extends RefKeyRefValueHashMap implements Map{ + public WeakKeySoftValueHashMap() { + super(new WeakHashMap>()); + } + + private static class SoftValueReference extends SoftReference implements ValueReference { + @NotNull private final RefHashMap.Key key; + + private SoftValueReference(@NotNull WeakHashMap.Key key, V referent, ReferenceQueue q) { + super(referent, q); + this.key = key; + } + + @NotNull + @Override + public RefHashMap.Key getKey() { + return key; + } + } + + @Override + protected ValueReference createValueReference(@NotNull RefHashMap.Key key, + V referent, + ReferenceQueue q) { + return new SoftValueReference(key, referent, q); + } +} diff --git a/platform/util/src/com/intellij/util/containers/WeakKeyWeakValueHashMap.java b/platform/util/src/com/intellij/util/containers/WeakKeyWeakValueHashMap.java index 4335505ac20e..184557d0c85a 100644 --- a/platform/util/src/com/intellij/util/containers/WeakKeyWeakValueHashMap.java +++ b/platform/util/src/com/intellij/util/containers/WeakKeyWeakValueHashMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,115 +15,36 @@ */ package com.intellij.util.containers; -import com.intellij.reference.SoftReference; import org.jetbrains.annotations.NotNull; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; -import java.util.*; +import java.util.Map; -public final class WeakKeyWeakValueHashMap implements Map{ - private final WeakHashMap> myWeakKeyMap = new WeakHashMap>(); - private final ReferenceQueue myQueue = new ReferenceQueue(); +public final class WeakKeyWeakValueHashMap extends RefKeyRefValueHashMap implements Map{ + public WeakKeyWeakValueHashMap() { + super(new WeakHashMap>()); + } - private static class ValueReference extends WeakReference { + private static class WeakValueReference extends WeakReference implements ValueReference { @NotNull private final WeakHashMap.Key key; - private ValueReference(@NotNull WeakHashMap.Key key, V referent, ReferenceQueue q) { + private WeakValueReference(@NotNull WeakHashMap.Key key, V referent, ReferenceQueue q) { super(referent, q); this.key = key; } - } - // returns true if some refs were tossed - boolean processQueue() { - boolean processed = myWeakKeyMap.processQueue(); - while(true) { - ValueReference ref = (ValueReference)myQueue.poll(); - if (ref == null) break; - WeakHashMap.Key weakKey = ref.key; - myWeakKeyMap.removeKey(weakKey); - processed = true; + @NotNull + @Override + public RefHashMap.Key getKey() { + return key; } - return processed; } @Override - public V get(Object key) { - ValueReference ref = myWeakKeyMap.get(key); - return SoftReference.dereference(ref); - } - - @Override - public V put(K key, V value) { - processQueue(); - WeakHashMap.Key weakKey = myWeakKeyMap.createKey(key); - ValueReference reference = new ValueReference(weakKey, value, myQueue); - ValueReference oldRef = myWeakKeyMap.putKey(weakKey, reference); - return SoftReference.dereference(oldRef); - } - - @Override - public V remove(Object key) { - processQueue(); - ValueReference ref = myWeakKeyMap.remove(key); - return SoftReference.dereference(ref); - } - - @Override - public void putAll(@NotNull Map t) { - throw new RuntimeException("method not implemented"); - } - - @Override - public void clear() { - myWeakKeyMap.clear(); - processQueue(); - } - - @Override - public int size() { - return myWeakKeyMap.size(); //? - } - - @Override - public boolean isEmpty() { - return myWeakKeyMap.isEmpty(); //? - } - - @Override - public boolean containsKey(Object key) { - return get(key) != null; - } - - @Override - public boolean containsValue(Object value) { - throw new RuntimeException("method not implemented"); - } - - @NotNull - @Override - public Set keySet() { - return myWeakKeyMap.keySet(); - } - - @NotNull - @Override - public Collection values() { - List result = new ArrayList(); - final Collection> refs = myWeakKeyMap.values(); - for (ValueReference ref : refs) { - final V value = ref.get(); - if (value != null) { - result.add(value); - } - } - return result; - } - - @NotNull - @Override - public Set> entrySet() { - throw new RuntimeException("method not implemented"); + protected ValueReference createValueReference(@NotNull RefHashMap.Key key, + V referent, + ReferenceQueue q) { + return new WeakValueReference(key, referent, q); } }