diff --git a/Jurassic/Library/ClrWrapper/ClrInstanceTypeWrapper.cs b/Jurassic/Library/ClrWrapper/ClrInstanceTypeWrapper.cs index c8262f63..0bc2ebc6 100644 --- a/Jurassic/Library/ClrWrapper/ClrInstanceTypeWrapper.cs +++ b/Jurassic/Library/ClrWrapper/ClrInstanceTypeWrapper.cs @@ -113,6 +113,18 @@ public override string DebuggerDisplayType + // OBJECTINSTANCE OVERRIDES + //_________________________________________________________________________________________ + + /// + /// This object is a Clr Wrapper + /// + protected override bool IsClrWrapper + { + get { return true; } + } + + // OBJECT OVERRIDES //_________________________________________________________________________________________ diff --git a/Jurassic/Library/ClrWrapper/ClrInstanceWrapper.cs b/Jurassic/Library/ClrWrapper/ClrInstanceWrapper.cs index 822d0497..a98905f5 100644 --- a/Jurassic/Library/ClrWrapper/ClrInstanceWrapper.cs +++ b/Jurassic/Library/ClrWrapper/ClrInstanceWrapper.cs @@ -122,6 +122,13 @@ public override string DebuggerDisplayType // return base.GetPrimitiveValue(typeHint); //} + /// + /// This object is a Clr Wrapper + /// + protected override bool IsClrWrapper + { + get { return true; } + } diff --git a/Jurassic/Library/ClrWrapper/ClrStaticTypeWrapper.cs b/Jurassic/Library/ClrWrapper/ClrStaticTypeWrapper.cs index 01e19dcf..a02e5334 100644 --- a/Jurassic/Library/ClrWrapper/ClrStaticTypeWrapper.cs +++ b/Jurassic/Library/ClrWrapper/ClrStaticTypeWrapper.cs @@ -199,6 +199,7 @@ internal static void PopulateMembers(ObjectInstance target, Type type, BindingFl { // Register static methods as functions. var methodGroups = new Dictionary>(); + int indexerCounter = 0; foreach (var member in type.GetMembers(BindingFlags.Public | BindingFlags.DeclaredOnly | flags)) { switch (member.MemberType) @@ -218,7 +219,17 @@ internal static void PopulateMembers(ObjectInstance target, Type type, BindingFl ClrFunction getter = getMethod == null ? null : new ClrFunction(target.Engine.Function.InstancePrototype, new ClrBinder(getMethod)); var setMethod = property.GetSetMethod(); ClrFunction setter = setMethod == null ? null : new ClrFunction(target.Engine.Function.InstancePrototype, new ClrBinder(setMethod)); - target.DefineProperty(property.Name, new PropertyDescriptor(getter, setter, PropertyAttributes.NonEnumerable), false); + + PropertyAttributes propertyAttributes = PropertyAttributes.NonEnumerable; + if (property.GetIndexParameters().Length > 0) + { + propertyAttributes |= PropertyAttributes.IsIndexProperty; + // Put index after name of indexer property to avoid conflict with other properties with same name and other indexers + target.DefineProperty(string.Format("{0}{1}", property.Name, indexerCounter++), + new PropertyDescriptor(getter, setter, propertyAttributes), false); + } + else + target.DefineProperty(property.Name, new PropertyDescriptor(getter, setter, propertyAttributes), false); // Property getters and setters also show up as methods, so remove them here. // NOTE: only works if properties are enumerated after methods. @@ -254,6 +265,18 @@ internal static void PopulateMembers(ObjectInstance target, Type type, BindingFl + // OBJECTINSTANCE OVERRIDES + //_________________________________________________________________________________________ + + /// + /// This object is a Clr Wrapper + /// + protected override bool IsClrWrapper + { + get { return true; } + } + + // OBJECT OVERRIDES //_________________________________________________________________________________________ diff --git a/Jurassic/Library/Object/HiddenClassSchema.cs b/Jurassic/Library/Object/HiddenClassSchema.cs index 9aab1b16..ec34631d 100644 --- a/Jurassic/Library/Object/HiddenClassSchema.cs +++ b/Jurassic/Library/Object/HiddenClassSchema.cs @@ -14,7 +14,7 @@ internal class HiddenClassSchema private Dictionary properties; // Transitions - private struct TransitionInfo + private struct TransitionInfo { public object Key; public PropertyAttributes Attributes; @@ -124,6 +124,14 @@ public SchemaProperty GetPropertyIndexAndAttributes(object key) return propertyInfo; } + public IEnumerable GetIndexers() + { + if (this.properties == null) + this.properties = CreatePropertiesDictionary(); + IEnumerable result = this.properties.Values.Where(sp => sp.IsIndexer); + return result; + } + /// /// Adds a property to the schema. /// diff --git a/Jurassic/Library/Object/ObjectInstance.cs b/Jurassic/Library/Object/ObjectInstance.cs index 4cf7bd47..a9a02136 100644 --- a/Jurassic/Library/Object/ObjectInstance.cs +++ b/Jurassic/Library/Object/ObjectInstance.cs @@ -321,7 +321,16 @@ private object GetPropertyValue(uint index, ObjectInstance thisValue) // The property might exist in the prototype. if (this.prototype == null) return thisValue.GetMissingPropertyValue(index.ToString()); - return this.prototype.GetPropertyValue(index, thisValue); + object result = this.prototype.GetPropertyValue(index, thisValue); + if (result == null) + { + result = GetArrayElementPropertyValue(index, thisValue); + } + if (result == null) + { + result = GetIndexerPropertyValue(index, thisValue); + } + return result; } /// @@ -339,7 +348,12 @@ public object GetPropertyValue(object key) return GetPropertyValue(arrayIndex); // Otherwise, the property is a name. - return GetNamedPropertyValue(key, this); + object result = GetNamedPropertyValue(key, this); + + // Otherwise, the property can be an indexer + if (result == null) + result = GetIndexerPropertyValue(key, this); + return result; } /// @@ -385,7 +399,11 @@ public object GetPropertyValue(PropertyReference propertyReference) propertyReference.ClearCache(); if (this.Prototype == null) return this.GetMissingPropertyValue(propertyReference.Name); - return this.Prototype.GetNamedPropertyValue(propertyReference.Name, this); + + var propertyValuePrototype = this.Prototype.GetNamedPropertyValue(propertyReference.Name, this); + if (propertyValuePrototype == null) + propertyValuePrototype = GetIndexerPropertyValue(propertyReference.Name, this); + return propertyValuePrototype; } } @@ -427,6 +445,71 @@ private object GetNamedPropertyValue(object key, ObjectInstance thisValue) return thisValue.GetMissingPropertyValue(key); } + /// + /// Gets the value of the property using indexer. + /// + /// The property key - string, int, and so on. + /// The type should be the same as the type of the indexer parameter + /// The value of the "this" keyword inside a getter. + /// The value of the property, or null if the property doesn't exist. + /// The prototype chain is searched if the indexer property does not exist directly on + /// this object. + private object GetIndexerPropertyValue(object key, ObjectInstance thisValue) + { + if (!IsClrWrapper) + return null; // This method is only for CLR clases + + ObjectInstance prototypeObject = thisValue; + do + { + // Retrieve information about the property. + IEnumerable properties = prototypeObject.schema.GetIndexers(); + foreach (SchemaProperty property in properties) + { + if (property.Exists == true) + { + // The property was found! + object value = prototypeObject.propertyValues[property.Index]; + try + { + return ((PropertyAccessorValue)value).GetValue(thisValue, new object[] { key }); + } + catch (Exception) + { + } + } + } + + // Traverse the prototype chain. + prototypeObject = prototypeObject.prototype; + } while (prototypeObject != null); + + // The indexer property doesn't exist. + return null; + } + + /// + /// Gets the value of the property with the given array index if current object is an Array. + /// + /// The array index of the property. + /// The value of the "this" keyword inside a getter. + /// The value of the property, or null if the object is not an Array. + private object GetArrayElementPropertyValue(uint index, ObjectInstance thisValue) + { + if (!IsClrWrapper) + return null; //This method is only for CLR clases + + object result = null; + ClrInstanceWrapper thisWrapper = thisValue as ClrInstanceWrapper; + if (thisWrapper != null && thisWrapper.WrappedInstance.GetType().IsArray) + { + Array thisArray = thisWrapper.WrappedInstance as Array; + result = thisArray.GetValue(index); + } + + return result; + } + /// /// Retrieves the value of a property which doesn't exist on the object. This method can /// be overridden to effectively construct properties on the fly. The default behavior is @@ -500,6 +583,11 @@ public virtual void SetPropertyValue(uint index, object value, bool throwOnError string indexStr = index.ToString(); bool exists = SetPropertyValueIfExists(indexStr, value, throwOnError); if (exists == false) + { + exists = SetArrayElementPropertyValue(index, this, value); + } + if (exists == false && + SetIndexerPropertyValue(index, value, this) == false) { // The property doesn't exist - add it. AddProperty(indexStr, value, PropertyAttributes.FullAccess, throwOnError); @@ -528,7 +616,8 @@ public void SetPropertyValue(object key, object value, bool throwOnError) } bool exists = SetPropertyValueIfExists(key, value, throwOnError); - if (exists == false) + if (exists == false && + SetIndexerPropertyValue(key, value, this) == false) { // The property doesn't exist - add it. AddProperty(key, value, PropertyAttributes.FullAccess, throwOnError); @@ -666,7 +755,95 @@ public bool SetPropertyValueIfExists(object key, object value, bool throwOnError // The property does not exist. return false; } - + + /// + /// Sets the value of the property using indexer. + /// + /// The property key - string, int, and so on. + /// The type should be the same as the type of the indexer parameter + /// The value to set + /// The value of the "this" keyword inside a setter. + /// true if the property exists; false otherwise. + /// The prototype chain is searched if the indexer property does not exist directly on + /// this object. + private bool SetIndexerPropertyValue(object key, object value, ObjectInstance thisValue) + { + if (!IsClrWrapper) + return false; // This method is only for CLR clases + + ObjectInstance prototypeObject = thisValue; + do + { + // Retrieve information about the property. + IEnumerable properties = prototypeObject.schema.GetIndexers(); + foreach (SchemaProperty property in properties) + { + if (property.Exists == true) + { + // The property was found! + object propertyAccessor = prototypeObject.propertyValues[property.Index]; + try + { + ((PropertyAccessorValue)propertyAccessor).SetValue(thisValue, key, value); + return true; + } + catch (Exception) + { + } + } + } + + // Traverse the prototype chain. + prototypeObject = prototypeObject.prototype; + } while (prototypeObject != null); + + // The indexer property doesn't exist. + return false; + } + + /// + /// Sets the value of the property with the given array index if current object is an Array + /// + /// The array index of the property to set. + /// The value of the "this" keyword inside a setter. + /// The value to set + /// true if the value is set; false otherwise. + private bool SetArrayElementPropertyValue(uint index, ObjectInstance thisValue, object value) + { + if (!IsClrWrapper) + return false; // This method is only for CLR clases + + ClrInstanceWrapper thisWrapper = thisValue as ClrInstanceWrapper; + if (thisWrapper != null && thisWrapper.WrappedInstance.GetType().IsArray) + { + Array thisArray = thisWrapper.WrappedInstance as Array; + object unwrappedValue = value; + if (value is ClrStaticTypeWrapper) + { + unwrappedValue = (value as ClrStaticTypeWrapper).WrappedType; + } + else if (value is ClrInstanceTypeWrapper) + { + unwrappedValue = (value as ClrInstanceTypeWrapper).WrappedType; + } + else if (value is ClrInstanceWrapper) + { + unwrappedValue = (value as ClrInstanceWrapper).WrappedInstance; + } + + // Convert to element type if possible + if (unwrappedValue != null && unwrappedValue is IConvertible) + { + Type elementType = thisArray.GetType().GetElementType(); + unwrappedValue = Convert.ChangeType(unwrappedValue, elementType); + } + thisArray.SetValue(unwrappedValue, (long)index); + return true; + } + + return false; + } + /// /// Deletes the property with the given array index. /// @@ -1053,6 +1230,14 @@ public override string ToString() } } + /// + /// Returns true when JavaScript object is a Clr Wrapper + /// + protected virtual bool IsClrWrapper + { + get { return false; } + } + // JAVASCRIPT FUNCTIONS diff --git a/Jurassic/Library/Object/PropertyAccessorValue.cs b/Jurassic/Library/Object/PropertyAccessorValue.cs index b8581981..59e6a637 100644 --- a/Jurassic/Library/Object/PropertyAccessorValue.cs +++ b/Jurassic/Library/Object/PropertyAccessorValue.cs @@ -45,24 +45,25 @@ public FunctionInstance Setter /// Gets the property value by calling the getter, if one is present. /// /// The value of the "this" keyword inside the getter. + /// /// The property value returned by the getter. - public object GetValue(ObjectInstance thisObject) + public object GetValue(ObjectInstance thisObject, params object[] argumentValues) { if (this.getter == null) return Undefined.Value; - return this.getter.CallLateBound(thisObject); + return this.getter.CallLateBound(thisObject, argumentValues); } /// /// Sets the property value by calling the setter, if one is present. /// /// The value of the "this" keyword inside the setter. - /// The desired value. - public void SetValue(ObjectInstance thisObject, object value) + /// The values as arguments list. + public void SetValue(ObjectInstance thisObject, params object[] argumentValues) { if (this.setter == null) return; - this.setter.CallLateBound(thisObject, value); + this.setter.CallLateBound(thisObject, argumentValues); } /// diff --git a/Jurassic/Library/Object/PropertyAttributes.cs b/Jurassic/Library/Object/PropertyAttributes.cs index 78761a69..c869948a 100644 --- a/Jurassic/Library/Object/PropertyAttributes.cs +++ b/Jurassic/Library/Object/PropertyAttributes.cs @@ -53,5 +53,10 @@ public enum PropertyAttributes /// Indicates the property is the "magic" length property (only found on arrays). /// IsLengthProperty = 16, + + /// + /// Indicates the property is index property + /// + IsIndexProperty = 32, } } diff --git a/Jurassic/Library/Object/SchemaProperty.cs b/Jurassic/Library/Object/SchemaProperty.cs index d8bc179d..5db234ea 100644 --- a/Jurassic/Library/Object/SchemaProperty.cs +++ b/Jurassic/Library/Object/SchemaProperty.cs @@ -94,6 +94,14 @@ public bool IsLength { get { return (this.Attributes & PropertyAttributes.IsLengthProperty) != 0; } } + + /// + /// Gets a value that indicates whether the property is indexer property. + /// + public bool IsIndexer + { + get { return (this.Attributes & PropertyAttributes.IsIndexProperty) != 0; } + } } }