using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using Jint.Native;
using Jint.Native.Object;
using Jint.Native.Symbol;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Descriptors.Specialized;
namespace Jint.Runtime.Interop
{
///
/// Wraps a CLR instance
///
public sealed class ObjectWrapper : ObjectInstance, IObjectWrapper
{
public ObjectWrapper(Engine engine, object obj)
: base(engine)
{
Target = obj;
var type = obj.GetType();
if (ObjectIsArrayLikeClrCollection(type))
{
// create a forwarder to produce length from Count or Length if one of them is present
var lengthProperty = type.GetProperty("Count") ?? type.GetProperty("Length");
if (lengthProperty is null)
{
return;
}
IsArrayLike = true;
var functionInstance = new ClrFunctionInstance(engine, "length", (thisObj, arguments) => JsNumber.Create((int) lengthProperty.GetValue(obj)));
var descriptor = new GetSetPropertyDescriptor(functionInstance, Undefined, PropertyFlag.Configurable);
SetProperty(KnownKeys.Length, descriptor);
}
}
private static bool ObjectIsArrayLikeClrCollection(Type type)
{
if (typeof(ICollection).IsAssignableFrom(type))
{
return true;
}
foreach (var interfaceType in type.GetInterfaces())
{
if (!interfaceType.IsGenericType)
{
continue;
}
if (interfaceType.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>)
|| interfaceType.GetGenericTypeDefinition() == typeof(ICollection<>))
{
return true;
}
}
return false;
}
public object Target { get; }
public override bool IsArrayLike { get; }
public override bool Set(JsValue property, JsValue value, JsValue receiver)
{
if (!CanPut(property))
{
return false;
}
var ownDesc = GetOwnProperty(property);
if (ownDesc == null)
{
return false;
}
ownDesc.Value = value;
return true;
}
public override List GetOwnPropertyKeys(Types types = Types.None | Types.String | Types.Symbol)
{
return new List(EnumerateOwnPropertyKeys(types));
}
public override IEnumerable> GetOwnProperties()
{
foreach (var key in EnumerateOwnPropertyKeys(Types.String | Types.Symbol))
{
yield return new KeyValuePair(key, GetOwnProperty(key));
}
}
private IEnumerable EnumerateOwnPropertyKeys(Types types)
{
var basePropertyKeys = base.GetOwnPropertyKeys(types);
// prefer object order, add possible other properties after
var processed = basePropertyKeys.Count > 0 ? new HashSet() : null;
var includeStrings = (types & Types.String) != 0;
if (Target is IDictionary dictionary && includeStrings)
{
// we take values exposed as dictionary keys only
foreach (var key in dictionary.Keys)
{
if (_engine.ClrTypeConverter.TryConvert(key, typeof(string), CultureInfo.InvariantCulture, out var stringKey))
{
var jsString = JsString.Create((string) stringKey);
processed?.Add(jsString);
yield return jsString;
}
}
}
else if (includeStrings)
{
// we take public properties and fields
var type = Target.GetType();
foreach (var p in type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
{
var indexParameters = p.GetIndexParameters();
if (indexParameters.Length == 0)
{
var jsString = JsString.Create(p.Name);
processed?.Add(jsString);
yield return jsString;
}
}
foreach (var f in type.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
{
var jsString = JsString.Create(f.Name);
processed?.Add(jsString);
yield return jsString;
}
}
if (processed != null)
{
// we have base keys
for (var i = 0; i < basePropertyKeys.Count; i++)
{
var key = basePropertyKeys[i];
if (processed.Add(key))
{
yield return key;
}
}
}
}
public override PropertyDescriptor GetOwnProperty(JsValue property)
{
if (TryGetProperty(property, out var x))
{
return x;
}
if (property.IsSymbol() && property == GlobalSymbolRegistry.Iterator)
{
var iteratorFunction = new ClrFunctionInstance(Engine, "iterator", (thisObject, arguments) => _engine.Iterator.Construct(this), 1, PropertyFlag.Configurable);
var iteratorProperty = new PropertyDescriptor(iteratorFunction, PropertyFlag.Configurable | PropertyFlag.Writable);
SetProperty(GlobalSymbolRegistry.Iterator, iteratorProperty);
return iteratorProperty;
}
var memberAccessor = Engine.Options._MemberAccessor;
if (memberAccessor != null)
{
var result = memberAccessor.Invoke(Engine, Target, property.ToString());
if (result != null)
{
return new PropertyDescriptor(result, PropertyFlag.OnlyEnumerable);
}
}
var type = Target.GetType();
var key = new ClrPropertyDescriptorFactoriesKey(type, property.ToString());
if (!_engine.ClrPropertyDescriptorFactories.TryGetValue(key, out var factory))
{
factory = ResolveProperty(type, property.ToString());
_engine.ClrPropertyDescriptorFactories[key] = factory;
}
var descriptor = factory(_engine, Target);
AddProperty(property, descriptor);
return descriptor;
}
private Func ResolveProperty(Type type, string propertyName)
{
var isNumber = uint.TryParse(propertyName, out _);
// properties and fields cannot be numbers
if (!isNumber)
{
// look for a property
PropertyInfo property = null;
foreach (var p in type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
{
if (EqualsIgnoreCasing(p.Name, propertyName))
{
property = p;
break;
}
}
if (property != null)
{
return (engine, target) => new PropertyInfoDescriptor(engine, property, target);
}
// look for a field
FieldInfo field = null;
foreach (var f in type.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
{
if (EqualsIgnoreCasing(f.Name, propertyName))
{
field = f;
break;
}
}
if (field != null)
{
return (engine, target) => new FieldInfoDescriptor(engine, field, target);
}
// if no properties were found then look for a method
List methods = null;
foreach (var m in type.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
{
if (EqualsIgnoreCasing(m.Name, propertyName))
{
methods ??= new List();
methods.Add(m);
}
}
if (methods?.Count > 0)
{
return (engine, target) => new PropertyDescriptor(new MethodInfoFunctionInstance(engine, methods.ToArray()), PropertyFlag.OnlyEnumerable);
}
}
// if no methods are found check if target implemented indexing
if (IndexDescriptor.TryFindIndexer(_engine, type, propertyName, out _, out _, out _))
{
return (engine, target) => new IndexDescriptor(engine, propertyName, target);
}
// try to find a single explicit property implementation
List list = null;
foreach (Type iface in type.GetInterfaces())
{
foreach (var iprop in iface.GetProperties())
{
if (EqualsIgnoreCasing(iprop.Name, propertyName))
{
list ??= new List();
list.Add(iprop);
}
}
}
if (list?.Count == 1)
{
return (engine, target) => new PropertyInfoDescriptor(engine, list[0], target);
}
// try to find explicit method implementations
List explicitMethods = null;
foreach (Type iface in type.GetInterfaces())
{
foreach (var imethod in iface.GetMethods())
{
if (EqualsIgnoreCasing(imethod.Name, propertyName))
{
explicitMethods ??= new List();
explicitMethods.Add(imethod);
}
}
}
if (explicitMethods?.Count > 0)
{
return (engine, target) => new PropertyDescriptor(new MethodInfoFunctionInstance(engine, explicitMethods.ToArray()), PropertyFlag.OnlyEnumerable);
}
// try to find explicit indexer implementations
foreach (Type interfaceType in type.GetInterfaces())
{
if (IndexDescriptor.TryFindIndexer(_engine, interfaceType, propertyName, out _, out _, out _))
{
return (engine, target) => new IndexDescriptor(engine, interfaceType, propertyName, target);
}
}
return (engine, target) => PropertyDescriptor.Undefined;
}
private static bool EqualsIgnoreCasing(string s1, string s2)
{
bool equals = false;
if (s1.Length == s2.Length)
{
if (s1.Length > 0)
{
equals = char.ToLowerInvariant(s1[0]) == char.ToLowerInvariant(s2[0]);
}
if (equals && s1.Length > 1)
{
equals = s1.Substring(1) == s2.Substring(1);
}
}
return equals;
}
}
}