From fd893067f87ab6c7afca43db6aeb7727ed66e94f Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 24 Mar 2015 12:26:19 +0100 Subject: [PATCH] intern recent vfs user data with more than one key-value mapping --- .../vfs/newvfs/impl/UserDataInterner.java | 108 ++++++++++++++++-- .../util/keyFMap/ArrayBackedFMap.java | 10 +- .../util/keyFMap/PairElementsFMap.java | 18 ++- 3 files changed, 123 insertions(+), 13 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/UserDataInterner.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/UserDataInterner.java index ad2e490d3784..0c5b331ad89c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/UserDataInterner.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/UserDataInterner.java @@ -15,30 +15,116 @@ */ package com.intellij.openapi.vfs.newvfs.impl; -import com.intellij.util.ConcurrencyUtil; -import com.intellij.util.containers.ContainerUtil; +import com.intellij.reference.SoftReference; +import com.intellij.util.containers.hash.LinkedHashMap; +import com.intellij.util.keyFMap.ArrayBackedFMap; import com.intellij.util.keyFMap.KeyFMap; import com.intellij.util.keyFMap.OneElementFMap; +import com.intellij.util.keyFMap.PairElementsFMap; import org.jetbrains.annotations.NotNull; -import java.nio.charset.Charset; -import java.util.concurrent.ConcurrentMap; +import java.lang.ref.WeakReference; +import java.util.Arrays; +import java.util.Map; /** * @author peter */ class UserDataInterner { - private static final ConcurrentMap ourCache = ContainerUtil.createConcurrentWeakMap(); - + private static final LinkedHashMap ourCache = new LinkedHashMap(20, true) { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > 15; + } + }; + static KeyFMap internUserData(@NotNull KeyFMap map) { - if (map instanceof OneElementFMap && shouldIntern((OneElementFMap)map)) { - return ConcurrencyUtil.cacheOrGet(ourCache, (OneElementFMap)map, (OneElementFMap)map); + if (shouldIntern(map)) { + MapReference key = new MapReference(map); + synchronized (ourCache) { + KeyFMap cached = SoftReference.dereference(ourCache.get(key)); + if (cached != null) return cached; + + ourCache.put(key, key); + } + return map; } return map; } - private static boolean shouldIntern(OneElementFMap map) { - Object value = map.getValue(); - return value instanceof Enum || value instanceof Boolean || value instanceof Charset; + private static boolean shouldIntern(@NotNull KeyFMap map) { + return map instanceof OneElementFMap || + map instanceof PairElementsFMap || + map instanceof ArrayBackedFMap && ((ArrayBackedFMap)map).getKeys().length <= 5; + } +} + +class MapReference extends WeakReference { + final int myHash; + + MapReference(KeyFMap referent) { + super(referent); + myHash = computeHashCode(referent); + } + + private static int computeHashCode(KeyFMap object) { + if (object instanceof OneElementFMap) { + return ((OneElementFMap)object).getKey().hashCode() * 31 + System.identityHashCode(((OneElementFMap)object).getValue()); + } + if (object instanceof PairElementsFMap) { + PairElementsFMap map = (PairElementsFMap)object; + return (map.getKey1().hashCode() * 31 + map.getKey2().hashCode()) * 31 + + System.identityHashCode(map.getValue1()) + System.identityHashCode(map.getValue2()); + } + if (object instanceof ArrayBackedFMap) { + int hc = Arrays.hashCode(((ArrayBackedFMap)object).getKeys()); + for (Object o : ((ArrayBackedFMap)object).getValues()) { + hc = hc * 31 + System.identityHashCode(o); + } + return hc; + } + return 0; + } + + @Override + public int hashCode() { + return myHash; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (!(obj instanceof MapReference) || myHash != ((MapReference)obj).myHash) return false; + + KeyFMap o1 = get(); + KeyFMap o2 = ((MapReference)obj).get(); + if (o1 == null || o2 == null) return false; + + if (o1 instanceof OneElementFMap && o2 instanceof OneElementFMap) { + OneElementFMap m1 = (OneElementFMap)o1; + OneElementFMap m2 = (OneElementFMap)o2; + return m1.getKey() == m2.getKey() && m1.getValue() == m2.getValue(); + } + if (o1 instanceof PairElementsFMap && o2 instanceof PairElementsFMap) { + PairElementsFMap m1 = (PairElementsFMap)o1; + PairElementsFMap m2 = (PairElementsFMap)o2; + return m1.getKey1() == m2.getKey1() && m1.getKey2() == m2.getKey2() && + m1.getValue1() == m2.getValue1() && m1.getValue2() == m2.getValue2(); + } + if (o1 instanceof ArrayBackedFMap && o2 instanceof ArrayBackedFMap) { + ArrayBackedFMap m1 = (ArrayBackedFMap)o1; + ArrayBackedFMap m2 = (ArrayBackedFMap)o2; + return Arrays.equals(m1.getKeys(), m2.getKeys()) && containSameElements(m1.getValues(), m2.getValues()); + } + return false; + } + + private static boolean containSameElements(Object[] v1, Object[] v2) { + if (v1.length != v2.length) return false; + + for (int i = 0; i < v1.length; i++) { + if (v1[i] != v2[i]) return false; + } + return true; } } diff --git a/platform/util/src/com/intellij/util/keyFMap/ArrayBackedFMap.java b/platform/util/src/com/intellij/util/keyFMap/ArrayBackedFMap.java index 000f225bc1f2..05717393eee8 100644 --- a/platform/util/src/com/intellij/util/keyFMap/ArrayBackedFMap.java +++ b/platform/util/src/com/intellij/util/keyFMap/ArrayBackedFMap.java @@ -18,7 +18,7 @@ package com.intellij.util.keyFMap; import com.intellij.openapi.util.Key; import org.jetbrains.annotations.NotNull; -class ArrayBackedFMap implements KeyFMap { +public class ArrayBackedFMap implements KeyFMap { static final int ARRAY_THRESHOLD = 8; private final int[] keys; private final Object[] values; @@ -134,4 +134,12 @@ class ArrayBackedFMap implements KeyFMap { public boolean isEmpty() { return false; } + + public int[] getKeys() { + return keys; + } + + public Object[] getValues() { + return values; + } } diff --git a/platform/util/src/com/intellij/util/keyFMap/PairElementsFMap.java b/platform/util/src/com/intellij/util/keyFMap/PairElementsFMap.java index bb8b8d0ace15..b6e5a21bd58e 100644 --- a/platform/util/src/com/intellij/util/keyFMap/PairElementsFMap.java +++ b/platform/util/src/com/intellij/util/keyFMap/PairElementsFMap.java @@ -18,7 +18,7 @@ package com.intellij.util.keyFMap; import com.intellij.openapi.util.Key; import org.jetbrains.annotations.NotNull; -class PairElementsFMap implements KeyFMap { +public class PairElementsFMap implements KeyFMap { private final Key key1; private final Key key2; private final Object value1; @@ -63,4 +63,20 @@ class PairElementsFMap implements KeyFMap { public boolean isEmpty() { return false; } + + public Key getKey1() { + return key1; + } + + public Key getKey2() { + return key2; + } + + public Object getValue1() { + return value1; + } + + public Object getValue2() { + return value2; + } }