blob: 181ee1f5c951f6e21cc22b50e696f619c89acb8a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.tinkerpop.gremlin.process.traversal.translator;
import org.apache.commons.configuration2.ConfigurationConverter;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Script;
import org.apache.tinkerpop.gremlin.process.traversal.TextP;
import org.apache.tinkerpop.gremlin.process.traversal.Translator;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy;
import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP;
import org.apache.tinkerpop.gremlin.process.traversal.util.OrP;
import org.apache.tinkerpop.gremlin.structure.Direction;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.T;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.structure.VertexProperty;
import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
import org.apache.tinkerpop.gremlin.util.function.Lambda;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
/**
* Converts bytecode to a C# string of Gremlin.
*
* @author Stephen Mallette (http://stephen.genoprime.com)
*/
public final class DotNetTranslator implements Translator.ScriptTranslator {
private final String traversalSource;
private final TypeTranslator typeTranslator;
private static final List<String> methodsWithArgsNotNeedingGeneric = Arrays.asList(GraphTraversal.Symbols.group,
GraphTraversal.Symbols.groupCount, GraphTraversal.Symbols.sack);
private DotNetTranslator(final String traversalSource, final TypeTranslator typeTranslator) {
this.traversalSource = traversalSource;
this.typeTranslator = typeTranslator;
}
/**
* Creates the translator with a {@code false} argument to {@code withParameters} using
* {@link #of(String, boolean)}.
*/
public static DotNetTranslator of(final String traversalSource) {
return of(traversalSource, false);
}
/**
* Creates the translator with the {@link DefaultTypeTranslator} passing the {@code withParameters} option to it
* which will handle type translation in a fashion that should typically increase cache hits and reduce
* compilation times if enabled at the sacrifice to rewriting of the script that could reduce readability.
*/
public static DotNetTranslator of(final String traversalSource, final boolean withParameters) {
return of(traversalSource, new DefaultTypeTranslator(withParameters));
}
/**
* Creates the translator with a custom {@link TypeTranslator} instance.
*/
public static DotNetTranslator of(final String traversalSource, final TypeTranslator typeTranslator) {
return new DotNetTranslator(traversalSource, typeTranslator);
}
@Override
public Script translate(final Bytecode bytecode) {
return typeTranslator.apply(traversalSource, bytecode);
}
@Override
public String getTargetLanguage() {
return "gremlin-dotnet";
}
@Override
public String toString() {
return StringFactory.translatorString(this);
}
@Override
public String getTraversalSource() {
return this.traversalSource;
}
/**
* Performs standard type translation for the TinkerPop types to C#.
*/
public static class DefaultTypeTranslator extends AbstractTypeTranslator {
public DefaultTypeTranslator(final boolean withParameters) {
super(withParameters);
}
@Override
protected String getNullSyntax() {
return "null";
}
@Override
protected String getSyntax(final String o) {
return "\"" + StringEscapeUtils.escapeJava(o) + "\"";
}
@Override
protected String getSyntax(final Boolean o) {
return o.toString();
}
@Override
protected String getSyntax(final Date o) {
return "DateTimeOffset.FromUnixTimeMilliseconds(" + o.getTime() + ")";
}
@Override
protected String getSyntax(final Timestamp o) {
return "DateTimeOffset.FromUnixTimeMilliseconds(" + o.getTime() + ")";
}
@Override
protected String getSyntax(final UUID o) {
return "new Guid(\"" + o.toString() + "\")";
}
@Override
protected String getSyntax(final Lambda o) {
return "Lambda.Groovy(\"" + StringEscapeUtils.escapeEcmaScript(o.getLambdaScript().trim()) + "\")";
}
@Override
protected String getSyntax(final SackFunctions.Barrier o) {
return "Barrier." + SymbolHelper.toCSharp(o.toString());
}
@Override
protected String getSyntax(final VertexProperty.Cardinality o) {
return "Cardinality." + SymbolHelper.toCSharp(o.toString());
}
@Override
protected String getSyntax(final TraversalOptionParent.Pick o) {
return "Pick." + SymbolHelper.toCSharp(o.toString());
}
@Override
protected String getSyntax(final Number o) {
return o.toString();
}
@Override
protected Script produceScript(final Set<?> o) {
final Iterator<?> iterator = o.iterator();
script.append("new HashSet<object> {");
while (iterator.hasNext()) {
final Object nextItem = iterator.next();
convertToScript(nextItem);
if (iterator.hasNext())
script.append(", ");
}
return script.append("}");
}
@Override
protected Script produceScript(final List<?> o) {
final Iterator<?> iterator = ((List<?>) o).iterator();
script.append("new List<object> {");
while (iterator.hasNext()) {
final Object nextItem = iterator.next();
convertToScript(nextItem);
if (iterator.hasNext())
script.append(", ");
}
return script.append("}");
}
@Override
protected Script produceScript(final Map<?, ?> o) {
script.append("new Dictionary<object,object> {");
produceKeyValuesForMap(o);
return script.append("}");
}
@Override
protected Script produceScript(final Class<?> o) {
return script.append(o.getCanonicalName());
}
@Override
protected Script produceScript(final Enum<?> o) {
final String e = o instanceof Direction ?
o.name().substring(0,1).toUpperCase() + o.name().substring(1).toLowerCase() :
o.name().substring(0,1).toUpperCase() + o.name().substring(1);
return script.append(o.getDeclaringClass().getSimpleName() + "." + e);
}
@Override
protected Script produceScript(final Vertex o) {
script.append("new Vertex(");
convertToScript(o.id());
script.append(", ");
convertToScript(o.label());
return script.append(")");
}
@Override
protected Script produceScript(final Edge o) {
script.append("new Edge(");
convertToScript(o.id());
script.append(", new Vertex(");
convertToScript(o.outVertex().id());
script.append(", ");
convertToScript(o.outVertex().label());
script.append("), ");
convertToScript(o.label());
script.append(", new Vertex(");
convertToScript(o.inVertex().id());
script.append(", ");
convertToScript(o.inVertex().label());
return script.append("))");
}
@Override
protected Script produceScript(final VertexProperty<?> o) {
script.append("new VertexProperty(");
convertToScript(o.id());
script.append(", ");
convertToScript(o.label());
script.append(", ");
convertToScript(o.value());
script.append(", ");
return script.append("null)");
}
@Override
protected Script produceScript(final TraversalStrategyProxy<?> o) {
if (o.getConfiguration().isEmpty()) {
return script.append("new " + o.getStrategyClass().getSimpleName() + "()");
} else {
script.append("new " + o.getStrategyClass().getSimpleName() + "(");
final Iterator<String> keys = o.getConfiguration().getKeys();
while (keys.hasNext()) {
final String k = keys.next();
script.append(k);
script.append(": ");
convertToScript(o.getConfiguration().getProperty(k));
if (keys.hasNext())
script.append(", ");
}
return script.append(")");
}
}
private Script produceKeyValuesForMap(final Map<?,?> m) {
final Iterator<? extends Map.Entry<?, ?>> itty = m.entrySet().iterator();
while (itty.hasNext()) {
final Map.Entry<?,?> entry = itty.next();
script.append("{");
convertToScript(entry.getKey());
script.append(", ");
convertToScript(entry.getValue());
script.append("}");
if (itty.hasNext())
script.append(", ");
}
return script;
}
@Override
protected Script produceScript(final String traversalSource, final Bytecode o) {
script.append(traversalSource);
for (final Bytecode.Instruction instruction : o.getInstructions()) {
final String methodName = instruction.getOperator();
// perhaps too many if/then conditions for specifying generics. doesnt' seem like there is a clear
// way to refactor this more nicely though.
if (0 == instruction.getArguments().length) {
if (methodName.equals(GraphTraversal.Symbols.fold) && o.getSourceInstructions().size() + o.getStepInstructions().size() > 1)
script.append(".").append(resolveSymbol(methodName).replace("<object>", "")).append("()");
else
script.append(".").append(resolveSymbol(methodName)).append("()");
} else {
if (methodsWithArgsNotNeedingGeneric.contains(methodName) ||
(methodName.equals(GraphTraversal.Symbols.inject) && Arrays.stream(instruction.getArguments()).noneMatch(Objects::isNull)))
script.append(".").append(resolveSymbol(methodName).replace("<object>", "").replace("<object,object>", "")).append("(");
else
script.append(".").append(resolveSymbol(methodName)).append("(");
// have to special case withSack() because UnaryOperator and BinaryOperator signatures
// make it impossible for the interpreter to figure out which function to call. specifically we need
// to discern between:
// withSack(A initialValue, UnaryOperator<A> splitOperator)
// withSack(A initialValue, BinaryOperator<A> splitOperator)
// and:
// withSack(Supplier<A> initialValue, UnaryOperator<A> mergeOperator)
// withSack(Supplier<A> initialValue, BinaryOperator<A> mergeOperator)
if (methodName.equals(TraversalSource.Symbols.withSack) &&
instruction.getArguments().length == 2 && instruction.getArguments()[1] instanceof Lambda) {
final String castFirstArgTo = instruction.getArguments()[0] instanceof Lambda ? "ISupplier" : "";
final Lambda secondArg = (Lambda) instruction.getArguments()[1];
final String castSecondArgTo = secondArg.getLambdaArguments() == 1 ? "IUnaryOperator" : "IBinaryOperator";
if (!castFirstArgTo.isEmpty())
script.append(String.format("(%s) ", castFirstArgTo));
convertToScript(instruction.getArguments()[0]);
script.append(", (").append(castSecondArgTo).append(") ");
convertToScript(instruction.getArguments()[1]);
script.append(",");
} else {
for (final Object object : instruction.getArguments()) {
// overloads might have trouble with null. add more as we find them i guess
if (null == object && methodName.equals(GraphTraversal.Symbols.addV))
script.append("(string) ");
convertToScript(object);
script.append(",");
}
}
script.setCharAtEnd(')');
}
}
return script;
}
@Override
protected Script produceScript(final P<?> p) {
if (p instanceof TextP) {
script.append("TextP.").append(SymbolHelper.toCSharp(p.getBiPredicate().toString())).append("(");
convertToScript(p.getValue());
} else if (p instanceof ConnectiveP) {
// ConnectiveP gets some special handling because it's reduced to and(P, P, P) and we want it
// generated the way it was written which was P.and(P).and(P)
final List<P<?>> list = ((ConnectiveP) p).getPredicates();
final String connector = p instanceof OrP ? "Or" : "And";
for (int i = 0; i < list.size(); i++) {
produceScript(list.get(i));
// for the first/last P there is no parent to close
if (i > 0 && i < list.size() - 1) script.append(")");
// add teh connector for all but last P
if (i < list.size() - 1) {
script.append(".").append(connector).append("(");
}
}
} else {
script.append("P.").append(SymbolHelper.toCSharp(p.getBiPredicate().toString())).append("(");
convertToScript(p.getValue());
}
script.append(")");
return script;
}
protected String resolveSymbol(final String methodName) {
return SymbolHelper.toCSharp(methodName);
}
}
static final class SymbolHelper {
private final static Map<String, String> TO_CS_MAP = new HashMap<>();
private final static Map<String, String> FROM_CS_MAP = new HashMap<>();
static {
TO_CS_MAP.put(GraphTraversal.Symbols.branch, "Branch<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.cap, "Cap<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.choose, "Choose<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.coalesce, "Coalesce<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.constant, "Constant<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.elementMap, "ElementMap<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.flatMap, "FlatMap<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.fold, "Fold<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.group, "Group<object,object>");
TO_CS_MAP.put(GraphTraversal.Symbols.groupCount, "GroupCount<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.index, "Index<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.inject, "Inject<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.io, "Io<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.limit, "Limit<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.local, "Local<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.match, "Match<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.map, "Map<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.max, "Max<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.min, "Min<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.mean, "Mean<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.optional, "Optional<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.project, "Project<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.properties, "Properties<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.range, "Range<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.sack, "Sack<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.select, "Select<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.skip, "Skip<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.sum, "Sum<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.tail, "Tail<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.unfold, "Unfold<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.union, "Union<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.value, "Value<object>");
TO_CS_MAP.put(GraphTraversal.Symbols.valueMap, "ValueMap<object,object>");
TO_CS_MAP.put(GraphTraversal.Symbols.values, "Values<object>");
//
TO_CS_MAP.forEach((k, v) -> FROM_CS_MAP.put(v, k));
}
private SymbolHelper() {
// static methods only, do not instantiate
}
public static String toCSharp(final String symbol) {
return TO_CS_MAP.getOrDefault(symbol, symbol.substring(0,1).toUpperCase() + symbol.substring(1));
}
public static String toJava(final String symbol) {
return FROM_CS_MAP.getOrDefault(symbol, symbol.substring(0,1).toLowerCase() + symbol.substring(1));
}
}
}