org.neo4j.graphdb.impl.StandardExpander Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of neo4j-graphdb-api Show documentation
Show all versions of neo4j-graphdb-api Show documentation
Graph Database API for Neo4j.
/*
* 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.graphdb.impl;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.NotFoundException;
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.PathExpander;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.ResourceIterable;
import org.neo4j.graphdb.ResourceIterator;
import org.neo4j.graphdb.traversal.BranchState;
import org.neo4j.internal.helpers.collection.ArrayIterator;
import org.neo4j.internal.helpers.collection.FilteringIterator;
import org.neo4j.internal.helpers.collection.MappingResourceIterator;
import org.neo4j.internal.helpers.collection.NestingResourceIterator;
import static java.util.Arrays.asList;
import static org.neo4j.graphdb.traversal.Paths.singleNodePath;
import static org.neo4j.internal.helpers.collection.Iterators.asResourceIterator;
import static org.neo4j.internal.helpers.collection.ResourceClosingIterator.newResourceIterator;
public abstract class StandardExpander implements PathExpander
{
private StandardExpander()
{
}
abstract static class StandardExpansion implements ResourceIterable
{
final StandardExpander expander;
final Path path;
final BranchState state;
StandardExpansion( StandardExpander expander, Path path, BranchState state )
{
this.expander = expander;
this.path = path;
this.state = state;
}
String stringRepresentation( String nodesORrelationships )
{
return "Expansion[" + path + ".expand( " + expander + " )." + nodesORrelationships + "()]";
}
abstract StandardExpansion createNew( StandardExpander expander );
public StandardExpansion including( RelationshipType type )
{
return createNew( expander.add( type ) );
}
public StandardExpansion including( RelationshipType type, Direction direction )
{
return createNew( expander.add( type, direction ) );
}
public StandardExpansion excluding( RelationshipType type )
{
return createNew( expander.remove( type ) );
}
public T getSingle()
{
final Iterator expanded = iterator();
if ( expanded.hasNext() )
{
final T result = expanded.next();
if ( expanded.hasNext() )
{
throw new NotFoundException( "More than one relationship found for " + this );
}
return result;
}
return null;
}
public boolean isEmpty()
{
return !expander.doExpand( path, state ).hasNext();
}
public StandardExpansion nodes()
{
return new NodeExpansion( expander, path, state );
}
public StandardExpansion relationships()
{
return new RelationshipExpansion( expander, path, state );
}
}
private static final class RelationshipExpansion extends
StandardExpansion
{
RelationshipExpansion( StandardExpander expander, Path path, BranchState state )
{
super( expander, path, state );
}
@Override
public String toString()
{
return stringRepresentation( "relationships" );
}
@Override
StandardExpansion createNew( StandardExpander expander )
{
return new RelationshipExpansion( expander, path, state );
}
@Override
public StandardExpansion relationships()
{
return this;
}
@Override
public ResourceIterator iterator()
{
return expander.doExpand( path, state );
}
}
private static final class NodeExpansion extends StandardExpansion
{
NodeExpansion( StandardExpander expander, Path path, BranchState state )
{
super( expander, path, state );
}
@Override
public String toString()
{
return stringRepresentation( "nodes" );
}
@Override
StandardExpansion createNew( StandardExpander expander )
{
return new NodeExpansion( expander, path, state );
}
@Override
public StandardExpansion nodes()
{
return this;
}
@Override
public ResourceIterator iterator()
{
final Node node = path.endNode();
return new MappingResourceIterator<>( expander.doExpand( path, state ) )
{
@Override
protected Node map( Relationship rel )
{
return rel.getOtherNode( node );
}
};
}
}
private static class AllExpander extends StandardExpander
{
private final Direction direction;
AllExpander( Direction direction )
{
this.direction = direction;
}
@Override
void buildString( StringBuilder result )
{
if ( direction != Direction.BOTH )
{
result.append( direction );
result.append( ':' );
}
result.append( '*' );
}
@Override
ResourceIterator doExpand( Path path, BranchState state )
{
return asResourceIterator( path.endNode().getRelationships( direction ).iterator() );
}
@Override
public StandardExpander add( RelationshipType type, Direction dir )
{
return this;
}
@Override
public StandardExpander remove( RelationshipType type )
{
Map exclude = new HashMap<>();
exclude.put( type.name(), Exclusion.ALL );
return new ExcludingExpander( Exclusion.include( direction ), exclude );
}
@Override
public StandardExpander reversed()
{
return reverse();
}
@Override
public StandardExpander reverse()
{
return new AllExpander( direction.reverse() );
}
}
private enum Exclusion
{
ALL( null, "!" )
{
@Override
public boolean accept( Node start, Relationship rel )
{
return false;
}
},
INCOMING( Direction.OUTGOING )
{
@Override
Exclusion reversed()
{
return OUTGOING;
}
},
OUTGOING( Direction.INCOMING )
{
@Override
Exclusion reversed()
{
return INCOMING;
}
},
NONE( Direction.BOTH, "" )
{
@Override
boolean includes( Direction direction )
{
return true;
}
};
private final String string;
private final Direction direction;
Exclusion( Direction direction, String string )
{
this.direction = direction;
this.string = string;
}
Exclusion( Direction direction )
{
this.direction = direction;
this.string = "!" + name() + ":";
}
@Override
public final String toString()
{
return string;
}
boolean accept( Node start, Relationship rel )
{
return matchDirection( direction, start, rel );
}
Exclusion reversed()
{
return this;
}
boolean includes( Direction dir )
{
return this.direction == dir;
}
static Exclusion include( Direction direction )
{
switch ( direction )
{
case INCOMING:
return OUTGOING;
case OUTGOING:
return INCOMING;
default:
return NONE;
}
}
}
private static final class ExcludingExpander extends StandardExpander
{
private final Exclusion defaultExclusion;
private final Map exclusion;
ExcludingExpander( Exclusion defaultExclusion,
Map exclusion )
{
this.defaultExclusion = defaultExclusion;
this.exclusion = exclusion;
}
@Override
void buildString( StringBuilder result )
{
// FIXME: not really correct
result.append( defaultExclusion );
result.append( '*' );
for ( Map.Entry entry : exclusion.entrySet() )
{
result.append( ',' );
result.append( entry.getValue() );
result.append( entry.getKey() );
}
}
@Override
ResourceIterator doExpand( Path path, BranchState state )
{
final Node node = path.endNode();
ResourceIterator resourceIterator = asResourceIterator( node.getRelationships().iterator() );
return newResourceIterator( new FilteringIterator<>( resourceIterator, rel ->
{
Exclusion exclude = exclusion.get( rel.getType().name() );
exclude = (exclude == null) ? defaultExclusion : exclude;
return exclude.accept( node, rel );
} ), resourceIterator );
}
@Override
public StandardExpander add( RelationshipType type, Direction direction )
{
Exclusion excluded = exclusion.get( type.name() );
final Map newExclusion;
if ( (excluded == null ? defaultExclusion : excluded).includes( direction ) )
{
return this;
}
else
{
excluded = Exclusion.include( direction );
if ( excluded == defaultExclusion )
{
if ( exclusion.size() == 1 )
{
return new AllExpander( defaultExclusion.direction );
}
else
{
newExclusion = new HashMap<>( exclusion );
newExclusion.remove( type.name() );
}
}
else
{
newExclusion = new HashMap<>( exclusion );
newExclusion.put( type.name(), excluded );
}
}
return new ExcludingExpander( defaultExclusion, newExclusion );
}
@Override
public StandardExpander remove( RelationshipType type )
{
Exclusion excluded = exclusion.get( type.name() );
if ( excluded == Exclusion.ALL )
{
return this;
}
Map newExclusion = new HashMap<>( exclusion );
newExclusion.put( type.name(), Exclusion.ALL );
return new ExcludingExpander( defaultExclusion, newExclusion );
}
@Override
public StandardExpander reversed()
{
return reverse();
}
@Override
public StandardExpander reverse()
{
Map newExclusion = new HashMap<>();
for ( Map.Entry entry : exclusion.entrySet() )
{
newExclusion.put( entry.getKey(), entry.getValue().reversed() );
}
return new ExcludingExpander( defaultExclusion.reversed(), newExclusion );
}
}
public static final StandardExpander DEFAULT = new AllExpander(
Direction.BOTH )
{
@Override
public StandardExpander add( RelationshipType type, Direction direction )
{
return create( type, direction );
}
};
public static final StandardExpander EMPTY =
new RegularExpander( Collections.emptyMap() );
private static class DirectionAndTypes
{
final Direction direction;
final RelationshipType[] types;
DirectionAndTypes( Direction direction, RelationshipType[] types )
{
this.direction = direction;
this.types = types;
}
}
static class RegularExpander extends StandardExpander
{
final Map typesMap;
final DirectionAndTypes[] directions;
RegularExpander( Map types )
{
this.typesMap = types;
this.directions = new DirectionAndTypes[types.size()];
int i = 0;
for ( Map.Entry entry : types.entrySet() )
{
this.directions[i++] = new DirectionAndTypes( entry.getKey(), entry.getValue() );
}
}
@Override
void buildString( StringBuilder result )
{
result.append( typesMap );
}
@Override
ResourceIterator doExpand( Path path, BranchState state )
{
final Node node = path.endNode();
if ( directions.length == 1 )
{
DirectionAndTypes direction = directions[0];
return asResourceIterator( node.getRelationships( direction.direction, direction.types ) );
}
else
{
return new NestingResourceIterator<>( new ArrayIterator<>( directions ) )
{
@Override
protected ResourceIterator createNestedIterator( DirectionAndTypes item )
{
return asResourceIterator( node.getRelationships( item.direction, item.types ) );
}
};
}
}
StandardExpander createNew( Map types )
{
if ( types.isEmpty() )
{
return new AllExpander( Direction.BOTH );
}
return new RegularExpander( types );
}
@Override
public StandardExpander add( RelationshipType type, Direction direction )
{
Map> tempMap = temporaryTypeMapFrom( typesMap );
tempMap.get( direction ).add( type );
return createNew( toTypeMap( tempMap ) );
}
@Override
public StandardExpander remove( RelationshipType type )
{
Map> tempMap = temporaryTypeMapFrom( typesMap );
for ( Direction direction : Direction.values() )
{
tempMap.get( direction ).remove( type );
}
return createNew( toTypeMap( tempMap ) );
}
@Override
public StandardExpander reversed()
{
return reverse();
}
@Override
public StandardExpander reverse()
{
Map> tempMap = temporaryTypeMapFrom( typesMap );
Collection out = tempMap.get( Direction.OUTGOING );
Collection in = tempMap.get( Direction.INCOMING );
tempMap.put( Direction.OUTGOING, in );
tempMap.put( Direction.INCOMING, out );
return createNew( toTypeMap( tempMap ) );
}
}
private static final class FilteringExpander extends StandardExpander
{
private final StandardExpander expander;
private final Filter[] filters;
FilteringExpander( StandardExpander expander, Filter... filters )
{
this.expander = expander;
this.filters = filters;
}
@Override
void buildString( StringBuilder result )
{
expander.buildString( result );
result.append( "; filter:" );
for ( Filter filter : filters )
{
result.append( ' ' );
result.append( filter );
}
}
@Override
ResourceIterator doExpand( final Path path, BranchState state )
{
ResourceIterator resourceIterator = expander.doExpand( path, state );
return newResourceIterator( new FilteringIterator<>( resourceIterator, item ->
{
Path extendedPath = ExtendedPath.extend( path, item );
for ( Filter filter : filters )
{
if ( filter.exclude( extendedPath ) )
{
return false;
}
}
return true;
} ), resourceIterator );
}
@Override
public StandardExpander addNodeFilter( Predicate super Node> filter )
{
return new FilteringExpander( expander, append( filters,
new NodeFilter( filter ) ) );
}
@Override
public StandardExpander addRelationshipFilter(
Predicate super Relationship> filter )
{
return new FilteringExpander( expander, append( filters,
new RelationshipFilter( filter ) ) );
}
@Override
public StandardExpander add( RelationshipType type, Direction direction )
{
return new FilteringExpander( expander.add( type, direction ),
filters );
}
@Override
public StandardExpander remove( RelationshipType type )
{
return new FilteringExpander( expander.remove( type ), filters );
}
@Override
public StandardExpander reversed()
{
return reverse();
}
@Override
public StandardExpander reverse()
{
return new FilteringExpander( expander.reversed(), filters );
}
}
private abstract static class Filter
{
abstract boolean exclude( Path path );
}
private static final class NodeFilter extends Filter
{
private final Predicate super Node> predicate;
NodeFilter( Predicate super Node> predicate )
{
this.predicate = predicate;
}
@Override
public String toString()
{
return predicate.toString();
}
@Override
boolean exclude( Path path )
{
return !predicate.test( path.endNode() );
}
}
private static final class RelationshipFilter extends Filter
{
private final Predicate super Relationship> predicate;
RelationshipFilter( Predicate super Relationship> predicate )
{
this.predicate = predicate;
}
@Override
public String toString()
{
return predicate.toString();
}
@Override
boolean exclude( Path path )
{
return !predicate.test( path.lastRelationship() );
}
}
public final StandardExpansion expand( Node node )
{
return new RelationshipExpansion( this, singleNodePath( node ), BranchState.NO_STATE );
}
@Override
public final StandardExpansion expand( Path path, BranchState state )
{
return new RelationshipExpansion( this, path, state );
}
@SuppressWarnings( "unchecked" )
static T[] append( T[] array, T item )
{
T[] result = (T[]) Array.newInstance(
array.getClass().getComponentType(), array.length + 1 );
System.arraycopy( array, 0, result, 0, array.length );
result[array.length] = item;
return result;
}
private static boolean matchDirection( Direction dir, Node start, Relationship rel )
{
switch ( dir )
{
case INCOMING:
return rel.getEndNode().equals( start );
case OUTGOING:
return rel.getStartNode().equals( start );
case BOTH:
return true;
default:
throw new IllegalArgumentException( "Unknown direction: " + dir );
}
}
abstract ResourceIterator doExpand( Path path, BranchState state );
@Override
public final String toString()
{
StringBuilder result = new StringBuilder( "Expander[" );
buildString( result );
result.append( ']' );
return result.toString();
}
abstract void buildString( StringBuilder result );
public final StandardExpander add( RelationshipType type )
{
return add( type, Direction.BOTH );
}
public abstract StandardExpander add( RelationshipType type,
Direction direction );
public abstract StandardExpander remove( RelationshipType type );
@Override
public abstract StandardExpander reverse();
public abstract StandardExpander reversed();
public StandardExpander addNodeFilter( Predicate super Node> filter )
{
return new FilteringExpander( this, new NodeFilter( filter ) );
}
public StandardExpander addRelationshipFilter( Predicate super Relationship> filter )
{
return new FilteringExpander( this, new RelationshipFilter( filter ) );
}
public static StandardExpander create( Direction direction )
{
return new AllExpander( direction );
}
public static StandardExpander create( RelationshipType type, Direction dir )
{
Map types = new EnumMap<>( Direction.class );
types.put( dir, new RelationshipType[]{type} );
return new RegularExpander( types );
}
private static Map toTypeMap(
Map> tempMap )
{
// Remove OUT/IN where there is a BOTH
Collection both = tempMap.get( Direction.BOTH );
tempMap.get( Direction.OUTGOING ).removeAll( both );
tempMap.get( Direction.INCOMING ).removeAll( both );
// Convert into a final map
Map map = new EnumMap<>( Direction.class );
for ( Map.Entry> entry : tempMap.entrySet() )
{
if ( !entry.getValue().isEmpty() )
{
map.put( entry.getKey(), entry.getValue().toArray( new RelationshipType[0] ) );
}
}
return map;
}
private static Map> temporaryTypeMap()
{
Map> map = new EnumMap<>( Direction.class );
for ( Direction direction : Direction.values() )
{
map.put( direction, new ArrayList<>() );
}
return map;
}
private static Map> temporaryTypeMapFrom( Map typeMap )
{
Map> map = new EnumMap<>( Direction.class );
for ( Direction direction : Direction.values() )
{
List types = new ArrayList<>();
map.put( direction, types );
RelationshipType[] existing = typeMap.get( direction );
if ( existing != null )
{
types.addAll( asList( existing ) );
}
}
return map;
}
public static StandardExpander create( RelationshipType type1, Direction dir1,
RelationshipType type2, Direction dir2, Object... more )
{
Map> tempMap = temporaryTypeMap();
tempMap.get( dir1 ).add( type1 );
tempMap.get( dir2 ).add( type2 );
for ( int i = 0; i < more.length; i++ )
{
RelationshipType type = (RelationshipType) more[i++];
Direction direction = (Direction) more[i];
tempMap.get( direction ).add( type );
}
return new RegularExpander( toTypeMap( tempMap ) );
}
}