org.neo4j.values.virtual.MapValue Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of neo4j-values Show documentation
Show all versions of neo4j-values Show documentation
Neo4j property value system.
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://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 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;
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;
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 )
{
this.map = map;
this.wrappedHeapSize = sizeOfHashMap( map ) + payloadSize;
}
MapWrappingMapValue( Map map, long mapSize, long payloadSize )
{
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) )
{
return false;
}
MapValue that = (MapValue) other;
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();
}
}