/*
*
* * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com)
* *
* * Licensed 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.
* *
* * For more information: http://www.orientechnologies.com
*
*/
package com.orientechnologies.orient.graph.sql.functions;
import com.orientechnologies.common.collection.OMultiValue;
import com.orientechnologies.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.command.OCommandExecutorAbstract;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.OSQLHelper;
import com.orientechnologies.orient.core.sql.functions.math.OSQLFunctionMathAbstract;
import com.orientechnologies.orient.graph.sql.OGraphCommandExecutorSQLFactory;
import com.tinkerpop.blueprints.Direction;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.blueprints.impls.orient.OrientBaseGraph;
import com.tinkerpop.blueprints.impls.orient.OrientVertex;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Shortest path algorithm to find the shortest path from one node to another node in a directed graph.
*
* @author Luca Garulli (l.garulli--at--orientechnologies.com)
*/
public class OSQLFunctionShortestPath extends OSQLFunctionMathAbstract {
public static final String NAME = "shortestPath";
public static final String PARAM_MAX_DEPTH = "maxDepth";
protected static final float DISTANCE = 1f;
public OSQLFunctionShortestPath() {
super(NAME, 2, 5);
}
private class OShortestPathContext {
OrientVertex sourceVertex;
OrientVertex destinationVertex;
Direction directionLeft = Direction.BOTH;
Direction directionRight = Direction.BOTH;
String edgeType;
String[] edgeTypeParam;
ArrayDeque<OrientVertex> queueLeft = new ArrayDeque<OrientVertex>();
ArrayDeque<OrientVertex> queueRight = new ArrayDeque<OrientVertex>();
final Set<ORID> leftVisited = new HashSet<ORID>();
final Set<ORID> rightVisited = new HashSet<ORID>();
final Map<ORID, ORID> previouses = new HashMap<ORID, ORID>();
final Map<ORID, ORID> nexts = new HashMap<ORID, ORID>();
OrientVertex current;
OrientVertex currentRight;
public Integer maxDepth;
}
public List<ORID> execute(Object iThis, final OIdentifiable iCurrentRecord, final Object iCurrentResult, final Object[] iParams,
final OCommandContext iContext) {
return OGraphCommandExecutorSQLFactory.runWithAnyGraph(new OGraphCommandExecutorSQLFactory.GraphCallBack<List<ORID>>() {
@Override
public List<ORID> call(final OrientBaseGraph graph) {
final ORecord record = iCurrentRecord != null ? iCurrentRecord.getRecord() : null;
final OShortestPathContext ctx = new OShortestPathContext();
Object source = iParams[0];
if (OMultiValue.isMultiValue(source)) {
if (OMultiValue.getSize(source) > 1)
throw new IllegalArgumentException("Only one sourceVertex is allowed");
source = OMultiValue.getFirstValue(source);
}
ctx.sourceVertex = graph.getVertex(OSQLHelper.getValue(source, record, iContext));
Object dest = iParams[1];
if (OMultiValue.isMultiValue(dest)) {
if (OMultiValue.getSize(dest) > 1)
throw new IllegalArgumentException("Only one destinationVertex is allowed");
dest = OMultiValue.getFirstValue(dest);
}
ctx.destinationVertex = graph.getVertex(OSQLHelper.getValue(dest, record, iContext));
if (ctx.sourceVertex.equals(ctx.destinationVertex)) {
final List<ORID> result = new ArrayList<ORID>(1);
result.add(ctx.destinationVertex.getIdentity());
return result;
}
if (iParams.length > 2 && iParams[2] != null) {
ctx.directionLeft = Direction.valueOf(iParams[2].toString().toUpperCase());
}
if (ctx.directionLeft == Direction.OUT) {
ctx.directionRight = Direction.IN;
} else if (ctx.directionLeft == Direction.IN) {
ctx.directionRight = Direction.OUT;
}
ctx.edgeType = null;
if (iParams.length > 3) {
ctx.edgeType = iParams[3] == null ? null : "" + iParams[3];
}
ctx.edgeTypeParam = new String[] { ctx.edgeType };
if (iParams.length > 4) {
bindAdditionalParams(iParams[4], ctx);
}
ctx.queueLeft.add(ctx.sourceVertex);
ctx.leftVisited.add(ctx.sourceVertex.getIdentity());
ctx.queueRight.add(ctx.destinationVertex);
ctx.rightVisited.add(ctx.destinationVertex.getIdentity());
int depth = 1;
while (true) {
if (ctx.maxDepth != null && ctx.maxDepth <= depth) {
break;
}
if (ctx.queueLeft.isEmpty() || ctx.queueRight.isEmpty())
break;
if (Thread.interrupted())
throw new OCommandExecutionException("The shortestPath() function has been interrupted");
if (!OCommandExecutorAbstract.checkInterruption(iContext))
break;
List<ORID> neighborIdentity;
if (ctx.queueLeft.size() <= ctx.queueRight.size()) {
// START EVALUATING FROM LEFT
neighborIdentity = walkLeft(ctx);
if (neighborIdentity != null)
return neighborIdentity;
depth++;
if (ctx.maxDepth != null && ctx.maxDepth <= depth) {
break;
}
if (ctx.queueLeft.isEmpty())
break;
neighborIdentity = walkRight(ctx);
if (neighborIdentity != null)
return neighborIdentity;
} else {
// START EVALUATING FROM RIGHT
neighborIdentity = walkRight(ctx);
if (neighborIdentity != null)
return neighborIdentity;
depth++;
if (ctx.maxDepth != null && ctx.maxDepth <= depth) {
break;
}
if (ctx.queueRight.isEmpty())
break;
neighborIdentity = walkLeft(ctx);
if (neighborIdentity != null)
return neighborIdentity;
}
depth++;
}
return new ArrayList<ORID>();
}
});
}
private void bindAdditionalParams(Object additionalParams, OShortestPathContext ctx) {
if (additionalParams == null) {
return;
}
Map<String, Object> mapParams = null;
if (additionalParams instanceof Map) {
mapParams = (Map) additionalParams;
} else if (additionalParams instanceof OIdentifiable) {
mapParams = ((ODocument) ((OIdentifiable) additionalParams).getRecord()).toMap();
}
if (mapParams != null) {
ctx.maxDepth = integer(mapParams.get("maxDepth"));
}
}
private Integer integer(Object fromObject) {
if (fromObject == null) {
return null;
}
if (fromObject instanceof Number) {
return ((Number) fromObject).intValue();
}
if (fromObject instanceof String) {
try {
return Integer.parseInt((String) fromObject);
} catch (Exception e) {
}
}
return null;
}
public String getSyntax() {
return "shortestPath(<sourceVertex>, <destinationVertex>, [<direction>, [ <edgeTypeAsString> ]])";
}
protected List<ORID> walkLeft(final OSQLFunctionShortestPath.OShortestPathContext ctx) {
ArrayDeque<OrientVertex> nextLevelQueue = new ArrayDeque<OrientVertex>();
while (!ctx.queueLeft.isEmpty()) {
ctx.current = ctx.queueLeft.poll();
Iterable<Vertex> neighbors;
if (ctx.edgeType == null) {
neighbors = ctx.current.getVertices(ctx.directionLeft);
} else {
neighbors = ctx.current.getVertices(ctx.directionLeft, ctx.edgeTypeParam);
}
for (Vertex neighbor : neighbors) {
final OrientVertex v = (OrientVertex) neighbor;
final ORID neighborIdentity = v.getIdentity();
if (ctx.rightVisited.contains(neighborIdentity)) {
ctx.previouses.put(neighborIdentity, ctx.current.getIdentity());
return computePath(ctx.previouses, ctx.nexts, neighborIdentity);
}
if (!ctx.leftVisited.contains(neighborIdentity)) {
ctx.previouses.put(neighborIdentity, ctx.current.getIdentity());
nextLevelQueue.offer(v);
ctx.leftVisited.add(neighborIdentity);
}
}
}
ctx.queueLeft = nextLevelQueue;
return null;
}
protected List<ORID> walkRight(final OSQLFunctionShortestPath.OShortestPathContext ctx) {
final ArrayDeque<OrientVertex> nextLevelQueue = new ArrayDeque<OrientVertex>();
while (!ctx.queueRight.isEmpty()) {
ctx.currentRight = ctx.queueRight.poll();
Iterable<Vertex> neighbors;
if (ctx.edgeType == null) {
neighbors = ctx.currentRight.getVertices(ctx.directionRight);
} else {
neighbors = ctx.currentRight.getVertices(ctx.directionRight, ctx.edgeTypeParam);
}
for (Vertex neighbor : neighbors) {
final OrientVertex v = (OrientVertex) neighbor;
final ORID neighborIdentity = v.getIdentity();
if (ctx.leftVisited.contains(neighborIdentity)) {
ctx.nexts.put(neighborIdentity, ctx.currentRight.getIdentity());
return computePath(ctx.previouses, ctx.nexts, neighborIdentity);
}
if (!ctx.rightVisited.contains(neighborIdentity)) {
ctx.nexts.put(neighborIdentity, ctx.currentRight.getIdentity());
nextLevelQueue.offer(v);
ctx.rightVisited.add(neighborIdentity);
}
}
}
ctx.queueRight = nextLevelQueue;
return null;
}
private List<ORID> computePath(final Map<ORID, ORID> leftDistances, final Map<ORID, ORID> rightDistances, final ORID neighbor) {
final List<ORID> result = new ArrayList<ORID>();
ORID current = neighbor;
while (current != null) {
result.add(0, current);
current = leftDistances.get(current);
}
current = neighbor;
while (current != null) {
current = rightDistances.get(current);
if (current != null) {
result.add(current);
}
}
return result;
}
}