diff --git a/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/README b/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/README new file mode 100644 index 00000000000..73e9540f17c --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/README @@ -0,0 +1,2 @@ +TODO: remove all files from this package when these changes will be available in Android Studio EAP, i.e. `apiVersion` should be 141.1009.5 or higher +TODO: Don't forget to update min idea-version in plugin.xml diff --git a/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/UserDataHolderBase.java b/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/UserDataHolderBase.java new file mode 100644 index 00000000000..d2d49e4373c --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/UserDataHolderBase.java @@ -0,0 +1,153 @@ +/* + * Copyright 2010-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 org.jetbrains.kotlin.util.userDataHolder; + + +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.UserDataHolderEx; +import com.intellij.util.concurrency.AtomicFieldUpdater; +import org.jetbrains.kotlin.util.userDataHolder.keyFMap.KeyFMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +public class UserDataHolderBase implements UserDataHolderEx, Cloneable { + 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 KeyFMap myUserMap = KeyFMap.EMPTY_MAP; + + @Override + protected Object clone() { + try { + UserDataHolderBase clone = (UserDataHolderBase)super.clone(); + clone.setUserMap(KeyFMap.EMPTY_MAP); + copyCopyableDataTo(clone); + return clone; + } + catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + + @TestOnly + public String getUserDataString() { + final KeyFMap userMap = getUserMap(); + final KeyFMap copyableMap = getUserData(COPYABLE_USER_MAP_KEY); + return userMap.toString() + (copyableMap == null ? "" : copyableMap.toString()); + } + + public void copyUserDataTo(UserDataHolderBase other) { + other.setUserMap(getUserMap()); + } + + @Override + public T getUserData(@NotNull Key key) { + //noinspection unchecked + return getUserMap().get(key); + } + + @NotNull + protected KeyFMap getUserMap() { + return myUserMap; + } + + @Override + public void putUserData(@NotNull Key key, @Nullable T value) { + while (true) { + KeyFMap map = getUserMap(); + KeyFMap newMap = value == null ? map.minus(key) : map.plus(key, value); + if (newMap == map || changeUserMap(map, newMap)) { + break; + } + } + } + + protected boolean changeUserMap(KeyFMap oldMap, KeyFMap newMap) { + return updater.compareAndSet(this, oldMap, newMap); + } + + public T getCopyableUserData(@NotNull Key key) { + KeyFMap map = getUserData(COPYABLE_USER_MAP_KEY); + //noinspection unchecked,ConstantConditions + return map == null ? null : map.get(key); + } + + public void putCopyableUserData(@NotNull Key key, T value) { + while (true) { + KeyFMap map = getUserMap(); + KeyFMap copyableMap = map.get(COPYABLE_USER_MAP_KEY); + if (copyableMap == null) { + copyableMap = KeyFMap.EMPTY_MAP; + } + 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 || changeUserMap(map, newMap)) { + return; + } + } + } + + @Override + public boolean replace(@NotNull Key key, @Nullable T oldValue, @Nullable T newValue) { + while (true) { + KeyFMap map = getUserMap(); + if (map.get(key) != oldValue) { + return false; + } + KeyFMap newMap = newValue == null ? map.minus(key) : map.plus(key, newValue); + if (newMap == map || changeUserMap(map, newMap)) { + return true; + } + } + } + + @Override + @NotNull + public T putUserDataIfAbsent(@NotNull final Key key, @NotNull final T value) { + while (true) { + KeyFMap map = getUserMap(); + T oldValue = map.get(key); + if (oldValue != null) { + return oldValue; + } + KeyFMap newMap = map.plus(key, value); + if (newMap == map || changeUserMap(map, newMap)) { + return value; + } + } + } + + public void copyCopyableDataTo(@NotNull UserDataHolderBase clone) { + clone.putUserData(COPYABLE_USER_MAP_KEY, getUserData(COPYABLE_USER_MAP_KEY)); + } + + protected void clearUserData() { + setUserMap(KeyFMap.EMPTY_MAP); + } + + protected void setUserMap(@NotNull KeyFMap map) { + myUserMap = map; + } + + public boolean isUserDataEmpty() { + return getUserMap().isEmpty(); + } + + private static final AtomicFieldUpdater updater = AtomicFieldUpdater.forFieldOfType(UserDataHolderBase.class, KeyFMap.class); +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/ArrayBackedFMap.java b/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/ArrayBackedFMap.java new file mode 100644 index 00000000000..61bfa40f5fc --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/ArrayBackedFMap.java @@ -0,0 +1,164 @@ +/* + * Copyright 2010-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 org.jetbrains.kotlin.util.userDataHolder.keyFMap; + +import com.intellij.openapi.util.Key; +import org.jetbrains.annotations.NotNull; + +public 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; + Key key1 = Key.getKeyByIndex(keys[i1]); + Key key2 = Key.getKeyByIndex(keys[i2]); + if (key1 == null && key2 == null) return EMPTY_MAP; + if (key1 == null) return new OneElementFMap(key2, values[i2]); + if (key2 == null) return new OneElementFMap(key1, values[i1]); + return new PairElementsFMap(key1, values[i1], key2, 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; + } + + @NotNull + public int[] getKeyIds() { + return keys; + } + + @NotNull + @Override + public Key[] getKeys() { + return getKeysByIndices(keys); + } + + @NotNull + public Object[] getValues() { + return values; + } + + @NotNull + static Key[] getKeysByIndices(int[] indexes) { + Key[] result = new Key[indexes.length]; + + for (int i =0; i < indexes.length; i++) { + result[i] = Key.getKeyByIndex(indexes[i]); + } + + return result; + } +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/EmptyFMap.java b/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/EmptyFMap.java new file mode 100644 index 00000000000..f14236a6c6e --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/EmptyFMap.java @@ -0,0 +1,59 @@ +/* + * Copyright 2010-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 org.jetbrains.kotlin.util.userDataHolder.keyFMap; + +import com.intellij.openapi.util.Key; +import org.jetbrains.annotations.NotNull; + +class EmptyFMap implements KeyFMap { + private static final Key[] EMPTY_KEYS_ARRAY = {}; + + EmptyFMap() { + } + + @NotNull + @Override + public KeyFMap plus(@NotNull Key key, @NotNull V value) { + return new OneElementFMap(key, value); + } + + @NotNull + @Override + public KeyFMap minus(@NotNull Key key) { + return this; + } + + @Override + public V get(@NotNull Key key) { + return null; + } + + @NotNull + @Override + public Key[] getKeys() { + return EMPTY_KEYS_ARRAY; + } + + @Override + public String toString() { + return ""; + } + + @Override + public boolean isEmpty() { + return true; + } +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/KeyFMap.java b/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/KeyFMap.java new file mode 100644 index 00000000000..69987daa988 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/KeyFMap.java @@ -0,0 +1,43 @@ +/* + * Copyright 2010-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 org.jetbrains.kotlin.util.userDataHolder.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} + */ +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); + + @NotNull + Key[] getKeys(); + + String toString(); + + boolean isEmpty(); +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/MapBackedFMap.java b/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/MapBackedFMap.java new file mode 100644 index 00000000000..e1e1da949d3 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/MapBackedFMap.java @@ -0,0 +1,109 @@ +/* + * Copyright 2010-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 org.jetbrains.kotlin.util.userDataHolder.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; + +import static org.jetbrains.kotlin.util.userDataHolder.keyFMap.ArrayBackedFMap.getKeysByIndices; + +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) { + super(keys.length + 1); + 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()); + } + + @NotNull + @Override + public Key[] getKeys() { + return getKeysByIndices(keys()); + } + + @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/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/OneElementFMap.java b/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/OneElementFMap.java new file mode 100644 index 00000000000..7a8352d2f53 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/OneElementFMap.java @@ -0,0 +1,92 @@ +/* + * Copyright 2010-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 org.jetbrains.kotlin.util.userDataHolder.keyFMap; + +import com.intellij.openapi.util.Key; +import org.jetbrains.annotations.NotNull; + +public class OneElementFMap implements KeyFMap { + private final Key myKey; + private final V myValue; + + public OneElementFMap(@NotNull Key key, @NotNull V value) { + myKey = key; + myValue = value; + } + + @NotNull + @Override + public KeyFMap plus(@NotNull Key key, @NotNull V value) { + if (myKey == key) return new OneElementFMap(key, value); + return new PairElementsFMap(myKey, myValue, key, value); + } + + @NotNull + @Override + public KeyFMap minus(@NotNull Key key) { + return key == myKey ? KeyFMap.EMPTY_MAP : this; + } + + @Override + public V get(@NotNull Key key) { + //noinspection unchecked + return myKey == key ? (V)myValue : null; + } + + @NotNull + @Override + public Key[] getKeys() { + return new Key[] { myKey }; + } + + @Override + public String toString() { + return "<" + myKey + " -> " + myValue+">"; + } + + @Override + public boolean isEmpty() { + return false; + } + + public Key getKey() { + return myKey; + } + + public V getValue() { + return myValue; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof OneElementFMap)) return false; + + OneElementFMap map = (OneElementFMap)o; + + if (myKey != map.myKey) return false; + if (!myValue.equals(map.myValue)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = myKey.hashCode(); + result = 31 * result + myValue.hashCode(); + return result; + } +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/PairElementsFMap.java b/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/PairElementsFMap.java new file mode 100644 index 00000000000..cd509df820d --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/util/userDataHolder/keyFMap/PairElementsFMap.java @@ -0,0 +1,72 @@ +/* + * Copyright 2010-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 org.jetbrains.kotlin.util.userDataHolder.keyFMap; + +import com.intellij.openapi.util.Key; +import org.jetbrains.annotations.NotNull; + +class PairElementsFMap implements KeyFMap { + private final Key key1; + private final Key key2; + private final Object value1; + private final Object value2; + + PairElementsFMap(@NotNull Key key1, @NotNull Object value1, @NotNull Key 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) { + if (key == key1) return new PairElementsFMap(key, value, key2, value2); + if (key == key2) return new PairElementsFMap(key, value, key1, value1); + return new ArrayBackedFMap(new int[]{key1.hashCode(), key2.hashCode(), key.hashCode()}, new Object[]{value1, value2, value}); + } + + @NotNull + @Override + public KeyFMap minus(@NotNull Key key) { + if (key == key1) return new OneElementFMap(key2, value2); + if (key == key2) return new OneElementFMap(key1, value1); + return this; + } + + @Override + public V get(@NotNull Key key) { + //noinspection unchecked + return key == key1 ? (V)value1 : key == key2 ? (V)value2 : null; + } + + @NotNull + @Override + public Key[] getKeys() { + return new Key[] { key1, key2 }; + } + + @Override + public String toString() { + return "Pair: (" + key1 + " -> " + value1 + "; " + key2 + " -> " + value2 + ")"; + } + + @Override + public boolean isEmpty() { + return false; + } +}