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

org.neo4j.graphdb.traversal.Evaluators 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.traversal;

import java.util.HashSet;
import java.util.Set;

import org.neo4j.annotations.api.PublicApi;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;

import static java.util.Arrays.asList;
import static org.neo4j.graphdb.traversal.Evaluation.INCLUDE_AND_CONTINUE;

/**
 * Common {@link Evaluator}s useful during common traversals.
 *
 * @see Evaluator
 * @see TraversalDescription
 */
@PublicApi
public abstract class Evaluators
{
    @SuppressWarnings( "rawtypes" )
    private static final PathEvaluator ALL = new PathEvaluator.Adapter()
    {
        @Override
        public Evaluation evaluate( Path path, BranchState state )
        {
            return INCLUDE_AND_CONTINUE;
        }
    };

    private static final PathEvaluator ALL_BUT_START_POSITION = fromDepth( 1 );

    /**
     * @param  the type of the state object.
     * @return an evaluator which includes everything it encounters and doesn't prune
     *         anything.
     */
    public static  PathEvaluator all()
    {
        //noinspection unchecked
        return (PathEvaluator) ALL;
    }

    /**
     * @return an evaluator which never prunes and includes everything except
     *         the first position, i.e. the the start node.
     */
    public static PathEvaluator excludeStartPosition()
    {
        return ALL_BUT_START_POSITION;
    }

    /**
     * Returns an {@link Evaluator} which includes positions down to {@code depth}
     * and prunes everything deeper than that.
     *
     * @param depth   the max depth to traverse to.
     * @param  the type of the state object.
     * @return Returns an {@link Evaluator} which includes positions down to
     *         {@code depth} and prunes everything deeper than that.
     */
    public static  PathEvaluator toDepth( final int depth )
    {
        return new PathEvaluator.Adapter<>()
        {
            @Override
            public Evaluation evaluate( Path path, BranchState state )
            {
                int pathLength = path.length();
                return Evaluation.of( pathLength <= depth, pathLength < depth );
            }
        };
    }

    /**
     * Returns an {@link Evaluator} which only includes positions from {@code depth}
     * and deeper and never prunes anything.
     *
     * @param depth   the depth to start include positions from.
     * @param  the type of the state object.
     * @return Returns an {@link Evaluator} which only includes positions from
     *         {@code depth} and deeper and never prunes anything.
     */
    public static  PathEvaluator fromDepth( final int depth )
    {
        return new PathEvaluator.Adapter<>()
        {
            @Override
            public Evaluation evaluate( Path path, BranchState state )
            {
                return Evaluation.ofIncludes( path.length() >= depth );
            }
        };
    }

    /**
     * Returns an {@link Evaluator} which only includes positions at {@code depth}
     * and prunes everything deeper than that.
     *
     * @param depth   the depth to start include positions from.
     * @param  the type of the state object.
     * @return Returns an {@link Evaluator} which only includes positions at
     *         {@code depth} and prunes everything deeper than that.
     */
    public static  PathEvaluator atDepth( final int depth )
    {
        return new PathEvaluator.Adapter<>()
        {
            @Override
            public Evaluation evaluate( Path path, BranchState state )
            {
                return path.length() == depth ? Evaluation.INCLUDE_AND_PRUNE : Evaluation.EXCLUDE_AND_CONTINUE;
            }
        };
    }

    /**
     * Returns an {@link Evaluator} which only includes positions between
     * depths {@code minDepth} and {@code maxDepth}. It prunes everything deeper
     * than {@code maxDepth}.
     *
     * @param minDepth minimum depth a position must have to be included.
     * @param maxDepth maximum depth a position must have to be included.
     * @param   the type of the state object.
     * @return Returns an {@link Evaluator} which only includes positions between
     *         depths {@code minDepth} and {@code maxDepth}. It prunes everything deeper
     *         than {@code maxDepth}.
     */
    public static  PathEvaluator includingDepths( final int minDepth, final int maxDepth )
    {
        return new PathEvaluator.Adapter<>()
        {
            @Override
            public Evaluation evaluate( Path path, BranchState state )
            {
                int length = path.length();
                return Evaluation.of( length >= minDepth && length <= maxDepth, length < maxDepth );
            }
        };
    }

    /**
     * Returns an {@link Evaluator} which compares the type of the last relationship
     * in a {@link Path} to a given set of relationship types (one or more).If the type of
     * the last relationship in a path is of one of the given types then
     * {@code evaluationIfMatch} will be returned, otherwise
     * {@code evaluationIfNoMatch} will be returned.
     *
     * @param evaluationIfMatch   the {@link Evaluation} to return if the type of the
     *                            last relationship in the path matches any of the given types.
     * @param evaluationIfNoMatch the {@link Evaluation} to return if the type of the
     *                            last relationship in the path doesn't match any of the given types.
     * @param type                the (first) type (of possibly many) to match the last relationship
     *                            in paths with.
     * @param orAnyOfTheseTypes   additional types to match the last relationship in
     *                            paths with.
     * @param              the type of the state object.
     * @return an {@link Evaluator} which compares the type of the last relationship
     *         in a {@link Path} to a given set of relationship types.
     */
    public static  PathEvaluator lastRelationshipTypeIs( final Evaluation evaluationIfMatch,
            final Evaluation evaluationIfNoMatch, final RelationshipType type, RelationshipType... orAnyOfTheseTypes )
    {
        if ( orAnyOfTheseTypes.length == 0 )
        {
            return new PathEvaluator.Adapter<>()
            {
                @Override
                public Evaluation evaluate( Path path, BranchState state )
                {
                    Relationship rel = path.lastRelationship();
                    return rel != null && rel.isType( type ) ? evaluationIfMatch : evaluationIfNoMatch;
                }
            };
        }

        final Set expectedTypes = new HashSet<>();
        expectedTypes.add( type.name() );
        for ( RelationshipType otherType : orAnyOfTheseTypes )
        {
            expectedTypes.add( otherType.name() );
        }

        return new PathEvaluator.Adapter<>()
        {
            @Override
            public Evaluation evaluate( Path path, BranchState state )
            {
                Relationship lastRelationship = path.lastRelationship();
                if ( lastRelationship == null )
                {
                    return evaluationIfNoMatch;
                }

                return expectedTypes.contains( lastRelationship.getType().name() ) ?
                       evaluationIfMatch : evaluationIfNoMatch;
            }
        };
    }

    /**
     * @param type              the (first) type (of possibly many) to match the last relationship
     *                          in paths with.
     * @param orAnyOfTheseTypes types to match the last relationship in paths with. If any matches
     *                          it's considered a match.
     * @param            the type of the state object.
     * @return an {@link Evaluator} which compares the type of the last relationship
     *         in a {@link Path} to a given set of relationship types.
     * @see #lastRelationshipTypeIs(Evaluation, Evaluation, RelationshipType, RelationshipType...)
     *      Uses {@link Evaluation#INCLUDE_AND_CONTINUE} for {@code evaluationIfMatch}
     *      and {@link Evaluation#EXCLUDE_AND_CONTINUE} for {@code evaluationIfNoMatch}.
     */
    public static  PathEvaluator includeWhereLastRelationshipTypeIs( RelationshipType type,
            RelationshipType... orAnyOfTheseTypes )
    {
        return lastRelationshipTypeIs( Evaluation.INCLUDE_AND_CONTINUE, Evaluation.EXCLUDE_AND_CONTINUE,
                type, orAnyOfTheseTypes );
    }

    /**
     * @param type              the (first) type (of possibly many) to match the last relationship
     *                          in paths with.
     * @param orAnyOfTheseTypes types to match the last relationship in paths with. If any matches
     *                          it's considered a match.
     * @param            the type of the state object.
     * @return an {@link Evaluator} which compares the type of the last relationship
     *         in a {@link Path} to a given set of relationship types.
     * @see #lastRelationshipTypeIs(Evaluation, Evaluation, RelationshipType, RelationshipType...)
     *      Uses {@link Evaluation#INCLUDE_AND_PRUNE} for {@code evaluationIfMatch}
     *      and {@link Evaluation#INCLUDE_AND_CONTINUE} for {@code evaluationIfNoMatch}.
     */
    public static  PathEvaluator pruneWhereLastRelationshipTypeIs( RelationshipType type,
            RelationshipType... orAnyOfTheseTypes )
    {
        return lastRelationshipTypeIs( Evaluation.INCLUDE_AND_PRUNE, Evaluation.EXCLUDE_AND_CONTINUE,
                type, orAnyOfTheseTypes );
    }

    /**
     * An {@link Evaluator} which will return {@code evaluationIfMatch} if {@link Path#endNode()}
     * for a given path is any of {@code nodes}, else {@code evaluationIfNoMatch}.
     *
     * @param evaluationIfMatch   the {@link Evaluation} to return if the {@link Path#endNode()}
     *                            is any of the nodes in {@code nodes}.
     * @param evaluationIfNoMatch the {@link Evaluation} to return if the {@link Path#endNode()}
     *                            doesn't match any of the nodes in {@code nodes}.
     * @param possibleEndNodes    a set of nodes to match to end nodes in paths.
     * @param              the type of the state object.
     * @return an {@link Evaluator} which will return {@code evaluationIfMatch} if
     *         {@link Path#endNode()} for a given path is any of {@code nodes},
     *         else {@code evaluationIfNoMatch}.
     */
    public static  PathEvaluator endNodeIs( final Evaluation evaluationIfMatch, final Evaluation
        evaluationIfNoMatch,
            Node... possibleEndNodes )
    {
        if ( possibleEndNodes.length == 1 )
        {
            final Node target = possibleEndNodes[0];
            return new PathEvaluator.Adapter<>()
            {
                @Override
                public Evaluation evaluate( Path path, BranchState state )
                {
                    return target.equals( path.endNode() ) ? evaluationIfMatch : evaluationIfNoMatch;
                }
            };
        }

        final Set endNodes = new HashSet<>( asList( possibleEndNodes ) );
        return new PathEvaluator.Adapter<>()
        {
            @Override
            public Evaluation evaluate( Path path, BranchState state )
            {
                return endNodes.contains( path.endNode() ) ? evaluationIfMatch : evaluationIfNoMatch;
            }
        };
    }

    /**
     * Include paths with the specified end nodes.
     *
     * Uses Evaluators#endNodeIs(Evaluation, Evaluation, Node...) with
     * {@link Evaluation#INCLUDE_AND_CONTINUE} for {@code evaluationIfMatch} and
     * {@link Evaluation#EXCLUDE_AND_CONTINUE} for {@code evaluationIfNoMatch}.
     *
     * @param nodes   end nodes for paths to be included in the result.
     * @param  the type of the state object.
     * @return paths where the end node is one of {@code nodes}
     */
    public static  PathEvaluator includeWhereEndNodeIs( Node... nodes )
    {
        return endNodeIs( Evaluation.INCLUDE_AND_CONTINUE, Evaluation.EXCLUDE_AND_CONTINUE, nodes );
    }

    public static  PathEvaluator pruneWhereEndNodeIs( Node... nodes )
    {
        return endNodeIs( Evaluation.INCLUDE_AND_PRUNE, Evaluation.EXCLUDE_AND_CONTINUE, nodes );
    }

    /**
     * Evaluator which decides to include a {@link Path} if all the
     * {@code nodes} exist in it.
     *
     * @param nodes   {@link Node}s that must exist in a {@link Path} for it
     *                to be included.
     * @param  the type of the state object.
     * @return {@link Evaluation#INCLUDE_AND_CONTINUE} if all {@code nodes}
     *         exist in a given {@link Path}, otherwise
     *         {@link Evaluation#EXCLUDE_AND_CONTINUE}.
     */
    public static  PathEvaluator includeIfContainsAll( final Node... nodes )
    {
        if ( nodes.length == 1 )
        {
            return new PathEvaluator.Adapter<>()
            {
                @Override
                public Evaluation evaluate( Path path, BranchState state )
                {
                    for ( Node node : path.reverseNodes() )
                    {
                        if ( node.equals( nodes[0] ) )
                        {
                            return Evaluation.INCLUDE_AND_CONTINUE;
                        }
                    }
                    return Evaluation.EXCLUDE_AND_CONTINUE;
                }
            };
        }

        final Set fullSet = new HashSet<>( asList( nodes ) );
        return new PathEvaluator.Adapter<>()
        {
            @Override
            public Evaluation evaluate( Path path, BranchState state )
            {
                Set set = new HashSet<>( fullSet );
                for ( Node node : path.reverseNodes() )
                {
                    if ( set.remove( node ) && set.isEmpty() )
                    {
                        return Evaluation.INCLUDE_AND_CONTINUE;
                    }
                }
                return Evaluation.EXCLUDE_AND_CONTINUE;
            }
        };
    }

    /**
     * Whereas adding {@link Evaluator}s to a {@link TraversalDescription} puts those
     * evaluators in {@code AND-mode} this can group many evaluators in {@code OR-mode}.
     *
     * @param evaluators represented as one evaluators. If any of the evaluators decides
     *                   to include a path it will be included.
     * @param     the type of the state object.
     * @return an {@link Evaluator} which decides to include a path if any of the supplied
     *         evaluators wants to include it.
     */
    public static  PathEvaluator includeIfAcceptedByAny( final PathEvaluator... evaluators )
    {
        return new PathEvaluator.Adapter<>()
        {
            @SuppressWarnings( "unchecked" )
            @Override
            public Evaluation evaluate( Path path, BranchState state )
            {
                for ( PathEvaluator evaluator : evaluators )
                {
                    if ( evaluator.evaluate( path, state ).includes() )
                    {
                        return Evaluation.INCLUDE_AND_CONTINUE;
                    }
                }
                return Evaluation.EXCLUDE_AND_CONTINUE;
            }
        };
    }

    /**
     * Whereas adding {@link Evaluator}s to a {@link TraversalDescription} puts those
     * evaluators in {@code AND-mode} this can group many evaluators in {@code OR-mode}.
     *
     * @param evaluators represented as one evaluators. If any of the evaluators decides
     *                   to include a path it will be included.
     * @param     the type of the state object.
     * @return an {@link Evaluator} which decides to include a path if any of the supplied
     *         evaluators wants to include it.
     */
    public static  PathEvaluator includeIfAcceptedByAny( final Evaluator... evaluators )
    {
        return new PathEvaluator.Adapter<>()
        {
            @Override
            public Evaluation evaluate( Path path, BranchState state )
            {
                for ( Evaluator evaluator : evaluators )
                {
                    if ( evaluator.evaluate( path ).includes() )
                    {
                        return Evaluation.INCLUDE_AND_CONTINUE;
                    }
                }
                return Evaluation.EXCLUDE_AND_CONTINUE;
            }
        };
    }

    /**
     * Returns {@link Evaluator}s for paths with the specified depth and with an end node from the list of
     * possibleEndNodes.
     * @param depth The exact depth to filter the returned path evaluators.
     * @param possibleEndNodes Filter for the possible nodes to end the path on.
     * @param  the type of the state object.
     * @return {@link Evaluator}s for paths with the specified depth and with an end node from the list of
     * possibleEndNodes.
     */
    public static  PathEvaluator endNodeIsAtDepth( final int depth, Node... possibleEndNodes )
    {
        if ( possibleEndNodes.length == 1 )
        {
            final Node target = possibleEndNodes[0];
            return new PathEvaluator.Adapter<>()
            {
                @Override
                public Evaluation evaluate( Path path, BranchState state )
                {
                    if ( path.length() == depth )
                    {
                        return target.equals( path.endNode() ) ? Evaluation.INCLUDE_AND_PRUNE : Evaluation.EXCLUDE_AND_PRUNE;
                    }
                    return Evaluation.EXCLUDE_AND_CONTINUE;
                }
            };
        }

        final Set endNodes = new HashSet<>( asList( possibleEndNodes ) );
        return new PathEvaluator.Adapter<>()
        {
            @Override
            public Evaluation evaluate( Path path, BranchState state )
            {
                if ( path.length() == depth )
                {
                    return endNodes.contains( path.endNode() ) ? Evaluation.INCLUDE_AND_PRUNE : Evaluation.EXCLUDE_AND_PRUNE;
                }
                return Evaluation.EXCLUDE_AND_CONTINUE;
            }
        };
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy