using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.Serialization;
using GitHub.Services.WebApi;
using Newtonsoft.Json;
namespace GitHub.DistributedTask.Pipelines
{
///
/// Provides a mechanism for performing delayed evaluation of a value based on the environment context as runtime.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public abstract class ExpressionValue
{
public static Boolean IsExpression(String value)
{
return !String.IsNullOrEmpty(value) &&
value.Length > 3 &&
value.StartsWith("$[", StringComparison.Ordinal) &&
value.EndsWith("]", StringComparison.Ordinal);
}
///
/// Attempts to parse the specified string as an expression value.
///
/// The expected type of the expression result
/// The expression string
/// The value which was parsed, if any
/// True if the value was successfully parsed; otherwise, false
public static Boolean TryParse(
String expression,
out ExpressionValue value)
{
if (IsExpression(expression))
{
value = new ExpressionValue(expression, isExpression: true);
}
else
{
value = null;
}
return value != null;
}
///
/// Creates an ExpressionValue from expression string.
/// Returns null if argument is not an expression
///
public static ExpressionValue FromExpression(String expression)
{
return new ExpressionValue(expression, isExpression: true);
}
///
/// Creates an ExpressionValue from literal.
///
public static ExpressionValue FromLiteral(T literal)
{
return new ExpressionValue(literal);
}
///
/// When T is String, we cannot distiguish between literals and expressions solely by type.
/// Use this function when parsing and you want to err on the side of expressions.
///
public static ExpressionValue FromToken(String token)
{
if (ExpressionValue.IsExpression(token))
{
return ExpressionValue.FromExpression(token);
}
return ExpressionValue.FromLiteral(token);
}
internal static String TrimExpression(String value)
{
var expression = value.Substring(2, value.Length - 3).Trim();
if (String.IsNullOrEmpty(expression))
{
throw new ArgumentException(PipelineStrings.ExpressionInvalid(value));
}
return expression;
}
}
///
/// Provides a mechanism for performing delayed evaluation of a value based on the environment context at runtime.
///
/// The type of value
[DataContract]
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class ExpressionValue : ExpressionValue, IEquatable>
{
///
/// Initializes a new ExpressionValue instance with the specified literal value.
///
/// The literal value which should be used
public ExpressionValue(T literalValue)
{
m_literalValue = literalValue;
}
///
/// Initializes a new ExpressionValue with the given expression.
/// Throws if expression is invalid.
///
/// The expression to be used
/// This parameter is unused other than to discriminate this constructor from the literal constructor
internal ExpressionValue(
String expression,
Boolean isExpression)
{
if (!IsExpression(expression))
{
throw new ArgumentException(PipelineStrings.ExpressionInvalid(expression));
}
m_expression = ExpressionValue.TrimExpression(expression);
}
[JsonConstructor]
private ExpressionValue()
{
}
internal T Literal
{
get
{
return m_literalValue;
}
}
internal String Expression
{
get
{
return m_expression;
}
}
///
/// Gets a value indicating whether or not the expression is backed by a literal value.
///
internal Boolean IsLiteral => String.IsNullOrEmpty(m_expression);
///
/// Converts the value to a string representation.
///
/// A string representation of the current value
public override String ToString()
{
if (!String.IsNullOrEmpty(m_expression))
{
return String.Concat("$[ ", m_expression, " ]");
}
else
{
return m_literalValue?.ToString();
}
}
///
/// Provides automatic conversion of a literal value into a pipeline value for convenience.
///
/// The value which the pipeline value represents
public static implicit operator ExpressionValue(T value)
{
return new ExpressionValue(value);
}
public Boolean Equals(ExpressionValue rhs)
{
if (rhs is null)
{
return false;
}
if (ReferenceEquals(this, rhs))
{
return true;
}
if (IsLiteral)
{
return EqualityComparer.Default.Equals(this.Literal, rhs.Literal);
}
else
{
return this.Expression == rhs.Expression;
}
}
public override Boolean Equals(object obj)
{
return Equals(obj as ExpressionValue);
}
public static Boolean operator ==(ExpressionValue lhs, ExpressionValue rhs)
{
if (lhs is null)
{
return rhs is null;
}
return lhs.Equals(rhs);
}
public static Boolean operator !=(ExpressionValue lhs, ExpressionValue rhs)
{
return !(lhs == rhs);
}
public override Int32 GetHashCode()
{
if (IsLiteral)
{
if (Literal != null)
{
return Literal.GetHashCode();
}
}
else if (Expression != null)
{
return Expression.GetHashCode();
}
return 0; // unspecified expression values are all the same.
}
[DataMember(Name = "LiteralValue", EmitDefaultValue = false)]
private readonly T m_literalValue;
[DataMember(Name = "VariableValue", EmitDefaultValue = false)]
private readonly String m_expression;
}
internal class ExpressionValueJsonConverter : VssSecureJsonConverter
{
public override Boolean CanConvert(Type objectType)
{
return objectType.GetTypeInfo().Equals(typeof(String).GetTypeInfo()) || typeof(T).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
public override Object ReadJson(
JsonReader reader,
Type objectType,
Object existingValue,
JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
{
// string types are either expressions of any type T, or literals of type String
var s = (String)(Object)reader.Value;
if (ExpressionValue.IsExpression(s))
{
return ExpressionValue.FromExpression(s);
}
else
{
return new ExpressionValue(s);
}
}
else
{
var parsedValue = serializer.Deserialize(reader);
return new ExpressionValue(parsedValue);
}
}
public override void WriteJson(
JsonWriter writer,
Object value,
JsonSerializer serializer)
{
base.WriteJson(writer, value, serializer);
if (value is ExpressionValue expressionValue)
{
if (!String.IsNullOrEmpty(expressionValue.Expression))
{
serializer.Serialize(writer, $"$[ {expressionValue.Expression} ]");
}
else
{
serializer.Serialize(writer, expressionValue.Literal);
}
}
}
}
}