org.neo4j.values.virtual.MapValue Maven / Gradle / Ivy
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [https://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.neo4j.values.virtual;
import static org.neo4j.memory.HeapEstimator.shallowSizeOfInstance;
import static org.neo4j.memory.HeapEstimator.sizeOf;
import static org.neo4j.memory.HeapEstimator.sizeOfHashMap;
import static org.neo4j.values.storable.Values.NO_VALUE;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.StreamSupport;
import org.neo4j.function.ThrowingBiConsumer;
import org.neo4j.internal.helpers.collection.PrefetchingIterator;
import org.neo4j.values.AnyValue;
import org.neo4j.values.AnyValueWriter;
import org.neo4j.values.Comparison;
import org.neo4j.values.Equality;
import org.neo4j.values.TernaryComparator;
import org.neo4j.values.ValueMapper;
import org.neo4j.values.VirtualValue;
import org.neo4j.values.storable.Values;
public abstract class MapValue extends VirtualValue {
public static final MapValue EMPTY = new MapValue() {
@Override
public Iterable keySet() {
return Collections.emptyList();
}
@Override
public void foreach(ThrowingBiConsumer f) {
// do nothing
}
@Override
public boolean containsKey(String key) {
return false;
}
@Override
public AnyValue get(String key) {
return NO_VALUE;
}
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public long estimatedHeapUsage() {
return 0L;
}
};
private static final long MAP_WRAPPING_MAP_VALUE_SHALLOW_SIZE = shallowSizeOfInstance(MapWrappingMapValue.class);
static final class MapWrappingMapValue extends MapValue {
private final Map map;
private final long wrappedHeapSize;
MapWrappingMapValue(Map map, long payloadSize) {
assert payloadSize >= 0;
this.map = map;
this.wrappedHeapSize = sizeOfHashMap(map) + payloadSize;
}
MapWrappingMapValue(Map map, long mapSize, long payloadSize) {
assert payloadSize >= 0;
this.map = map;
this.wrappedHeapSize = mapSize + payloadSize;
}
@Override
public Iterable keySet() {
return map.keySet();
}
@Override
public void foreach(ThrowingBiConsumer f) throws E {
for (Map.Entry entry : map.entrySet()) {
f.accept(entry.getKey(), entry.getValue());
}
}
@Override
public boolean containsKey(String key) {
return map.containsKey(key);
}
@Override
public AnyValue get(String key) {
return map.getOrDefault(key, NO_VALUE);
}
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public long estimatedHeapUsage() {
return MAP_WRAPPING_MAP_VALUE_SHALLOW_SIZE + wrappedHeapSize;
}
}
private static final long FILTERING_MAP_VALUE_SHALLOW_SIZE = shallowSizeOfInstance(FilteringMapValue.class);
private static final class FilteringMapValue extends MapValue {
private final MapValue map;
private final BiFunction filter;
private int size = -1;
FilteringMapValue(MapValue map, BiFunction filter) {
this.map = map;
this.filter = filter;
}
@Override
public Iterable keySet() {
List keys = size >= 0 ? new ArrayList<>(size) : new ArrayList<>();
foreach((key, value) -> {
if (filter.apply(key, value)) {
keys.add(key);
}
});
return keys;
}
@Override
public void foreach(ThrowingBiConsumer f) throws E {
map.foreach((s, anyValue) -> {
if (filter.apply(s, anyValue)) {
f.accept(s, anyValue);
}
});
}
@Override
public boolean containsKey(String key) {
AnyValue value = map.get(key);
if (value == NO_VALUE) {
return false;
} else {
return filter.apply(key, value);
}
}
@Override
public AnyValue get(String key) {
AnyValue value = map.get(key);
if (value == NO_VALUE) {
return NO_VALUE;
} else if (filter.apply(key, value)) {
return value;
} else {
return NO_VALUE;
}
}
@Override
public int size() {
if (size < 0) {
size = 0;
foreach((k, v) -> {
if (filter.apply(k, v)) {
size++;
}
});
}
return size;
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public long estimatedHeapUsage() {
return FILTERING_MAP_VALUE_SHALLOW_SIZE + map.estimatedHeapUsage();
}
}
private static final long MAPPED_MAP_VALUE_SHALLOW_SIZE = shallowSizeOfInstance(MappedMapValue.class);
private static final class MappedMapValue extends MapValue {
private final MapValue map;
private final BiFunction mapFunction;
MappedMapValue(MapValue map, BiFunction mapFunction) {
this.map = map;
this.mapFunction = mapFunction;
}
@Override
public ListValue keys() {
return map.keys();
}
@Override
public Iterable keySet() {
return map.keySet();
}
@Override
public void foreach(ThrowingBiConsumer f) throws E {
map.foreach((s, anyValue) -> f.accept(s, mapFunction.apply(s, anyValue)));
}
@Override
public boolean containsKey(String key) {
return map.containsKey(key);
}
@Override
public AnyValue get(String key) {
return mapFunction.apply(key, map.get(key));
}
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public long estimatedHeapUsage() {
return MAPPED_MAP_VALUE_SHALLOW_SIZE + map.estimatedHeapUsage();
}
}
private static final long UPDATED_MAP_VALUE_SHALLOW_SIZE = shallowSizeOfInstance(UpdatedMapValue.class);
private static final class UpdatedMapValue extends MapValue {
private final MapValue map;
private final String updatedKey;
private final AnyValue updatedValue;
UpdatedMapValue(MapValue map, String updatedKey, AnyValue updatedValue) {
assert !map.containsKey(updatedKey);
this.map = map;
this.updatedKey = updatedKey;
this.updatedValue = updatedValue;
}
@Override
public ListValue keys() {
return VirtualValues.concat(map.keys(), VirtualValues.fromArray(Values.stringArray(updatedKey)));
}
@Override
public Iterable keySet() {
return () -> new Iterator<>() {
private Iterator internal = map.keySet().iterator();
private boolean hasNext = true;
@Override
public boolean hasNext() {
if (internal.hasNext()) {
return true;
} else {
return hasNext;
}
}
@Override
public String next() {
if (internal.hasNext()) {
return internal.next();
} else if (hasNext) {
hasNext = false;
return updatedKey;
} else {
throw new NoSuchElementException();
}
}
};
}
@Override
public void foreach(ThrowingBiConsumer f) throws E {
map.foreach(f);
f.accept(updatedKey, updatedValue);
}
@Override
public boolean containsKey(String key) {
if (updatedKey.equals(key)) {
return true;
}
return map.containsKey(key);
}
@Override
public AnyValue get(String key) {
if (updatedKey.equals(key)) {
return updatedValue;
}
return map.get(key);
}
@Override
public int size() {
return map.size() + 1;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public long estimatedHeapUsage() {
return UPDATED_MAP_VALUE_SHALLOW_SIZE
+ map.estimatedHeapUsage()
+ sizeOf(updatedKey)
+ updatedValue.estimatedHeapUsage();
}
}
private static final long COMBINED_MAP_VALUE_SHALLOW_SIZE = shallowSizeOfInstance(CombinedMapValue.class);
private static final class CombinedMapValue extends MapValue {
private final MapValue map1;
private final MapValue map2;
CombinedMapValue(MapValue map1, MapValue map2) {
this.map1 = map1;
this.map2 = map2;
}
@Override
public Iterable keySet() {
return () -> new PrefetchingIterator<>() {
private boolean iteratingMap2;
private Iterator iterator = map1.keySet().iterator();
private Set seen = new HashSet<>();
@Override
protected String fetchNextOrNull() {
while (!iteratingMap2 || iterator.hasNext()) {
if (!iterator.hasNext()) {
iterator = map2.keySet().iterator();
iteratingMap2 = true;
}
while (iterator.hasNext()) {
String key = iterator.next();
if (seen.add(key)) {
return key;
}
}
}
return null;
}
};
}
@Override
public void foreach(ThrowingBiConsumer f) throws E {
Set seen = new HashSet<>();
ThrowingBiConsumer consume = (key, value) -> {
if (seen.add(key)) {
f.accept(key, value);
}
};
map2.foreach(consume);
map1.foreach(consume);
}
@Override
public boolean containsKey(String key) {
if (map1.containsKey(key)) {
return true;
}
return map2.containsKey(key);
}
@Override
public AnyValue get(String key) {
AnyValue value2 = map2.get(key);
if (value2 != NO_VALUE) {
return value2;
}
return map1.get(key);
}
@Override
public int size() {
int[] size = {0};
Set seen = new HashSet<>();
ThrowingBiConsumer consume = (key, value) -> {
if (seen.add(key)) {
size[0]++;
}
};
map1.foreach(consume);
map2.foreach(consume);
return size[0];
}
@Override
public boolean isEmpty() {
return map1.isEmpty() && map2.isEmpty();
}
@Override
public long estimatedHeapUsage() {
return COMBINED_MAP_VALUE_SHALLOW_SIZE + map1.estimatedHeapUsage() + map2.estimatedHeapUsage();
}
}
@Override
protected int computeHashToMemoize() {
int[] h = new int[1];
foreach((key, value) -> h[0] += key.hashCode() ^ value.hashCode());
return h[0];
}
@Override
public void writeTo(AnyValueWriter writer) throws E {
writer.beginMap(size());
foreach((s, anyValue) -> {
writer.writeString(s);
anyValue.writeTo(writer);
});
writer.endMap();
}
@Override
public boolean equals(VirtualValue other) {
if (!(other instanceof MapValue that)) {
return false;
}
int size = size();
if (size != that.size()) {
return false;
}
Iterable keys = keySet();
for (String key : keys) {
if (!get(key).equals(that.get(key))) {
return false;
}
}
return true;
}
public abstract Iterable keySet();
public ListValue keys() {
String[] keys = new String[size()];
int i = 0;
for (String key : keySet()) {
keys[i++] = key;
}
return VirtualValues.fromArray(Values.stringArray(keys));
}
@Override
public VirtualValueGroup valueGroup() {
return VirtualValueGroup.MAP;
}
@Override
public int unsafeCompareTo(VirtualValue other, Comparator comparator) {
MapValue otherMap = (MapValue) other;
int size = size();
int compare = Integer.compare(size, otherMap.size());
if (compare == 0) {
String[] thisKeys =
StreamSupport.stream(keySet().spliterator(), false).toArray(String[]::new);
Arrays.sort(thisKeys, String::compareTo);
String[] thatKeys =
StreamSupport.stream(otherMap.keySet().spliterator(), false).toArray(String[]::new);
Arrays.sort(thatKeys, String::compareTo);
for (int i = 0; i < size; i++) {
compare = thisKeys[i].compareTo(thatKeys[i]);
if (compare != 0) {
return compare;
}
}
for (int i = 0; i < size; i++) {
String key = thisKeys[i];
compare = comparator.compare(get(key), otherMap.get(key));
if (compare != 0) {
return compare;
}
}
}
return compare;
}
@Override
public Comparison unsafeTernaryCompareTo(VirtualValue other, TernaryComparator comparator) {
MapValue otherMap = (MapValue) other;
int size = size();
int compare = Integer.compare(size, otherMap.size());
if (compare == 0) {
String[] thisKeys =
StreamSupport.stream(keySet().spliterator(), false).toArray(String[]::new);
Arrays.sort(thisKeys, String::compareTo);
String[] thatKeys =
StreamSupport.stream(otherMap.keySet().spliterator(), false).toArray(String[]::new);
Arrays.sort(thatKeys, String::compareTo);
for (int i = 0; i < size; i++) {
compare = thisKeys[i].compareTo(thatKeys[i]);
if (compare != 0) {
return Comparison.from(compare);
}
}
for (int i = 0; i < size; i++) {
String key = thisKeys[i];
Comparison comparison = comparator.ternaryCompare(get(key), otherMap.get(key));
if (comparison != Comparison.EQUAL) {
return comparison;
}
}
}
return Comparison.from(compare);
}
@Override
public Equality ternaryEquals(AnyValue other) {
assert other != null : "null values are not supported, use NoValue.NO_VALUE instead";
if (other == NO_VALUE) {
return Equality.UNDEFINED;
} else if (!(other instanceof MapValue)) {
return Equality.FALSE;
}
MapValue otherMap = (MapValue) other;
int size = size();
if (size != otherMap.size()) {
return Equality.FALSE;
}
String[] thisKeys = StreamSupport.stream(keySet().spliterator(), false).toArray(String[]::new);
Arrays.sort(thisKeys, String::compareTo);
String[] thatKeys =
StreamSupport.stream(otherMap.keySet().spliterator(), false).toArray(String[]::new);
Arrays.sort(thatKeys, String::compareTo);
for (int i = 0; i < size; i++) {
if (thisKeys[i].compareTo(thatKeys[i]) != 0) {
return Equality.FALSE;
}
}
Equality equalityResult = Equality.TRUE;
for (int i = 0; i < size; i++) {
String key = thisKeys[i];
AnyValue thisValue = get(key);
AnyValue otherValue = otherMap.get(key);
Equality equality = thisValue.ternaryEquals(otherValue);
if (equality == Equality.UNDEFINED) {
equalityResult = Equality.UNDEFINED;
} else if (equality == Equality.FALSE) {
return Equality.FALSE;
}
}
return equalityResult;
}
@Override
public T map(ValueMapper mapper) {
return mapper.mapMap(this);
}
public abstract void foreach(ThrowingBiConsumer f) throws E;
public abstract boolean containsKey(String key);
public abstract AnyValue get(String key);
public abstract int size();
public abstract boolean isEmpty();
public MapValue filter(BiFunction filterFunction) {
return new FilteringMapValue(this, filterFunction);
}
public MapValue updatedWith(String key, AnyValue value) {
if (!containsKey(key)) {
return new UpdatedMapValue(this, key, value);
} else {
AnyValue current = get(key);
if (current.equals(value)) {
return this;
} else {
return new MappedMapValue(this, (k, v) -> {
if (k.equals(key)) {
return value;
} else {
return v;
}
});
}
}
}
public MapValue updatedWith(MapValue other) {
return new CombinedMapValue(this, other);
}
@Override
public String getTypeName() {
return "Map";
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(getTypeName()).append('{');
final String[] sep = new String[] {""};
foreach((key, value) -> {
sb.append(sep[0]);
sb.append(key);
sb.append(" -> ");
sb.append(value);
sep[0] = ", ";
});
sb.append('}');
return sb.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy