// METHODS //_________________________________________________________________________________________ /// <summary> /// Populates the given object with properties, field and methods based on the given .NET /// type. /// </summary> /// <param name="target"> The object to populate. </param> /// <param name="type"> The .NET type to search for methods. </param> /// <param name="flags"> <c>BindingFlags.Static</c> to populate static methods; /// <c>BindingFlags.Instance</c> to populate instance methods. </param> internal static void PopulateMembers(ObjectInstance target, Type type, BindingFlags flags) { // Register static methods as functions. var methodGroups = new Dictionary <string, List <MethodBase> >(); foreach (var member in type.GetMembers(BindingFlags.Public | BindingFlags.DeclaredOnly | flags)) { switch (member.MemberType) { case MemberTypes.Method: MethodInfo method = (MethodInfo)member; List <MethodBase> methodGroup; if (methodGroups.TryGetValue(method.Name, out methodGroup) == true) { methodGroup.Add(method); } else { methodGroups.Add(method.Name, new List <MethodBase>() { method }); } break; case MemberTypes.Property: PropertyInfo property = (PropertyInfo)member; var getMethod = property.GetGetMethod(); 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); // Property getters and setters also show up as methods, so remove them here. // NOTE: only works if properties are enumerated after methods. if (getMethod != null) { methodGroups.Remove(getMethod.Name); } if (setMethod != null) { methodGroups.Remove(setMethod.Name); } break; case MemberTypes.Field: FieldInfo field = (FieldInfo)member; ClrFunction fieldGetter = new ClrFunction(target.Engine.Function.InstancePrototype, new FieldGetterBinder(field)); ClrFunction fieldSetter = new ClrFunction(target.Engine.Function.InstancePrototype, new FieldSetterBinder(field)); target.DefineProperty(field.Name, new PropertyDescriptor(fieldGetter, fieldSetter, PropertyAttributes.NonEnumerable), false); break; case MemberTypes.Constructor: case MemberTypes.NestedType: case MemberTypes.Event: case MemberTypes.TypeInfo: // Support not yet implemented. break; } } foreach (var methodGroup in methodGroups.Values) { var binder = new ClrBinder(methodGroup); var function = new ClrFunction(target.Engine.Function.InstancePrototype, binder); target.FastSetProperty(binder.Name, function, PropertyAttributes.NonEnumerable, overwriteAttributes: true); } }
// METHODS //_________________________________________________________________________________________ /// <summary> /// Populates the given object with properties, field and methods based on the given .NET /// type. /// </summary> /// <param name="target"> The object to populate. </param> /// <param name="type"> The .NET type to search for methods. </param> /// <param name="flags"> <c>BindingFlags.Static</c> to populate static methods; /// <c>BindingFlags.Instance</c> to populate instance methods. </param> internal static void PopulateMembers(ObjectInstance target, Type type, BindingFlags flags) { // Register static methods as functions. var methodGroups = new Dictionary<string, List<MethodBase>>(); foreach (var member in type.GetMembers(BindingFlags.Public | BindingFlags.DeclaredOnly | flags)) { switch (member.MemberType) { case MemberTypes.Method: MethodInfo method = (MethodInfo)member; List<MethodBase> methodGroup; if (methodGroups.TryGetValue(method.Name, out methodGroup) == true) methodGroup.Add(method); else methodGroups.Add(method.Name, new List<MethodBase>() { method }); break; case MemberTypes.Property: PropertyInfo property = (PropertyInfo)member; var getMethod = property.GetGetMethod(); 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); // Property getters and setters also show up as methods, so remove them here. // NOTE: only works if properties are enumerated after methods. if (getMethod != null) methodGroups.Remove(getMethod.Name); if (setMethod != null) methodGroups.Remove(setMethod.Name); break; case MemberTypes.Field: FieldInfo field = (FieldInfo)member; ClrFunction fieldGetter = new ClrFunction(target.Engine.Function.InstancePrototype, new FieldGetterBinder(field)); ClrFunction fieldSetter = new ClrFunction(target.Engine.Function.InstancePrototype, new FieldSetterBinder(field)); target.DefineProperty(field.Name, new PropertyDescriptor(fieldGetter, fieldSetter, PropertyAttributes.NonEnumerable), false); break; case MemberTypes.Constructor: case MemberTypes.NestedType: case MemberTypes.Event: case MemberTypes.TypeInfo: // Support not yet implemented. break; } } foreach (var methodGroup in methodGroups.Values) { var binder = new ClrBinder(methodGroup); var function = new ClrFunction(target.Engine.Function.InstancePrototype, binder); target.FastSetProperty(binder.Name, function, PropertyAttributes.NonEnumerable, overwriteAttributes: true); } }
/// <summary> /// Populates the object with functions by searching a .NET type for methods marked with /// the [JSFunction] attribute. Should be called only once at startup. /// </summary> /// <param name="type"> The type to search for methods. </param> internal protected void PopulateFunctions(Type type) { if (type == null) type = this.GetType(); // Group the methods on the given type by name. var functions = new Dictionary<string, MethodGroup>(20); var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); foreach (var method in methods) { // Make sure the method has the [JSFunction] attribute. var attribute = (JSFunctionAttribute)Attribute.GetCustomAttribute(method, typeof(JSFunctionAttribute)); if (attribute == null) continue; // Determine the name of the method. string name; if (attribute.Name != null) { name = Compiler.Lexer.ResolveIdentifier(this.Engine, attribute.Name); if (name == null) throw new InvalidOperationException(string.Format("The name provided to [JSFunction] on {0} is not a valid identifier.", method)); } else name = method.Name; // Get a reference to the method group. MethodGroup methodGroup; if (functions.ContainsKey(name) == false) { methodGroup = new MethodGroup { Methods = new List<JSBinderMethod>(1), Length = -1 }; functions.Add(name, methodGroup); } else methodGroup = functions[name]; // Add the method to the list. methodGroup.Methods.Add(new JSBinderMethod(method, attribute.Flags)); // If the length doesn't equal -1, that indicates an explicit length has been set. // Make sure it is consistant with the other methods. if (attribute.Length >= 0) { if (methodGroup.Length != -1 && methodGroup.Length != attribute.Length) throw new InvalidOperationException(string.Format("Inconsistant Length property detected on {0}.", method)); methodGroup.Length = attribute.Length; } } // Now set the relevant properties on the object. foreach (KeyValuePair<string, MethodGroup> pair in functions) { string name = pair.Key; MethodGroup methodGroup = pair.Value; // Add the function as a property of the object. this.FastSetProperty(name, new ClrFunction(this.Engine.Function.InstancePrototype, methodGroup.Methods, name, methodGroup.Length), PropertyAttributes.NonEnumerable); } PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); foreach (PropertyInfo prop in properties) { var attribute = Attribute.GetCustomAttribute(prop, typeof(JSPropertyAttribute), false) as JSPropertyAttribute; if (attribute == null) continue; string name; if (!string.IsNullOrEmpty(attribute.Name)) { name = Compiler.Lexer.ResolveIdentifier(this.Engine, attribute.Name); if (name == null) throw new InvalidOperationException(string.Format("The name provided to [JSProperty] on {0} is not a valid identifier.", prop)); } else { name = prop.Name; } ClrFunction getter = null, setter = null; PropertyAttributes descriptorAttributes = PropertyAttributes.Sealed; if (prop.CanRead) { var getMethod = prop.GetGetMethod(true); getter = new ClrFunction(engine.Function.InstancePrototype, new ClrBinder(getMethod)); descriptorAttributes |= PropertyAttributes.IsAccessorProperty; } if (prop.CanWrite) { var setMethod = prop.GetSetMethod(); setter = new ClrFunction(engine.Function.InstancePrototype, new ClrBinder(setMethod)); descriptorAttributes |= PropertyAttributes.Writable; } if (attribute.IsEnumerable) descriptorAttributes |= PropertyAttributes.Enumerable; if (attribute.IsConfigurable) descriptorAttributes |= PropertyAttributes.Configurable; PropertyDescriptor descriptor = new PropertyDescriptor(getter, setter, descriptorAttributes); this.DefineProperty(name, descriptor, true); } }
/// <summary> /// Populates the object with functions by searching a .NET type for methods marked with /// the [JSFunction] attribute. Should be called only once at startup. Also automatically /// populates properties marked with the [JSProperty] attribute. /// </summary> /// <param name="type"> The type to search for methods. </param> /// <param name="bindingFlags"> The binding flags to use to search for properties and methods. </param> internal protected void PopulateFunctions(Type type, BindingFlags bindingFlags) { if (type == null) type = this.GetType(); // Group the methods on the given type by name. var functions = new Dictionary<string, MethodGroup>(20); var methods = type.GetMethods(bindingFlags); foreach (var method in methods) { // Make sure the method has the [JSInternalFunction] attribute. var attribute = (JSFunctionAttribute)Attribute.GetCustomAttribute(method, typeof(JSFunctionAttribute)); if (attribute == null) continue; // Determine the name of the method. string name; if (attribute.Name != null) name = attribute.Name; else name = method.Name; // Get a reference to the method group. MethodGroup methodGroup; if (functions.ContainsKey(name) == false) { methodGroup = new MethodGroup { Methods = new List<JSBinderMethod>(1), Length = -1 }; functions.Add(name, methodGroup); } else methodGroup = functions[name]; // Internal functions return nulls as undefined. if (attribute is JSInternalFunctionAttribute) attribute.Flags |= JSFunctionFlags.ConvertNullReturnValueToUndefined; // Add the method to the list. methodGroup.Methods.Add(new JSBinderMethod(method, attribute.Flags)); // If the length doesn't equal -1, that indicates an explicit length has been set. // Make sure it is consistant with the other methods. if (attribute.Length >= 0) { if (methodGroup.Length != -1 && methodGroup.Length != attribute.Length) throw new InvalidOperationException(string.Format("Inconsistant Length property detected on {0}.", method)); methodGroup.Length = attribute.Length; } // Check property attributes. var descriptorAttributes = PropertyAttributes.Sealed; if (attribute.IsEnumerable) descriptorAttributes |= PropertyAttributes.Enumerable; if (attribute.IsConfigurable) descriptorAttributes |= PropertyAttributes.Configurable; if (attribute.IsWritable) descriptorAttributes |= PropertyAttributes.Writable; if (methodGroup.Methods.Count > 1 && methodGroup.PropertyAttributes != descriptorAttributes) throw new InvalidOperationException(string.Format("Inconsistant property attributes detected on {0}.", method)); methodGroup.PropertyAttributes = descriptorAttributes; } // Now set the relevant properties on the object. foreach (KeyValuePair<string, MethodGroup> pair in functions) { string name = pair.Key; MethodGroup methodGroup = pair.Value; // Add the function as a property of the object. this.FastSetProperty(name, new ClrFunction(this.Engine.Function.InstancePrototype, methodGroup.Methods, name, methodGroup.Length), methodGroup.PropertyAttributes); } PropertyInfo[] properties = type.GetProperties(bindingFlags); foreach (PropertyInfo prop in properties) { var attribute = Attribute.GetCustomAttribute(prop, typeof(JSPropertyAttribute), false) as JSPropertyAttribute; if (attribute == null) continue; // The property name. string name; if (attribute.Name != null) name = attribute.Name; else name = prop.Name; // The property getter. ClrFunction getter = null; if (prop.CanRead) { var getMethod = prop.GetGetMethod(true); getter = new ClrFunction(engine.Function.InstancePrototype, new JSBinderMethod[] { new JSBinderMethod(getMethod) }, name, 0); } // The property setter. ClrFunction setter = null; if (prop.CanWrite) { var setMethod = prop.GetSetMethod((bindingFlags & BindingFlags.NonPublic) != 0); if (setMethod != null) setter = new ClrFunction(engine.Function.InstancePrototype, new JSBinderMethod[] { new JSBinderMethod(setMethod) }, name, 1); } // The property attributes. var descriptorAttributes = PropertyAttributes.Sealed; if (attribute.IsEnumerable) descriptorAttributes |= PropertyAttributes.Enumerable; if (attribute.IsConfigurable) descriptorAttributes |= PropertyAttributes.Configurable; // Define the property. var descriptor = new PropertyDescriptor(getter, setter, descriptorAttributes); this.DefineProperty(name, descriptor, true); } }