/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.optimization; import org.apache.tinkerpop.gremlin.process.computer.Computer; import org.apache.tinkerpop.gremlin.process.computer.GraphComputer; import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.TraversalVertexProgramStep; import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.VertexProgramStep; import org.apache.tinkerpop.gremlin.process.computer.util.EmptyMemory; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; import org.apache.tinkerpop.gremlin.process.traversal.step.LambdaHolder; import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.VertexStep; import org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy; import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Graph; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * @author Marko A. Rodriguez (http://markorodriguez.com) */ public final class GraphFilterStrategy extends AbstractTraversalStrategy<TraversalStrategy.OptimizationStrategy> implements TraversalStrategy.OptimizationStrategy { private static final GraphFilterStrategy INSTANCE = new GraphFilterStrategy(); private GraphFilterStrategy() { } @Override public void apply(final Traversal.Admin<?, ?> traversal) { if (TraversalHelper.getStepsOfAssignableClass(VertexProgramStep.class, traversal).size() > 1) // do not do if there is an OLAP chain return; final Graph graph = traversal.getGraph().orElse(EmptyGraph.instance()); // given that this strategy only works for single OLAP jobs, the graph is the traversal graph for (final TraversalVertexProgramStep step : TraversalHelper.getStepsOfClass(TraversalVertexProgramStep.class, traversal)) { // will be zero or one step final Traversal.Admin<?, ?> computerTraversal = step.generateProgram(graph, EmptyMemory.instance()).getTraversal().get().clone(); if (!computerTraversal.isLocked()) computerTraversal.applyStrategies(); final Computer computer = step.getComputer(); if (null == computer.getEdges() && !GraphComputer.Persist.EDGES.equals(computer.getPersist())) { // if edges() already set, use it final Traversal.Admin<Vertex, Edge> edgeFilter = getEdgeFilter(computerTraversal); if (null != edgeFilter) // if no edges can be filtered, then don't set edges() step.setComputer(computer.edges(edgeFilter)); } } } protected static Traversal.Admin<Vertex, Edge> getEdgeFilter(final Traversal.Admin<?, ?> traversal) { if (traversal.getStartStep() instanceof GraphStep && ((GraphStep) traversal.getStartStep()).returnsEdge()) return null; // if the traversal is an edge traversal, don't filter (this can be made less stringent) if (TraversalHelper.hasStepOfAssignableClassRecursively(LambdaHolder.class, traversal)) return null; // if the traversal contains lambdas, don't filter as you don't know what is being accessed by the lambdas final Map<Direction, Set<String>> directionLabels = new HashMap<>(); final Set<String> outLabels = new HashSet<>(); final Set<String> inLabels = new HashSet<>(); final Set<String> bothLabels = new HashSet<>(); directionLabels.put(Direction.OUT, outLabels); directionLabels.put(Direction.IN, inLabels); directionLabels.put(Direction.BOTH, bothLabels); TraversalHelper.getStepsOfAssignableClassRecursively(VertexStep.class, traversal).forEach(step -> { // in-edge traversals require the outgoing edges for attachment final Direction direction = step.getDirection().equals(Direction.IN) && step.returnsEdge() ? Direction.BOTH : step.getDirection(); final String[] edgeLabels = step.getEdgeLabels(); if (edgeLabels.length == 0) directionLabels.get(direction).add(null); // null means all edges (don't filter) else Collections.addAll(directionLabels.get(direction), edgeLabels); // add edge labels associated with that direction }); for (final String label : outLabels) { // if both in and out share the same labels, add them to both if (inLabels.contains(label)) { bothLabels.add(label); } } if (bothLabels.contains(null)) // if both on everything, you can't edges() filter return null; for (final String label : bothLabels) { // remove labels from out and in that are already handled by both outLabels.remove(label); inLabels.remove(label); } // construct edges(...) if (outLabels.isEmpty() && inLabels.isEmpty() && bothLabels.isEmpty()) // out/in/both are never called, thus, filter all edges return __.<Vertex>bothE().limit(0).asAdmin(); else { final String[] ins = inLabels.contains(null) ? new String[]{} : inLabels.toArray(new String[inLabels.size()]); final String[] outs = outLabels.contains(null) ? new String[]{} : outLabels.toArray(new String[outLabels.size()]); final String[] boths = bothLabels.contains(null) ? new String[]{} : bothLabels.toArray(new String[bothLabels.size()]); if (outLabels.isEmpty() && inLabels.isEmpty()) // only both has labels return __.<Vertex>bothE(boths).asAdmin(); else if (inLabels.isEmpty() && bothLabels.isEmpty()) // only out has labels return __.<Vertex>outE(outs).asAdmin(); else if (outLabels.isEmpty() && bothLabels.isEmpty()) // only in has labels return __.<Vertex>inE(ins).asAdmin(); else if (bothLabels.isEmpty()) // out and in both have labels return __.<Vertex, Edge>union(__.inE(ins), __.outE(outs)).asAdmin(); else if (outLabels.isEmpty() && ins.length > 0) // in and both have labels (and in is not null) return __.<Vertex, Edge>union(__.inE(ins), __.bothE(boths)).asAdmin(); else if (inLabels.isEmpty() && outs.length > 0) // out and both have labels (and out is not null) return __.<Vertex, Edge>union(__.outE(outs), __.bothE(boths)).asAdmin(); else return null; //throw new IllegalStateException("The label combination should not have reached this point: " + outLabels + "::" + inLabels + "::" + bothLabels); } } public static GraphFilterStrategy instance() { return INSTANCE; } }