From c5588decdae46fc0a62f8c7bc21549c66775bc9a Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Wed, 11 Jul 2012 13:08:39 +0400 Subject: [PATCH] performance/memory improvements --- .../src/com/intellij/openapi/util/Key.java | 9 + .../openapi/util/UserDataHolderBase.java | 62 ++-- .../util/src/com/intellij/util/ArrayUtil.java | 18 + .../containers/WeakValueIntObjectHashMap.java | 107 ++++++ .../util/keyFMap/ArrayBackedFMap.java | 132 ++++++++ .../com/intellij/util/keyFMap/EmptyFMap.java | 51 +++ .../com/intellij/util/keyFMap/KeyFMap.java | 42 +++ .../intellij/util/keyFMap/MapBackedFMap.java | 100 ++++++ .../intellij/util/keyFMap/OneElementFMap.java | 62 ++++ .../util/keyFMap/PairElementsFMap.java | 69 ++++ .../util/keyFMap/ShortObjectHashMap.java | 317 ++++++++++++++++++ .../uiDesigner/core/AsmCodeGeneratorTest.java | 4 +- 12 files changed, 945 insertions(+), 28 deletions(-) create mode 100644 platform/util/src/com/intellij/util/containers/WeakValueIntObjectHashMap.java create mode 100644 platform/util/src/com/intellij/util/keyFMap/ArrayBackedFMap.java create mode 100644 platform/util/src/com/intellij/util/keyFMap/EmptyFMap.java create mode 100644 platform/util/src/com/intellij/util/keyFMap/KeyFMap.java create mode 100644 platform/util/src/com/intellij/util/keyFMap/MapBackedFMap.java create mode 100644 platform/util/src/com/intellij/util/keyFMap/OneElementFMap.java create mode 100644 platform/util/src/com/intellij/util/keyFMap/PairElementsFMap.java create mode 100644 platform/util/src/com/intellij/util/keyFMap/ShortObjectHashMap.java diff --git a/platform/util/src/com/intellij/openapi/util/Key.java b/platform/util/src/com/intellij/openapi/util/Key.java index 4412b3827be7..8a0b388a25c6 100644 --- a/platform/util/src/com/intellij/openapi/util/Key.java +++ b/platform/util/src/com/intellij/openapi/util/Key.java @@ -15,6 +15,7 @@ */ package com.intellij.openapi.util; +import com.intellij.util.containers.ConcurrentWeakValueHashMap; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -33,9 +34,11 @@ public class Key { private static final AtomicInteger ourKeysCounter = new AtomicInteger(); private final int myIndex = ourKeysCounter.getAndIncrement(); private final String myName; // for debug purposes only + private static final ConcurrentWeakValueHashMap allKeys = new ConcurrentWeakValueHashMap(); public Key(@NotNull @NonNls String name) { myName = name; + allKeys.put(myIndex, this); } public final int hashCode() { @@ -62,6 +65,7 @@ public class Key { @Nullable public T get(@Nullable Map holder) { + //noinspection unchecked return holder == null ? null : (T)holder.get(this); } @@ -93,4 +97,9 @@ public class Key { holder.put(this, value); } } + + public static Key getKeyByIndex(int index) { + //noinspection unchecked + return (Key)allKeys.get(index); + } } \ No newline at end of file diff --git a/platform/util/src/com/intellij/openapi/util/UserDataHolderBase.java b/platform/util/src/com/intellij/openapi/util/UserDataHolderBase.java index 01f2d081612b..2bc838f66c83 100644 --- a/platform/util/src/com/intellij/openapi/util/UserDataHolderBase.java +++ b/platform/util/src/com/intellij/openapi/util/UserDataHolderBase.java @@ -16,39 +16,37 @@ package com.intellij.openapi.util; -import com.intellij.util.SmartFMap; import com.intellij.util.concurrency.AtomicFieldUpdater; +import com.intellij.util.keyFMap.KeyFMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; -import java.util.Map; - public class UserDataHolderBase implements UserDataHolderEx, Cloneable { - private static final Key> COPYABLE_USER_MAP_KEY = Key.create("COPYABLE_USER_MAP_KEY"); + public 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} */ - @NotNull private volatile SmartFMap myUserMap = SmartFMap.emptyMap(); + @NotNull private volatile KeyFMap myUserMap = KeyFMap.EMPTY_MAP; + @Override protected Object clone() { try { UserDataHolderBase clone = (UserDataHolderBase)super.clone(); - clone.myUserMap = SmartFMap.emptyMap(); + clone.myUserMap = KeyFMap.EMPTY_MAP; copyCopyableDataTo(clone); return clone; } catch (CloneNotSupportedException e) { throw new RuntimeException(e); - } } @TestOnly public String getUserDataString() { - final SmartFMap userMap = myUserMap; - final Map copyableMap = getUserData(COPYABLE_USER_MAP_KEY); + final KeyFMap userMap = myUserMap; + final KeyFMap copyableMap = getUserData(COPYABLE_USER_MAP_KEY); return userMap.toString() + (copyableMap == null ? "" : copyableMap.toString()); } @@ -56,64 +54,70 @@ public class UserDataHolderBase implements UserDataHolderEx, Cloneable { other.myUserMap = myUserMap; } + @Override public T getUserData(@NotNull Key key) { //noinspection unchecked - return (T)myUserMap.get(key); + return myUserMap.get(key); } + @Override public void putUserData(@NotNull Key key, @Nullable T value) { while (true) { - SmartFMap map = myUserMap; - SmartFMap newMap = value == null ? map.minus(key) : map.plus(key, value); + KeyFMap map = myUserMap; + KeyFMap newMap = value == null ? map.minus(key) : map.plus(key, value); if (newMap == map || updater.compareAndSet(this, map, newMap)) { - return; + break; } } + T data = getUserData(key); + assert Comparing.equal(data, value) : key + " -> " + value + "; actual: " + data + "; " + myUserMap + ": " + myUserMap.getClass(); } public T getCopyableUserData(Key key) { - SmartFMap map = getUserData(COPYABLE_USER_MAP_KEY); + KeyFMap map = getUserData(COPYABLE_USER_MAP_KEY); //noinspection unchecked,ConstantConditions - return map == null ? null : (T)map.get(key); + return map == null ? null : map.get(key); } public void putCopyableUserData(Key key, T value) { while (true) { - SmartFMap map = myUserMap; - @SuppressWarnings("unchecked") SmartFMap copyableMap = (SmartFMap)map.get(COPYABLE_USER_MAP_KEY); + KeyFMap map = myUserMap; + KeyFMap copyableMap = map.get(COPYABLE_USER_MAP_KEY); if (copyableMap == null) { - copyableMap = SmartFMap.emptyMap(); + copyableMap = KeyFMap.EMPTY_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); + KeyFMap newCopyableMap = value == null ? copyableMap.minus(key) : copyableMap.plus(key, value); + KeyFMap 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; } } } + @Override public boolean replace(@NotNull Key key, @Nullable T oldValue, @Nullable T newValue) { while (true) { - SmartFMap map = myUserMap; + KeyFMap map = myUserMap; if (map.get(key) != oldValue) { return false; } - SmartFMap newMap = newValue == null ? map.minus(key) : map.plus(key, newValue); + KeyFMap newMap = newValue == null ? map.minus(key) : map.plus(key, newValue); if (newMap == map || updater.compareAndSet(this, map, newMap)) { return true; } } } + @Override @NotNull public T putUserDataIfAbsent(@NotNull final Key key, @NotNull final T value) { while (true) { - SmartFMap map = myUserMap; - @SuppressWarnings("unchecked") T oldValue = (T)map.get(key); + KeyFMap map = myUserMap; + T oldValue = map.get(key); if (oldValue != null) { return oldValue; } - SmartFMap newMap = map.plus(key, value); + KeyFMap newMap = map.plus(key, value); if (newMap == map || updater.compareAndSet(this, map, newMap)) { return value; } @@ -125,8 +129,12 @@ public class UserDataHolderBase implements UserDataHolderEx, Cloneable { } protected void clearUserData() { - myUserMap = SmartFMap.emptyMap(); + myUserMap = KeyFMap.EMPTY_MAP; } - private static final AtomicFieldUpdater updater = AtomicFieldUpdater.forFieldOfType(UserDataHolderBase.class, SmartFMap.class); + public boolean isUserDataEmpty() { + return myUserMap.isEmpty(); + } + + private static final AtomicFieldUpdater updater = AtomicFieldUpdater.forFieldOfType(UserDataHolderBase.class, KeyFMap.class); } diff --git a/platform/util/src/com/intellij/util/ArrayUtil.java b/platform/util/src/com/intellij/util/ArrayUtil.java index b011492ecf40..aca3963f21f3 100644 --- a/platform/util/src/com/intellij/util/ArrayUtil.java +++ b/platform/util/src/com/intellij/util/ArrayUtil.java @@ -375,6 +375,17 @@ public class ArrayUtil extends ArrayUtilRt { System.arraycopy(src, idx + 1, result, idx, length - idx - 1); return result; } + @NotNull + public static short[] remove(@NotNull final short[] src, int idx) { + int length = src.length; + if (idx < 0 || idx >= length) { + throw new IllegalArgumentException("invalid index: " + idx); + } + short[] result = new short[src.length - 1]; + System.arraycopy(src, 0, result, 0, idx); + System.arraycopy(src, idx + 1, result, idx, length - idx - 1); + return result; + } /** * @param src source array. @@ -650,6 +661,13 @@ public class ArrayUtil extends ArrayUtilRt { return -1; } + public static int indexOf(@NotNull short[] ints, short value) { + for (int i = 0; i < ints.length; i++) { + if (ints[i] == value) return i; + } + + return -1; + } public static boolean contains(final Object o, final Object... objects) { return indexOf(objects, o) >= 0; diff --git a/platform/util/src/com/intellij/util/containers/WeakValueIntObjectHashMap.java b/platform/util/src/com/intellij/util/containers/WeakValueIntObjectHashMap.java new file mode 100644 index 000000000000..e58460a92faf --- /dev/null +++ b/platform/util/src/com/intellij/util/containers/WeakValueIntObjectHashMap.java @@ -0,0 +1,107 @@ +/* + * Copyright 2000-2012 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 gnu.trove.TIntObjectHashMap; +import org.jetbrains.annotations.NotNull; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class WeakValueIntObjectHashMap { + private final TIntObjectHashMap> myMap = new TIntObjectHashMap>(); + private final ReferenceQueue myQueue = new ReferenceQueue(); + + private static class MyReference extends WeakReference { + private final int key; + String name; + + private MyReference(int key, T referent, ReferenceQueue q) { + super(referent, q); + this.key = key; + } + } + + private void processQueue() { + while(true){ + MyReference ref = (MyReference)myQueue.poll(); + if (ref == null) { + return; + } + int key = ref.key; + myMap.remove(key); + keyExpired(key); + } + } + + protected void keyExpired(int key) { + + } + + public final V get(int key) { + MyReference ref = myMap.get(key); + if (ref == null) return null; + return ref.get(); + } + + public final V put(int key, @NotNull V value) { + processQueue(); + MyReference ref = new MyReference(key, value, myQueue); + ref.name = value.toString(); + MyReference oldRef = myMap.put(key, ref); + return oldRef != null ? oldRef.get() : null; + } + + public final V remove(int key) { + processQueue(); + MyReference ref = myMap.remove(key); + return ref != null ? ref.get() : null; + } + + public final void clear() { + myMap.clear(); + processQueue(); + } + + public final int size() { + return myMap.size(); + } + + public final boolean isEmpty() { + return myMap.isEmpty(); + } + + public final boolean containsKey(int key) { + return get(key) != null; + } + + @NotNull + public final Collection values() { + List result = new ArrayList(); + Object[] refs = myMap.getValues(); + for (Object o : refs) { + @SuppressWarnings("unchecked") + final V value = ((MyReference)o).get(); + if (value != null) { + result.add(value); + } + } + return result; + } +} diff --git a/platform/util/src/com/intellij/util/keyFMap/ArrayBackedFMap.java b/platform/util/src/com/intellij/util/keyFMap/ArrayBackedFMap.java new file mode 100644 index 000000000000..0f408fa8dd8a --- /dev/null +++ b/platform/util/src/com/intellij/util/keyFMap/ArrayBackedFMap.java @@ -0,0 +1,132 @@ +/* + * Copyright 2000-2012 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.keyFMap; + +import com.intellij.openapi.util.Key; +import org.jetbrains.annotations.NotNull; + +class ArrayBackedFMap implements KeyFMap { + static final int ARRAY_THRESHOLD = 8; + private final int[] keys; + private final Object[] values; + + ArrayBackedFMap(@NotNull int[] keys, @NotNull Object[] values) { + this.keys = keys; + this.values = values; + } + + @NotNull + @Override + public KeyFMap plus(@NotNull Key key, @NotNull V value) { + int oldSize = size(); + int keyCode = key.hashCode(); + int[] newKeys = null; + Object[] newValues = null; + int i; + for (i = 0; i < oldSize; i++) { + int oldKey = keys[i]; + if (keyCode == oldKey) { + if (value == values[i]) return this; + newKeys = new int[oldSize]; + newValues = new Object[oldSize]; + System.arraycopy(keys, 0, newKeys, 0, oldSize); + System.arraycopy(values, 0, newValues, 0, oldSize); + newValues[i] = value; + break; + } + } + if (i == oldSize) { + if (oldSize == ARRAY_THRESHOLD) { + return new MapBackedFMap(keys, keyCode, values, value); + } + int newSize = oldSize + 1; + newKeys = new int[newSize]; + newValues = new Object[newSize]; + System.arraycopy(keys, 0, newKeys, 0, oldSize); + System.arraycopy(values, 0, newValues, 0, oldSize); + newKeys[oldSize] = keyCode; + newValues[oldSize] = value; + } + return new ArrayBackedFMap(newKeys, newValues); + } + + private int size() { + return keys.length; + } + + @NotNull + @Override + public KeyFMap minus(@NotNull Key key) { + int oldSize = size(); + int keyCode = key.hashCode(); + for (int i = 0; i< oldSize; i++) { + int oldKey = keys[i]; + if (keyCode == oldKey) { + if (oldSize == 3) { + int i1 = (2-i)/2; + int i2 = 3 - (i+2)/2; + return new PairElementsFMap(keys[i1], values[i1], keys[i2], values[i2]); + } + int newSize = oldSize - 1; + int[] newKeys = new int[newSize]; + Object[] newValues = new Object[newSize]; + System.arraycopy(keys, 0, newKeys, 0, i); + System.arraycopy(values, 0, newValues, 0, i); + System.arraycopy(keys, i+1, newKeys, i, oldSize-i-1); + System.arraycopy(values, i+1, newValues, i, oldSize-i-1); + return new ArrayBackedFMap(newKeys, newValues); + } + } + return this; + //if (i == oldSize) { + //newKeys = new int[oldSize]; + //newValues = new Object[oldSize]; + //System.arraycopy(keys, 0, newKeys, 0, oldSize); + //System.arraycopy(values, 0, newValues, 0, oldSize); + //} + + } + + @Override + public V get(@NotNull Key key) { + int oldSize = size(); + int keyCode = key.hashCode(); + for (int i = 0; i < oldSize; i++) { + int oldKey = keys[i]; + if (keyCode == oldKey) { + //noinspection unchecked + return (V)values[i]; + } + } + return null; + } + + @Override + public String toString() { + String s = ""; + for (int i = 0; i < keys.length; i++) { + int key = keys[i]; + Object value = values[i]; + s += (s.isEmpty() ? "" : ", ") + Key.getKeyByIndex(key) + " -> " + value; + } + return "(" + s + ")"; + } + + @Override + public boolean isEmpty() { + return false; + } +} diff --git a/platform/util/src/com/intellij/util/keyFMap/EmptyFMap.java b/platform/util/src/com/intellij/util/keyFMap/EmptyFMap.java new file mode 100644 index 000000000000..d682bcb4dd46 --- /dev/null +++ b/platform/util/src/com/intellij/util/keyFMap/EmptyFMap.java @@ -0,0 +1,51 @@ +/* + * Copyright 2000-2012 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.keyFMap; + +import com.intellij.openapi.util.Key; +import org.jetbrains.annotations.NotNull; + +class EmptyFMap implements KeyFMap { + EmptyFMap() { + } + + @NotNull + @Override + public KeyFMap plus(@NotNull Key key, @NotNull V value) { + return new OneElementFMap(key.hashCode(), value); + } + + @NotNull + @Override + public KeyFMap minus(@NotNull Key key) { + return this; + } + + @Override + public V get(@NotNull Key key) { + return null; + } + + @Override + public String toString() { + return ""; + } + + @Override + public boolean isEmpty() { + return true; + } +} diff --git a/platform/util/src/com/intellij/util/keyFMap/KeyFMap.java b/platform/util/src/com/intellij/util/keyFMap/KeyFMap.java new file mode 100644 index 000000000000..8c32384c1992 --- /dev/null +++ b/platform/util/src/com/intellij/util/keyFMap/KeyFMap.java @@ -0,0 +1,42 @@ +/* + * Copyright 2000-2012 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.keyFMap; + +import com.intellij.openapi.util.Key; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * An immutable map optimized for storing few {@link Key} entries with relatively rare updates + * To construct a map, start with {@link KeyFMap#EMPTY_MAP} and call {@link #plus} and {@link #minus} + * + * @author peter + */ +public interface KeyFMap { + KeyFMap EMPTY_MAP = new EmptyFMap(); + + @NotNull + KeyFMap plus(@NotNull Key key, @NotNull V value); + @NotNull + KeyFMap minus(@NotNull Key key); + + @Nullable + V get(@NotNull Key key); + + String toString(); + + boolean isEmpty(); +} diff --git a/platform/util/src/com/intellij/util/keyFMap/MapBackedFMap.java b/platform/util/src/com/intellij/util/keyFMap/MapBackedFMap.java new file mode 100644 index 000000000000..1622132f0118 --- /dev/null +++ b/platform/util/src/com/intellij/util/keyFMap/MapBackedFMap.java @@ -0,0 +1,100 @@ +/* + * Copyright 2000-2012 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.keyFMap; + +import com.intellij.openapi.util.Key; +import com.intellij.util.ArrayUtil; +import gnu.trove.TIntObjectHashMap; +import gnu.trove.TIntObjectProcedure; +import org.jetbrains.annotations.NotNull; + +class MapBackedFMap extends TIntObjectHashMap implements KeyFMap { + private MapBackedFMap(@NotNull MapBackedFMap oldMap, final int exclude) { + super(oldMap.size()); + oldMap.forEachEntry(new TIntObjectProcedure() { + @Override + public boolean execute(int key, Object val) { + if (key != exclude) put(key, val); + assert key >= 0 : key; + return true; + } + }); + assert size() > ArrayBackedFMap.ARRAY_THRESHOLD; + } + + MapBackedFMap(@NotNull int[] keys, int newKey, @NotNull Object[] values, @NotNull Object newValue) { + for (int i = 0; i < keys.length; i++) { + int key = keys[i]; + Object value = values[i]; + put(key, value); + assert key >= 0 : key; + } + put(newKey, newValue); + assert newKey >= 0 : newKey; + assert size() > ArrayBackedFMap.ARRAY_THRESHOLD; + } + + @NotNull + @Override + public KeyFMap plus(@NotNull Key key, @NotNull V value) { + int keyCode = key.hashCode(); + assert keyCode >= 0 : key; + @SuppressWarnings("unchecked") + V oldValue = (V)get(keyCode); + if (value == oldValue) return this; + MapBackedFMap newMap = new MapBackedFMap(this, -1); + newMap.put(keyCode, value); + return newMap; + } + + @NotNull + @Override + public KeyFMap minus(@NotNull Key key) { + int oldSize = size(); + int keyCode = key.hashCode(); + if (!containsKey(keyCode)) { + return this; + } + if (oldSize == ArrayBackedFMap.ARRAY_THRESHOLD + 1) { + int[] keys = keys(); + Object[] values = getValues(); + int i = ArrayUtil.indexOf(keys, keyCode); + keys = ArrayUtil.remove(keys, i); + values = ArrayUtil.remove(values, i); + return new ArrayBackedFMap(keys, values); + } + return new MapBackedFMap(this, keyCode); + } + + @Override + public V get(@NotNull Key key) { + //noinspection unchecked + return (V)get(key.hashCode()); + } + + @Override + public String toString() { + final StringBuilder s = new StringBuilder(); + forEachEntry(new TIntObjectProcedure() { + @Override + public boolean execute(int key, Object value) { + s.append(s.length() == 0 ? "" : ", ").append(Key.getKeyByIndex(key)).append(" -> ").append(value); + return true; + } + }); + return "[" + s.toString() + "]"; + } +} diff --git a/platform/util/src/com/intellij/util/keyFMap/OneElementFMap.java b/platform/util/src/com/intellij/util/keyFMap/OneElementFMap.java new file mode 100644 index 000000000000..76488c68c979 --- /dev/null +++ b/platform/util/src/com/intellij/util/keyFMap/OneElementFMap.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2012 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.keyFMap; + +import com.intellij.openapi.util.Key; +import org.jetbrains.annotations.NotNull; + +class OneElementFMap implements KeyFMap { + private final int myKeyCode; + private final V myValue; + + OneElementFMap(int keyCode, @NotNull V value) { + myKeyCode = keyCode; + myValue = value; + } + + @NotNull + @Override + public KeyFMap plus(@NotNull Key key, @NotNull V value) { + int keyCode = key.hashCode(); + if (myKeyCode == keyCode) return new OneElementFMap(keyCode, value); + return new PairElementsFMap(myKeyCode, myValue, keyCode, value); + } + + @NotNull + @Override + public KeyFMap minus(@NotNull Key key) { + if (key.hashCode() == myKeyCode) { + return KeyFMap.EMPTY_MAP; + } + return this; + } + + @Override + public V get(@NotNull Key key) { + //noinspection unchecked + return myKeyCode == key.hashCode() ? (V)myValue : null; + } + + @Override + public String toString() { + return "<"+Key.getKeyByIndex(myKeyCode) + " -> " + myValue+">"; + } + + @Override + public boolean isEmpty() { + return false; + } +} diff --git a/platform/util/src/com/intellij/util/keyFMap/PairElementsFMap.java b/platform/util/src/com/intellij/util/keyFMap/PairElementsFMap.java new file mode 100644 index 000000000000..35ea8b957e36 --- /dev/null +++ b/platform/util/src/com/intellij/util/keyFMap/PairElementsFMap.java @@ -0,0 +1,69 @@ +/* + * Copyright 2000-2012 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.keyFMap; + +import com.intellij.openapi.util.Key; +import org.jetbrains.annotations.NotNull; + +class PairElementsFMap implements KeyFMap { + private final int key1; + private final int key2; + private final Object value1; + private final Object value2; + + PairElementsFMap(int key1, @NotNull Object value1, int key2, @NotNull Object value2) { + this.key1 = key1; + this.value1 = value1; + this.key2 = key2; + this.value2 = value2; + assert key1 != key2; + } + + @NotNull + @Override + public KeyFMap plus(@NotNull Key key, @NotNull V value) { + int keyCode = key.hashCode(); + if (keyCode == key1) return new PairElementsFMap(keyCode, value, key2, value2); + if (keyCode == key2) return new PairElementsFMap(keyCode, value, key1, value1); + return new ArrayBackedFMap(new int[]{key1, key2, keyCode}, new Object[]{value1, value2, value}); + } + + @NotNull + @Override + public KeyFMap minus(@NotNull Key key) { + int keyCode = key.hashCode(); + if (keyCode == key1) return new OneElementFMap(key2, value2); + if (keyCode == key2) return new OneElementFMap(key1, value1); + return this; + } + + @Override + public V get(@NotNull Key key) { + int keyCode = key.hashCode(); + //noinspection unchecked + return keyCode == key1 ? (V)value1 : keyCode == key2 ? (V)value2 : null; + } + + @Override + public String toString() { + return "Pair: ("+ Key.getKeyByIndex(key1) + " -> " + value1+"; "+Key.getKeyByIndex(key2) + " -> " + value2 + ")"; + } + + @Override + public boolean isEmpty() { + return false; + } +} diff --git a/platform/util/src/com/intellij/util/keyFMap/ShortObjectHashMap.java b/platform/util/src/com/intellij/util/keyFMap/ShortObjectHashMap.java new file mode 100644 index 000000000000..4bd812fb0622 --- /dev/null +++ b/platform/util/src/com/intellij/util/keyFMap/ShortObjectHashMap.java @@ -0,0 +1,317 @@ +/* + * Copyright 2000-2012 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.keyFMap; + +import gnu.trove.TIntObjectProcedure; +import gnu.trove.TPrimitiveHash; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; + +public class ShortObjectHashMap extends TPrimitiveHash { + private V[] _values; + private short[] _set; + + + /** + * Creates a new TIntObjectHashMap instance with the default + * capacity and load factor. + */ + public ShortObjectHashMap() { + super(); + } + + /** + * Creates a new TIntObjectHashMap instance with a prime + * capacity equal to or greater than initialCapacity and + * with the default load factor. + * + * @param initialCapacity an int value + */ + public ShortObjectHashMap(int initialCapacity) { + super(initialCapacity); + } + + /** + * initializes the hashtable to a prime capacity which is at least + * initialCapacity + 1. + * + * @param initialCapacity an int value + * @return the actual capacity chosen + */ + @Override + protected int setUp(int initialCapacity) { + + int capacity = super.setUp(initialCapacity); + //noinspection unchecked + _values = (V[])new Object[capacity]; + _set = new short[capacity]; + return capacity; + } + + /** + * Inserts a key/value pair into the map. + * + * @param key an int value + * @param value an Object value + * @return the previous value associated with key, + * or null if none was found. + */ + public V put(short key, @NotNull V value) { + V previous = null; + int index = insertionIndex(key); + boolean isNewMapping = true; + if (index < 0) { + index = -index - 1; + previous = _values[index]; + isNewMapping = false; + } + byte previousState = _states[index]; + _set[index] = key; + _states[index] = FULL; + _values[index] = value; + if (isNewMapping) { + postInsertHook(previousState == FREE); + } + + return previous; + } + + /** + * rehashes the map to the new capacity. + * + * @param newCapacity an int value + */ + @Override + protected void rehash(int newCapacity) { + int oldCapacity = _set.length; + short[] oldKeys = _set; + V[] oldVals = _values; + byte[] oldStates = _states; + + _set = new short[newCapacity]; + //noinspection unchecked + _values = (V[])new Object[newCapacity]; + _states = new byte[newCapacity]; + + for (int i = oldCapacity; i-- > 0; ) { + if (oldStates[i] == FULL) { + short o = oldKeys[i]; + int index = insertionIndex(o); + _set[index] = o; + _values[index] = oldVals[i]; + _states[index] = FULL; + } + } + } + + /** + * retrieves the value for key + * + * @param key an int value + * @return the value of key or null if no such mapping exists. + */ + public V get(short key) { + int index = index(key); + return index < 0 ? null : _values[index]; + } + + /** + * Empties the map. + */ + @Override + public void clear() { + super.clear(); + + Arrays.fill(_set, (short)0); + Arrays.fill(_values, null); + Arrays.fill(_states, FREE); + } + + /** + * Deletes a key/value pair from the map. + * + * @param key an int value + * @return an Object value + */ + public V remove(short key) { + V prev = null; + int index = index(key); + if (index >= 0) { + prev = _values[index]; + removeAt(index); // clear key,state; adjust size + } + return prev; + } + + + /** + * removes the mapping at index from the map. + * + * @param index an int value + */ + @Override + protected void removeAt(int index) { + _values[index] = null; + _set[index] = 0; + super.removeAt(index); // clear key, state; adjust size + } + + /** + * Returns the values of the map. + * + * @return a Collection value + */ + public Object[] getValues() { + Object[] vals = new Object[size()]; + V[] v = _values; + byte[] states = _states; + + for (int i = v.length, j = 0; i-- > 0; ) { + if (states[i] == FULL) { + vals[j++] = v[i]; + } + } + return vals; + } + + /** + * returns the keys of the map. + * + * @return a Set value + */ + public short[] keys() { + short[] keys = new short[size()]; + short[] k = _set; + byte[] states = _states; + + for (int i = k.length, j = 0; i-- > 0; ) { + if (states[i] == FULL) { + keys[j++] = k[i]; + } + } + return keys; + } + + + /** + * checks for the present of key in the keys of the map. + * + * @param key an int value + * @return a boolean value + */ + public boolean containsKey(int key) { + return index(key) >= 0; + } + + + /** + * Locates the index of val. + * + * @param val an int value + * @return the index of val or -1 if it isn't in the set. + */ + protected int index(int val) { + byte[] states = _states; + short[] set = _set; + int length = states.length; + int hash = val & 0x7fffffff; + int index = hash % length; + + if (states[index] != FREE && + (states[index] == REMOVED || set[index] != val)) { + // see Knuth, p. 529 + int probe = 1 + hash % (length - 2); + + do { + index -= probe; + if (index < 0) { + index += length; + } + } + while (states[index] != FREE && + (states[index] == REMOVED || set[index] != val)); + } + + return states[index] == FREE ? -1 : index; + } + + /** + * Locates the index at which val can be inserted. if + * there is already a value equal()ing val in the set, + * returns that value as a negative integer. + * + * @param val an int value + * @return an int value + */ + protected int insertionIndex(int val) { + + byte[] states = _states; + short[] set = _set; + int length = states.length; + int hash = val & 0x7fffffff; + int index = hash % length; + + if (states[index] == FREE) { + return index; // empty, all done + } + else if (states[index] == FULL && set[index] == val) { + return -index - 1; // already stored + } + else { // already FULL or REMOVED, must probe + // compute the double hash + int probe = 1 + hash % (length - 2); + // starting at the natural offset, probe until we find an + // offset that isn't full. + do { + index -= probe; + if (index < 0) { + index += length; + } + } + while (states[index] == FULL && set[index] != val); + + // if the index we found was removed: continue probing until we + // locate a free location or an element which equal()s the + // one we have. + if (states[index] == REMOVED) { + int firstRemoved = index; + while (states[index] != FREE && + (states[index] == REMOVED || set[index] != val)) { + index -= probe; + if (index < 0) { + index += length; + } + } + return states[index] == FULL ? -index - 1 : firstRemoved; + } + // if it's full, the key is already stored + return states[index] == FULL ? -index - 1 : index; + } + } + + public boolean forEachEntry(TIntObjectProcedure procedure) { + byte[] states = _states; + short[] keys = _set; + V[] values = _values; + for (int i = keys.length; i-- > 0; ) { + if (states[i] == FULL && !procedure.execute(keys[i], values[i])) { + return false; + } + } + return true; + } +} // TIntObjectHashMap diff --git a/plugins/ui-designer/testSrc/com/intellij/uiDesigner/core/AsmCodeGeneratorTest.java b/plugins/ui-designer/testSrc/com/intellij/uiDesigner/core/AsmCodeGeneratorTest.java index a5630623c131..c41db743dab4 100644 --- a/plugins/ui-designer/testSrc/com/intellij/uiDesigner/core/AsmCodeGeneratorTest.java +++ b/plugins/ui-designer/testSrc/com/intellij/uiDesigner/core/AsmCodeGeneratorTest.java @@ -30,6 +30,7 @@ import com.intellij.uiDesigner.lw.CompiledClassPropertiesProvider; import com.intellij.uiDesigner.lw.LwRootContainer; import com.intellij.util.PathUtil; import com.intellij.util.ui.UIUtil; +import gnu.trove.TIntObjectHashMap; import junit.framework.TestCase; import org.jetbrains.asm4.ClassWriter; @@ -65,6 +66,7 @@ public class AsmCodeGeneratorTest extends TestCase { java.util.List cp = new ArrayList(); appendPath(cp, JBTabbedPane.class); + appendPath(cp, TIntObjectHashMap.class); appendPath(cp, UIUtil.class); appendPath(cp, SystemInfoRt.class); appendPath(cp, ApplicationManager.class); @@ -319,7 +321,7 @@ public class AsmCodeGeneratorTest extends TestCase { assertTrue(panel.getBorder() instanceof TitledBorder); TitledBorder border = (TitledBorder) panel.getBorder(); assertEquals("BorderTitle", border.getTitle()); - assertTrue(border.getBorder() instanceof EtchedBorder); + assertTrue(border.getBorder().toString(), border.getBorder() instanceof EtchedBorder); } public void testMnemonic() throws Exception {