diff --git a/hugegraph-api/pom.xml b/hugegraph-api/pom.xml index 0ef7194e7e..2da1f4dea8 100644 --- a/hugegraph-api/pom.xml +++ b/hugegraph-api/pom.xml @@ -101,7 +101,7 @@ - 0.32.0.0 + 0.33.0.0 diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CrosspointsAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CrosspointsAPI.java index 4cec79b3fd..8d02cae284 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CrosspointsAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CrosspointsAPI.java @@ -39,11 +39,16 @@ import com.baidu.hugegraph.backend.id.Id; import com.baidu.hugegraph.core.GraphManager; import com.baidu.hugegraph.server.RestServer; -import com.baidu.hugegraph.traversal.optimize.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.PathsTraverser; import com.baidu.hugegraph.type.define.Directions; import com.baidu.hugegraph.util.Log; import com.codahale.metrics.annotation.Timed; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT; + @Path("graphs/{graph}/traversers/crosspoints") @Singleton public class CrosspointsAPI extends API { @@ -60,9 +65,12 @@ public String get(@Context GraphManager manager, @QueryParam("direction") String direction, @QueryParam("label") String edgeLabel, @QueryParam("max_depth") int depth, - @QueryParam("max_degree") @DefaultValue("-1") long degree, - @QueryParam("capacity") @DefaultValue("-1") long capacity, - @QueryParam("limit") @DefaultValue("10") long limit) { + @QueryParam("max_degree") + @DefaultValue(DEFAULT_DEGREE) long degree, + @QueryParam("capacity") + @DefaultValue(DEFAULT_CAPACITY) long capacity, + @QueryParam("limit") + @DefaultValue(DEFAULT_PATHS_LIMIT) long limit) { LOG.debug("Graph [{}] get crosspoints with paths from '{}', to '{}' " + "with direction '{}', edge label '{}', max depth '{}', " + "max degree '{}', capacity '{}' and limit '{}'", @@ -74,7 +82,7 @@ public String get(@Context GraphManager manager, Directions dir = Directions.convert(EdgeAPI.parseDirection(direction)); HugeGraph g = graph(manager, graph); - HugeTraverser traverser = new HugeTraverser(g); + PathsTraverser traverser = new PathsTraverser(g); Set paths = traverser.paths(sourceId, dir, targetId, dir, edgeLabel, depth, diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedCrosspointsAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedCrosspointsAPI.java new file mode 100644 index 0000000000..df14df0c33 --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedCrosspointsAPI.java @@ -0,0 +1,201 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.api.traversers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.inject.Singleton; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; + +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.api.API; +import com.baidu.hugegraph.api.filter.StatusFilter.Status; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.core.GraphManager; +import com.baidu.hugegraph.schema.EdgeLabel; +import com.baidu.hugegraph.server.RestServer; +import com.baidu.hugegraph.structure.HugeVertex; +import com.baidu.hugegraph.traversal.algorithm.CustomizedCrosspointsTraverser; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; +import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.E; +import com.baidu.hugegraph.util.Log; +import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.annotation.JsonProperty; + +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.*; + +@Path("graphs/{graph}/traversers/customizedcrosspoints") +@Singleton +public class CustomizedCrosspointsAPI extends API { + + private static final Logger LOG = Log.logger(RestServer.class); + + @POST + @Timed + @Status(Status.CREATED) + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON_WITH_CHARSET) + public String post(@Context GraphManager manager, + @PathParam("graph") String graph, + CrosspointsRequest request) { + E.checkArgumentNotNull(request, + "The crosspoints request body can't be null"); + E.checkArgumentNotNull(request.sources, + "The sources of crosspoints request " + + "can't be null"); + E.checkArgument(request.pathPatterns != null && + !request.pathPatterns.isEmpty(), + "The steps of crosspoints request can't be empty"); + + LOG.debug("Graph [{}] get customized crosspoints from source vertex " + + "'{}', with path_pattern '{}', with_path '{}', with_vertex " + + "'{}', capacity '{}' and limit '{}'", graph, request.sources, + request.pathPatterns, request.withPath, request.withVertex, + request.capacity, request.limit); + + HugeGraph g = graph(manager, graph); + List sources = request.sources.sourcesVertices(g); + List patterns; + patterns = pathPatterns(g, request); + + CustomizedCrosspointsTraverser traverser = + new CustomizedCrosspointsTraverser(g); + CustomizedCrosspointsTraverser.CrosspointsPaths paths; + paths = traverser.crosspointsPaths(sources, patterns, request.capacity, + request.limit); + Iterator iter = Collections.emptyIterator(); + if (!request.withVertex) { + return manager.serializer(g).writeCrosspoints(paths, iter, + request.withPath); + } + Set ids = new HashSet<>(); + if (request.withPath) { + for (HugeTraverser.Path p : paths.paths()) { + ids.addAll(p.vertices()); + } + } else { + ids = paths.crosspoints(); + } + if (!ids.isEmpty()) { + iter = g.vertices(ids.toArray()); + } + return manager.serializer(g).writeCrosspoints(paths, iter, + request.withPath); + } + + private static List + pathPatterns(HugeGraph graph, CrosspointsRequest request) { + int stepSize = request.pathPatterns.size(); + List pathPatterns; + pathPatterns = new ArrayList<>(stepSize); + for (PathPattern pattern : request.pathPatterns) { + CustomizedCrosspointsTraverser.PathPattern pathPattern; + pathPattern = new CustomizedCrosspointsTraverser.PathPattern(); + for (Step step : pattern.steps) { + pathPattern.add(step.jsonToStep(graph)); + } + pathPatterns.add(pathPattern); + } + return pathPatterns; + } + + private static class CrosspointsRequest { + + @JsonProperty("sources") + public SourceVertices sources; + @JsonProperty("path_patterns") + public List pathPatterns; + @JsonProperty("capacity") + public long capacity = Long.valueOf(DEFAULT_CAPACITY); + @JsonProperty("limit") + public long limit = Long.valueOf(DEFAULT_PATHS_LIMIT); + @JsonProperty("with_path") + public boolean withPath = false; + @JsonProperty("with_vertex") + public boolean withVertex = false; + + @Override + public String toString() { + return String.format("pathRequest{sourceVertex=%s,pathPatterns=%s" + + ",withPath=%s,withVertex=%s,capacity=%s," + + "limit=%s}", this.sources, this.pathPatterns, + this.withPath, this.withVertex, this.capacity, + this.limit); + } + } + + private static class PathPattern { + + @JsonProperty("steps") + public List steps; + } + + private static class Step { + + @JsonProperty("direction") + public Directions direction; + @JsonProperty("labels") + public List labels; + @JsonProperty("properties") + public Map properties; + @JsonProperty("degree") + public long degree = Long.valueOf(DEFAULT_DEGREE); + + @Override + public String toString() { + return String.format("step:{direction=%s,labels=%s,properties=%s," + + "degree=%s}", this.direction, this.labels, + this.properties, this.degree); + } + + private CustomizedCrosspointsTraverser.Step jsonToStep(HugeGraph g) { + E.checkArgument(this.degree > 0 || this.degree == NO_LIMIT, + "The degree must be > 0 or == -1, but got: %s", + this.degree); + Map labelIds = new HashMap<>(); + if (this.labels != null) { + for (String label : this.labels) { + EdgeLabel el = g.edgeLabel(label); + labelIds.put(el.id(), label); + } + } + return new CustomizedCrosspointsTraverser.Step(this.direction, + labelIds, + this.properties, + this.degree); + } + } +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedPathsAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedPathsAPI.java new file mode 100644 index 0000000000..74b49ad771 --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedPathsAPI.java @@ -0,0 +1,215 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.api.traversers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.inject.Singleton; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; + +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.api.API; +import com.baidu.hugegraph.api.filter.StatusFilter.Status; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.core.GraphManager; +import com.baidu.hugegraph.schema.EdgeLabel; +import com.baidu.hugegraph.schema.PropertyKey; +import com.baidu.hugegraph.server.RestServer; +import com.baidu.hugegraph.structure.HugeVertex; +import com.baidu.hugegraph.traversal.algorithm.CustomizePathsTraverser; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; +import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.E; +import com.baidu.hugegraph.util.Log; +import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.annotation.JsonProperty; + +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.*; + +@Path("graphs/{graph}/traversers/customizedpaths") +@Singleton +public class CustomizedPathsAPI extends API { + + private static final Logger LOG = Log.logger(RestServer.class); + + @POST + @Timed + @Status(Status.CREATED) + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON_WITH_CHARSET) + public String post(@Context GraphManager manager, + @PathParam("graph") String graph, + PathRequest request) { + E.checkArgumentNotNull(request, "The path request body can't be null"); + E.checkArgumentNotNull(request.sources, + "The sources of path request can't be null"); + E.checkArgument(request.steps != null && !request.steps.isEmpty(), + "The steps of path request can't be empty"); + if (request.sortBy == null) { + request.sortBy = SortBy.NONE; + } + + LOG.debug("Graph [{}] get customized paths from source vertex '{}', " + + "with steps '{}', sort by '{}', capacity '{}', limit '{}' " + + "and with_vertex '{}'", graph, request.sources, request.steps, + request.sortBy, request.capacity, request.limit, + request.withVertex); + + HugeGraph g = graph(manager, graph); + List sources = request.sources.sourcesVertices(g); + List steps = step(g, request); + boolean sorted = request.sortBy != SortBy.NONE; + + CustomizePathsTraverser traverser = new CustomizePathsTraverser(g); + List paths; + paths = traverser.customizedPaths(sources, steps, sorted, + request.capacity, request.limit); + + if (sorted) { + boolean incr = request.sortBy == SortBy.INCR; + paths = CustomizePathsTraverser.topNPath(paths, incr, + request.limit); + } + + if (!request.withVertex) { + return manager.serializer(g).writePaths("paths", paths, false); + } + + Set ids = new HashSet<>(); + for (HugeTraverser.Path p : paths) { + ids.addAll(p.vertices()); + } + Iterator iter = Collections.emptyIterator(); + if (!ids.isEmpty()) { + iter = g.vertices(ids.toArray()); + } + return manager.serializer(g).writePaths("paths", paths, false, iter); + } + + private static List step(HugeGraph graph, + PathRequest req) { + int stepSize = req.steps.size(); + List steps = new ArrayList<>(stepSize); + for (Step step : req.steps) { + steps.add(step.jsonToStep(graph)); + } + return steps; + } + + private static class PathRequest { + + @JsonProperty("sources") + public SourceVertices sources; + @JsonProperty("steps") + public List steps; + @JsonProperty("sort_by") + public SortBy sortBy; + @JsonProperty("capacity") + public long capacity = Long.valueOf(DEFAULT_CAPACITY); + @JsonProperty("limit") + public long limit = Long.valueOf(DEFAULT_PATHS_LIMIT); + @JsonProperty("with_vertex") + public boolean withVertex = false; + + @Override + public String toString() { + return String.format("pathRequest{sourceVertex=%s,steps=%s," + + "sortBy=%s,capacity=%s,limit=%s," + + "withVertex=%s}", this.sources, this.steps, + this.sortBy, this.capacity, this.limit, + this.withVertex); + } + } + + private static class Step { + + @JsonProperty("direction") + public Directions direction; + @JsonProperty("labels") + public List labels; + @JsonProperty("properties") + public Map properties; + @JsonProperty("weight_by") + public String weightBy; + @JsonProperty("default_weight") + public double defaultWeight = Double.valueOf(DEFAULT_WEIGHT); + @JsonProperty("degree") + public long degree = Long.valueOf(DEFAULT_DEGREE); + @JsonProperty("sample") + public long sample = Long.valueOf(DEFAULT_SAMPLE); + + @Override + public String toString() { + return String.format("step:{direction=%s,labels=%s,properties=%s," + + "weightBy=%s,defaultWeight=%s,degree=%s," + + "sample=%s}", this.direction, this.labels, + this.properties, this.weightBy, + this.defaultWeight, this.degree, this.sample); + } + + private CustomizePathsTraverser.Step jsonToStep(HugeGraph graph) { + E.checkArgument(this.degree > 0 || this.degree == NO_LIMIT, + "The degree must be > 0 or == -1, but got: %s", + this.degree); + E.checkArgument(this.sample > 0 || this.sample == NO_LIMIT, + "The sample must be > 0, but got: %s", + this.sample); + E.checkArgument(this.degree == NO_LIMIT || this.degree >= sample, + "Degree must be greater than or equal to sample," + + " but got degree %s and sample %s", degree, sample); + Map labelIds = new HashMap<>(); + if (this.labels != null) { + for (String label : this.labels) { + EdgeLabel el = graph.edgeLabel(label); + labelIds.put(el.id(), label); + } + } + PropertyKey weightBy = null; + if (this.weightBy != null) { + weightBy = graph.propertyKey(this.weightBy); + } + return new CustomizePathsTraverser.Step(this.direction, labelIds, + this.properties, weightBy, + this.defaultWeight, + this.degree, this.sample); + } + } + + private enum SortBy { + INCR, + DECR, + NONE + } +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/KneighborAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/KneighborAPI.java index 273cdd86f7..b4e1d9fcb0 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/KneighborAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/KneighborAPI.java @@ -39,11 +39,14 @@ import com.baidu.hugegraph.backend.id.Id; import com.baidu.hugegraph.core.GraphManager; import com.baidu.hugegraph.server.RestServer; -import com.baidu.hugegraph.traversal.optimize.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; import com.baidu.hugegraph.type.define.Directions; import com.baidu.hugegraph.util.Log; import com.codahale.metrics.annotation.Timed; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_ELEMENTS_LIMIT; + @Path("graphs/{graph}/traversers/kneighbor") @Singleton public class KneighborAPI extends API { @@ -59,8 +62,10 @@ public String get(@Context GraphManager manager, @QueryParam("direction") String direction, @QueryParam("label") String edgeLabel, @QueryParam("max_depth") int depth, - @QueryParam("max_degree") @DefaultValue("-1") long degree, - @QueryParam("limit") @DefaultValue("-1") long limit) { + @QueryParam("max_degree") + @DefaultValue(DEFAULT_DEGREE) long degree, + @QueryParam("limit") + @DefaultValue(DEFAULT_ELEMENTS_LIMIT) long limit) { LOG.debug("Graph [{}] get k-neighbor from '{}' with " + "direction '{}', edge label '{}', max depth '{}', " + "max degree '{}' and limit '{}'", diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/KoutAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/KoutAPI.java index 6fc5e7d6f0..4825637778 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/KoutAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/KoutAPI.java @@ -39,11 +39,15 @@ import com.baidu.hugegraph.backend.id.Id; import com.baidu.hugegraph.core.GraphManager; import com.baidu.hugegraph.server.RestServer; -import com.baidu.hugegraph.traversal.optimize.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; import com.baidu.hugegraph.type.define.Directions; import com.baidu.hugegraph.util.Log; import com.codahale.metrics.annotation.Timed; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_ELEMENTS_LIMIT; + @Path("graphs/{graph}/traversers/kout") @Singleton public class KoutAPI extends API { @@ -61,9 +65,12 @@ public String get(@Context GraphManager manager, @QueryParam("max_depth") int depth, @QueryParam("nearest") @DefaultValue("true") boolean nearest, - @QueryParam("max_degree") @DefaultValue("-1") long degree, - @QueryParam("capacity") @DefaultValue("-1") long capacity, - @QueryParam("limit") @DefaultValue("-1") long limit) { + @QueryParam("max_degree") + @DefaultValue(DEFAULT_DEGREE) long degree, + @QueryParam("capacity") + @DefaultValue(DEFAULT_CAPACITY) long capacity, + @QueryParam("limit") + @DefaultValue(DEFAULT_ELEMENTS_LIMIT) long limit) { LOG.debug("Graph [{}] get k-out from '{}' with " + "direction '{}', edge label '{}', max depth '{}', nearest " + "'{}', max degree '{}', capacity '{}' and limit '{}'", diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/PathsAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/PathsAPI.java index 823d03c93f..7da795edc2 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/PathsAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/PathsAPI.java @@ -39,11 +39,16 @@ import com.baidu.hugegraph.backend.id.Id; import com.baidu.hugegraph.core.GraphManager; import com.baidu.hugegraph.server.RestServer; -import com.baidu.hugegraph.traversal.optimize.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.PathsTraverser; import com.baidu.hugegraph.type.define.Directions; import com.baidu.hugegraph.util.Log; import com.codahale.metrics.annotation.Timed; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT; + @Path("graphs/{graph}/traversers/paths") @Singleton public class PathsAPI extends API { @@ -60,9 +65,12 @@ public String get(@Context GraphManager manager, @QueryParam("direction") String direction, @QueryParam("label") String edgeLabel, @QueryParam("max_depth") int depth, - @QueryParam("max_degree") @DefaultValue("-1") long degree, - @QueryParam("capacity") @DefaultValue("-1") long capacity, - @QueryParam("limit") @DefaultValue("10") long limit) { + @QueryParam("max_degree") + @DefaultValue(DEFAULT_DEGREE) long degree, + @QueryParam("capacity") + @DefaultValue(DEFAULT_CAPACITY) long capacity, + @QueryParam("limit") + @DefaultValue(DEFAULT_PATHS_LIMIT) long limit) { LOG.debug("Graph [{}] get paths from '{}', to '{}' with " + "direction {}, edge label {}, max depth '{}', " + "max degree '{}', capacity '{}' and limit '{}'", @@ -74,7 +82,7 @@ public String get(@Context GraphManager manager, Directions dir = Directions.convert(EdgeAPI.parseDirection(direction)); HugeGraph g = graph(manager, graph); - HugeTraverser traverser = new HugeTraverser(g); + PathsTraverser traverser = new PathsTraverser(g); Set paths; paths = traverser.paths(sourceId, dir, targetId, dir.opposite(), edgeLabel, depth, degree, capacity, limit); diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/Rays.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RaysAPI.java similarity index 77% rename from hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/Rays.java rename to hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RaysAPI.java index 059f2d54c5..edeccbe34e 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/Rays.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RaysAPI.java @@ -39,14 +39,19 @@ import com.baidu.hugegraph.backend.id.Id; import com.baidu.hugegraph.core.GraphManager; import com.baidu.hugegraph.server.RestServer; -import com.baidu.hugegraph.traversal.optimize.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.SubGraphTraverser; import com.baidu.hugegraph.type.define.Directions; import com.baidu.hugegraph.util.Log; import com.codahale.metrics.annotation.Timed; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT; + @Path("graphs/{graph}/traversers/rays") @Singleton -public class Rays extends API { +public class RaysAPI extends API { private static final Logger LOG = Log.logger(RestServer.class); @@ -59,9 +64,12 @@ public String get(@Context GraphManager manager, @QueryParam("direction") String direction, @QueryParam("label") String edgeLabel, @QueryParam("max_depth") int depth, - @QueryParam("max_degree") @DefaultValue("-1") long degree, - @QueryParam("capacity") @DefaultValue("-1") long capacity, - @QueryParam("limit") @DefaultValue("-1") long limit) { + @QueryParam("max_degree") + @DefaultValue(DEFAULT_DEGREE) long degree, + @QueryParam("capacity") + @DefaultValue(DEFAULT_CAPACITY) long capacity, + @QueryParam("limit") + @DefaultValue(DEFAULT_PATHS_LIMIT) long limit) { LOG.debug("Graph [{}] get rays paths from '{}' with " + "direction '{}', edge label '{}', max depth '{}', " + "max degree '{}' and limit '{}'", @@ -72,7 +80,7 @@ public String get(@Context GraphManager manager, HugeGraph g = graph(manager, graph); - HugeTraverser traverser = new HugeTraverser(g); + SubGraphTraverser traverser = new SubGraphTraverser(g); List paths = traverser.rays(source, dir, edgeLabel, depth, degree, capacity, limit); diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/Rings.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RingsAPI.java similarity index 77% rename from hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/Rings.java rename to hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RingsAPI.java index 7e1f4444c0..3921f3b4e1 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/Rings.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RingsAPI.java @@ -39,14 +39,19 @@ import com.baidu.hugegraph.backend.id.Id; import com.baidu.hugegraph.core.GraphManager; import com.baidu.hugegraph.server.RestServer; -import com.baidu.hugegraph.traversal.optimize.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.SubGraphTraverser; import com.baidu.hugegraph.type.define.Directions; import com.baidu.hugegraph.util.Log; import com.codahale.metrics.annotation.Timed; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT; + @Path("graphs/{graph}/traversers/rings") @Singleton -public class Rings extends API { +public class RingsAPI extends API { private static final Logger LOG = Log.logger(RestServer.class); @@ -59,9 +64,12 @@ public String get(@Context GraphManager manager, @QueryParam("direction") String direction, @QueryParam("label") String edgeLabel, @QueryParam("max_depth") int depth, - @QueryParam("max_degree") @DefaultValue("-1") long degree, - @QueryParam("capacity") @DefaultValue("-1") long capacity, - @QueryParam("limit") @DefaultValue("-1") long limit) { + @QueryParam("max_degree") + @DefaultValue(DEFAULT_DEGREE) long degree, + @QueryParam("capacity") + @DefaultValue(DEFAULT_CAPACITY) long capacity, + @QueryParam("limit") + @DefaultValue(DEFAULT_PATHS_LIMIT) long limit) { LOG.debug("Graph [{}] get rings paths reachable from '{}' with " + "direction '{}', edge label '{}', max depth '{}', " + "max degree '{}' and limit '{}'", @@ -72,7 +80,7 @@ public String get(@Context GraphManager manager, HugeGraph g = graph(manager, graph); - HugeTraverser traverser = new HugeTraverser(g); + SubGraphTraverser traverser = new SubGraphTraverser(g); List paths = traverser.rings(source, dir, edgeLabel, depth, degree, capacity, limit); diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/ShortestPathAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/ShortestPathAPI.java index 13e6687c13..841a177436 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/ShortestPathAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/ShortestPathAPI.java @@ -39,11 +39,14 @@ import com.baidu.hugegraph.backend.id.Id; import com.baidu.hugegraph.core.GraphManager; import com.baidu.hugegraph.server.RestServer; -import com.baidu.hugegraph.traversal.optimize.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.ShortestPathTraverser; import com.baidu.hugegraph.type.define.Directions; import com.baidu.hugegraph.util.Log; import com.codahale.metrics.annotation.Timed; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; + @Path("graphs/{graph}/traversers/shortestpath") @Singleton public class ShortestPathAPI extends API { @@ -60,9 +63,10 @@ public String get(@Context GraphManager manager, @QueryParam("direction") String direction, @QueryParam("label") String edgeLabel, @QueryParam("max_depth") int depth, - @QueryParam("max_degree") @DefaultValue("-1") long degree, + @QueryParam("max_degree") + @DefaultValue(DEFAULT_DEGREE) long degree, @QueryParam("capacity") - @DefaultValue("-1") long capacity) { + @DefaultValue(DEFAULT_CAPACITY) long capacity) { LOG.debug("Graph [{}] get shortest path from '{}', to '{}' with " + "direction {}, edge label {}, max depth '{}', " + "max degree '{}' and capacity '{}'", @@ -75,7 +79,7 @@ public String get(@Context GraphManager manager, HugeGraph g = graph(manager, graph); - HugeTraverser traverser = new HugeTraverser(g); + ShortestPathTraverser traverser = new ShortestPathTraverser(g); List path = traverser.shortestPath(sourceId, targetId, dir, edgeLabel, depth, degree, capacity); diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/SourceVertices.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/SourceVertices.java new file mode 100644 index 0000000000..3ab05b5255 --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/SourceVertices.java @@ -0,0 +1,98 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.api.traversers; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.tinkerpop.gremlin.structure.Vertex; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.backend.query.Condition; +import com.baidu.hugegraph.backend.query.ConditionQuery; +import com.baidu.hugegraph.structure.HugeVertex; +import com.baidu.hugegraph.type.HugeType; +import com.baidu.hugegraph.type.define.HugeKeys; +import com.baidu.hugegraph.util.E; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class SourceVertices { + + @JsonProperty("ids") + public Set ids; + @JsonProperty("label") + public String label; + @JsonProperty("properties") + public Map properties; + + public List sourcesVertices(HugeGraph g) { + Map props = this.properties; + E.checkArgument(!((this.ids == null || this.ids.isEmpty()) && + (props == null || props.isEmpty()) && + this.label == null), "No source vertices provided"); + List vertices = new ArrayList<>(); + Iterator iter; + if (this.ids != null && !this.ids.isEmpty()) { + List sourceIds = new ArrayList<>(this.ids.size()); + for (Object id : this.ids) { + sourceIds.add(HugeVertex.getIdValue(id)); + } + iter = g.vertices(sourceIds.toArray()); + E.checkArgument(iter.hasNext(), + "Not exist source vertexes with ids %s", + this.ids); + } else { + ConditionQuery query = new ConditionQuery(HugeType.VERTEX); + if (this.label != null) { + Id label = g.vertexLabel(this.label).id(); + query.eq(HugeKeys.LABEL, label); + } + if (props != null && !props.isEmpty()) { + for (Map.Entry entry : props.entrySet()) { + Id pkeyId = g.propertyKey(entry.getKey()).id(); + Object value = entry.getValue(); + if (value instanceof List) { + query.query(Condition.in(pkeyId, (List) value)); + } else { + query.query(Condition.eq(pkeyId, value)); + } + } + } + iter = g.vertices(query); + E.checkArgument(iter.hasNext(), "Not exist source vertex with " + + "label '%s' and properties '%s'", + this.label, props); + } + while (iter.hasNext()) { + vertices.add((HugeVertex) iter.next()); + } + return vertices; + } + + @Override + public String toString() { + return String.format("sourceVertex{ids=%s,label=%s,properties=%s}", + this.ids, this.label, this.properties); + } +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/JsonSerializer.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/JsonSerializer.java index 3bdb339948..e030e99678 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/JsonSerializer.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/JsonSerializer.java @@ -25,6 +25,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; import org.apache.tinkerpop.gremlin.structure.Edge; @@ -40,8 +41,11 @@ import com.baidu.hugegraph.schema.IndexLabel; import com.baidu.hugegraph.schema.PropertyKey; import com.baidu.hugegraph.schema.VertexLabel; -import com.baidu.hugegraph.traversal.optimize.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.CustomizedCrosspointsTraverser; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; import com.baidu.hugegraph.traversal.optimize.TraversalUtil; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; public class JsonSerializer implements Serializer { @@ -202,12 +206,40 @@ public String writeIds(String name, Collection ids) { @Override public String writePaths(String name, Collection paths, - boolean withCrossPoint) { + boolean withCrossPoint, + Iterator vertices) { List> pathList = new ArrayList<>(paths.size()); for (HugeTraverser.Path path : paths) { pathList.add(path.toMap(withCrossPoint)); } - return writeList(name, pathList); + + Map results; + if (vertices == null) { + results = ImmutableMap.of(name, pathList); + } else { + results = ImmutableMap.of(name, pathList, "vertices", vertices); + } + return writeObject(results); + } + + @Override + public String writeCrosspoints( + CustomizedCrosspointsTraverser.CrosspointsPaths paths, + Iterator iterator, boolean withPath) { + Map results; + List> pathList; + if (withPath) { + pathList = new ArrayList<>(); + for (HugeTraverser.Path path : paths.paths()) { + pathList.add(path.toMap(false)); + } + } else { + pathList = ImmutableList.of(); + } + results = ImmutableMap.of("crosspoints", paths.crosspoints(), + "paths", pathList, + "vertices", iterator); + return writeObject(results); } @Override diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/Serializer.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/Serializer.java index 4e210e5c0e..ac534af2f1 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/Serializer.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/Serializer.java @@ -32,7 +32,8 @@ import com.baidu.hugegraph.schema.IndexLabel; import com.baidu.hugegraph.schema.PropertyKey; import com.baidu.hugegraph.schema.VertexLabel; -import com.baidu.hugegraph.traversal.optimize.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.CustomizedCrosspointsTraverser; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; public interface Serializer { @@ -65,7 +66,17 @@ public interface Serializer { public String writeIds(String name, Collection ids); public String writePaths(String name, Collection paths, - boolean withCrossPoint); + boolean withCrossPoint, Iterator vertices); + + public default String writePaths(String name, + Collection paths, + boolean withCrossPoint) { + return this.writePaths(name, paths, withCrossPoint, null); + } + + public String writeCrosspoints( + CustomizedCrosspointsTraverser.CrosspointsPaths paths, + Iterator iterator, boolean withPath); public String writeShards(List shards); } diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java index 2a285b8326..52359f2a06 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java @@ -76,10 +76,11 @@ public final class ApiVersion { * [0.30] Issue-32: Change index create API to return indexLabel and task id * [0.31] Issue-182: Support restore graph in restoring and merging mode * [0.32] Issue-250: Keep depth and degree consistent for traverser api + * [0.33] Issue-305: Implement customized paths and crosspoints RESTful API */ // The second parameter of Version.of() is for IDE running without JAR - public static final Version VERSION = Version.of(ApiVersion.class, "0.32"); + public static final Version VERSION = Version.of(ApiVersion.class, "0.33"); public static final void check() { // Check version of hugegraph-core. Firstly do check from version 0.3 diff --git a/hugegraph-core/pom.xml b/hugegraph-core/pom.xml index 01ec1f1520..a1d53b1847 100644 --- a/hugegraph-core/pom.xml +++ b/hugegraph-core/pom.xml @@ -19,7 +19,7 @@ com.baidu.hugegraph hugegraph-common - 1.5.6 + 1.5.8 diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/CustomizePathsTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/CustomizePathsTraverser.java new file mode 100644 index 0000000000..6ed5ea5138 --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/CustomizePathsTraverser.java @@ -0,0 +1,273 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.traversal.algorithm; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.MultivaluedMap; + +import org.apache.tinkerpop.gremlin.structure.Edge; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.schema.PropertyKey; +import com.baidu.hugegraph.structure.HugeEdge; +import com.baidu.hugegraph.structure.HugeVertex; +import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.CollectionUtil; +import com.baidu.hugegraph.util.E; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +public class CustomizePathsTraverser extends HugeTraverser { + + public CustomizePathsTraverser(HugeGraph graph) { + super(graph); + } + + public List customizedPaths(List vertices, + List steps, boolean sorted, + long capacity, long limit) { + E.checkArgument(!vertices.isEmpty(), + "The source vertices can't be empty"); + E.checkArgument(!steps.isEmpty(), "The steps can't be empty"); + checkCapacity(capacity); + checkLimit(limit); + + MultivaluedMap sources = newMultivalueMap(); + for (HugeVertex vertex : vertices) { + Node node = sorted ? + new WeightNode(vertex.id(), null, 0) : + new Node(vertex.id(), null); + sources.add(vertex.id(), node); + } + int stepNum = steps.size(); + int pathCount = 0; + long accessCount = 0; + MultivaluedMap newVertices = null; + root : for (Step step : steps) { + stepNum--; + newVertices = newMultivalueMap(); + Iterator edges; + + // Traversal vertices of previous level + for (Map.Entry> entry : sources.entrySet()) { + List adjacency = new ArrayList<>(); + edges = edgesOfVertex(entry.getKey(), step.direction, + step.labels, step.properties, + step.degree); + while (edges.hasNext()) { + HugeEdge edge = (HugeEdge) edges.next(); + Id target = edge.id().otherVertexId(); + for (Node n : entry.getValue()) { + // If have loop, skip target + if (n.contains(target)) { + continue; + } + Node newNode; + if (sorted) { + double w = step.weightBy != null ? + edge.value(step.weightBy.name()) : + step.defaultWeight; + newNode = new WeightNode(target, n, w); + } else { + newNode = new Node(target, n); + } + adjacency.add(newNode); + + // Avoid exceeding capacity + if (capacity != NO_LIMIT && + ++accessCount >= capacity) { + break root; + } + } + } + + if (step.sample > 0) { + // Sample current node's adjacent nodes + adjacency = sample(adjacency, step.sample); + } + + // Add current node's adjacent nodes + for (Node node : adjacency) { + newVertices.add(node.id(), node); + // Avoid exceeding limit + if (stepNum == 0) { + if (limit != NO_LIMIT && !sorted && + ++pathCount >= limit) { + break root; + } + } + } + } + // Re-init sources + sources = newVertices; + } + if (stepNum != 0) { + return ImmutableList.of(); + } + List paths = new ArrayList<>(); + for (List nodes : newVertices.values()) { + for (Node n : nodes) { + if (sorted) { + WeightNode wn = (WeightNode) n; + paths.add(new WeightPath(null, wn.path(), wn.weights())); + } else { + paths.add(new Path(null, n.path())); + } + } + } + return paths; + } + + public static List topNPath(List paths, + boolean incr, long limit) { + if (limit == HugeTraverser.NO_LIMIT || paths.size() <= limit) { + return paths; + } + + paths.sort(((p1, p2) -> { + CustomizePathsTraverser.WeightPath wp1 = (CustomizePathsTraverser.WeightPath) p1; + CustomizePathsTraverser.WeightPath wp2 = (CustomizePathsTraverser.WeightPath) p2; + int result; + if (wp1.totalWeight() > wp2.totalWeight()) { + result = 1; + } else if (wp1.totalWeight() < wp2.totalWeight()) { + result = -1; + } else { + result = 0; + } + if (incr) { + return result; + } else { + return -result; + } + })); + + return paths.subList(0, (int) limit); + } + + private static List sample(List nodes, long sample) { + if (nodes.size() <= sample) { + return nodes; + } + List result = new ArrayList<>((int) sample); + int size = nodes.size(); + for (int random : CollectionUtil.randomSet(0, size, (int) sample)) { + result.add(nodes.get(random)); + } + return result; + } + + public static class WeightNode extends Node { + + private double weight; + + public WeightNode(Id id, Node parent, double weight) { + super(id, parent); + this.weight = weight; + } + + public List weights() { + List weights = new ArrayList<>(); + WeightNode current = this; + while (current.parent() != null) { + weights.add(current.weight); + current = (WeightNode) current.parent(); + } + Collections.reverse(weights); + return weights; + } + } + + public static class WeightPath extends Path { + + private List weights; + private double totalWeight; + + public WeightPath(Id crosspoint, List vertices, + List weights) { + super(crosspoint, vertices); + this.weights = weights; + this.calcTotalWeight(); + } + + public List weights() { + return this.weights; + } + + public double totalWeight() { + return this.totalWeight; + } + + @Override + public void reverse() { + super.reverse(); + Collections.reverse(this.weights); + } + + @Override + public Map toMap(boolean withCrossPoint) { + if (withCrossPoint) { + return ImmutableMap.of("crosspoint", this.crosspoint(), + "objects", this.vertices(), + "weights", this.weights()); + } else { + return ImmutableMap.of("objects", this.vertices(), + "weights", this.weights()); + } + } + + private void calcTotalWeight() { + double sum = 0; + for (double w : this.weights()) { + sum += w; + } + this.totalWeight = sum; + } + } + + public static class Step { + + private Directions direction; + private Map labels; + private Map properties; + private PropertyKey weightBy; + private double defaultWeight; + private long degree; + private long sample; + + public Step(Directions direction, Map labels, + Map properties, PropertyKey weightBy, + double defaultWeight, long degree, long sample) { + this.direction = direction; + this.labels = labels; + this.properties = properties; + this.weightBy = weightBy; + this.defaultWeight = defaultWeight; + this.degree = degree; + this.sample = sample; + } + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/CustomizedCrosspointsTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/CustomizedCrosspointsTraverser.java new file mode 100644 index 0000000000..8be26ab59b --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/CustomizedCrosspointsTraverser.java @@ -0,0 +1,227 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.traversal.algorithm; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.ws.rs.core.MultivaluedMap; + +import org.apache.tinkerpop.gremlin.structure.Edge; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.structure.HugeEdge; +import com.baidu.hugegraph.structure.HugeVertex; +import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.CollectionUtil; +import com.baidu.hugegraph.util.E; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +public class CustomizedCrosspointsTraverser extends HugeTraverser { + + public CustomizedCrosspointsTraverser(HugeGraph graph) { + super(graph); + } + + public CrosspointsPaths crosspointsPaths(List vertices, + List pathPatterns, + long capacity, long limit) { + E.checkArgument(!vertices.isEmpty(), + "The source vertices can't be empty"); + E.checkArgument(!pathPatterns.isEmpty(), + "The steps pattern can't be empty"); + checkCapacity(capacity); + checkLimit(limit); + MultivaluedMap sources = newMultivalueMap(); + for (HugeVertex vertex : vertices) { + Node node = new Node(vertex.id(), null); + sources.add(vertex.id(), node); + } + List paths = new ArrayList<>(); + + for (PathPattern pathPattern : pathPatterns) { + int stepNum = pathPattern.size(); + long accessCount = 0; + MultivaluedMap newVertices = null; + root: for (Step step : pathPattern.steps()) { + stepNum--; + newVertices = newMultivalueMap(); + Iterator edges; + + // Traversal vertices of previous level + for (Map.Entry> entry : sources.entrySet()) { + List adjacency = new ArrayList<>(); + edges = edgesOfVertex(entry.getKey(), step.direction, + step.labels, step.properties, + step.degree); + while (edges.hasNext()) { + HugeEdge edge = (HugeEdge) edges.next(); + Id target = edge.id().otherVertexId(); + for (Node n : entry.getValue()) { + // If have loop, skip target + if (n.contains(target)) { + continue; + } + Node newNode = new Node(target, n); + adjacency.add(newNode); + + // Avoid exceeding capacity + if (capacity != NO_LIMIT && ++accessCount >= capacity) { + break root; + } + } + } + + // Add current node's adjacent nodes + for (Node node : adjacency) { + newVertices.add(node.id(), node); + } + } + // Re-init sources + sources = newVertices; + } + if (stepNum != 0) { + return CrosspointsPaths.EMPTY; + } + for (List nodes : newVertices.values()) { + for (Node n : nodes) { + paths.add(new Path(null, n.path())); + } + } + } + return insectionPaths(vertices, paths, limit); + } + + private static CrosspointsPaths insectionPaths(List sources, + List paths, + long limit) { + // Split paths by end vertices + MultivaluedMap endVertices = newMultivalueMap(); + for (Path path : paths) { + List vertices = path.vertices(); + int length = vertices.size(); + endVertices.add(vertices.get(0), vertices.get(length - 1)); + } + + Set sourceIds = sources.stream().map(HugeVertex::id) + .collect(Collectors.toSet()); + Set ids = endVertices.keySet(); + if (sourceIds.size() != ids.size() || !sourceIds.containsAll(ids)) { + return CrosspointsPaths.EMPTY; + } + + // Get intersection of end vertices + Collection intersection = null; + for (List ends : endVertices.values()) { + if (intersection == null) { + intersection = ends; + } else { + intersection = CollectionUtil.intersect(intersection, ends); + } + if (intersection == null || intersection.isEmpty()) { + return CrosspointsPaths.EMPTY; + } + } + assert intersection != null; + int size = intersection.size(); + if (limit != NO_LIMIT && size > limit) { + intersection = new ArrayList<>(intersection).subList(0, size - 1); + } + + // Filter intersection paths + List results = new ArrayList<>(); + for (Path path : paths) { + List vertices = path.vertices(); + int length = vertices.size(); + if (intersection.contains(vertices.get(length - 1))) { + results.add(path); + } + } + return new CrosspointsPaths(new HashSet<>(intersection), results); + } + + public static class PathPattern { + + private List steps; + + public PathPattern() { + this.steps = new ArrayList<>(); + } + + public List steps() { + return this.steps; + } + + public int size() { + return this.steps.size(); + } + + public void add(Step step) { + this.steps.add(step); + } + } + + public static class Step { + + private Directions direction; + private Map labels; + private Map properties; + private long degree; + + public Step(Directions direction, Map labels, + Map properties, long degree) { + this.direction = direction; + this.labels = labels; + this.properties = properties; + this.degree = degree; + } + } + + public static class CrosspointsPaths { + + private static final CrosspointsPaths EMPTY = new CrosspointsPaths( + ImmutableSet.of(), ImmutableList.of() + ); + + private Set crosspoints; + private List paths; + + public CrosspointsPaths(Set crosspoints, List paths) { + this.crosspoints = crosspoints; + this.paths = paths; + } + + public Set crosspoints() { + return this.crosspoints; + } + + public List paths() { + return this.paths; + } + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java new file mode 100644 index 0000000000..cccf5b051f --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java @@ -0,0 +1,414 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.traversal.algorithm; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Vertex; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.backend.query.Query; +import com.baidu.hugegraph.backend.tx.GraphTransaction; +import com.baidu.hugegraph.iterator.ExtendableIterator; +import com.baidu.hugegraph.rest.ClientException; +import com.baidu.hugegraph.schema.SchemaLabel; +import com.baidu.hugegraph.structure.HugeEdge; +import com.baidu.hugegraph.type.HugeType; +import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.E; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +public class HugeTraverser { + + private HugeGraph graph; + + public static final List PATH_NONE = ImmutableList.of(); + + public static final String DEFAULT_CAPACITY = "10000000"; + public static final String DEFAULT_ELEMENTS_LIMIT = "10000000"; + public static final String DEFAULT_PATHS_LIMIT = "10"; + public static final String DEFAULT_DEGREE = "10000"; + public static final String DEFAULT_SAMPLE = "100"; + public static final String DEFAULT_WEIGHT = "0"; + + public static final long NO_LIMIT = -1L; + + public HugeTraverser(HugeGraph graph) { + this.graph = graph; + } + + public HugeGraph graph() { + return this.graph; + } + + public Set kout(Id sourceV, Directions dir, String label, + int depth, boolean nearest, + long degree, long capacity, long limit) { + E.checkNotNull(sourceV, "source vertex id"); + E.checkNotNull(dir, "direction"); + checkPositive(depth, "k-out depth"); + checkDegree(degree); + checkCapacity(capacity); + checkLimit(limit); + if (capacity != NO_LIMIT) { + // Capacity must > limit because sourceV is counted in capacity + E.checkArgument(capacity >= limit && limit != NO_LIMIT, + "Capacity can't be less than limit, " + + "but got capacity '%s' and limit '%s'", + capacity, limit); + } + + Id labelId = this.getEdgeLabelId(label); + + Set latest = newSet(); + latest.add(sourceV); + + Set all = newSet(); + all.add(sourceV); + + long remaining = capacity == NO_LIMIT ? + NO_LIMIT : capacity - latest.size(); + while (depth-- > 0) { + // Just get limit nodes in last layer if limit < remaining capacity + if (depth == 0 && limit != NO_LIMIT && + (limit < remaining || remaining == NO_LIMIT)) { + remaining = limit; + } + if (nearest) { + latest = this.adjacentVertices(latest, dir, labelId, all, + degree, remaining); + all.addAll(latest); + } else { + latest = this.adjacentVertices(latest, dir, labelId, null, + degree, remaining); + } + if (capacity != NO_LIMIT) { + // Update 'remaining' value to record remaining capacity + remaining -= latest.size(); + + if (remaining <= 0 && depth > 0) { + throw new ClientException( + "Reach limit '%s' while remaining depth '%s'", + limit, depth); + } + } + } + + return latest; + } + + public Set kneighbor(Id sourceV, Directions dir, + String label, int depth, + long degree, long limit) { + E.checkNotNull(sourceV, "source vertex id"); + E.checkNotNull(dir, "direction"); + checkPositive(depth, "k-neighbor depth"); + checkDegree(degree); + checkLimit(limit); + + Id labelId = this.getEdgeLabelId(label); + + Set latest = newSet(); + latest.add(sourceV); + + Set all = newSet(); + all.add(sourceV); + + while (depth-- > 0) { + long remaining = limit == NO_LIMIT ? NO_LIMIT : limit - all.size(); + latest = this.adjacentVertices(latest, dir, labelId, all, + degree, remaining); + all.addAll(latest); + if (limit != NO_LIMIT && all.size() >= limit) { + break; + } + } + + return all; + } + + private Set adjacentVertices(Set vertices, Directions dir, + Id label, Set excluded, + long degree, long limit) { + if (limit == 0) { + return ImmutableSet.of(); + } + + Set neighbors = newSet(); + for (Id source : vertices) { + Iterator edges = this.edgesOfVertex(source, dir, + label, degree); + while (edges.hasNext()) { + HugeEdge e = (HugeEdge) edges.next(); + Id target = e.id().otherVertexId(); + if (excluded != null && excluded.contains(target)) { + continue; + } + neighbors.add(target); + if (limit != NO_LIMIT && neighbors.size() >= limit) { + return neighbors; + } + } + } + return neighbors; + } + + protected Iterator edgesOfVertex(Id source, Directions dir, + Id label, long limit) { + Id[] labels = {}; + if (label != null) { + labels = new Id[]{label}; + } + + Query query = GraphTransaction.constructEdgesQuery(source, dir, labels); + if (limit != NO_LIMIT) { + query.limit(limit); + } + return this.graph.edges(query); + } + + protected Iterator edgesOfVertex(Id source, Directions dir, + Set labels, long limit) { + if (labels == null || labels.isEmpty()) { + return this.edgesOfVertex(source, dir, (Id) null, limit); + } + ExtendableIterator results = new ExtendableIterator<>(); + for (Id label : labels) { + E.checkNotNull(label, "edge label"); + results.extend(this.edgesOfVertex(source, dir, label, limit)); + } + return results; + } + + protected Iterator edgesOfVertex(Id source, Directions dir, + Map labels, + Map properties, + long limit) { + if (properties == null || properties.isEmpty()) { + return edgesOfVertex(source, dir, labels.keySet(), limit); + } + // Use traversal format if has properties filter + String[] els = labels.values().toArray(new String[labels.size()]); + GraphTraversal g; + g = this.graph().traversal().V(source).toE(dir.direction(), els); + for (Map.Entry entry : properties.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (value instanceof List) { + g = g.has(key, P.within((Collection) value)); + } else { + g = g.has(key, value); + } + } + return g.limit(limit); + } + + protected Id getEdgeLabelId(Object label) { + if (label == null) { + return null; + } + return SchemaLabel.getLabelId(this.graph, HugeType.EDGE, label); + } + + protected static void checkPositive(int value, String name) { + E.checkArgument(value > 0, + "The %s parameter must be > 0, but got '%s'", + name, value); + } + + protected static void checkDegree(long degree) { + checkPositiveOrNoLimit(degree, "max degree"); + } + + protected static void checkCapacity(long capacity) { + checkPositiveOrNoLimit(capacity, "capacity"); + } + + protected static void checkLimit(long limit) { + checkPositiveOrNoLimit(limit, "limit"); + } + + protected static void checkPositiveOrNoLimit(long value, String name) { + E.checkArgument(value > 0 || value == NO_LIMIT, + "The %s parameter must be > 0 or == %s, but got: %s", + name, NO_LIMIT, value); + } + + protected static Set newSet() { + return new HashSet<>(); + } + + protected static Map newMap() { + return new HashMap<>(); + } + + protected static MultivaluedMap newMultivalueMap() { + return new MultivaluedHashMap<>(); + } + + public static class Node { + + private Id id; + private Node parent; + + public Node(Id id) { + this(id, null); + } + + public Node(Id id, Node parent) { + E.checkArgumentNotNull(id, "Id of Node can't be null"); + this.id = id; + this.parent = parent; + } + + public Id id() { + return this.id; + } + + @SuppressWarnings("unused") + public Node parent() { + return this.parent; + } + + public List path() { + List ids = new ArrayList<>(); + Node current = this; + do { + ids.add(current.id); + current = current.parent; + } while (current != null); + Collections.reverse(ids); + return ids; + } + + public List joinPath(Node back) { + // Get self path + List path = this.path(); + + // Get reversed other path + List backPath = back.path(); + Collections.reverse(backPath); + + // Avoid loop in path + if (CollectionUtils.containsAny(path, backPath)) { + return ImmutableList.of(); + } + + // Append other path behind self path + path.addAll(backPath); + return path; + } + + public boolean contains(Id id) { + Node node = this; + do { + if (node.id.equals(id)) { + return true; + } + node = node.parent; + } while (node != null); + return false; + } + + @Override + public int hashCode() { + return this.id.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof Node)) { + return false; + } + Node other = (Node) object; + return this.id.equals(other.id); + } + } + + public static class Path { + + private Id crosspoint; + private List vertices; + + public Path(Id crosspoint, List vertices) { + this.crosspoint = crosspoint; + this.vertices = vertices; + } + + public Id crosspoint() { + return this.crosspoint; + } + + public List vertices() { + return this.vertices; + } + + public void reverse() { + Collections.reverse(this.vertices); + } + + public Map toMap(boolean withCrossPoint) { + if (withCrossPoint) { + return ImmutableMap.of("crosspoint", this.crosspoint, + "objects", this.vertices); + } else { + return ImmutableMap.of("objects", this.vertices); + } + } + + @Override + public int hashCode() { + return this.vertices.hashCode(); + } + + /** + * Compares the specified object with this path for equality. + * Returns true if and only if both have same vertices list + * without regard of crosspoint. + * @param other the object to be compared for equality with this path + * @return true if the specified object is equal to this path + */ + @Override + public boolean equals(Object other) { + if (other == null || !(other instanceof Path)) { + return false; + } + return this.vertices.equals(((Path) other).vertices); + } + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/PathsTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/PathsTraverser.java new file mode 100644 index 0000000000..9da39b0809 --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/PathsTraverser.java @@ -0,0 +1,229 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.traversal.algorithm; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.core.MultivaluedMap; + +import org.apache.tinkerpop.gremlin.structure.Edge; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.structure.HugeEdge; +import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.E; +import com.google.common.collect.ImmutableList; + +public class PathsTraverser extends HugeTraverser { + + public PathsTraverser(HugeGraph graph) { + super(graph); + } + + public Set paths(Id sourceV, Directions sourceDir, + Id targetV, Directions targetDir, String label, + int depth, long degree, long capacity, long limit) { + E.checkNotNull(sourceV, "source vertex id"); + E.checkNotNull(targetV, "target vertex id"); + E.checkNotNull(sourceDir, "source direction"); + E.checkNotNull(targetDir, "target direction"); + E.checkArgument(sourceDir == targetDir || + sourceDir == targetDir.opposite(), + "Source direction must equal to target direction," + + "or opposite to target direction"); + checkPositive(depth, "max depth"); + checkDegree(degree); + checkCapacity(capacity); + checkLimit(limit); + + Set paths = new HashSet<>(); + if (sourceV.equals(targetV)) { + paths.add(new Path(sourceV, ImmutableList.of(sourceV))); + } + + Id labelId = this.getEdgeLabelId(label); + Traverser traverser = new Traverser(sourceV, targetV, labelId, + degree, capacity, limit); + while (true) { + if (--depth < 0 || traverser.reachLimit()) { + break; + } + List foundPaths = traverser.forward(sourceDir); + paths.addAll(foundPaths); + + if (--depth < 0 || traverser.reachLimit()) { + break; + } + foundPaths = traverser.backward(targetDir); + for (Path path : foundPaths) { + path.reverse(); + paths.add(path); + } + } + return paths; + } + + private class Traverser { + + private MultivaluedMap sources = newMultivalueMap(); + private MultivaluedMap targets = newMultivalueMap(); + private MultivaluedMap sourcesAll = newMultivalueMap(); + private MultivaluedMap targetsAll = newMultivalueMap(); + + private final Id label; + private final long degree; + private final long capacity; + private final long limit; + private long count; + + public Traverser(Id sourceV, Id targetV, Id label, + long degree, long capacity, long limit) { + this.sources.add(sourceV, new Node(sourceV)); + this.targets.add(targetV, new Node(targetV)); + this.sourcesAll.putAll(this.sources); + this.targetsAll.putAll(this.targets); + this.label = label; + this.degree = degree; + this.capacity = capacity; + this.limit = limit; + this.count = 0L; + } + + /** + * Search forward from source + */ + public List forward(Directions direction) { + List paths = new ArrayList<>(); + MultivaluedMap newVertices = newMultivalueMap(); + Iterator edges; + // Traversal vertices of previous level + for (Map.Entry> entry : this.sources.entrySet()) { + Id vid = entry.getKey(); + edges = edgesOfVertex(vid, direction, this.label, this.degree); + + while (edges.hasNext()) { + HugeEdge edge = (HugeEdge) edges.next(); + Id target = edge.id().otherVertexId(); + + for (Node n : entry.getValue()) { + // If have loop, skip target + if (n.contains(target)) { + continue; + } + + // If cross point exists, path found, concat them + if (this.targetsAll.containsKey(target)) { + for (Node node : this.targetsAll.get(target)) { + List path = n.joinPath(node); + if (!path.isEmpty()) { + paths.add(new Path(target, path)); + ++this.count; + if (this.reachLimit()) { + return paths; + } + } + } + } + + // Add node to next start-nodes + newVertices.add(target, new Node(target, n)); + } + } + } + // Re-init sources + this.sources = newVertices; + // Record all passed vertices + this.sourcesAll.putAll(newVertices); + + return paths; + } + + /** + * Search backward from target + */ + public List backward(Directions direction) { + List paths = new ArrayList<>(); + MultivaluedMap newVertices = newMultivalueMap(); + Iterator edges; + // Traversal vertices of previous level + for (Map.Entry> entry : this.targets.entrySet()) { + Id vid = entry.getKey(); + edges = edgesOfVertex(vid, direction, this.label, this.degree); + + while (edges.hasNext()) { + HugeEdge edge = (HugeEdge) edges.next(); + Id target = edge.id().otherVertexId(); + + for (Node n : entry.getValue()) { + // If have loop, skip target + if (n.contains(target)) { + continue; + } + + // If cross point exists, path found, concat them + if (this.sourcesAll.containsKey(target)) { + for (Node node : this.sourcesAll.get(target)) { + List path = n.joinPath(node); + if (!path.isEmpty()) { + paths.add(new Path(target, path)); + ++this.count; + if (this.reachLimit()) { + return paths; + } + } + } + } + + // Add node to next start-nodes + newVertices.add(target, new Node(target, n)); + } + } + } + + // Re-init targets + this.targets = newVertices; + // Record all passed vertices + this.targetsAll.putAll(newVertices); + + return paths; + } + + private int accessedNodes() { + return this.sourcesAll.size() + this.targetsAll.size(); + } + + private boolean reachLimit() { + if (this.capacity != NO_LIMIT && + this.accessedNodes() > this.capacity) { + return true; + } + if (this.limit == NO_LIMIT || this.count < this.limit) { + return false; + } + return true; + } + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/ShortestPathTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/ShortestPathTraverser.java new file mode 100644 index 0000000000..ca7c2e9676 --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/ShortestPathTraverser.java @@ -0,0 +1,185 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.traversal.algorithm; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.tinkerpop.gremlin.structure.Edge; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.structure.HugeEdge; +import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.E; +import com.google.common.collect.ImmutableList; + +public class ShortestPathTraverser extends HugeTraverser { + + public ShortestPathTraverser(HugeGraph graph) { + super(graph); + } + + public List shortestPath(Id sourceV, Id targetV, Directions dir, + String label, int depth, long degree, + long capacity) { + E.checkNotNull(sourceV, "source vertex id"); + E.checkNotNull(targetV, "target vertex id"); + E.checkNotNull(dir, "direction"); + checkPositive(depth, "max depth"); + checkDegree(degree); + checkCapacity(capacity); + + if (sourceV.equals(targetV)) { + return ImmutableList.of(sourceV); + } + + Id labelId = this.getEdgeLabelId(label); + Traverser traverser = new Traverser(sourceV, targetV, dir, labelId, + degree, capacity); + List path; + while (true) { + // Found, reach max depth or reach capacity, stop searching + if ((path = traverser.forward()) != PATH_NONE || + --depth <= 0 || traverser.reachCapacity()) { + break; + } + + if ((path = traverser.backward()) != PATH_NONE || + --depth <= 0 || traverser.reachCapacity()) { + Collections.reverse(path); + break; + } + } + return path; + } + + private class Traverser { + + // TODO: change Map to Set to reduce memory cost + private Map sources = newMap(); + private Map targets = newMap(); + + private final Directions direction; + private final Id label; + private final long degree; + private final long capacity; + private long size; + + public Traverser(Id sourceV, Id targetV, Directions dir, + Id label, long degree, long capacity) { + this.sources.put(sourceV, new Node(sourceV)); + this.targets.put(targetV, new Node(targetV)); + this.direction = dir; + this.label = label; + this.degree = degree; + this.capacity = capacity; + this.size = 0L; + } + + /** + * Search forward from source + */ + public List forward() { + Map newVertices = newMap(); + // Traversal vertices of previous level + for (Node v : this.sources.values()) { + Iterator edges = edgesOfVertex(v.id(), this.direction, + this.label, this.degree); + while (edges.hasNext()) { + HugeEdge edge = (HugeEdge) edges.next(); + Id target = edge.id().otherVertexId(); + + // If cross point exists, shortest path found, concat them + if (this.targets.containsKey(target)) { + return v.joinPath(this.targets.get(target)); + } + + /* + * Not found shortest path yet, node is added to + * newVertices if: + * 1. not in sources and newVertices yet + * 2. path of node doesn't have loop + */ + if (!newVertices.containsKey(target) && + !this.sources.containsKey(target) && + !v.contains(target)) { + newVertices.put(target, new Node(target, v)); + } + } + } + + // Re-init sources + this.sources = newVertices; + this.size += newVertices.size(); + + return PATH_NONE; + } + + /** + * Search backward from target + */ + public List backward() { + Map newVertices = newMap(); + Directions opposite = this.direction.opposite(); + // Traversal vertices of previous level + for (Node v : this.targets.values()) { + Iterator edges = edgesOfVertex(v.id(), opposite, + this.label, this.degree); + while (edges.hasNext()) { + HugeEdge edge = (HugeEdge) edges.next(); + Id target = edge.id().otherVertexId(); + + // If cross point exists, shortest path found, concat them + if (this.sources.containsKey(target)) { + return v.joinPath(this.sources.get(target)); + } + + /* + * Not found shortest path yet, node is added to + * newVertices if: + * 1. not in targets and newVertices yet + * 2. path of node doesn't have loop + */ + if (!newVertices.containsKey(target) && + !this.targets.containsKey(target) && + !v.contains(target)) { + newVertices.put(target, new Node(target, v)); + } + } + } + + // Re-init targets + this.targets = newVertices; + this.size += newVertices.size(); + + return PATH_NONE; + } + + private boolean reachCapacity() { + if (this.capacity == NO_LIMIT || this.size < this.capacity) { + return false; + } + return true; + } + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/SubGraphTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/SubGraphTraverser.java new file mode 100644 index 0000000000..33ab1ff2b0 --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/SubGraphTraverser.java @@ -0,0 +1,176 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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 com.baidu.hugegraph.traversal.algorithm; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.core.MultivaluedMap; + +import org.apache.tinkerpop.gremlin.structure.Edge; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.structure.HugeEdge; +import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.E; + +public class SubGraphTraverser extends HugeTraverser { + + public SubGraphTraverser(HugeGraph graph) { + super(graph); + } + + public List rays(Id sourceV, Directions dir, String label, + int depth, long degree, long capacity, long limit) { + return this.subGraphPaths(sourceV, dir, label, depth, degree, + capacity, limit, false); + } + + public List rings(Id sourceV, Directions dir, String label, + int depth, long degree, long capacity, long limit) { + return this.subGraphPaths(sourceV, dir, label, depth, degree, + capacity, limit, true); + } + + private List subGraphPaths(Id sourceV, Directions dir, String label, + int depth, long degree, long capacity, + long limit, boolean rings) { + E.checkNotNull(sourceV, "source vertex id"); + E.checkNotNull(dir, "direction"); + checkPositive(depth, "max depth"); + checkDegree(degree); + checkCapacity(capacity); + checkLimit(limit); + + Id labelId = this.getEdgeLabelId(label); + Traverser traverser = new Traverser(sourceV, labelId, degree, + capacity, limit, rings); + List paths = new ArrayList<>(); + while (true) { + paths.addAll(traverser.forward(dir)); + boolean reachDepth = rings ? --depth <= 0 : depth-- <= 0; + if (reachDepth || traverser.reachLimit() || + traverser.finished()) { + break; + } + } + return paths; + } + + private class Traverser { + + private MultivaluedMap sources = newMultivalueMap(); + private Set accessedVertices = newSet(); + + private final Id label; + private final long degree; + private final long capacity; + private final long limit; + private final boolean rings; + private long pathCount; + + public Traverser(Id sourceV, Id label, long degree, + long capacity, long limit, boolean rings) { + this.sources.add(sourceV, new Node(sourceV)); + this.accessedVertices.add(sourceV); + this.label = label; + this.degree = degree; + this.capacity = capacity; + this.limit = limit; + this.rings = rings; + this.pathCount = 0L; + } + + /** + * Search forward from source + */ + public List forward(Directions direction) { + List paths = new ArrayList<>(); + MultivaluedMap newVertices = newMultivalueMap(); + Iterator edges; + // Traversal vertices of previous level + for (Map.Entry> entry : this.sources.entrySet()) { + Id vid = entry.getKey(); + edges = edgesOfVertex(vid, direction, this.label, this.degree); + + if (!edges.hasNext()) { + // Reach the end, rays found + if (this.rings) { + continue; + } + for (Node n : entry.getValue()) { + // Store rays + paths.add(new Path(null, n.path())); + this.pathCount++; + if (reachLimit()) { + return paths; + } + } + } + while (edges.hasNext()) { + HugeEdge edge = (HugeEdge) edges.next(); + Id target = edge.id().otherVertexId(); + this.accessedVertices.add(target); + for (Node n : entry.getValue()) { + if (!n.contains(target)) { + // Add node to next start-nodes + newVertices.add(target, new Node(target, n)); + continue; + } + // Rings found and expect rings + if (this.rings) { + assert n.contains(target); + List prePath = n.path(); + prePath.add(target); + paths.add(new Path(null, prePath)); + this.pathCount++; + if (reachLimit()) { + return paths; + } + } + } + } + } + // Re-init sources + this.sources = newVertices; + + return paths; + } + + private boolean reachLimit() { + if (this.capacity != NO_LIMIT && + this.accessedVertices.size() > this.capacity) { + return true; + } + if (this.limit == NO_LIMIT || this.pathCount < this.limit) { + return false; + } + return true; + } + + private boolean finished() { + return this.sources.isEmpty(); + } + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/optimize/HugeTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/optimize/HugeTraverser.java deleted file mode 100644 index 0d2f26db28..0000000000 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/optimize/HugeTraverser.java +++ /dev/null @@ -1,820 +0,0 @@ -/* - * Copyright 2017 HugeGraph Authors - * - * 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 com.baidu.hugegraph.traversal.optimize; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.ws.rs.core.MultivaluedHashMap; -import javax.ws.rs.core.MultivaluedMap; - -import org.apache.commons.collections.CollectionUtils; -import org.apache.tinkerpop.gremlin.structure.Edge; - -import com.baidu.hugegraph.HugeGraph; -import com.baidu.hugegraph.backend.id.Id; -import com.baidu.hugegraph.backend.query.Query; -import com.baidu.hugegraph.backend.tx.GraphTransaction; -import com.baidu.hugegraph.rest.ClientException; -import com.baidu.hugegraph.schema.SchemaLabel; -import com.baidu.hugegraph.structure.HugeEdge; -import com.baidu.hugegraph.type.HugeType; -import com.baidu.hugegraph.type.define.Directions; -import com.baidu.hugegraph.util.E; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; - -public class HugeTraverser { - - private HugeGraph graph; - - public static final List PATH_NONE = ImmutableList.of(); - public static final long NO_LIMIT = -1L; - - public HugeTraverser(HugeGraph graph) { - this.graph = graph; - } - - public List shortestPath(Id sourceV, Id targetV, Directions dir, - String label, int depth, long degree, - long capacity) { - E.checkNotNull(sourceV, "source vertex id"); - E.checkNotNull(targetV, "target vertex id"); - E.checkNotNull(dir, "direction"); - checkPositive(depth, "max depth"); - checkDegree(degree); - checkCapacity(capacity); - - if (sourceV.equals(targetV)) { - return ImmutableList.of(sourceV); - } - - Id labelId = this.getEdgeLabelId(label); - ShortestPathTraverser traverser = new ShortestPathTraverser( - sourceV, targetV, dir, labelId, - degree, capacity); - List path; - while (true) { - // Found, reach max depth or reach capacity, stop searching - if ((path = traverser.forward()) != PATH_NONE || - --depth <= 0 || traverser.reachCapacity()) { - break; - } - - if ((path = traverser.backward()) != PATH_NONE || - --depth <= 0 || traverser.reachCapacity()) { - Collections.reverse(path); - break; - } - } - return path; - } - - public Set paths(Id sourceV, Directions sourceDir, - Id targetV, Directions targetDir, String label, - int depth, long degree, long capacity, long limit) { - E.checkNotNull(sourceV, "source vertex id"); - E.checkNotNull(targetV, "target vertex id"); - E.checkNotNull(sourceDir, "source direction"); - E.checkNotNull(targetDir, "target direction"); - E.checkArgument(sourceDir == targetDir || - sourceDir == targetDir.opposite(), - "Source direction must equal to target direction," + - "or opposite to target direction"); - checkPositive(depth, "max depth"); - checkDegree(degree); - checkCapacity(capacity); - checkLimit(limit); - - Set paths = new HashSet<>(); - if (sourceV.equals(targetV)) { - paths.add(new Path(sourceV, ImmutableList.of(sourceV))); - } - - Id labelId = this.getEdgeLabelId(label); - PathsTraverser traverser = new PathsTraverser(sourceV, targetV, labelId, - degree, capacity, limit); - while (true) { - if (--depth < 0 || traverser.reachLimit()) { - break; - } - List foundPaths = traverser.forward(sourceDir); - paths.addAll(foundPaths); - - if (--depth < 0 || traverser.reachLimit()) { - break; - } - foundPaths = traverser.backward(targetDir); - for (Path path : foundPaths) { - path.reverse(); - paths.add(path); - } - } - return paths; - } - - public List rays(Id sourceV, Directions dir, String label, - int depth, long degree, long capacity, long limit) { - return this.subGraphPaths(sourceV, dir, label, depth, degree, - capacity, limit, false); - } - - public List rings(Id sourceV, Directions dir, String label, - int depth, long degree, long capacity, long limit) { - return this.subGraphPaths(sourceV, dir, label, depth, degree, - capacity, limit, true); - } - - private List subGraphPaths(Id sourceV, Directions dir, String label, - int depth, long degree, long capacity, - long limit, boolean rings) { - E.checkNotNull(sourceV, "source vertex id"); - E.checkNotNull(dir, "direction"); - checkPositive(depth, "max depth"); - checkDegree(degree); - checkCapacity(capacity); - checkLimit(limit); - - Id labelId = this.getEdgeLabelId(label); - SubGraphTraverser traverser = new SubGraphTraverser(sourceV, labelId, - degree, capacity, - limit, rings); - List paths = new ArrayList<>(); - while (true) { - paths.addAll(traverser.forward(dir)); - if (--depth < 0 || traverser.reachLimit() || - traverser.finished()) { - break; - } - } - return paths; - } - - public Set kout(Id sourceV, Directions dir, String label, - int depth, boolean nearest, - long degree, long capacity, long limit) { - E.checkNotNull(sourceV, "source vertex id"); - E.checkNotNull(dir, "direction"); - checkPositive(depth, "k-out depth"); - checkDegree(degree); - checkCapacity(capacity); - checkLimit(limit); - if (capacity != NO_LIMIT) { - // Capacity must > limit because sourceV is counted in capacity - E.checkArgument(capacity > limit && limit != NO_LIMIT, - "Capacity must be greater than limit, " + - "but got capacity '%s' and limit '%s'", - capacity, limit); - } - - Id labelId = this.getEdgeLabelId(label); - - Set latest = newSet(); - latest.add(sourceV); - - Set all = newSet(); - all.add(sourceV); - - long remaining = capacity == NO_LIMIT ? - NO_LIMIT : capacity - latest.size(); - while (depth-- > 0) { - // Just get limit nodes in last layer if limit < remaining capacity - if (depth == 0 && limit != NO_LIMIT && - (limit < remaining || remaining == NO_LIMIT)) { - remaining = limit; - } - if (nearest) { - latest = this.adjacentVertices(latest, dir, labelId, all, - degree, remaining); - all.addAll(latest); - } else { - latest = this.adjacentVertices(latest, dir, labelId, null, - degree, remaining); - } - if (capacity != NO_LIMIT) { - // Update 'remaining' value to record remaining capacity - remaining -= latest.size(); - - if (remaining <= 0 && depth > 0) { - throw new ClientException( - "Reach limit '%s' while remaining depth '%s'", - limit, depth); - } - } - } - - return latest; - } - - public Set kneighbor(Id sourceV, Directions dir, - String label, int depth, - long degree, long limit) { - E.checkNotNull(sourceV, "source vertex id"); - E.checkNotNull(dir, "direction"); - checkPositive(depth, "k-neighbor depth"); - checkDegree(degree); - checkLimit(limit); - - Id labelId = this.getEdgeLabelId(label); - - Set latest = newSet(); - latest.add(sourceV); - - Set all = newSet(); - all.add(sourceV); - - while (depth-- > 0) { - long remaining = limit == NO_LIMIT ? NO_LIMIT : limit - all.size(); - latest = this.adjacentVertices(latest, dir, labelId, all, - degree, remaining); - all.addAll(latest); - if (limit != NO_LIMIT && all.size() >= limit) { - break; - } - } - - return all; - } - - private Set adjacentVertices(Set vertices, Directions dir, - Id label, Set excluded, - long degree, long limit) { - if (limit == 0) { - return ImmutableSet.of(); - } - - Set neighbors = newSet(); - for (Id source : vertices) { - Iterator edges = this.edgesOfVertex(source, dir, - label, degree); - while (edges.hasNext()) { - HugeEdge e = (HugeEdge) edges.next(); - Id target = e.id().otherVertexId(); - if (excluded != null && excluded.contains(target)) { - continue; - } - neighbors.add(target); - if (limit != NO_LIMIT && neighbors.size() >= limit) { - return neighbors; - } - } - } - return neighbors; - } - - private Iterator edgesOfVertex(Id source, Directions dir, - Id label, long limit) { - Id[] labels = {}; - if (label != null) { - labels = new Id[]{label}; - } - - Query query = GraphTransaction.constructEdgesQuery(source, dir, labels); - if (limit != NO_LIMIT) { - query.limit(limit); - } - return this.graph.edges(query); - } - - private Id getEdgeLabelId(Object label) { - if (label == null) { - return null; - } - return SchemaLabel.getLabelId(this.graph, HugeType.EDGE, label); - } - - private static void checkPositive(int value, String name) { - E.checkArgument(value > 0, - "The %s parameter must be > 0, but got '%s'", - name, value); - } - - private static void checkDegree(long degree) { - checkPositiveOrNoLimit(degree, "max degree"); - } - - private static void checkCapacity(long capacity) { - checkPositiveOrNoLimit(capacity, "capacity"); - } - - private static void checkLimit(long limit) { - checkPositiveOrNoLimit(limit, "limit"); - } - - private static void checkPositiveOrNoLimit(long value, String name) { - E.checkArgument(value > 0 || value == NO_LIMIT, - "The %s parameter must be > 0 or == %s, but got: %s", - name, NO_LIMIT, value); - } - - private static Set newSet() { - return new HashSet<>(); - } - - private static Map newMap() { - return new HashMap<>(); - } - - private static MultivaluedMap newMultivalueMap() { - return new MultivaluedHashMap<>(); - } - - private class ShortestPathTraverser { - - // TODO: change Map to Set to reduce memory cost - private Map sources = newMap(); - private Map targets = newMap(); - - private final Directions direction; - private final Id label; - private final long degree; - private final long capacity; - private long size; - - public ShortestPathTraverser(Id sourceV, Id targetV, Directions dir, - Id label, long degree, long capacity) { - this.sources.put(sourceV, new Node(sourceV)); - this.targets.put(targetV, new Node(targetV)); - this.direction = dir; - this.label = label; - this.degree = degree; - this.capacity = capacity; - this.size = 0L; - } - - /** - * Search forward from source - */ - public List forward() { - Map newVertices = newMap(); - // Traversal vertices of previous level - for (Node v : this.sources.values()) { - Iterator edges = edgesOfVertex(v.id(), this.direction, - this.label, this.degree); - while (edges.hasNext()) { - HugeEdge edge = (HugeEdge) edges.next(); - Id target = edge.id().otherVertexId(); - - // If cross point exists, shortest path found, concat them - if (this.targets.containsKey(target)) { - return v.joinPath(this.targets.get(target)); - } - - /* - * Not found shortest path yet, node is added to - * newVertices if: - * 1. not in sources and newVertices yet - * 2. path of node doesn't have loop - */ - if (!newVertices.containsKey(target) && - !this.sources.containsKey(target) && - !v.contains(target)) { - newVertices.put(target, new Node(target, v)); - } - } - } - - // Re-init sources - this.sources = newVertices; - this.size += newVertices.size(); - - return PATH_NONE; - } - - /** - * Search backward from target - */ - public List backward() { - Map newVertices = newMap(); - Directions opposite = this.direction.opposite(); - // Traversal vertices of previous level - for (Node v : this.targets.values()) { - Iterator edges = edgesOfVertex(v.id(), opposite, - this.label, this.degree); - while (edges.hasNext()) { - HugeEdge edge = (HugeEdge) edges.next(); - Id target = edge.id().otherVertexId(); - - // If cross point exists, shortest path found, concat them - if (this.sources.containsKey(target)) { - return v.joinPath(this.sources.get(target)); - } - - /* - * Not found shortest path yet, node is added to - * newVertices if: - * 1. not in targets and newVertices yet - * 2. path of node doesn't have loop - */ - if (!newVertices.containsKey(target) && - !this.targets.containsKey(target) && - !v.contains(target)) { - newVertices.put(target, new Node(target, v)); - } - } - } - - // Re-init targets - this.targets = newVertices; - this.size += newVertices.size(); - - return PATH_NONE; - } - - private boolean reachCapacity() { - if (this.capacity == NO_LIMIT || this.size < this.capacity) { - return false; - } - return true; - } - } - - private class PathsTraverser { - - private MultivaluedMap sources = newMultivalueMap(); - private MultivaluedMap targets = newMultivalueMap(); - private MultivaluedMap sourcesAll = newMultivalueMap(); - private MultivaluedMap targetsAll = newMultivalueMap(); - - private final Id label; - private final long degree; - private final long capacity; - private final long limit; - private long count; - - public PathsTraverser(Id sourceV, Id targetV, Id label, - long degree, long capacity, long limit) { - this.sources.add(sourceV, new Node(sourceV)); - this.targets.add(targetV, new Node(targetV)); - this.sourcesAll.putAll(this.sources); - this.targetsAll.putAll(this.targets); - this.label = label; - this.degree = degree; - this.capacity = capacity; - this.limit = limit; - this.count = 0L; - } - - /** - * Search forward from source - */ - public List forward(Directions direction) { - List paths = new ArrayList<>(); - MultivaluedMap newVertices = newMultivalueMap(); - Iterator edges; - // Traversal vertices of previous level - for (List nodes : this.sources.values()) { - for (Node n : nodes) { - edges = edgesOfVertex(n.id(), direction, - this.label, this.degree); - while (edges.hasNext()) { - HugeEdge edge = (HugeEdge) edges.next(); - Id target = edge.id().otherVertexId(); - - // If have loop, skip target - if (n.contains(target)) { - continue; - } - - // If cross point exists, path found, concat them - if (this.targetsAll.containsKey(target)) { - for (Node node : this.targetsAll.get(target)) { - List path = n.joinPath(node); - if (!path.isEmpty()) { - paths.add(new Path(target, path)); - ++this.count; - if (this.reachLimit()) { - return paths; - } - } - } - } - - // Add node to next start-nodes - newVertices.add(target, new Node(target, n)); - } - } - } - // Re-init sources - this.sources = newVertices; - // Record all passed vertices - this.sourcesAll.putAll(newVertices); - - return paths; - } - - /** - * Search backward from target - */ - public List backward(Directions direction) { - List paths = new ArrayList<>(); - MultivaluedMap newVertices = newMultivalueMap(); - Iterator edges; - // Traversal vertices of previous level - for (List nodes : this.targets.values()) { - for (Node n : nodes) { - edges = edgesOfVertex(n.id(), direction, - this.label, this.degree); - while (edges.hasNext()) { - HugeEdge edge = (HugeEdge) edges.next(); - Id target = edge.id().otherVertexId(); - - // If have loop, skip target - if (n.contains(target)) { - continue; - } - - // If cross point exists, path found, concat them - if (this.sourcesAll.containsKey(target)) { - for (Node node : this.sourcesAll.get(target)) { - List path = n.joinPath(node); - if (!path.isEmpty()) { - paths.add(new Path(target, path)); - ++this.count; - if (this.reachLimit()) { - return paths; - } - } - } - } - - // Add node to next start-nodes - newVertices.add(target, new Node(target, n)); - } - } - } - - // Re-init targets - this.targets = newVertices; - // Record all passed vertices - this.targetsAll.putAll(newVertices); - - return paths; - } - - private int accessedNodes() { - return this.sourcesAll.size() + this.targetsAll.size(); - } - - private boolean reachLimit() { - if (this.capacity != NO_LIMIT && - this.accessedNodes() > this.capacity) { - return true; - } - if (this.limit == NO_LIMIT || this.count < this.limit) { - return false; - } - return true; - } - } - - private class SubGraphTraverser { - - private MultivaluedMap sources = newMultivalueMap(); - private Set accessedVertices = newSet(); - - private final Id label; - private final long degree; - private final long capacity; - private final long limit; - private final boolean rings; - private long pathCount; - - public SubGraphTraverser(Id sourceV, Id label, long degree, - long capacity, long limit, boolean rings) { - this.sources.add(sourceV, new Node(sourceV)); - this.accessedVertices.add(sourceV); - this.label = label; - this.degree = degree; - this.capacity = capacity; - this.limit = limit; - this.rings = rings; - this.pathCount = 0L; - } - - /** - * Search forward from source - */ - public List forward(Directions direction) { - List paths = new ArrayList<>(); - MultivaluedMap newVertices = newMultivalueMap(); - Iterator edges; - // Traversal vertices of previous level - for (List nodes : this.sources.values()) { - for (Node n : nodes) { - edges = edgesOfVertex(n.id(), direction, - this.label, this.degree); - if (!edges.hasNext()) { - // Reach the end, rays found - if (this.rings) { - continue; - } - // Store rays - paths.add(new Path(null, n.path())); - this.pathCount++; - if (reachLimit()) { - return paths; - } - continue; - } - while (edges.hasNext()) { - HugeEdge edge = (HugeEdge) edges.next(); - Id target = edge.id().otherVertexId(); - this.accessedVertices.add(target); - - if (!n.contains(target)) { - // Add node to next start-nodes - newVertices.add(target, new Node(target, n)); - continue; - } - // Rings found and expect rings - if (this.rings) { - assert n.contains(target); - List prePath = n.path(); - prePath.add(target); - paths.add(new Path(null, prePath)); - this.pathCount++; - if (reachLimit()) { - return paths; - } - } - } - } - } - // Re-init sources - this.sources = newVertices; - - return paths; - } - - private boolean reachLimit() { - if (this.capacity != NO_LIMIT && - this.accessedVertices.size() > this.capacity) { - return true; - } - if (this.limit == NO_LIMIT || this.pathCount < this.limit) { - return false; - } - return true; - } - - private boolean finished() { - return this.sources.isEmpty(); - } - } - - private static class Node { - - private Id id; - private Node parent; - - public Node(Id id) { - this(id, null); - } - - public Node(Id id, Node parent) { - E.checkArgumentNotNull(id, "Id of Node can't be null"); - this.id = id; - this.parent = parent; - } - - public Id id() { - return this.id; - } - - @SuppressWarnings("unused") - public Node parent() { - return this.parent; - } - - public List path() { - List ids = new ArrayList<>(); - Node current = this; - do { - ids.add(current.id); - current = current.parent; - } while (current != null); - Collections.reverse(ids); - return ids; - } - - public List joinPath(Node back) { - // Get self path - List path = this.path(); - - // Get reversed other path - List backPath = back.path(); - Collections.reverse(backPath); - - // Avoid loop in path - if (CollectionUtils.containsAny(path, backPath)) { - return ImmutableList.of(); - } - - // Append other path behind self path - path.addAll(backPath); - return path; - } - - public boolean contains(Id id) { - Node node = this; - do { - if (node.id.equals(id)) { - return true; - } - node = node.parent; - } while (node != null); - return false; - } - - @Override - public int hashCode() { - return this.id.hashCode(); - } - - @Override - public boolean equals(Object object) { - if (!(object instanceof Node)) { - return false; - } - Node other = (Node) object; - return this.id.equals(other.id); - } - } - - public static class Path { - - private Id crosspoint; - private List vertices; - - public Path(Id crosspoint, List vertices) { - this.crosspoint = crosspoint; - this.vertices = vertices; - } - - public Id crosspoint() { - return this.crosspoint; - } - - public List vertices() { - return this.vertices; - } - - public void reverse() { - Collections.reverse(this.vertices); - } - - public Map toMap(boolean withCrossPoint) { - if (withCrossPoint) { - return ImmutableMap.of("crosspoint", this.crosspoint, - "objects", this.vertices); - } else { - return ImmutableMap.of("objects", this.vertices); - } - } - - @Override - public int hashCode() { - return this.vertices.hashCode(); - } - - /** - * Compares the specified object with this path for equality. - * Returns true if and only if both have same vertices list - * without regard of crosspoint. - * @param other the object to be compared for equality with this path - * @return true if the specified object is equal to this path - */ - @Override - public boolean equals(Object other) { - if (other == null || !(other instanceof Path)) { - return false; - } - return this.vertices.equals(((Path) other).vertices); - } - } -} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/type/define/Directions.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/type/define/Directions.java index fcef4dc555..9e685e25e9 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/type/define/Directions.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/type/define/Directions.java @@ -56,6 +56,20 @@ public Directions opposite() { } } + public Direction direction() { + switch (this) { + case OUT: + return Direction.OUT; + case IN: + return Direction.IN; + case BOTH: + return Direction.BOTH; + default: + throw new AssertionError(String.format( + "Unrecognized direction: '%s'", this)); + } + } + public static Directions convert(Direction direction) { switch (direction) { case OUT: