/// <summary> /// Deletes a property from the schema. /// </summary> /// <param name="key"> The property key of the property to delete. </param> /// <returns> A new schema without the property. </returns> public HiddenClassSchema DeleteProperty(object key) { // Check if there is a transition to the schema already. HiddenClassSchema newSchema = null; if (this.deleteTransitions != null) { this.deleteTransitions.TryGetValue(key, out newSchema); } if (newSchema == null) { // Create a new schema based on this one. var properties = this.properties == null?CreatePropertiesDictionary() : new Dictionary <object, SchemaProperty>(this.properties); if (properties.Remove(key) == false) { throw new InvalidOperationException(string.Format("The property '{0}' does not exist.", key)); } newSchema = new HiddenClassSchema(properties, this.NextValueIndex); // Add a transition to the new schema. if (this.deleteTransitions == null) { this.deleteTransitions = new Dictionary <object, HiddenClassSchema>(1); } this.deleteTransitions.Add(key, newSchema); } return(newSchema); }
/// <summary> /// Called by derived classes to create a new object instance. /// </summary> /// <param name="prototype"> The next object in the prototype chain. Cannot be <c>null</c>. </param> protected ObjectInstance(ObjectInstance prototype) { if (prototype == null) throw new ArgumentNullException("prototype"); this.prototype = prototype; this.engine = prototype.Engine; this.schema = this.engine.EmptySchema; }
/// <summary> /// Called by derived classes to create a new object instance. /// </summary> /// <param name="engine"> The script engine associated with this object. </param> /// <param name="prototype"> The next object in the prototype chain. Can be <c>null</c>. </param> protected ObjectInstance(ScriptEngine engine, ObjectInstance prototype) { if (engine == null) throw new ArgumentNullException("engine"); this.engine = engine; this.prototype = prototype; this.schema = engine.EmptySchema; }
/// <summary> /// Enumerates the property names for this schema. /// </summary> /// <returns> An enumerable collection of property names. </returns> public IEnumerable <object> EnumeratePropertyNames() { if (this.properties == null) { this.properties = CreatePropertiesDictionary(); } this.parent = null; // Prevents the properties dictionary from being stolen while an enumeration is in progress. foreach (var pair in this.properties) { yield return(pair.Key); } }
/// <summary> /// Enumerates the property names and values for this schema. /// </summary> /// <param name="values"> The array containing the property values. </param> /// <returns> An enumerable collection of property names and values. </returns> public IEnumerable <PropertyNameAndValue> EnumeratePropertyNamesAndValues(object[] values) { if (this.properties == null) { this.properties = CreatePropertiesDictionary(); } this.parent = null; // Prevents the properties dictionary from being stolen while an enumeration is in progress. foreach (var pair in this.properties) { yield return(new PropertyNameAndValue(pair.Key, values[pair.Value.Index], pair.Value.Attributes)); } }
/// <summary> /// Modifies the attributes for a property in the schema. /// </summary> /// <param name="key"> The property key of the property to modify. </param> /// <param name="attributes"> The new attributes. </param> /// <returns> A new schema with the modified property. </returns> public HiddenClassSchema SetPropertyAttributes(object key, PropertyAttributes attributes) { // Package the name and attributes into a struct. var transitionInfo = new TransitionInfo() { Key = key, Attributes = attributes }; // Check if there is a transition to the schema already. HiddenClassSchema newSchema = null; if (this.modifyTransitions != null) { this.modifyTransitions.TryGetValue(transitionInfo, out newSchema); } if (newSchema == null) { // Create the properties dictionary if it hasn't already been created. if (this.properties == null) { this.properties = CreatePropertiesDictionary(); } // Check the attributes differ from the existing attributes. SchemaProperty propertyInfo; if (this.properties.TryGetValue(key, out propertyInfo) == false) { throw new InvalidOperationException(string.Format("The property '{0}' does not exist.", key)); } if (attributes == propertyInfo.Attributes) { return(this); } // Create a new schema based on this one. var properties = new Dictionary <object, SchemaProperty>(this.properties); properties[key] = new SchemaProperty(propertyInfo.Index, attributes); newSchema = new HiddenClassSchema(properties, this.NextValueIndex); // Add a transition to the new schema. if (this.modifyTransitions == null) { this.modifyTransitions = new Dictionary <TransitionInfo, HiddenClassSchema>(1); } this.modifyTransitions.Add(transitionInfo, newSchema); } return(newSchema); }
/// <summary> /// Adds a property to the schema. /// </summary> /// <param name="key"> The property key of the property to add. </param> /// <param name="attributes"> The property attributes. </param> /// <returns> A new schema with the extra property. </returns> public HiddenClassSchema AddProperty(object key, PropertyAttributes attributes = PropertyAttributes.FullAccess) { // Package the name and attributes into a struct. var transitionInfo = new TransitionInfo() { Key = key, Attributes = attributes }; // Check if there is a transition to the schema already. HiddenClassSchema newSchema = null; if (this.addTransitions != null) { this.addTransitions.TryGetValue(transitionInfo, out newSchema); } if (newSchema == null) { if (this.parent == null) { // Create a new schema based on this one. A complete copy must be made of the properties hashtable. var properties = new Dictionary <object, SchemaProperty>(this.properties); properties.Add(key, new SchemaProperty(this.NextValueIndex, attributes)); newSchema = new HiddenClassSchema(properties, this.NextValueIndex + 1, this, transitionInfo); } else { // Create a new schema based on this one. The properties hashtable is "given // away" so a copy does not have to be made. if (this.properties == null) { this.properties = CreatePropertiesDictionary(); } this.properties.Add(key, new SchemaProperty(this.NextValueIndex, attributes)); newSchema = new HiddenClassSchema(this.properties, this.NextValueIndex + 1, this, transitionInfo); this.properties = null; } // Add a transition to the new schema. if (this.addTransitions == null) { this.addTransitions = new Dictionary <TransitionInfo, HiddenClassSchema>(1); } this.addTransitions.Add(transitionInfo, newSchema); } return(newSchema); }
/// <summary> /// Adds multiple properties to the schema. /// </summary> /// <param name="properties"> The properties to add. </param> /// <returns> A new schema with the extra properties. </returns> public HiddenClassSchema AddProperties(IEnumerable <PropertyNameAndValue> properties) { if (this.properties == null) { var propertyDictionary = new Dictionary <object, SchemaProperty>(properties.Count()); int nextValueIndex = 0; foreach (var property in properties) { propertyDictionary.Add(property.Key, new SchemaProperty(nextValueIndex++, property.Attributes)); } return(new HiddenClassSchema(propertyDictionary, nextValueIndex)); } else { // There are already properties in the schema. Just add them one by one. HiddenClassSchema newSchema = this; foreach (var property in properties) { newSchema = AddProperty(property.Key, property.Attributes); } return(newSchema); } }
/// <summary> /// Creates a new HiddenClassSchema instance from an add operation. /// </summary> private HiddenClassSchema(Dictionary<string, SchemaProperty> properties, int nextValueIndex, HiddenClassSchema parent, TransitionInfo addPropertyTransitionInfo) : this(properties, nextValueIndex) { this.parent = parent; this.addPropertyTransitionInfo = addPropertyTransitionInfo; }
/// <summary> /// Modifies the attributes for a property in the schema. /// </summary> /// <param name="name"> The name of the property to modify. </param> /// <param name="attributes"> The new attributes. </param> /// <returns> A new schema with the modified property. </returns> public HiddenClassSchema SetPropertyAttributes(string name, PropertyAttributes attributes) { // Package the name and attributes into a struct. var transitionInfo = new TransitionInfo() { Name = name, Attributes = attributes }; // Check if there is a transition to the schema already. HiddenClassSchema newSchema = null; if (this.modifyTransitions != null) this.modifyTransitions.TryGetValue(transitionInfo, out newSchema); if (newSchema == null) { // Create the properties dictionary if it hasn't already been created. if (this.properties == null) this.properties = CreatePropertiesDictionary(); // Check the attributes differ from the existing attributes. SchemaProperty propertyInfo; if (this.properties.TryGetValue(name, out propertyInfo) == false) throw new InvalidOperationException(string.Format("The property '{0}' does not exist.", name)); if (attributes == propertyInfo.Attributes) return this; // Create a new schema based on this one. var properties = new Dictionary<string, SchemaProperty>(this.properties); properties[name] = new SchemaProperty(propertyInfo.Index, attributes); newSchema = new HiddenClassSchema(properties, this.NextValueIndex); // Add a transition to the new schema. if (this.modifyTransitions == null) this.modifyTransitions = new Dictionary<TransitionInfo, HiddenClassSchema>(1); this.modifyTransitions.Add(transitionInfo, newSchema); } return newSchema; }
/// <summary> /// Deletes a property from the schema. /// </summary> /// <param name="name"> The name of the property to delete. </param> /// <returns> A new schema without the property. </returns> public HiddenClassSchema DeleteProperty(string name) { // Check if there is a transition to the schema already. HiddenClassSchema newSchema = null; if (this.deleteTransitions != null) this.deleteTransitions.TryGetValue(name, out newSchema); if (newSchema == null) { // Create a new schema based on this one. var properties = this.properties == null ? CreatePropertiesDictionary() : new Dictionary<string, SchemaProperty>(this.properties); if (properties.Remove(name) == false) throw new InvalidOperationException(string.Format("The property '{0}' does not exist.", name)); newSchema = new HiddenClassSchema(properties, this.NextValueIndex); // Add a transition to the new schema. if (this.deleteTransitions == null) this.deleteTransitions = new Dictionary<string, HiddenClassSchema>(1); this.deleteTransitions.Add(name, newSchema); } return newSchema; }
/// <summary> /// Adds a property to the schema. /// </summary> /// <param name="name"> The name of the property to add. </param> /// <param name="attributes"> The property attributes. </param> /// <returns> A new schema with the extra property. </returns> public HiddenClassSchema AddProperty(string name, PropertyAttributes attributes = PropertyAttributes.FullAccess) { // Package the name and attributes into a struct. var transitionInfo = new TransitionInfo() { Name = name, Attributes = attributes }; // Check if there is a transition to the schema already. HiddenClassSchema newSchema = null; if (this.addTransitions != null) this.addTransitions.TryGetValue(transitionInfo, out newSchema); if (newSchema == null) { if (this.parent == null) { // Create a new schema based on this one. A complete copy must be made of the properties hashtable. var properties = new Dictionary<string, SchemaProperty>(this.properties); properties.Add(name, new SchemaProperty(this.NextValueIndex, attributes)); newSchema = new HiddenClassSchema(properties, this.NextValueIndex + 1, this, transitionInfo); } else { // Create a new schema based on this one. The properties hashtable is "given // away" so a copy does not have to be made. if (this.properties == null) this.properties = CreatePropertiesDictionary(); this.properties.Add(name, new SchemaProperty(this.NextValueIndex, attributes)); newSchema = new HiddenClassSchema(this.properties, this.NextValueIndex + 1, this, transitionInfo); this.properties = null; } // Add a transition to the new schema. if (this.addTransitions == null) this.addTransitions = new Dictionary<TransitionInfo, HiddenClassSchema>(1); this.addTransitions.Add(transitionInfo, newSchema); } return newSchema; }
/// <summary> /// Sets a property value and attributes, or adds a new property if it doesn't already /// exist. Any existing attributes are ignored (and not modified). /// </summary> /// <param name="propertyName"> The name of the property. </param> /// <param name="value"> The intended value of the property. </param> /// <param name="attributes"> Attributes that indicate whether the property is writable, /// configurable and enumerable. </param> /// <param name="overwriteAttributes"> Indicates whether to overwrite any existing attributes. </param> internal void FastSetProperty(string propertyName, object value, PropertyAttributes attributes = PropertyAttributes.Sealed, bool overwriteAttributes = false) { int index = this.schema.GetPropertyIndex(propertyName); if (index < 0) { // The property is doesn't exist - add a new property. AddProperty(propertyName, value, attributes, false); return; } if (overwriteAttributes == true) this.schema = this.schema.SetPropertyAttributes(propertyName, attributes); this.propertyValues[index] = value; }
/// <summary> /// Adds a property to this object. The property must not already exist. /// </summary> /// <param name="propertyName"> The name of the property to add. </param> /// <param name="value"> The desired value of the property. This can be a /// <see cref="PropertyAccessorValue"/>. </param> /// <param name="attributes"> Attributes describing how the property may be modified. </param> /// <param name="throwOnError"> <c>true</c> to throw an exception if the property could not /// be added (i.e. if the object is not extensible). </param> /// <returns> <c>true</c> if the property was successfully added; <c>false</c> otherwise. </returns> private bool AddProperty(string propertyName, object value, PropertyAttributes attributes, bool throwOnError) { // Make sure adding a property is allowed. if (this.IsExtensible == false) { if (throwOnError == true) throw new JavaScriptException(this.Engine, "TypeError", string.Format("The property '{0}' cannot be created as the object is not extensible.", propertyName)); return false; } // To avoid running out of memory, restrict the number of properties. if (this.schema.PropertyCount == 16384) throw new JavaScriptException(this.engine, "Error", "Maximum number of named properties reached."); // Do not store nulls - null represents a non-existant value. value = value ?? Undefined.Value; // Add a new property to the schema. this.schema = this.schema.AddProperty(propertyName, attributes); // Check if the value array needs to be resized. int propertyIndex = this.schema.NextValueIndex - 1; if (propertyIndex >= this.InlinePropertyValues.Length) Array.Resize(ref this.propertyValues, this.InlinePropertyValues.Length * 2); // Set the value of the property. this.propertyValues[propertyIndex] = value; // Success. return true; }
/// <summary> /// Defines or redefines the value and attributes of a property. The prototype chain is /// not searched so if the property exists but only in the prototype chain a new property /// will be created. /// </summary> /// <param name="propertyName"> The name of the property to modify. </param> /// <param name="descriptor"> The property value and attributes. </param> /// <param name="throwOnError"> <c>true</c> to throw an exception if the property could not /// be set. This can happen if the property is not configurable or the object is sealed. </param> /// <returns> <c>true</c> if the property was successfully modified; <c>false</c> otherwise. </returns> public virtual bool DefineProperty(string propertyName, PropertyDescriptor descriptor, bool throwOnError) { // Retrieve info on the property. var current = this.schema.GetPropertyIndexAndAttributes(propertyName); if (current.Exists == false) { // Create a new property. return AddProperty(propertyName, descriptor.Value, descriptor.Attributes, throwOnError); } // If the current property is not configurable, then the only change that is allowed is // a change from one simple value to another (i.e. accessors are not allowed) and only // if the writable attribute is set. if (current.IsConfigurable == false) { // Get the current value of the property. object currentValue = this.propertyValues[current.Index]; object getter = null, setter = null; if (currentValue is PropertyAccessorValue) { getter = ((PropertyAccessorValue)currentValue).Getter; setter = ((PropertyAccessorValue)currentValue).Setter; } // Check if the modification is allowed. if (descriptor.Attributes != current.Attributes || (descriptor.IsAccessor == true && (getter != descriptor.Getter || setter != descriptor.Setter)) || (descriptor.IsAccessor == false && current.IsWritable == false && TypeComparer.SameValue(currentValue, descriptor.Value) == false)) { if (throwOnError == true) throw new JavaScriptException(this.Engine, "TypeError", string.Format("The property '{0}' is non-configurable.", propertyName)); return false; } } // Set the property value and attributes. this.schema = this.schema.SetPropertyAttributes(propertyName, descriptor.Attributes); this.propertyValues[current.Index] = descriptor.Value; return true; }
/// <summary> /// Deletes the property with the given name. /// </summary> /// <param name="propertyName"> The name of the property to delete. </param> /// <param name="throwOnError"> <c>true</c> to throw an exception if the property could not /// be set because the property was marked as non-configurable. </param> /// <returns> <c>true</c> if the property was successfully deleted, or if the property did /// not exist; <c>false</c> if the property was marked as non-configurable and /// <paramref name="throwOnError"/> was <c>false</c>. </returns> public bool Delete(string propertyName, bool throwOnError) { // Check if the property is an indexed property. uint arrayIndex = ArrayInstance.ParseArrayIndex(propertyName); if (arrayIndex != uint.MaxValue) return Delete(arrayIndex, throwOnError); // Retrieve the attributes for the property. var propertyInfo = this.schema.GetPropertyIndexAndAttributes(propertyName); if (propertyInfo.Exists == false) return true; // Property doesn't exist - delete succeeded! // Check if the property can be deleted. if (propertyInfo.IsConfigurable == false) { if (throwOnError == true) throw new JavaScriptException(this.Engine, "TypeError", string.Format("The property '{0}' cannot be deleted.", propertyName)); return false; } // Delete the property. this.schema = this.schema.DeleteProperty(propertyName); return true; }
/// <summary> /// Sets up multiple properties at once. /// </summary> /// <param name="properties"> The list of properties to set. </param> internal void FastSetProperties(IEnumerable<PropertyNameAndValue> properties) { if (this.schema.NextValueIndex != 0) throw new InvalidOperationException("This method can only be called on a virgin object (one with no properties)."); if (this.propertyValues.Length < properties.Count()) this.propertyValues = new object[properties.Count()]; var propertyDictionary = new Dictionary<object, SchemaProperty>(properties.Count()); int nextValueIndex = 0; foreach (var property in properties) { this.propertyValues[nextValueIndex] = property.Value; propertyDictionary.Add(property.Key, new SchemaProperty(nextValueIndex++, property.Attributes)); } this.schema = new HiddenClassSchema(propertyDictionary, nextValueIndex); }
/// <summary> /// Enumerates the property names and values for this schema. /// </summary> /// <param name="values"> The array containing the property values. </param> /// <returns> An enumerable collection of property names and values. </returns> public IEnumerable<PropertyNameAndValue> EnumeratePropertyNamesAndValues(object[] values) { if (this.properties == null) this.properties = CreatePropertiesDictionary(); this.parent = null; // Prevents the properties dictionary from being stolen while an enumeration is in progress. foreach (var pair in this.properties) yield return new PropertyNameAndValue(pair.Key, new PropertyDescriptor(values[pair.Value.Index], pair.Value.Attributes)); }
/// <summary> /// Creates a new HiddenClassSchema instance from an add operation. /// </summary> private HiddenClassSchema(Dictionary <object, SchemaProperty> properties, int nextValueIndex, HiddenClassSchema parent, TransitionInfo addPropertyTransitionInfo) : this(properties, nextValueIndex) { this.parent = parent; this.addPropertyTransitionInfo = addPropertyTransitionInfo; }
/// <summary> /// Caches property details. /// </summary> /// <param name="schema"> A reference to a schema that defines how properties are stored. </param> /// <param name="index"> The index into the property array. </param> internal void CachePropertyDetails(HiddenClassSchema schema, int index) { this.CachedSchema = schema; this.CachedIndex = index; }
/// <summary> /// Deletes the property with the given array index. /// </summary> /// <param name="index"> The array index of the property to delete. </param> /// <param name="throwOnError"> <c>true</c> to throw an exception if the property could not /// be set because the property was marked as non-configurable. </param> /// <returns> <c>true</c> if the property was successfully deleted, or if the property did /// not exist; <c>false</c> if the property was marked as non-configurable and /// <paramref name="throwOnError"/> was <c>false</c>. </returns> public virtual bool Delete(uint index, bool throwOnError) { string indexStr = index.ToString(); // Retrieve the attributes for the property. var propertyInfo = this.schema.GetPropertyIndexAndAttributes(indexStr); if (propertyInfo.Exists == false) return true; // Property doesn't exist - delete succeeded! // Delete the property. this.schema = this.schema.DeleteProperty(indexStr); return true; }