All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.graphdb.impl.StandardExpander Maven / Gradle / Ivy

There is a newer version: 5.25.1
Show newest version
/*
 * 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 filter )
        {
            return new FilteringExpander( expander, append( filters,
                    new NodeFilter( filter ) ) );
        }

        @Override
        public StandardExpander addRelationshipFilter(
                Predicate 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 predicate;

        NodeFilter( Predicate 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 predicate;

        RelationshipFilter( Predicate 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 filter )
    {
        return new FilteringExpander( this, new NodeFilter( filter ) );
    }

    public StandardExpander addRelationshipFilter( Predicate 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 ) );
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy