blob: 577bc7e0a129db98fa95b90a33d8d916cdf2f801 [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.lang3.StringUtils;
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.Pick;
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.TraversalStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
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.Edge;
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.NumberHelper;
import org.apache.tinkerpop.gremlin.util.function.Lambda;
import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* Translates Gremlin {@link Bytecode} into a Golang string representation.
*
* @author Simon Zhao (simonz@bitquilltech.com)
*/
public final class GolangTranslator implements Translator.ScriptTranslator {
private final String traversalSource;
private final TypeTranslator typeTranslator;
private final static String GO_PACKAGE_NAME = "gremlingo.";
private GolangTranslator(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 GolangTranslator 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 GolangTranslator of(final String traversalSource, final boolean withParameters) {
return of(traversalSource, new DefaultTypeTranslator(withParameters));
}
/**
* Creates the translator with a custom {@link TypeTranslator} instance.
*/
public static GolangTranslator of(final String traversalSource, final TypeTranslator typeTranslator) {
return new GolangTranslator(traversalSource, typeTranslator);
}
@Override
public String getTraversalSource() {
return this.traversalSource;
}
@Override
public Script translate(final Bytecode bytecode) {
return typeTranslator.apply(traversalSource, bytecode);
}
@Override
public String getTargetLanguage() {
return "gremlin-go";
}
@Override
public String toString() {
return StringFactory.translatorString(this);
}
/**
* Performs standard type translation for the TinkerPop types to Go.
*/
public static class DefaultTypeTranslator extends AbstractTypeTranslator {
public DefaultTypeTranslator(final boolean withParameters) {
super(withParameters);
}
@Override
protected String getNullSyntax() {
return "nil";
}
@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 "time.UnixMilli(" + o.getTime() + ")";
}
@Override
protected String getSyntax(final Timestamp o) {
return "time.UnixMilli(" + o.getTime() + ")";
}
@Override
protected String getSyntax(final UUID o) {
return "uuid.MustParse(\"" + o.toString() + "\")";
}
@Override
protected String getSyntax(final Lambda o) {
return "&gremlingo.Lambda{Script:\"" + o.getLambdaScript().trim() + "\", Language:\"\"}";
}
@Override
protected String getSyntax(final Number o) {
if (o instanceof Float || o instanceof Double) {
if (NumberHelper.isNaN(o))
return "math.NaN()";
else if (NumberHelper.isPositiveInfinity(o))
return "math.Inf(1)";
else if (NumberHelper.isNegativeInfinity(o))
return "math.Inf(-11)";
}
return o.toString();
}
@Override
protected String getSyntax(final SackFunctions.Barrier o) {
return GO_PACKAGE_NAME + "Barrier." + resolveSymbol(o.toString());
}
@Override
protected String getSyntax(final VertexProperty.Cardinality o) {
return GO_PACKAGE_NAME + "Cardinality." + resolveSymbol(o.toString());
}
@Override
protected String getSyntax(final Pick o) {
return GO_PACKAGE_NAME + "Pick." + resolveSymbol(o.toString());
}
@Override
protected Script produceCardinalityValue(final Bytecode o) {
final Bytecode.Instruction inst = o.getSourceInstructions().get(0);
final String card = inst.getArguments()[0].toString();
script.append(GO_PACKAGE_NAME + "CardinalityValue." + card.substring(0, 1).toUpperCase() + card.substring(1) + "(");
convertToScript(inst.getArguments()[1]);
script.append(")");
return script;
}
@Override
protected Script produceScript(final Set<?> o) {
final Iterator<?> iterator = o.iterator();
script.append(GO_PACKAGE_NAME + "NewSimpleSet(");
while(iterator.hasNext()) {
convertToScript(iterator.next());
if (iterator.hasNext())
script.append(", ");
}
return script.append(")");
}
@Override
protected Script produceScript(final List<?> o) {
final Iterator<?> iterator = o.iterator();
script.append("[]interface{}{");
while(iterator.hasNext()) {
convertToScript(iterator.next());
if (iterator.hasNext())
script.append(", ");
}
script.append("}");
return script;
}
@Override
protected Script produceScript(final Map<?, ?> o) {
script.append("map[interface{}]interface{}{");
final Iterator<? extends Map.Entry<?, ?>> itty = o.entrySet().iterator();
while (itty.hasNext()) {
final Map.Entry<?,?> entry = itty.next();
convertToScript(entry.getKey()).append(": ");
convertToScript(entry.getValue());
if (itty.hasNext())
script.append(", ");
}
return script.append("}");
}
@Override
protected Script produceScript(final Class<?> o) {
return script.append(o.getCanonicalName());
}
@Override
protected Script produceScript(final Enum<?> o) {
return script.append(GO_PACKAGE_NAME + o.getDeclaringClass().getSimpleName() + "." + resolveSymbol(o.toString()));
}
@Override
protected Script produceScript(final Vertex o) {
script.append(GO_PACKAGE_NAME + "Vertex{Element{");
convertToScript(o.id()).append(", ");
return convertToScript(o.label()).append("}}");
}
@Override
protected Script produceScript(final Edge o) {
script.append(GO_PACKAGE_NAME + "Edge{Element{");
convertToScript(o.id()).append(", ");
convertToScript(o.label()).append("}, ");
convertToScript(o.outVertex()).append(",");
return convertToScript(o.inVertex()).append("}");
}
@Override
protected Script produceScript(final VertexProperty<?> o) {
script.append(GO_PACKAGE_NAME + "VertexProperty{");
convertToScript(o.id()).append(", ");
convertToScript(o.label()).append("}, ");
return convertToScript(o.value()).append("}");
}
@Override
protected Script produceScript(final TraversalStrategyProxy<?> o) {
if (o.getConfiguration().isEmpty()) {
return script.append(GO_PACKAGE_NAME + o.getStrategyClass().getSimpleName() + "()");
} else {
script.append(GO_PACKAGE_NAME + o.getStrategyClass().getSimpleName() + "(");
script.append(GO_PACKAGE_NAME + o.getStrategyClass().getSimpleName() + "Config{");
final Iterator<String> keys = IteratorUtils.stream(o.getConfiguration().getKeys()).
filter(e -> !e.equals(TraversalStrategy.STRATEGY)).iterator();
while (keys.hasNext()) {
final String k = keys.next();
script.append(SymbolHelper.toGolang(k));
script.append(": ");
if (o.getConfiguration().getProperty(k) instanceof List) {
List<Object> list =(List<Object>) o.getConfiguration().getProperty(k);
Iterator iterator = list.iterator();
script.append("[]string{");
while(iterator.hasNext()) {
convertToScript(iterator.next());
if (iterator.hasNext())
script.append(", ");
}
script.append("}");
} else if (o.getConfiguration().getProperty(k) == null) {
script.append("nil");
} else {
convertToScript(o.getConfiguration().getProperty(k));
}
if (keys.hasNext())
script.append(", ");
}
return script.append("})");
}
}
@Override
protected Script produceScript(final String traversalSource, final Bytecode o) {
final String source = traversalSource.equals("__") ? GO_PACKAGE_NAME + "T__" : traversalSource;
script.append(source);
for (final Bytecode.Instruction instruction : o.getInstructions()) {
final String methodName = instruction.getOperator();
final Object[] arguments = instruction.getArguments();
if (methodName.equals(GraphTraversalSource.Symbols.tx)) {
final String command = resolveSymbol(arguments[0].toString());
script.append(".").append(resolveSymbol(methodName)).append("().").append(resolveSymbol(command)).append("()");
} else {
script.append(".").append(resolveSymbol(methodName)).append("(");
for (int i = 0; i < arguments.length; i++) {
convertToScript(arguments[i]);
if (i != arguments.length - 1) {
script.append(", ");
}
}
script.append(")");
}
}
return script;
}
@Override
protected Script produceScript(final P<?> p) {
if (p instanceof TextP) {
script.append(GO_PACKAGE_NAME + "TextP.").append(resolveSymbol(p.getPredicateName())).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 the connector for all but last P.
if (i < list.size() - 1) {
script.append(".").append(connector).append("(");
}
}
} else {
script.append(GO_PACKAGE_NAME + "P.").append(resolveSymbol(p.getPredicateName())).append("(");
convertToScript(p.getValue());
}
script.append(")");
return script;
}
protected String resolveSymbol(final String methodName) {
return SymbolHelper.toGolang(methodName);
}
}
static final class SymbolHelper {
private final static Map<String, String> TO_GO_MAP = new HashMap<>();
private final static Map<String, String> FROM_GO_MAP = new HashMap<>();
static {
TO_GO_MAP.put("OUT", "Out");
TO_GO_MAP.put("IN", "In");
TO_GO_MAP.put("BOTH", "Both");
TO_GO_MAP.forEach((k, v) -> FROM_GO_MAP.put(v, k));
}
private SymbolHelper() {
// static methods only, do not instantiate
}
public static String toGolang(final String symbol) {
return TO_GO_MAP.getOrDefault(symbol, StringUtils.capitalize(symbol));
}
public static String toJava(final String symbol) {
return FROM_GO_MAP.getOrDefault(symbol, StringUtils.uncapitalize(symbol));
}
}
}