mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-10-06 06:21:16 +07:00
introduce FMap: immutable map for storing entries with relatively rare updates
GitOrigin-RevId: 3ba3dba2d4935bd2e9b504e030e76bb6e1d6a939
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d58ed02bca
commit
ae2c5179dd
@@ -13,6 +13,7 @@ import java.util.function.BiConsumer;
|
||||
* An immutable map optimized for storing few entries with relatively rare updates.
|
||||
*
|
||||
* @author peter
|
||||
* @see com.intellij.util.fmap.FMap
|
||||
*/
|
||||
public final class SmartFMap<K,V> implements Map<K,V> {
|
||||
private static final SmartFMap<?, ?> EMPTY = new SmartFMap<>(ArrayUtilRt.EMPTY_OBJECT_ARRAY);
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.util.fmap;
|
||||
|
||||
import gnu.trove.THashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
final class ArrayBackedFMap<@NotNull K, @NotNull V> implements FMap<K, V> {
|
||||
|
||||
static final int ARRAY_THRESHOLD = 8;
|
||||
|
||||
private final @NotNull Object @NotNull [] myData;
|
||||
|
||||
ArrayBackedFMap(@NotNull Object @NotNull ... data) {
|
||||
assert 3 * 2 <= data.length; // at least 3 key-value pairs
|
||||
assert data.length <= ARRAY_THRESHOLD * 2; // at most ARRAY_THRESHOLD key-value pairs
|
||||
myData = data;
|
||||
}
|
||||
|
||||
ArrayBackedFMap(@NotNull Map<K, V> map) {
|
||||
assert map.size() <= ARRAY_THRESHOLD;
|
||||
Object[] data = new Object[map.size() * 2];
|
||||
int i = 0;
|
||||
for (Map.Entry<K, V> entry : map.entrySet()) {
|
||||
data[i++] = entry.getKey();
|
||||
data[i++] = entry.getValue();
|
||||
}
|
||||
assert i == data.length;
|
||||
myData = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FMap<K, V> plus(K key, V value) {
|
||||
for (int i = 0; i < myData.length; i += 2) {
|
||||
if (myData[i].equals(key)) {
|
||||
if (myData[i + 1].equals(value)) {
|
||||
return this;
|
||||
}
|
||||
else {
|
||||
Object[] newData = myData.clone();
|
||||
newData[i + 1] = value;
|
||||
return new ArrayBackedFMap<>(newData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (size() < ARRAY_THRESHOLD) {
|
||||
int length = myData.length;
|
||||
Object[] newData = Arrays.copyOf(myData, length + 2);
|
||||
newData[length] = key;
|
||||
newData[length + 1] = value;
|
||||
return new ArrayBackedFMap<>(newData);
|
||||
}
|
||||
|
||||
Map<K, V> map = toMapInner();
|
||||
map.put(key, value);
|
||||
return new MapBackedFMap<>(map);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public @NotNull FMap<K, V> minus(K key) {
|
||||
for (int i = 0; i < myData.length; i += 2) {
|
||||
if (myData[i].equals(key)) {
|
||||
if (size() == 3) {
|
||||
if (i == 0) {
|
||||
return new TwoKeysFMap<>((K)myData[2], (V)myData[3], (K)myData[4], (V)myData[5]);
|
||||
}
|
||||
else if (i == 2) {
|
||||
return new TwoKeysFMap<>((K)myData[0], (V)myData[1], (K)myData[4], (V)myData[5]);
|
||||
}
|
||||
else {
|
||||
assert i == 4;
|
||||
return new TwoKeysFMap<>((K)myData[0], (V)myData[1], (K)myData[2], (V)myData[3]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Object[] newData = new Object[myData.length - 2];
|
||||
System.arraycopy(myData, 0, newData, 0, i);
|
||||
System.arraycopy(myData, i + 2, newData, i, myData.length - 2 - i);
|
||||
return new ArrayBackedFMap<>(newData);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public @Nullable V get(K key) {
|
||||
for (int i = 0; i < myData.length; i += 2) {
|
||||
if (myData[i].equals(key)) {
|
||||
return (V)myData[i + 1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return myData.length / 2;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public @NotNull Collection<K> keys() {
|
||||
List<K> result = new ArrayList<>(size());
|
||||
for (int i = 0; i < myData.length; i += 2) {
|
||||
result.add((K)myData[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Map<K, V> toMap() {
|
||||
return Collections.unmodifiableMap(toMapInner());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@NotNull
|
||||
private Map<K, V> toMapInner() {
|
||||
Map<K, V> map = new THashMap<>(size());
|
||||
for (int i = 0; i < myData.length; i += 2) {
|
||||
map.put((K)myData[i], (V)myData[i + 1]);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ArrayBackedFMap<?, ?> map = (ArrayBackedFMap<?, ?>)o;
|
||||
if (size() != map.size()) return false;
|
||||
return toMapInner().equals(map.toMapInner());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int keysHash = 0;
|
||||
int valuesHash = 0;
|
||||
for (int i = 0; i < myData.length; i += 2) {
|
||||
keysHash = keysHash ^ myData[i].hashCode();
|
||||
valuesHash = valuesHash ^ myData[i + 1].hashCode();
|
||||
}
|
||||
return keysHash + 31 * valuesHash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("[\n");
|
||||
for (int i = 0; i < myData.length; i += 2) {
|
||||
sb.append(" ").append(myData[i]).append(": ").append(myData[i + 1]).append(",\n");
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.util.fmap;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
final class EmptyFMap<@NotNull K, @NotNull V> implements FMap<K, V> {
|
||||
|
||||
static final EmptyFMap<?, ?> INSTANCE = new EmptyFMap<>();
|
||||
|
||||
@Override
|
||||
public @NotNull FMap<K, V> plus(K key, V value) {
|
||||
return new OneKeyFMap<>(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FMap<K, V> minus(K key) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable V get(K key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<K> keys() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Map<K, V> toMap() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[:]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.util.fmap;
|
||||
|
||||
import gnu.trove.THashMap;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Debug;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An immutable map optimized for storing few entries with relatively rare updates.
|
||||
* To construct a map, start with {@link FMap#empty()} and call {@link #plus} and {@link #minus},
|
||||
* or use {@link #of} or {@link #from}.
|
||||
* <p>
|
||||
* This map was adapted from {@link com.intellij.util.keyFMap.KeyFMap}.
|
||||
*
|
||||
* @param <K> type of keys
|
||||
* @param <V> type of values
|
||||
*/
|
||||
@Debug.Renderer(
|
||||
text = "\"size = \" + size()",
|
||||
hasChildren = "!isEmpty()",
|
||||
childrenArray = "toMap().entrySet().toArray()"
|
||||
)
|
||||
@ApiStatus.NonExtendable
|
||||
public interface FMap<@NotNull K, @NotNull V> {
|
||||
|
||||
/**
|
||||
* @return an empty {@code FMap}. No additional instances of empty {@code FMap} should be created as they are indistinguishable
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static <K, V> FMap<K, V> empty() {
|
||||
return (FMap<K, V>)EmptyFMap.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an {@code FMap} with a single key-value entry
|
||||
*/
|
||||
static <@NotNull K, @NotNull V> @NotNull FMap<K, V> of(K key, V value) {
|
||||
return new OneKeyFMap<>(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an {@code FMap} with a two key-value entries
|
||||
*/
|
||||
static <@NotNull K, @NotNull V> @NotNull FMap<K, V> of(K key1, V value1, K key2, V value2) {
|
||||
assert !key1.equals(key2);
|
||||
return new TwoKeysFMap<>(key1, value1, key2, value2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an {@code FMap} containing entries from the specified {@code map}
|
||||
*/
|
||||
static <@NotNull K, @NotNull V> @NotNull FMap<K, V> from(@NotNull Map<K, V> map) {
|
||||
if (map.isEmpty()) {
|
||||
return empty();
|
||||
}
|
||||
else if (map.size() == 1) {
|
||||
Map.Entry<K, V> entry = map.entrySet().iterator().next();
|
||||
return of(entry.getKey(), entry.getValue());
|
||||
}
|
||||
else if (map.size() == 2) {
|
||||
Iterator<Map.Entry<K, V>> iterator = map.entrySet().iterator();
|
||||
Map.Entry<K, V> entry1 = iterator.next();
|
||||
Map.Entry<K, V> entry2 = iterator.next();
|
||||
return of(entry1.getKey(), entry1.getValue(), entry2.getKey(), entry2.getValue());
|
||||
}
|
||||
else if (map.size() <= ArrayBackedFMap.ARRAY_THRESHOLD) {
|
||||
return new ArrayBackedFMap<>(map);
|
||||
}
|
||||
else {
|
||||
return new MapBackedFMap<>(new THashMap<>(map));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code FMap} which consists of the same elements as this {@code FMap},
|
||||
* but the key {@code key} is associated with the supplied {@code value}.
|
||||
* May return itself if the {@code key} is already associated with the supplied {@code value}.
|
||||
*
|
||||
* @param key a key to add or replace
|
||||
* @param value a value to be associated with the key
|
||||
* @return an updated {@code FMap} (this or newly created)
|
||||
*/
|
||||
@NotNull FMap<K, V> plus(K key, V value);
|
||||
|
||||
/**
|
||||
* Returns a {@code FMap} which consists of the same elements as this FMap,
|
||||
* except the supplied key which is removed.
|
||||
* May return itself if the supplied key is absent in this map.
|
||||
*
|
||||
* @param key a key to remove
|
||||
* @return an updated {@code FMap} (this or newly created)
|
||||
*/
|
||||
@NotNull FMap<K, V> minus(K key);
|
||||
|
||||
/**
|
||||
* Returns a value associated with given key in this {@code FMap}, or {@code null} if no value is associated.
|
||||
* Note that unlike {@link java.util.HashMap} {@code FMap} cannot hold null values.
|
||||
*
|
||||
* @param key a key to get the value associated with
|
||||
* @return a value associated with a given {@code key} or {@code null} if there's no such value
|
||||
*/
|
||||
@Nullable V get(K key);
|
||||
|
||||
/**
|
||||
* @return {@code true} if this {@code FMap} is empty, otherwise {@code false}
|
||||
*/
|
||||
boolean isEmpty();
|
||||
|
||||
/**
|
||||
* @return size (number of keys) of this {@code FMap}
|
||||
*/
|
||||
int size();
|
||||
|
||||
/**
|
||||
* @return a collection of all keys present in this {@code FMap}, in no particular order
|
||||
*/
|
||||
@NotNull Collection<K> keys();
|
||||
|
||||
@NotNull Map<K, V> toMap();
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.util.fmap;
|
||||
|
||||
import gnu.trove.THashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
final class MapBackedFMap<@NotNull K, @NotNull V> implements FMap<K, V> {
|
||||
|
||||
private final @NotNull Map<K, V> myMap;
|
||||
|
||||
MapBackedFMap(@NotNull Map<K, V> map) {
|
||||
assert map.size() > ArrayBackedFMap.ARRAY_THRESHOLD;
|
||||
myMap = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FMap<K, V> plus(K key, V value) {
|
||||
if (value.equals(myMap.get(key))) {
|
||||
return this;
|
||||
}
|
||||
Map<K, V> newMap = new THashMap<>(myMap);
|
||||
newMap.put(key, value);
|
||||
return new MapBackedFMap<>(newMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FMap<K, V> minus(K key) {
|
||||
if (!myMap.containsKey(key)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
THashMap<K, V> newMap = new THashMap<>(myMap);
|
||||
newMap.remove(key);
|
||||
|
||||
if (newMap.size() > ArrayBackedFMap.ARRAY_THRESHOLD) {
|
||||
return new MapBackedFMap<>(newMap);
|
||||
}
|
||||
else {
|
||||
return new ArrayBackedFMap<>(newMap);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable V get(K key) {
|
||||
return myMap.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return myMap.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<K> keys() {
|
||||
return Collections.unmodifiableSet(myMap.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Map<K, V> toMap() {
|
||||
return Collections.unmodifiableMap(myMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
MapBackedFMap<?, ?> map = (MapBackedFMap<?, ?>)o;
|
||||
return myMap.equals(map.myMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(myMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("[\n");
|
||||
for (Map.Entry<K, V> entry : myMap.entrySet()) {
|
||||
sb.append(" ").append(entry.getKey()).append(": ").append(entry.getValue()).append(",\n");
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.util.fmap;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
final class OneKeyFMap<@NotNull K, @NotNull V> implements FMap<K, V> {
|
||||
|
||||
private final K myKey;
|
||||
private final V myValue;
|
||||
|
||||
OneKeyFMap(K key, V value) {
|
||||
myKey = key;
|
||||
myValue = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FMap<K, V> plus(K key, V value) {
|
||||
if (myKey.equals(key)) {
|
||||
if (myValue.equals(value)) {
|
||||
return this;
|
||||
}
|
||||
else {
|
||||
return new OneKeyFMap<>(key, value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return new TwoKeysFMap<>(myKey, myValue, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FMap<K, V> minus(K key) {
|
||||
return myKey.equals(key) ? FMap.empty() : this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable V get(K key) {
|
||||
return myKey.equals(key) ? myValue : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<K> keys() {
|
||||
return Collections.singleton(myKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Map<K, V> toMap() {
|
||||
return Collections.singletonMap(myKey, myValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
OneKeyFMap<?, ?> map = (OneKeyFMap<?, ?>)o;
|
||||
return myKey.equals(map.myKey) && myValue.equals(map.myValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(myKey, myValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[%s: %s,]", myKey, myValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.util.fmap;
|
||||
|
||||
import gnu.trove.THashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
final class TwoKeysFMap<@NotNull K, @NotNull V> implements FMap<K, V> {
|
||||
|
||||
private final K myKey1;
|
||||
private final K myKey2;
|
||||
private final V myValue1;
|
||||
private final V myValue2;
|
||||
|
||||
TwoKeysFMap(K key1, V value1, K key2, V value2) {
|
||||
myKey1 = key1;
|
||||
myKey2 = key2;
|
||||
myValue1 = value1;
|
||||
myValue2 = value2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FMap<K, V> plus(K key, V value) {
|
||||
if (myKey1.equals(key)) {
|
||||
if (myValue1.equals(value)) {
|
||||
return this;
|
||||
}
|
||||
else {
|
||||
return new TwoKeysFMap<>(key, value, myKey2, myValue2);
|
||||
}
|
||||
}
|
||||
else if (myKey2.equals(key)) {
|
||||
if (myValue2.equals(value)) {
|
||||
return this;
|
||||
}
|
||||
else {
|
||||
return new TwoKeysFMap<>(myKey1, myValue1, key, value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return new ArrayBackedFMap<>(myKey1, myValue1, myKey2, myValue2, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FMap<K, V> minus(K key) {
|
||||
return myKey1.equals(key)
|
||||
? new OneKeyFMap<>(myKey2, myValue2)
|
||||
: myKey2.equals(key)
|
||||
? new OneKeyFMap<>(myKey1, myValue1)
|
||||
: this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable V get(K key) {
|
||||
return myKey1.equals(key)
|
||||
? myValue1
|
||||
: myKey2.equals(key)
|
||||
? myValue2
|
||||
: null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<K> keys() {
|
||||
return Arrays.asList(myKey1, myKey2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Map<K, V> toMap() {
|
||||
Map<K, V> map = new THashMap<>(2);
|
||||
map.put(myKey1, myValue1);
|
||||
map.put(myKey2, myValue2);
|
||||
return Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
TwoKeysFMap<?, ?> map = (TwoKeysFMap<?, ?>)o;
|
||||
return myKey1.equals(map.myKey1) && myKey2.equals(map.myKey2) &&
|
||||
myValue1.equals(map.myValue1) && myValue2.equals(map.myValue2) ||
|
||||
myKey1.equals(map.myKey2) && myKey2.equals(map.myKey1) &&
|
||||
myValue1.equals(map.myValue2) && myValue2.equals(map.myValue1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (myKey1.hashCode() ^ myKey2.hashCode())
|
||||
+ 31 * (myValue1.hashCode() ^ myValue2.hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[\n %s: %s,\n %s: %s,\n]", myKey1, myValue1, myKey2, myValue2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.util.fmap;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static com.intellij.testFramework.UsefulTestCase.assertSameElements;
|
||||
import static com.intellij.util.fmap.ArrayBackedFMap.ARRAY_THRESHOLD;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
public class FMapTest {
|
||||
|
||||
@Test
|
||||
public void equality() {
|
||||
for (int i : new int[]{0, 1, 2, 3, ARRAY_THRESHOLD, ARRAY_THRESHOLD + 1, ARRAY_THRESHOLD + 5}) {
|
||||
var expected = createMap(i);
|
||||
var map = createMap(i);
|
||||
var reversedMap = createMapReversed(i);
|
||||
assertEquals(expected.hashCode(), map.hashCode());
|
||||
assertEquals(expected, map);
|
||||
assertEquals(expected.hashCode(), reversedMap.hashCode());
|
||||
assertEquals(expected, reversedMap);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void factoryOf() {
|
||||
assertEquals(FMap.<String, String>empty().plus("k", "v"), FMap.of("k", "v"));
|
||||
assertEquals(FMap.<String, String>empty().plus("k1", "v1").plus("k2", "v2"), FMap.of("k1", "v1", "k2", "v2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyMap() {
|
||||
testMap(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oneKey() {
|
||||
testMap(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void twoKeys() {
|
||||
testMap(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void threeKeys() {
|
||||
testMap(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void arrayThresholdKeys() {
|
||||
testMap(ARRAY_THRESHOLD);
|
||||
testMap(ARRAY_THRESHOLD + 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void manyKeys() {
|
||||
testMap(ARRAY_THRESHOLD + 5);
|
||||
}
|
||||
|
||||
private static void testMap(int size) {
|
||||
FMap<String, String> map = createMap(size);
|
||||
assertSize(size, map);
|
||||
assertKeys(map);
|
||||
assertValues(map);
|
||||
testPlus(map);
|
||||
testMinus(map);
|
||||
assertEquals(FMap.from(map.toMap()), map);
|
||||
}
|
||||
|
||||
private static FMap<String, String> createMap(int size) {
|
||||
FMap<String, String> map = FMap.empty();
|
||||
for (int i = 0; i < size; i++) {
|
||||
map = map.plus(key(i), value(i));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static FMap<String, String> createMapReversed(int size) {
|
||||
FMap<String, String> map = FMap.empty();
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
map = map.plus(key(i), value(i));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static @NotNull String key(int i) {
|
||||
return "k" + i;
|
||||
}
|
||||
|
||||
private static @NotNull String value(int i) {
|
||||
return "v" + i;
|
||||
}
|
||||
|
||||
private static void assertSize(int expectedSize, @NotNull FMap<String, String> map) {
|
||||
assertEquals(expectedSize == 0, map.isEmpty());
|
||||
assertEquals(expectedSize, map.size());
|
||||
}
|
||||
|
||||
private static void assertKeys(@NotNull FMap<String, String> map) {
|
||||
assertSameElements(map.keys(), IntStream.range(0, map.size()).mapToObj(FMapTest::key).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private static void assertValues(@NotNull FMap<String, String> map) {
|
||||
for (int i = 0; i < map.size(); i++) {
|
||||
assertEquals(value(i), map.get(key(i)));
|
||||
}
|
||||
}
|
||||
|
||||
private static void testPlus(@NotNull FMap<String, String> map) {
|
||||
int size = map.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
String key = key(i);
|
||||
String value = value(i);
|
||||
assertSame(map, map.plus(key, value)); // equal key, equal value
|
||||
var updatedMap = map.plus(key, "x" + value); // equal key, new value
|
||||
assertSize(size, updatedMap);
|
||||
assertKeys(updatedMap);
|
||||
assertValues(updatedMap, i);
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertValues(@NotNull FMap<String, String> map, int changedKey) {
|
||||
for (int i = 0; i < map.size(); i++) {
|
||||
String key = key(i);
|
||||
String expectedValue = i == changedKey ? "xv" + i : value(i);
|
||||
assertEquals(expectedValue, map.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
private static void testMinus(@NotNull FMap<String, String> map) {
|
||||
assertSame(map, map.minus("nonexistentKey"));
|
||||
int size = map.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
var updatedMap = map.minus(key(i));
|
||||
assertSize(size - 1, updatedMap);
|
||||
assertKeys(size, updatedMap, i);
|
||||
assertValues(size, updatedMap, i);
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertKeys(int sizeBefore, @NotNull FMap<String, String> updatedMap, int removedKey) {
|
||||
List<String> expectedKeys = IntStream.range(0, sizeBefore)
|
||||
.filter(i -> i != removedKey)
|
||||
.mapToObj(FMapTest::key)
|
||||
.collect(Collectors.toList());
|
||||
assertSameElements(updatedMap.keys(), expectedKeys);
|
||||
}
|
||||
|
||||
private static void assertValues(int sizeBefore, @NotNull FMap<String, String> updatedMap, int removedKey) {
|
||||
for (int i = 0; i < sizeBefore; i++) {
|
||||
String key = key(i);
|
||||
String expectedValue = i == removedKey ? null : value(i);
|
||||
assertEquals(expectedValue, updatedMap.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user