blob: 867c719a8da52ff69c8ea1a4e5e69719192b9ae9 [file] [log] [blame]
#region License
/*
* 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.
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Gremlin.Net.Process.Traversal.Step.Util;
using Gremlin.Net.Process.Traversal.Strategy;
using Gremlin.Net.Process.Traversal.Strategy.Decoration;
namespace Gremlin.Net.Process.Traversal.Translator
{
/// <summary>
/// Converts bytecode to a Groovy string of Gremlin.
/// </summary>
public class GroovyTranslator
{
private GroovyTranslator(string traversalSource)
{
TraversalSource = traversalSource;
}
/// <summary>
/// Creates the translator.
/// </summary>
/// <param name="traversalSource">The traversal source for the traversal to be translated.</param>
/// <returns>The created translator instance.</returns>
public static GroovyTranslator Of(string traversalSource)
{
return new GroovyTranslator(traversalSource);
}
/// <summary>
/// Get the language that the translator is converting the traversal byte code to.
/// </summary>
public string TargetLanguage => "gremlin-groovy";
/// <summary>
/// Gets the <see cref="TraversalSource"/> representation rooting this translator.
/// This is typically a "g".
/// </summary>
public string TraversalSource { get; }
/// <summary>
/// Translate <see cref="ITraversal"/> into gremlin-groovy.
/// </summary>
/// <param name="traversal">The traversal to translate.</param>
/// <param name="isChildTraversal">Whether this is an anonymous traversal (started via '__').</param>
/// <returns>The translated gremlin-groovy traversal.</returns>
public string Translate(ITraversal traversal, bool isChildTraversal = false)
{
return Translate(traversal.Bytecode, isChildTraversal);
}
/// <summary>
/// Translate <see cref="Bytecode"/> into gremlin-groovy.
/// </summary>
/// <param name="bytecode">The bytecode representing traversal source and traversal manipulations.</param>
/// <param name="isChildTraversal">Whether this is an anonymous traversal (started via '__').</param>
/// <returns>The translated gremlin-groovy traversal.</returns>
public string Translate(Bytecode bytecode, bool isChildTraversal = false)
{
var sb = new StringBuilder(isChildTraversal ? "__" : TraversalSource);
foreach (var step in bytecode.SourceInstructions)
{
sb.Append(TranslateStep(step));
}
foreach (var step in bytecode.StepInstructions)
{
sb.Append(TranslateStep(step));
}
return sb.ToString();
}
private string TranslateStep(Instruction step)
{
if (step.OperatorName == "with")
{
return $".{step.OperatorName}({TranslateWithArguments(step.Arguments)})";
}
return $".{step.OperatorName}({TranslateArguments(step.Arguments)})";
}
private string TranslateWithArguments(dynamic[] arguments)
{
if (arguments[0] == WithOptions.Tokens)
{
return string.Join(", ", arguments.Select(a => PropertyMapOptionsTranslation[a]));
}
if (arguments[0] == WithOptions.Indexer)
{
return string.Join(", ", arguments.Select(a => IndexOptionsTranslation[a]));
}
return string.Join(", ", arguments.Select(TranslateArgument));
}
private static readonly Dictionary<object, string> PropertyMapOptionsTranslation = new Dictionary<object, string>
{
{ WithOptions.Tokens, "WithOptions.tokens" },
{ WithOptions.None, "WithOptions.none" },
{ WithOptions.Ids, "WithOptions.ids" },
{ WithOptions.Labels, "WithOptions.labels" },
{ WithOptions.Keys, "WithOptions.keys" },
{ WithOptions.Values, "WithOptions.values" },
{ WithOptions.All, "WithOptions.all" }
};
private static readonly Dictionary<object, string> IndexOptionsTranslation = new Dictionary<object, string>
{
{ WithOptions.Indexer, "WithOptions.indexer" },
{ WithOptions.List, "WithOptions.list" },
{ WithOptions.Map, "WithOptions.map" }
};
private string TranslateArguments(IEnumerable<object> arguments) =>
string.Join(", ", arguments.Select(TranslateArgument));
private string TranslateArgument(object argument)
{
return argument switch
{
null => "null",
string str => $"'{str}'",
char c => $"'{c}'",
bool b => b ? "true" : "false",
DateTimeOffset dto => TranslateDateTimeOffset(dto),
DateTime dt => TranslateDateTimeOffset(dt),
Guid guid => $"UUID.fromString('{guid}')",
P p => TranslateP(p),
IDictionary dict => TranslateDictionary(dict),
IEnumerable e => TranslateCollection(e),
ITraversal t => TranslateTraversal(t),
AbstractTraversalStrategy strategy => TranslateStrategy(strategy),
_ => Convert.ToString(argument, CultureInfo.InvariantCulture)
};
}
private static string TranslateDateTimeOffset(DateTimeOffset dto)
{
var year = dto.Year - 1900;
var month = dto.Month - 1;
var dayOfMonth = dto.Day;
var hour = dto.Hour;
var minute = dto.Minute;
var second = dto.Second;
return $"new Date({year}, {month}, {dayOfMonth}, {hour}, {minute}, {second})";
}
private string TranslateP(P p)
{
return p.Other == null
? $"P.{p.OperatorName}({TranslateArgument(p.Value)})"
: $"P.{p.OperatorName}({TranslateArgument(p.Value)}, {TranslateArgument(p.Other)})";
}
private string TranslateDictionary(IDictionary dict)
{
var kvStrings = new List<string>(dict.Count);
foreach (DictionaryEntry kv in dict)
{
kvStrings.Add($"{TranslateArgument(kv.Key)}: {TranslateArgument(kv.Value)}");
}
return $"[{string.Join(", ", kvStrings)}]";
}
private string TranslateCollection(IEnumerable enumerable) =>
$"[{TranslateArguments(enumerable.Cast<object>().ToArray())}]";
private string TranslateTraversal(ITraversal traversal) => Translate(traversal.Bytecode, true);
private string TranslateStrategy(AbstractTraversalStrategy strategy)
{
var config = string.Join(", ",
strategy.Configuration.Select(opt =>
$"{(strategy.StrategyName == nameof(OptionsStrategy) ? $"'{opt.Key}'" : opt.Key)}: {TranslateArgument(opt.Value)}"));
return $"new {strategy.StrategyName}({config})";
}
}
}