blob: 7335fc72c4c50bc55dbf4db5f1a870ae13d5456e [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.configuration.ConfigurationConverter;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.tinkerpop.gremlin.jsr223.CoreImports;
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.TextP;
import org.apache.tinkerpop.gremlin.process.traversal.Translator;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
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.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.function.Lambda;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Converts bytecode to a Groovy string of Gremlin.
*
* @author Marko A. Rodriguez (http://markorodriguez.com)
* @author Stephen Mallette (http://stephen.genoprime.com)
*/
public final class GroovyTranslator implements Translator.ScriptTranslator {
private final String traversalSource;
private final TypeTranslator typeTranslator;
private GroovyTranslator(final String traversalSource, final TypeTranslator typeTranslator) {
this.traversalSource = traversalSource;
this.typeTranslator = typeTranslator;
}
public static GroovyTranslator of(final String traversalSource) {
return of(traversalSource, null);
}
public static GroovyTranslator of(final String traversalSource, final TypeTranslator typeTranslator) {
return new GroovyTranslator(traversalSource,
Optional.ofNullable(typeTranslator).orElseGet(DefaultTypeTranslator::new));
}
///////
@Override
public String translate(final Bytecode bytecode) {
return typeTranslator.apply(traversalSource, bytecode).toString();
}
@Override
public String getTargetLanguage() {
return "gremlin-groovy";
}
@Override
public String toString() {
return StringFactory.translatorString(this);
}
@Override
public String getTraversalSource() {
return this.traversalSource;
}
/**
* Performs standard type translation for the TinkerPop types to Groovy.
*/
public static class DefaultTypeTranslator implements TypeTranslator {
@Override
public Object apply(final String traversalSource, final Object o) {
if (o instanceof Bytecode)
return internalTranslate(traversalSource, (Bytecode) o);
else
return convertToString(o);
}
protected String convertToString(final Object object) {
if (object instanceof Bytecode.Binding)
return ((Bytecode.Binding) object).variable();
else if (object instanceof Bytecode)
return internalTranslate("__", (Bytecode) object);
else if (object instanceof Traversal)
return convertToString(((Traversal) object).asAdmin().getBytecode());
else if (object instanceof String) {
return (((String) object).contains("\"") ? "\"\"\"" + StringEscapeUtils.escapeJava((String) object) + "\"\"\"" : "\"" + StringEscapeUtils.escapeJava((String) object) + "\"")
.replace("$", "\\$");
} else if (object instanceof Set) {
final Set<String> set = new HashSet<>(((Set) object).size());
for (final Object item : (Set) object) {
set.add(convertToString(item));
}
return set.toString() + " as Set";
} else if (object instanceof List) {
final List<String> list = new ArrayList<>(((List) object).size());
for (final Object item : (List) object) {
list.add(convertToString(item));
}
return list.toString();
} else if (object instanceof Map) {
final StringBuilder map = new StringBuilder("[");
for (final Map.Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) {
map.append("(").
append(convertToString(entry.getKey())).
append("):(").
append(convertToString(entry.getValue())).
append("),");
}
// only need to remove this last bit if entries were added
if (!((Map<?, ?>) object).isEmpty())
map.deleteCharAt(map.length() - 1);
return map.append("]").toString();
} else if (object instanceof Long)
return object + "L";
else if (object instanceof Double)
return object + "d";
else if (object instanceof Float)
return object + "f";
else if (object instanceof Integer)
return "(int) " + object;
else if (object instanceof Class)
return convertClassToString((Class<?>) object);
else if (object instanceof Timestamp)
return "new Timestamp(" + ((Timestamp) object).getTime() + ")";
else if (object instanceof Date)
return "new Date(" + ((Date) object).getTime() + ")";
else if (object instanceof UUID)
return "UUID.fromString('" + object.toString() + "')";
else if (object instanceof P)
return convertPToString((P) object, new StringBuilder()).toString();
else if (object instanceof SackFunctions.Barrier)
return "SackFunctions.Barrier." + object.toString();
else if (object instanceof VertexProperty.Cardinality)
return "VertexProperty.Cardinality." + object.toString();
else if (object instanceof TraversalOptionParent.Pick)
return "TraversalOptionParent.Pick." + object.toString();
else if (object instanceof Enum)
return ((Enum) object).getDeclaringClass().getSimpleName() + "." + object.toString();
else if (object instanceof Vertex) {
final Vertex vertex = (Vertex) object;
return "new ReferenceVertex(" +
convertToString(vertex.id()) + "," +
convertToString(vertex.label()) + ")";
} else if (object instanceof Edge) {
final Edge edge = (Edge) object;
return "new ReferenceEdge(" +
convertToString(edge.id()) + "," +
convertToString(edge.label()) + "," +
"new ReferenceVertex(" + convertToString(edge.inVertex().id()) + "," +
convertToString(edge.inVertex().label()) + ")," +
"new ReferenceVertex(" + convertToString(edge.outVertex().id()) + "," +
convertToString(edge.outVertex().label()) + "))";
} else if (object instanceof VertexProperty) {
final VertexProperty<?> vertexProperty = (VertexProperty<?>) object;
return "new ReferenceVertexProperty(" +
convertToString(vertexProperty.id()) + "," +
convertToString(vertexProperty.label()) + "," +
convertToString(vertexProperty.value()) + ")";
} else if (object instanceof Lambda) {
final String lambdaString = ((Lambda) object).getLambdaScript().trim();
return lambdaString.startsWith("{") ? lambdaString : "{" + lambdaString + "}";
} else if (object instanceof TraversalStrategyProxy) {
final TraversalStrategyProxy proxy = (TraversalStrategyProxy) object;
final String className = convertClassToString(proxy.getStrategyClass());
if (proxy.getConfiguration().isEmpty())
return className;
else
return String.format("new %s(%s)", className, convertMapToArguments(ConfigurationConverter.getMap(proxy.getConfiguration())));
} else if (object instanceof TraversalStrategy) {
return convertToString(new TraversalStrategyProxy(((TraversalStrategy) object)));
} else
return null == object ? "null" : object.toString();
}
protected String internalTranslate(final String start, final Bytecode bytecode) {
final StringBuilder traversalScript = new StringBuilder(start);
for (final Bytecode.Instruction instruction : bytecode.getInstructions()) {
final String methodName = instruction.getOperator();
if (0 == instruction.getArguments().length)
traversalScript.append(".").append(methodName).append("()");
else {
traversalScript.append(".");
String temp = methodName + "(";
for (final Object object : instruction.getArguments()) {
temp = temp + convertToString(object) + ",";
}
traversalScript.append(temp.substring(0, temp.length() - 1)).append(")");
}
}
return traversalScript.toString();
}
protected StringBuilder convertPToString(final P p, final StringBuilder current) {
if (p instanceof TextP) return convertTextPToString((TextP) p, current);
if (p instanceof ConnectiveP) {
final List<P<?>> list = ((ConnectiveP) p).getPredicates();
for (int i = 0; i < list.size(); i++) {
convertPToString(list.get(i), current);
if (i < list.size() - 1)
current.append(p instanceof OrP ? ".or(" : ".and(");
}
current.append(")");
} else
current.append("P.").append(p.getBiPredicate().toString()).append("(").append(convertToString(p.getValue())).append(")");
return current;
}
protected StringBuilder convertTextPToString(final TextP p, final StringBuilder current) {
current.append("TextP.").append(p.getBiPredicate().toString()).append("(").append(convertToString(p.getValue())).append(")");
return current;
}
/**
* Gets the string representation of a class with the default implementation simply checking to see if the
* {@code Class} is in {@link CoreImports} or not. If it is present that means it can be referenced using the
* simple name otherwise it uses the canonical name.
* <p/>
* Those building custom {@link ScriptTranslator} instances might override this if they have other classes
* that are not in {@link CoreImports} by default.
*/
protected String convertClassToString(final Class<?> clazz) {
return CoreImports.getClassImports().contains(clazz) ? clazz.getSimpleName() : clazz.getCanonicalName();
}
private String convertMapToArguments(final Map<Object,Object> map) {
return map.entrySet().stream().map(entry ->
String.format("%s: %s", entry.getKey().toString(), convertToString(entry.getValue()))).
collect(Collectors.joining(", "));
}
}
}