/// <summary> /// Populates a list with items. /// </summary> /// <param name="state">The loader state.</param> /// <param name="list">The list to populate.</param> /// <param name="listElemType">The type of elements in the list.</param> /// <param name="element">The element that defines the list.</param> private void PopulateListItems(ObjectLoaderState state, Object list, Type listElemType, XElement element) { // Make sure that the list is properly defined. var itemsRoot = element.Element("Items"); if (itemsRoot != null && itemsRoot.Elements().Where(x => x.Name != "Item").Any()) { throw new InvalidOperationException(NucleusStrings.NonItemElementsInListDef); } // Populate the list's items. if (itemsRoot != null) { var add = list.GetType().GetMethod("Add", new[] { listElemType }); var items = itemsRoot.Elements("Item").ToList(); for (int i = 0; i < items.Count; i++) { var value = default(Object); var type = GetTypeFromElement(state, listElemType, items[i]); if (items[i].Elements().Any()) { value = CreateObject(state, type, null, GetSpecifiedConstructorArguments(items[i])); value = PopulateObjectFromElements(state, value, items[i]); } else { value = ObjectResolver.FromString(items[i].Value, type); } add.Invoke(list, new[] { value }); } } }
/// <summary> /// Populates an object with values. /// </summary> /// <param name="state">The loader state.</param> /// <param name="objectInstance">The object instance.</param> /// <param name="objectElement">The object element.</param> /// <returns>The object instance.</returns> private Object PopulateObject(ObjectLoaderState state, Object objectInstance, XElement objectElement) { objectInstance = PopulateObjectFromDefaults(state, objectInstance, objectElement); objectInstance = PopulateObjectFromAttributes(state, objectInstance, objectElement); objectInstance = PopulateObjectFromElements(state, objectInstance, objectElement); return(objectInstance); }
/// <summary> /// Populates an array value. /// </summary> /// <param name="state">The loader state.</param> /// <param name="member">The member to populate.</param> /// <param name="element">The element that defines the member's value.</param> private void PopulateArray(ObjectLoaderState state, ObjectLoaderMember member, XElement element) { // Make sure that the array is properly defined. var itemsRoot = element.Element("Items"); if (itemsRoot != null && itemsRoot.Elements().Where(x => x.Name != "Item").Any()) { throw new InvalidOperationException(NucleusStrings.NonItemElementsInArrayDef); } // Create the array. var items = (itemsRoot == null) ? new List <XElement>() : itemsRoot.Elements("Item").ToList(); var arrayElementType = member.MemberType.GetElementType(); var array = Array.CreateInstance(arrayElementType, items.Count); member.SetValueFromData(array, element); // Populate the array's items. for (int i = 0; i < items.Count; i++) { var value = default(Object); var type = GetTypeFromElement(state, arrayElementType, items[i]); if (items[i].Elements().Any()) { value = CreateObject(state, type, null, GetSpecifiedConstructorArguments(items[i])); value = PopulateObjectFromElements(state, value, items[i]); } else { value = ObjectResolver.FromString(items[i].Value, type); } array.SetValue(value, i); } }
/// <summary> /// Populates an object from the currently loaded default values. /// </summary> /// <param name="state">The loader state.</param> /// <param name="objectInstance">The object instance.</param> /// <param name="objectElement">The object element.</param> /// <returns>The object instance.</returns> private Object PopulateObjectFromDefaults(ObjectLoaderState state, Object objectInstance, XElement objectElement) { // Find our inheritance chain. var typeStack = new Stack <Type>(); var typeCurrent = objectInstance.GetType(); while (typeCurrent != null) { typeStack.Push(typeCurrent); typeCurrent = typeCurrent.BaseType; } // Populate the default values for the entire inheritance heirarchy. while (typeStack.Count > 0) { // Retrieve the defaults for the current type. var typeBeingPopulated = typeStack.Pop(); var typeDefaults = state.GetDefaultValues(typeBeingPopulated); if (typeDefaults == null) { continue; } // Populate the default values. foreach (var typeDefault in typeDefaults) { objectInstance = PopulateMemberFromElement(state, objectInstance, typeDefault.Value); } } return(objectInstance); }
/// <summary> /// Populates an enumerable value. /// </summary> /// <param name="state">The loader state.</param> /// <param name="member">The member to populate.</param> /// <param name="element">The element that defines the member's value.</param> private void PopulateEnumerable(ObjectLoaderState state, ObjectLoaderMember member, XElement element) { var listElemType = GetEnumerableElementType(element.Name.LocalName, member.MemberType); var listType = typeof(List <>).MakeGenericType(listElemType); var listInstance = Activator.CreateInstance(listType); PopulateListItems(state, listInstance, listElemType, element); member.SetValueFromData(listInstance, element); }
/// <summary> /// Populates an object from the elements descending from the specified data element. /// </summary> /// <param name="state">The loader state.</param> /// <param name="objectInstance">The object instance.</param> /// <param name="objectElement">The object element.</param> /// <param name="filter">A filter which determines which elements are used to populate the object.</param> /// <returns>The object instance.</returns> private Object PopulateObjectFromElements(ObjectLoaderState state, Object objectInstance, XElement objectElement, Func <XElement, Boolean> filter = null) { var children = (filter == null) ? objectElement.Elements() : objectElement.Elements().Where(filter); foreach (var element in children) { objectInstance = PopulateMemberFromElement(state, objectInstance, element, false); } return(objectInstance); }
/// <summary> /// Creates an object from the specified root element. /// </summary> /// <param name="state">The loader state.</param> /// <param name="type">The type of object to create.</param> /// <param name="argsBase">The base set of arguments for this object's constructor.</param> /// <param name="argsSpecified">The specified set of arguments for this object's constructor.</param> /// <returns>The object that was created.</returns> private Object CreateObject(ObjectLoaderState state, Type type, Object[] argsBase, XElement[] argsSpecified = null) { var argsBaseLength = (argsBase == null) ? 0 : argsBase.Length; var argsSpecifiedLength = (argsSpecified == null) ? 0 : argsSpecified.Length; // Try to find a constructor that matches our argument list. var ctorArgs = new Object[argsBaseLength + argsSpecifiedLength]; var ctors = type.GetConstructors(); if (!ctors.Any() && argsBaseLength == 0 && argsSpecifiedLength == 0) { return(Activator.CreateInstance(type)); } var ctorMatches = ctors.Where(x => x.GetParameters().Count() == ctorArgs.Length).ToList(); if (!ctorMatches.Any()) { throw new InvalidOperationException(NucleusStrings.CtorMatchNotFound); } if (ctorMatches.Count() > 1) { throw new InvalidOperationException(NucleusStrings.CtorMatchIsAmbiguous); } var ctorMatch = ctorMatches.Single(); var ctorParams = ctorMatch.GetParameters(); // Build the complete list of constructor arguments. for (int i = 0; i < argsBaseLength; i++) { ctorArgs[i] = argsBase[i]; } for (int i = 0; i < argsSpecifiedLength; i++) { var ctorArgElement = argsSpecified[i]; var ctorArgType = GetTypeFromElement(state, ctorParams[i + argsBase.Length].ParameterType, ctorArgElement); var ctorArgValue = default(Object); if (ctorArgElement.Elements().Any()) { ctorArgValue = CreateObject(state, ctorArgType, null, GetSpecifiedConstructorArguments(ctorArgElement)); ctorArgValue = PopulateObjectFromElements(state, ctorArgValue, ctorArgElement); } else { ctorArgValue = ParseValue(ctorArgElement.Value, ctorArgType, state.Culture); } ctorArgs[i + argsBase.Length] = ctorArgValue; } // Attempt to instantiate the object. return(ctorMatch.Invoke(ctorArgs)); }
/// <summary> /// Loads an object from the specified XML element. /// </summary> /// <param name="resolver">A custom <see cref="ObjectLoaderMemberResolutionHandler"/> which allows external /// code to optionally resolve deserialized member values.</param> /// <param name="type">The type of object to load.</param> /// <param name="xml">The XML element that contains the object data.</param> /// <param name="culture">The culture information to use when parsing values.</param> /// <param name="ignoreMissingMembers">A value indicating whether the object loader /// should ignore members which do not exist on the type.</param> /// <returns>The object that was loaded.</returns> public Object LoadObject(ObjectLoaderMemberResolutionHandler resolver, Type type, XElement xml, CultureInfo culture, Boolean ignoreMissingMembers = false) { Contract.Require(type, nameof(type)); Contract.Require(xml, nameof(xml)); Contract.Require(culture, nameof(culture)); var state = new ObjectLoaderState(globalAliases, culture, resolver); state.IgnoreMissingMembers = ignoreMissingMembers; state.ParseClassAliases(null, type); var objectElement = xml; var objectInstance = CreateObjectFromRootElement(state, type, objectElement); return(PopulateObject(state, objectInstance, objectElement)); }
/// <summary> /// Creates an object from the specified root element. /// </summary> /// <param name="state">The current loader state.</param> /// <param name="type">The type of object to create.</param> /// <param name="element">The element from which to create an object.</param> /// <returns>The object that was created.</returns> private Object CreateObjectFromRootElement(ObjectLoaderState state, Type type, XElement element) { // First, ensure that we have a class, key, and identifier. var objClassName = state.ResolveClass((String)element.Attribute("Class")); if (String.IsNullOrEmpty(objClassName)) { throw new InvalidOperationException(NucleusStrings.DataObjectMissingClass); } // If we're loading a Nucleus data object, parse its unique key and ID. var argsBase = default(Object[]); if (typeof(DataObject).IsAssignableFrom(type)) { var objKey = (String)element.Attribute("Key"); if (String.IsNullOrEmpty(objKey)) { throw new InvalidOperationException(NucleusStrings.DataObjectMissingKey); } var objID = (String)element.Attribute("ID"); if (String.IsNullOrEmpty(objID)) { throw new InvalidOperationException(NucleusStrings.DataObjectMissingID); } Guid objIDValue; if (!Guid.TryParse(objID, out objIDValue)) { throw new InvalidOperationException(NucleusStrings.DataObjectInvalidID.Format(objID)); } argsBase = new Object[] { objKey, objIDValue }; } // Attempt to find the object class and make sure it's of the correct type. var objClass = Type.GetType(objClassName, false); if (objClass == null || !type.IsAssignableFrom(objClass)) { throw new InvalidOperationException(NucleusStrings.DataObjectInvalidClass.Format(objClassName ?? "(null)", argsBase[0])); } // Attempt to instantiate the object. return(CreateObject(state, objClass, argsBase, GetSpecifiedConstructorArguments(element))); }
/// <summary> /// Populates a list value. /// </summary> /// <param name="state">The loader state.</param> /// <param name="member">The member to populate.</param> /// <param name="element">The element that defines the member's value.</param> private void PopulateList(ObjectLoaderState state, ObjectLoaderMember member, XElement element) { // Create the list. var listImplType = GetListImplementationType(member.MemberType); var listElemType = GetListElementType(element.Name.LocalName, listImplType); var list = Activator.CreateInstance(listImplType); // Populate the list's members. if (!member.IsIndexer) { PopulateObjectFromAttributes(state, list, element); } PopulateObjectFromElements(state, list, element, x => x.Name != "Items"); PopulateListItems(state, list, listElemType, element); // Set the list on the object. member.SetValueFromData(list, element); }
/// <summary> /// Gets the type defined by the specified element. /// </summary> /// <param name="state">The loader state.</param> /// <param name="baseType">The base type.</param> /// <param name="element">The element to evaluate.</param> /// <returns>The type defined by the specified element.</returns> private Type GetTypeFromElement(ObjectLoaderState state, Type baseType, XElement element) { var complexTypeAttr = element.Attribute("Type"); if (complexTypeAttr != null && String.IsNullOrEmpty(complexTypeAttr.Value)) { throw new InvalidOperationException(NucleusStrings.DataObjectInvalidType.Format(element.Name)); } var complexType = (complexTypeAttr == null) ? baseType : Type.GetType(state.ResolveClass(complexTypeAttr.Value), false); if (complexType == null) { throw new InvalidOperationException(NucleusStrings.DataObjectInvalidType.Format(element.Name)); } if (!baseType.IsAssignableFrom(complexType)) { throw new InvalidOperationException(NucleusStrings.DataObjectIncompatibleType.Format(element.Name)); } return(complexType); }
/// <summary> /// Loads object definitions contained in the specified XML document. /// </summary> /// <typeparam name="T">The type of object to load.</typeparam> /// <param name="root">The root data element of the document that contains the object definitions to load.</param> /// <param name="aliases">The elements defining class aliases.</param> /// <param name="defaults">The elements defining class defaults.</param> /// <param name="name">The name of the type of object to load, which corresponds to the names of the elements in the XML file.</param> /// <param name="defaultClass">The name of the default class to apply to loaded objects.</param> public IEnumerable <T> LoadDefinitions <T>(XElement root, IEnumerable <XElement> aliases, IEnumerable <XElement> defaults, String name, Type defaultClass) where T : DataObject { Contract.Require(root, nameof(root)); Contract.Require(name, nameof(name)); var cultureString = (String)root.Attribute("Culture"); var culture = String.IsNullOrWhiteSpace(cultureString) ? CultureInfo.InvariantCulture : new CultureInfo(cultureString); try { lockobj.EnterReadLock(); var state = new ObjectLoaderState(globalAliases, culture); state.ParseClassAliases(aliases, defaultClass); state.ParseClassDefaults(defaults); var objectElements = root.Elements(name); var objectList = new List <T>(); foreach (var objectElement in objectElements) { var objectInstance = (T)CreateObjectFromRootElement(state, typeof(T), objectElement); PopulateObject(state, objectInstance, objectElement); objectList.Add(objectInstance); } return(objectList); } finally { if (lockobj.IsReadLockHeld) { lockobj.ExitReadLock(); } } }
/// <summary> /// Populates an object from the attributes defined on the specified data element. /// </summary> /// <param name="state">The loader state.</param> /// <param name="objectInstance">The object instance.</param> /// <param name="objectElement">The object element.</param> /// <returns>The object instance.</returns> private Object PopulateObjectFromAttributes(ObjectLoaderState state, Object objectInstance, XElement objectElement) { foreach (var attr in objectElement.Attributes()) { if (IsReservedKeyword(attr.Name.LocalName)) { continue; } var attrMember = ObjectLoaderMember.Find(objectInstance, attr.Name.LocalName, state.IgnoreMissingMembers); if (attrMember != null) { if (state.Resolver != null && state.Resolver(objectInstance, attr.Name.LocalName, attr.Value)) { continue; } var attrValue = ObjectResolver.FromString(attr.Value, attrMember.MemberType); attrMember.SetValue(attrValue, null); } } return(objectInstance); }
/// <summary> /// Populates an object member from the specified data element. /// </summary> /// <param name="state">The loader state.</param> /// <param name="objectInstance">The object instance.</param> /// <param name="memberElement">The element that defines the member.</param> /// <param name="skipReservedKeywords">A value indicating whether to skip elements with the same names as reserved keywords.</param> /// <returns>The object instance.</returns> private Object PopulateMemberFromElement(ObjectLoaderState state, Object objectInstance, XElement memberElement, Boolean skipReservedKeywords = true) { if (IsReservedKeyword(memberElement.Name.LocalName)) { if (skipReservedKeywords || IsForbiddenKeyword(memberElement.Name.LocalName)) { return(objectInstance); } } if (state.Resolver != null && state.Resolver(objectInstance, memberElement.Name.LocalName, memberElement.Value)) { return(objectInstance); } var member = ObjectLoaderMember.Find(objectInstance, memberElement.Name.LocalName, state.IgnoreMissingMembers); if (member == null) { return(objectInstance); } // Handle array values. if (member.MemberType.IsArray) { PopulateArray(state, member, memberElement); return(objectInstance); } // Handle list values. if (IsListType(member.MemberType)) { PopulateList(state, member, memberElement); return(objectInstance); } // Handle generic enumerables. if (IsEnumerableType(member.MemberType)) { PopulateEnumerable(state, member, memberElement); return(objectInstance); } // Handle complex and simple objects. if (memberElement.Elements().Any()) { var complexType = GetTypeFromElement(state, member.MemberType, memberElement); var complexTypeValue = member.GetValueFromData(memberElement); if (complexTypeValue == null) { complexTypeValue = CreateObject(state, complexType, null, GetSpecifiedConstructorArguments(memberElement)); if (!complexType.IsValueType) { member.SetValueFromData(complexTypeValue, memberElement); } } if (!member.IsIndexer) { complexTypeValue = PopulateObjectFromAttributes(state, complexTypeValue, memberElement); } complexTypeValue = PopulateObjectFromElements(state, complexTypeValue, memberElement); if (complexType.IsValueType) { member.SetValueFromData(complexTypeValue, memberElement); } } else { if (String.IsNullOrEmpty(memberElement.Value)) { return(objectInstance); } var memberValue = ParseValue(memberElement.Value, member.MemberType, state.Culture); member.SetValueFromData(memberValue, memberElement); } return(objectInstance); }