diff --git a/platform/util/src/com/intellij/openapi/util/UserDataHolderBase.java b/platform/util/src/com/intellij/openapi/util/UserDataHolderBase.java index d9d541032c61..01f2d081612b 100644 --- a/platform/util/src/com/intellij/openapi/util/UserDataHolderBase.java +++ b/platform/util/src/com/intellij/openapi/util/UserDataHolderBase.java @@ -16,44 +16,26 @@ package com.intellij.openapi.util; +import com.intellij.util.SmartFMap; import com.intellij.util.concurrency.AtomicFieldUpdater; -import com.intellij.util.containers.StripedLockConcurrentHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; -import java.util.ConcurrentModificationException; import java.util.Map; -import java.util.concurrent.ConcurrentMap; public class UserDataHolderBase implements UserDataHolderEx, Cloneable { - private static final Key> COPYABLE_USER_MAP_KEY = Key.create("COPYABLE_USER_MAP_KEY"); + private static final Key> COPYABLE_USER_MAP_KEY = Key.create("COPYABLE_USER_MAP_KEY"); /** * Concurrent writes to this field are via CASes only, using the {@link #updater} - * When map becomes empty, this field set to null atomically - * - * Basic state transitions are as follows: - * - * (adding keyvalue) (putUserData(key,value)) - * [myUserMap=null] -> [myUserMap=(key->value)] - * - * (adding another) (putUserData(key2,value2)) - * [myUserMap=(key->value)] -> [myUserMap=(key->value, key2->value2)] - * - * (removing keyvalue) (putUserData(k2,null)) - * [myUserMap=(key->value, k2->v2)] -> [myUserMap=(key->value)] - * - * (removing last entry) (putUserData(key,null)) - * [myUserMap=(key->value)] -> [myUserMap=null] - * */ - private volatile ConcurrentMap myUserMap = null; + @NotNull private volatile SmartFMap myUserMap = SmartFMap.emptyMap(); protected Object clone() { try { UserDataHolderBase clone = (UserDataHolderBase)super.clone(); - clone.myUserMap = null; + clone.myUserMap = SmartFMap.emptyMap(); copyCopyableDataTo(clone); return clone; } @@ -65,207 +47,86 @@ public class UserDataHolderBase implements UserDataHolderEx, Cloneable { @TestOnly public String getUserDataString() { - final ConcurrentMap userMap = myUserMap; - if (userMap == null) { - return ""; - } + final SmartFMap userMap = myUserMap; final Map copyableMap = getUserData(COPYABLE_USER_MAP_KEY); return userMap.toString() + (copyableMap == null ? "" : copyableMap.toString()); } public void copyUserDataTo(UserDataHolderBase other) { - ConcurrentMap map = myUserMap; - if (map == null) { - other.myUserMap = null; - } - else { - ConcurrentMap fresh = createDataMap(map.size()); - fresh.putAll(map); - other.myUserMap = fresh; - } + other.myUserMap = myUserMap; } public T getUserData(@NotNull Key key) { - final Map map = myUserMap; //noinspection unchecked - return map == null ? null : (T)map.get(key); + return (T)myUserMap.get(key); } public void putUserData(@NotNull Key key, @Nullable T value) { while (true) { - try { - if (value == null) { - ConcurrentMap map = myUserMap; - if (map == null) break; - @SuppressWarnings("unchecked") - T previous = (T)map.remove(key); - boolean removed = previous != null; - if (removed) { - nullifyMapFieldIfEmpty(); - } - } - else { - getOrCreateMap().put(key, value); - } - break; - } - catch (ConcurrentModificationException ignored) { + SmartFMap map = myUserMap; + SmartFMap newMap = value == null ? map.minus(key) : map.plus(key, value); + if (newMap == map || updater.compareAndSet(this, map, newMap)) { + return; } } } - private static ConcurrentMap createDataMap(int initialCapacity) { - return new StripedLockConcurrentHashMap(initialCapacity); - } - public T getCopyableUserData(Key key) { - return getCopyableUserDataImpl(key); - } - - protected final T getCopyableUserDataImpl(Key key) { - Map map = getUserData(COPYABLE_USER_MAP_KEY); - //noinspection unchecked + SmartFMap map = getUserData(COPYABLE_USER_MAP_KEY); + //noinspection unchecked,ConstantConditions return map == null ? null : (T)map.get(key); } public void putCopyableUserData(Key key, T value) { - putCopyableUserDataImpl(key, value); - } - - private Map getOrCreateCopyableMap(boolean create) { - Map copyMap = getUserData(COPYABLE_USER_MAP_KEY); - if (copyMap == null && create) { - copyMap = createDataMap(1); - copyMap = putUserDataIfAbsent(COPYABLE_USER_MAP_KEY, copyMap); - } - - return copyMap; - } - - protected final void putCopyableUserDataImpl(Key key, T value) { while (true) { - try { - Map copyMap = getOrCreateCopyableMap(value != null); - if (copyMap == null) break; - - if (value == null) { - copyMap.remove(key); - if (copyMap.isEmpty()) { - ((StripedLockConcurrentHashMap)copyMap).blockModification(); - ConcurrentMap newCopyMap; - if (copyMap.isEmpty()) { - newCopyMap = null; - } - else { - newCopyMap = createDataMap(copyMap.size()); - newCopyMap.putAll(copyMap); - } - boolean replaced = replace(COPYABLE_USER_MAP_KEY, copyMap, newCopyMap); - if (!replaced) continue; - } - } - else { - copyMap.put(key, value); - } - break; + SmartFMap map = myUserMap; + @SuppressWarnings("unchecked") SmartFMap copyableMap = (SmartFMap)map.get(COPYABLE_USER_MAP_KEY); + if (copyableMap == null) { + copyableMap = SmartFMap.emptyMap(); } - catch (ConcurrentModificationException ignored) { - // someone blocked modification, retry - } - } - } - - private ConcurrentMap getOrCreateMap() { - while (true) { - ConcurrentMap map = myUserMap; - if (map != null) return map; - map = createDataMap(2); - boolean updated = updater.compareAndSet(this, null, map); - if (updated) { - return map; + SmartFMap newCopyableMap = value == null ? copyableMap.minus(key) : copyableMap.plus(key, value); + SmartFMap newMap = newCopyableMap.isEmpty() ? map.minus(COPYABLE_USER_MAP_KEY) : map.plus(COPYABLE_USER_MAP_KEY, newCopyableMap); + if (newMap == map || updater.compareAndSet(this, map, newMap)) { + return; } } } public boolean replace(@NotNull Key key, @Nullable T oldValue, @Nullable T newValue) { while (true) { - try { - ConcurrentMap map = getOrCreateMap(); - if (oldValue == null) { - return newValue == null || map.putIfAbsent(key, newValue) == null; - } - if (newValue == null) { - boolean removed = map.remove(key, oldValue); - if (removed) { - nullifyMapFieldIfEmpty(); - } - return removed; - } - return map.replace(key, oldValue, newValue); + SmartFMap map = myUserMap; + if (map.get(key) != oldValue) { + return false; } - catch (ConcurrentModificationException ignored) { - // someone blocked modification, retry + SmartFMap newMap = newValue == null ? map.minus(key) : map.plus(key, newValue); + if (newMap == map || updater.compareAndSet(this, map, newMap)) { + return true; } } } @NotNull public T putUserDataIfAbsent(@NotNull final Key key, @NotNull final T value) { - Object v = getOrCreateMap().get(key); - if (v != null) { - //noinspection unchecked - return (T)v; - } while (true) { - try { - @SuppressWarnings("unchecked") - T prev = (T)getOrCreateMap().putIfAbsent(key, value); - return prev == null ? value : prev; + SmartFMap map = myUserMap; + @SuppressWarnings("unchecked") T oldValue = (T)map.get(key); + if (oldValue != null) { + return oldValue; } - catch (ConcurrentModificationException ignored) { - // someone blocked modification, retry + SmartFMap newMap = map.plus(key, value); + if (newMap == map || updater.compareAndSet(this, map, newMap)) { + return value; } } } public void copyCopyableDataTo(@NotNull UserDataHolderBase clone) { - Map copyableMap = getUserData(COPYABLE_USER_MAP_KEY); - if (copyableMap != null) { - ConcurrentMap copy = createDataMap(copyableMap.size()); - copy.putAll(copyableMap); - copyableMap = copy; - } - clone.putUserData(COPYABLE_USER_MAP_KEY, copyableMap); + clone.putUserData(COPYABLE_USER_MAP_KEY, getUserData(COPYABLE_USER_MAP_KEY)); } protected void clearUserData() { - myUserMap = null; + myUserMap = SmartFMap.emptyMap(); } - private static final AtomicFieldUpdater updater = AtomicFieldUpdater.forFieldOfType(UserDataHolderBase.class, ConcurrentMap.class); - private void nullifyMapFieldIfEmpty() { - try { - while (true) { - StripedLockConcurrentHashMap map = (StripedLockConcurrentHashMap)myUserMap; - if (map == null || !map.isEmpty()) break; - map.blockModification(); // we block the map and either replace it with null or fail with replace, in both cases the map is thrown away - ConcurrentMap newMap; - if (map.isEmpty()) { - newMap = null; - } - else { - // someone managed to add something in the meantime - // atomically replace the blocked map with newly created map filled with the data sneaked in - newMap = createDataMap(map.size()); - newMap.putAll(map); - } - boolean replaced = updater.compareAndSet(this, map, newMap); - if (replaced) break; - // else someone has replaced map already and pushing back the changes is his responsibility - } - } - catch (ConcurrentModificationException ignored) { - // somebody has already blocked the map, back off - } - } + private static final AtomicFieldUpdater updater = AtomicFieldUpdater.forFieldOfType(UserDataHolderBase.class, SmartFMap.class); }