/* SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. * * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch B.V. 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. */ using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Dynamic; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Runtime.CompilerServices; using Microsoft.CSharp.RuntimeBinder; // ReSharper disable ArrangeConstructorOrDestructorBody // ReSharper disable ArrangeAccessorOwnerBody // ReSharper disable RemoveRedundantBraces // ReSharper disable ArrangeMethodOrOperatorBody namespace OpenSearch.Net { public class DynamicValue : DynamicObject, IEquatable, IConvertible { private readonly object _value; /// /// Initializes a new instance of the class. /// /// The value to store in the instance public DynamicValue(object value) => _value = value is DynamicValue av ? av.Value : value; /// /// Gets a value indicating whether this instance has value. /// /// true if this instance has value; otherwise, false. /// is considered as not being a value. public bool HasValue { get { return _value != null; } } public DynamicValue this[string name] { get { object r; Dispatch(out r, name); return (DynamicValue)r; } } public T Get(string path) { var dynamicDictionary = Value switch { DynamicDictionary v => v, IDictionary v => DynamicDictionary.Create(v), _ => null }; return dynamicDictionary == null ? default : dynamicDictionary.Get(path); } public static DynamicValue NullValue { get; } = new DynamicValue(null); public static DynamicValue SelfOrNew(object v) => v is DynamicValue av ? av : new DynamicValue(v); public DynamicValue this[int i] { get { if (!HasValue) return NullValue; var v = Value; if (v is IList l && l.Count - 1 >= i) return SelfOrNew(l[i]); if (v is IList o && o.Count - 1 >= i) return SelfOrNew(o[i]); if (v is IDictionary d) { if (d.TryGetValue(i.ToString(CultureInfo.InvariantCulture), out v)) return SelfOrNew(v); if (i >= d.Count) return new DynamicValue(null); var at = d[d.Keys.ElementAt(i)]; return SelfOrNew(at); } if (v is IDictionary dv) { if (dv.TryGetValue(i.ToString(CultureInfo.InvariantCulture), out var dvv)) return dvv; if (i >= dv.Count) return new DynamicValue(null); var at = dv[dv.Keys.ElementAt(i)]; return at; } return NullValue; } } /// /// Gets the inner value /// public object Value { get { return _value; } } /// /// Returns the for this instance. /// /// /// The enumerated constant that is the of the class or value type that implements this interface. /// /// 2 public TypeCode GetTypeCode() { if (_value == null) return TypeCode.Empty; return Type.GetTypeCode(_value.GetType()); } /// /// Converts the value of this instance to an equivalent Boolean value using the specified culture-specific formatting information. /// /// /// A Boolean value equivalent to the value of this instance. /// /// /// An interface implementation that supplies culture-specific formatting /// information. /// /// 2 public bool ToBoolean(IFormatProvider provider) { return Convert.ToBoolean(_value, provider); } /// /// Converts the value of this instance to an equivalent 8-bit unsigned integer using the specified culture-specific formatting information. /// /// /// An 8-bit unsigned integer equivalent to the value of this instance. /// /// /// An interface implementation that supplies culture-specific formatting /// information. /// /// 2 public byte ToByte(IFormatProvider provider) { return Convert.ToByte(_value, provider); } /// /// Converts the value of this instance to an equivalent Unicode character using the specified culture-specific formatting information. /// /// /// A Unicode character equivalent to the value of this instance. /// /// /// An interface implementation that supplies culture-specific formatting /// information. /// /// 2 public char ToChar(IFormatProvider provider) { return Convert.ToChar(_value, provider); } /// /// Converts the value of this instance to an equivalent using the specified culture-specific formatting /// information. /// /// /// A instance equivalent to the value of this instance. /// /// /// An interface implementation that supplies culture-specific formatting /// information. /// /// 2 public DateTime ToDateTime(IFormatProvider provider) { return Convert.ToDateTime(_value, provider); } /// /// Converts the value of this instance to an equivalent number using the specified culture-specific formatting /// information. /// /// /// A number equivalent to the value of this instance. /// /// /// An interface implementation that supplies culture-specific formatting /// information. /// /// 2 public decimal ToDecimal(IFormatProvider provider) { return Convert.ToDecimal(_value, provider); } /// /// Converts the value of this instance to an equivalent double-precision floating-point number using the specified culture-specific formatting /// information. /// /// /// A double-precision floating-point number equivalent to the value of this instance. /// /// /// An interface implementation that supplies culture-specific formatting /// information. /// /// 2 public double ToDouble(IFormatProvider provider) { return Convert.ToDouble(_value, provider); } /// /// Converts the value of this instance to an equivalent 16-bit signed integer using the specified culture-specific formatting information. /// /// /// An 16-bit signed integer equivalent to the value of this instance. /// /// /// An interface implementation that supplies culture-specific formatting /// information. /// /// 2 public short ToInt16(IFormatProvider provider) { return Convert.ToInt16(_value, provider); } /// /// Converts the value of this instance to an equivalent 32-bit signed integer using the specified culture-specific formatting information. /// /// /// An 32-bit signed integer equivalent to the value of this instance. /// /// /// An interface implementation that supplies culture-specific formatting /// information. /// /// 2 public int ToInt32(IFormatProvider provider) { return Convert.ToInt32(_value, provider); } /// /// Converts the value of this instance to an equivalent 64-bit signed integer using the specified culture-specific formatting information. /// /// /// An 64-bit signed integer equivalent to the value of this instance. /// /// /// An interface implementation that supplies culture-specific formatting /// information. /// /// 2 public long ToInt64(IFormatProvider provider) { return Convert.ToInt64(_value, provider); } /// /// Converts the value of this instance to an equivalent 8-bit signed integer using the specified culture-specific formatting information. /// /// /// An 8-bit signed integer equivalent to the value of this instance. /// /// /// An interface implementation that supplies culture-specific formatting /// information. /// /// 2 [CLSCompliant(false)] public sbyte ToSByte(IFormatProvider provider) { return Convert.ToSByte(_value, provider); } /// /// Converts the value of this instance to an equivalent single-precision floating-point number using the specified culture-specific formatting /// information. /// /// /// A single-precision floating-point number equivalent to the value of this instance. /// /// /// An interface implementation that supplies culture-specific formatting /// information. /// /// 2 public float ToSingle(IFormatProvider provider) { return Convert.ToSingle(_value, provider); } /// /// Converts the value of this instance to an equivalent using the specified culture-specific formatting /// information. /// /// /// A instance equivalent to the value of this instance. /// /// /// An interface implementation that supplies culture-specific formatting /// information. /// /// 2 public string ToString(IFormatProvider provider) { return Convert.ToString(_value, provider); } /// /// Converts the value of this instance to an of the specified that has an /// equivalent value, using the specified culture-specific formatting information. /// /// /// An instance of type whose value is equivalent to the value of this /// instance. /// /// The to which the value of this instance is converted. /// /// An interface implementation that supplies culture-specific formatting /// information. /// /// 2 public object ToType(Type conversionType, IFormatProvider provider) { return Convert.ChangeType(_value, conversionType, provider); } /// /// Converts the value of this instance to an equivalent 16-bit unsigned integer using the specified culture-specific formatting information. /// /// /// An 16-bit unsigned integer equivalent to the value of this instance. /// /// /// An interface implementation that supplies culture-specific formatting /// information. /// /// 2 [CLSCompliant(false)] public ushort ToUInt16(IFormatProvider provider) { return Convert.ToUInt16(_value, provider); } /// /// Converts the value of this instance to an equivalent 32-bit unsigned integer using the specified culture-specific formatting information. /// /// /// An 32-bit unsigned integer equivalent to the value of this instance. /// /// /// An interface implementation that supplies culture-specific formatting /// information. /// /// 2 [CLSCompliant(false)] public uint ToUInt32(IFormatProvider provider) { return Convert.ToUInt32(_value, provider); } /// /// Converts the value of this instance to an equivalent 64-bit unsigned integer using the specified culture-specific formatting information. /// /// /// An 64-bit unsigned integer equivalent to the value of this instance. /// /// /// An interface implementation that supplies culture-specific formatting /// information. /// /// 2 [CLSCompliant(false)] public ulong ToUInt64(IFormatProvider provider) { return Convert.ToUInt64(_value, provider); } /// /// Returns the value as a dictionary if the current value represents an object. /// Otherwise returns null. /// public IDictionary ToDictionary() { if (!(_value is IDictionary dict)) return null; return DynamicDictionary.Create(dict); } /// /// Indicates whether the current object is equal to another object of the same type. /// /// /// true if the current object is equal to the parameter; otherwise, false. /// /// An to compare with this instance. public bool Equals(DynamicValue compareValue) { if (ReferenceEquals(null, compareValue)) { return false; } return ReferenceEquals(this, compareValue) || Equals(compareValue._value, _value); } public override bool TryGetMember(GetMemberBinder binder, out object result) { var name = binder.Name; return Dispatch(out result, name); } private bool Dispatch(out object result, string name) { if (!HasValue) { result = NullValue; return true; } if (Value is IDictionary d) { result = d.TryGetValue(name, out var r) ? SelfOrNew(r) : NullValue; return true; } if (Value is IDynamicMetaObjectProvider x) { var dm = GetDynamicMember(Value, name); result = SelfOrNew(dm); return true; } if (Value is IDictionary ds) { result = ds.Contains(name) ? SelfOrNew(ds[name]) : NullValue; return true; } if (Value is IList l) { var projected = l .Cast() .Select(i => SelfOrNew(i).Dispatch(out var o, name) ? o : null) .Where(i => i != null) .ToArray(); result = SelfOrNew(projected); return projected.Length > 0; } if (Value is IList lo) { var projected = lo .Select(i => SelfOrNew(i).Dispatch(out var o, name) ? o : null) .Where(i => i != null) .ToArray(); result = SelfOrNew(projected); return projected.Length > 0; } result = NullValue; return true; } private static object GetDynamicMember(object obj, string memberName) { var binder = Binder.GetMember(CSharpBinderFlags.None, memberName, null, new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }); var callsite = CallSite>.Create(binder); return callsite.Target(callsite, obj); } /// /// Returns a default value if Value is null /// /// When no default value is supplied, required to supply the default type /// Optional parameter for default value, if not given it returns default of type T /// If value is not null, value is returned, else default value is returned public T Default(T defaultValue = default(T)) { if (HasValue) { try { return (T)_value; } catch { var typeName = _value.GetType().Name; var message = string.Format("Cannot convert value of type '{0}' to type '{1}'", typeName, typeof(T).Name); throw new InvalidCastException(message); } } return defaultValue; } /// /// Attempts to convert the value to type of T, failing to do so will return the defaultValue. /// /// When no default value is supplied, required to supply the default type /// Optional parameter for default value, if not given it returns default of type T /// If value is not null, value is returned, else default value is returned public T TryParse(T defaultValue = default) { if (!HasValue) return defaultValue; try { return TryParse(defaultValue, typeof(T), _value, out var o) ? (T)o : defaultValue; } catch { return defaultValue; } } internal bool TryParse(object defaultValue, Type targetReturnType, object value, out object newObject) { newObject = defaultValue; if (value == null) return false; if (targetReturnType.IsGenericType && targetReturnType.GetGenericTypeDefinition() == typeof(Nullable<>)) targetReturnType = targetReturnType.GenericTypeArguments[0]; try { var valueType = value.GetType(); if (targetReturnType.IsArray && value is DynamicValue v) { value = v.Value; valueType = value.GetType(); } if (targetReturnType.IsArray) { if (!valueType.IsArray) { return false; } var ar = (object[])value; var t = targetReturnType.GetElementType(); var objectArray = ar .Select(a => TryParse(defaultValue, t, a, out var o) ? o : null) .Where(a => a != null) .ToArray(); var arr = Array.CreateInstance(t, objectArray.Length); Array.Copy(objectArray, arr, objectArray.Length); newObject = arr; return true; } if (valueType.IsAssignableFrom(targetReturnType)) { newObject = value; return true; } var stringValue = value as string; if (targetReturnType == typeof(DateTime) && DateTime.TryParse(stringValue, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result)) { newObject = result; return true; } if (stringValue != null) { if (targetReturnType == typeof(object)) { newObject = Convert.ChangeType(value, targetReturnType); return true; } var converter = TypeDescriptor.GetConverter(targetReturnType); if (converter.IsValid(stringValue)) { newObject = converter.ConvertFromInvariantString(stringValue); return true; } } else if (value is DynamicValue dv) return dv.TryParse(defaultValue, targetReturnType, dv.Value, out newObject); else if (targetReturnType == typeof(string)) { newObject = Convert.ChangeType(value, TypeCode.String, CultureInfo.InvariantCulture); return true; } else if (valueType.IsValueType) { newObject = Convert.ChangeType(_value, targetReturnType); return true; } else if (targetReturnType == typeof(DynamicDictionary) && valueType == typeof(Dictionary)) { newObject = DynamicDictionary.Create(value as Dictionary); return true; } else if (targetReturnType == typeof(object)) { newObject = value; return true; } } catch { return false; } return false; } public static bool operator ==(DynamicValue dynamicValue, object compareValue) { if (dynamicValue._value == null && compareValue == null) { return true; } return dynamicValue._value != null && dynamicValue._value.Equals(compareValue); } public static bool operator !=(DynamicValue dynamicValue, object compareValue) { return !(dynamicValue == compareValue); } /// /// Determines whether the specified is equal to the current . /// /// /// true if the specified is equal to the current ; otherwise, /// false. /// /// The to compare with the current . public override bool Equals(object compareValue) { if (ReferenceEquals(null, compareValue)) { return false; } if (ReferenceEquals(this, compareValue) || ReferenceEquals(_value, compareValue) || Equals(_value, compareValue) ) { return true; } return compareValue.GetType() == typeof(DynamicValue) && Equals((DynamicValue)compareValue); } /// /// Serves as a hash function for a particular type. /// /// A hash code for the current instance. public override int GetHashCode() { return _value != null ? _value.GetHashCode() : 0; } /// /// Provides implementation for binary operations. Classes derived from the class can override /// this method to specify dynamic behavior for operations such as addition and multiplication. /// /// /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of /// the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.) /// /// /// Provides information about the binary operation. The binder.Operation property returns an /// object. For example, for the sum = first + second statement, where first and second /// are derived from the DynamicObject class, binder.Operation returns ExpressionType.Add. /// /// /// The right operand for the binary operation. For example, for the sum = first + second statement, where first and second /// are derived from the DynamicObject class, is equal to second. /// /// The result of the binary operation. public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result) { object resultOfCast; result = null; if (binder.Operation != ExpressionType.Equal) { return false; } var convert = Binder.Convert(CSharpBinderFlags.None, arg.GetType(), typeof(DynamicValue)); if (!TryConvert((ConvertBinder)convert, out resultOfCast)) { return false; } result = resultOfCast == null ? Equals(arg, resultOfCast) : resultOfCast.Equals(arg); return true; } /// /// Provides implementation for type conversion operations. Classes derived from the class can /// override this method to specify dynamic behavior for operations that convert an object from one type to another. /// /// /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of /// the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.) /// /// /// Provides information about the conversion operation. The binder.Type property provides the type to which the object /// must be converted. For example, for the statement (String)sampleObject in C# (CType(sampleObject, Type) in Visual Basic), where /// sampleObject is an instance of the class derived from the class, binder.Type returns the /// type. The binder.Explicit property provides information about the kind of conversion that occurs. It returns /// true for explicit conversion and false for implicit conversion. /// /// The result of the type conversion operation. public override bool TryConvert(ConvertBinder binder, out object result) { result = null; if (_value == null) { return true; } var binderType = binder.Type; if (binderType == typeof(string)) { result = Convert.ToString(_value); return true; } if (binderType == typeof(Guid) || binderType == typeof(Guid?)) { Guid guid; if (Guid.TryParse(Convert.ToString(_value), out guid)) { result = guid; return true; } } else if (binderType == typeof(TimeSpan) || binderType == typeof(TimeSpan?)) { TimeSpan timespan; if (TimeSpan.TryParse(Convert.ToString(_value), out timespan)) { result = timespan; return true; } } else { if (binderType.IsGenericType && binderType.GetGenericTypeDefinition() == typeof(Nullable<>)) binderType = binderType.GetGenericArguments()[0]; var typeCode = Type.GetTypeCode(binderType); if (typeCode == TypeCode.Object) { if (binderType.IsAssignableFrom(_value.GetType())) { result = _value; return true; } else return false; } result = Convert.ChangeType(_value, typeCode); return true; } return base.TryConvert(binder, out result); } public override string ToString() { return _value == null ? base.ToString() : Convert.ToString(_value); } public static implicit operator bool(DynamicValue dynamicValue) { if (!dynamicValue.HasValue) return false; if (dynamicValue._value.GetType().IsValueType) return Convert.ToBoolean(dynamicValue._value); if (bool.TryParse(dynamicValue.ToString(CultureInfo.InvariantCulture), out var result)) return result; return true; } public static implicit operator string(DynamicValue dynamicValue) { return dynamicValue.HasValue ? Convert.ToString(dynamicValue._value) : null; } public static implicit operator int(DynamicValue dynamicValue) { if (dynamicValue._value.GetType().IsValueType) return Convert.ToInt32(dynamicValue._value); return int.Parse(dynamicValue.ToString(CultureInfo.InvariantCulture)); } public static implicit operator Guid(DynamicValue dynamicValue) { if (dynamicValue._value is Guid) { return (Guid)dynamicValue._value; } return Guid.Parse(dynamicValue.ToString(CultureInfo.InvariantCulture)); } public static implicit operator DateTime(DynamicValue dynamicValue) { if (dynamicValue._value is DateTime) { return (DateTime)dynamicValue._value; } return DateTime.Parse(dynamicValue.ToString(CultureInfo.InvariantCulture)); } public static implicit operator TimeSpan(DynamicValue dynamicValue) { if (dynamicValue._value is TimeSpan) { return (TimeSpan)dynamicValue._value; } return TimeSpan.Parse(dynamicValue.ToString(CultureInfo.InvariantCulture)); } public static implicit operator long(DynamicValue dynamicValue) { if (dynamicValue._value.GetType().IsValueType) return Convert.ToInt64(dynamicValue._value); return long.Parse(dynamicValue.ToString(CultureInfo.InvariantCulture)); } public static implicit operator float(DynamicValue dynamicValue) { if (dynamicValue._value.GetType().IsValueType) return Convert.ToSingle(dynamicValue._value); return float.Parse(dynamicValue.ToString(CultureInfo.InvariantCulture)); } public static implicit operator decimal(DynamicValue dynamicValue) { if (dynamicValue._value.GetType().IsValueType) return Convert.ToDecimal(dynamicValue._value); return decimal.Parse(dynamicValue.ToString(CultureInfo.InvariantCulture)); } public static implicit operator double(DynamicValue dynamicValue) { if (dynamicValue._value.GetType().IsValueType) return Convert.ToDouble(dynamicValue._value); return double.Parse(dynamicValue.ToString(CultureInfo.InvariantCulture)); } } }