/// <summary> /// Examine the children of the specified element and process them as an aggregate. An aggregate is essentially a "reduce" operation /// that combines a series of inputs into a single value. /// </summary> /// <param name="aggFuncType"></param> /// <param name="aggFunc"></param> /// <param name="fieldOrProperty"></param> /// <param name="element"></param> /// <param name="ti"></param> /// <param name="o"></param> void _RecurseAggregate(Type aggFuncType, string aggFunc, IFieldOrProperty fieldOrProperty, XmlNode element, TypeInfo ti, ref object o) { // create list for storage of aggregate data List<object> data = new List<object>(); foreach (XmlNode child in element.ChildNodes) { if (child.NodeType != XmlNodeType.Element) continue; // check for obj type ref IXmlPostProcessAction action = null; action = _LookupObjectTypeRef(child); if (action != null) { data.Add(action); continue; } // check for name ref action = _LookupNameRef(child); if (action != null) { data.Add(action); continue; } // recurse on type TypeInfo subType = null; object subObj = null; // create new object _MakeNewObject(ti.Type.FullName, out subObj, out subType); if (subObj == null) { _Warn("Ignoring aggregate subelement: " + child.LocalName); continue; } // if the subType is a primitive or enum, do not recurse on it - instead just set the value directly if (TypeUtil.IsPrimitiveType(subType.Type)) { try { subObj = TypeUtil.GetPrimitiveValue(subType.Type, child.InnerText); } catch (Exception) { _Error("TorqueXmlDeserializer._SetFieldOrProperty - Unable to parse format string {0} for type {1}.", child.InnerText, subType.Type.FullName); } } else { _Recurse(child, ref subObj, subType); } data.Add(subObj); } // if we have data, create a post process task to handle the aggregation and set the final value if (data.Count > 0) { IXmlPostProcessAction action = new ProcessAggregateAction(aggFuncType, aggFunc, data, fieldOrProperty, ref o); _postProcessActions.Add(action); } }
/// <summary> /// Set the value of the input field or property using the element. This function will examine the inputs to determine the /// best match for the element. In some cases this may trigger a recursive examination of the element's children. /// </summary> /// <param name="fieldOrProperty">Field or property whose value will be set.</param> /// <param name="element">Element to obtain the value from.</param> /// <param name="o">Object instance whose field or property will be set.</param> void _SetFieldOrProperty(IFieldOrProperty fieldOrProperty, XmlNode element, ref object o) { // first check for valueOf object value = _CheckValueOf(element); if (value != null) { fieldOrProperty.SetValue(o, value); // done return; } // check for aggregate if (_CheckAggregate(fieldOrProperty, element, ref o)) // no need to set value; values of aggregates are set as post process action. return; string objName = _GetObjectName(o); // if its a string we can just set it if (fieldOrProperty.DeclaredType == typeof(System.String)) { fieldOrProperty.SetValue(o, element.InnerText); } else if (TypeUtil.IsPrimitiveType(fieldOrProperty.DeclaredType)) { if (element.InnerText == null || element.InnerText.Trim() == string.Empty) { _Error("Empty primitive value for type, won't set it: element: " + element.LocalName + ", type: " + fieldOrProperty.DeclaredType.FullName + ", object: " + objName); } else { // extract primitive value and set it try { fieldOrProperty.SetValue(o, TypeUtil.GetPrimitiveValue(fieldOrProperty.DeclaredType, element.InnerText)); } catch (Exception) { _Error("TorqueXmlDeserializer._SetFieldOrProperty - Unable to parse format string {0} for type {1} for element {2} in object {3}.", element.InnerText, fieldOrProperty.DeclaredType.FullName, element.LocalName, objName); } } } // if it is a reference type, we can deserialize in to it else if (fieldOrProperty.DeclaredType.IsClass || fieldOrProperty.DeclaredType.IsInterface || fieldOrProperty.DeclaredType.IsAbstract) { bool isInstantiable = !(fieldOrProperty.DeclaredType.IsInterface || fieldOrProperty.DeclaredType.IsAbstract); object subObj; TypeInfo subType; // check for name ref if (_BindNameRef(element, fieldOrProperty, ref o) != null) // done with this field return; // for reference types, allow deserializing into the existing object, if the attribute is present // and there is a valid object. subObj = fieldOrProperty.GetValue(o); bool inPlace = false; if (subObj != null) { // instance exists, check for attribute - use the property/field type for this, not // declared type. object[] attributes = fieldOrProperty.GetCustomAttributes(true); foreach (object attr in attributes) if (attr is TorqueXmlDeserializeInPlace) { inPlace = true; break; } if (!inPlace) { // check for xml attribute XmlNode inPlaceAttribute = element.Attributes.GetNamedItem("inPlace"); if (inPlaceAttribute != null && Boolean.Parse(inPlaceAttribute.Value)) inPlace = true; } } if (subObj != null && inPlace) { // ok to deserialize in place; get the typeinfo object for later use. subType = TypeUtil.FindTypeInfo(subObj.GetType().FullName); } else { // log if we are replacing, but there was an instance already //if (subObj != null) // _Info("Field has object instance but TorqueXmlDeserializeInPlace/inPlace attribute not specified: creating new object:" + element.LocalName); // in case we are making a fixed size list, pass the number of children down to the object maker int childCount = element.ChildNodes.Count; // if type is not instantiable, we might be able to come up with an instantiable type by looking for type mappings if (!isInstantiable) _MakeNewObject(element, out subObj, out subType, childCount); else // instantiable type, so make object used declared type _MakeNewObject(fieldOrProperty.DeclaredType.FullName, out subObj, out subType, childCount); if (subObj == null) { _Error("Unable to create new instance for element {0} on object {1}", element.LocalName, objName); return; } } // if it is a list of objects, handle it specially if (subType.IsList) { DeserializedList dlist = new DeserializedList(subObj); _RecurseList(element, ref dlist, subType); } else _Recurse(element, ref subObj, subType); // set value // it is important to do this after the element has been deserialized - some property accessors // assume this (such as lists of link points and object types). fieldOrProperty.SetValue(o, subObj); } // if it is a value type, then it must be a struct (because we handled primitives earlier) else if (fieldOrProperty.DeclaredType.IsValueType) { // look for object type ref if (_BindObjectTypeRef(element, fieldOrProperty, ref o) != null) return; object subObj; TypeInfo subType; _MakeNewObject(fieldOrProperty.DeclaredType.FullName, out subObj, out subType); if (subType == null) // check type for null instead of object, since this is a value type { _Error("Unable to create new instance for element {0} on object {1}", element.LocalName, objName); return; } _Recurse(element, ref subObj, subType); // set value fieldOrProperty.SetValue(o, subObj); } else _Error("Don't know how to set field or property of type {0} for element {1} on object: {2}", fieldOrProperty.DeclaredType.FullName, element.LocalName, objName); }
/// <summary> /// Process the specified name or object type ref. /// </summary> /// <param name="nameRefAttribute">the attribute to look for</param> /// <param name="lookupOnly">whether this is just a lookup (not a bind). if true, fieldOrProperty and object can be null.</param> /// <param name="isObjectTypeRef">true if this is an object type ref lookup, false if it is a nameref lookup</param> /// <param name="element">the element to examine</param> /// <param name="fieldOrProperty">the field or property to set the value on</param> /// <param name="o">the object instance to set the value on</param> /// <returns>A post process action, or null if none is appropriate.</returns> IXmlPostProcessAction _CheckNameRef(string nameRefAttribute, bool lookupOnly, bool isObjectTypeRef, XmlNode element, IFieldOrProperty fieldOrProperty, ref object o) { Assert.Fatal(lookupOnly || (fieldOrProperty != null && o != null), "field and instance required for name bind operations"); // see if the element has a nameRef IXmlPostProcessAction action = null; XmlNode nameRefAttr = element.Attributes[nameRefAttribute]; if (nameRefAttr != null) { if (lookupOnly) action = new LookupNameRefAction(isObjectTypeRef, nameRefAttr.Value, this); else action = new BindNameRefAction(isObjectTypeRef, nameRefAttr.Value, fieldOrProperty, ref o, this); _postProcessActions.Add(action); return action; } return action; }
/// <summary> /// Check to see if the element has an aggregate attribute and process it appropriately if so. /// </summary> /// <param name="fieldOrProperty">The IFieldOrProperty instance where the aggregate value will be set.</param> /// <param name="element">The element to check.</param> /// <param name="o">The object instance to set the field or property value on.</param> /// <returns>true if there is an aggregate, false otherwise</returns> bool _CheckAggregate(IFieldOrProperty fieldOrProperty, XmlNode element, ref object o) { // if the element has no child nodes, it cannot be an aggregate if (element.ChildNodes.Count == 0) return false; TypeInfo ti = TypeUtil.FindTypeInfo(fieldOrProperty.DeclaredType.FullName); if (ti == null) return false; bool isAggregate = false; string aggregateFunction = string.Empty; if (ti.Type == typeof(TorqueObjectType)) { isAggregate = true; aggregateFunction = "Aggregate"; } // allow override with aggregate attribute XmlNode aggAttr = element.Attributes.GetNamedItem("aggregateWith"); if (aggAttr != null && aggAttr.Value != null && aggAttr.Value.Trim() != string.Empty) { isAggregate = true; aggregateFunction = aggAttr.Value.Trim(); } if (!isAggregate) return false; Type aggFuncType = null; string baseType = string.Empty; string target = string.Empty; if (aggregateFunction.IndexOf(".") != -1) { // its possible to aggregate data using an arbitrary function type. lookup that type if (!TypeUtil.ParseType(aggregateFunction, out baseType, out target)) { _Error("Unable to parse aggregate function: " + aggregateFunction); return false; } if (baseType != string.Empty) { TypeInfo ati = TypeUtil.FindTypeInfo(baseType); if (ati == null) { _Error("Unable to find aggregate type: " + baseType); return false; } aggFuncType = ati.Type; } } else { // no "." in name, so use unqualifed function name on the declared type target = aggregateFunction; } _RecurseAggregate(aggFuncType, target, fieldOrProperty, element, ti, ref o); return true; }
/// <summary> /// Create an object type ref bind action for the specified element, if applicable. /// </summary> /// <param name="element"></param> /// <returns>The object type bind action, or null if no action is required.</returns> IXmlPostProcessAction _BindObjectTypeRef(XmlNode element, IFieldOrProperty fieldOrProperty, ref object o) { IXmlPostProcessAction action = _CheckNameRef("objTypeRef", false, true, element, fieldOrProperty, ref o); // cannot bind these on value types, because we cannot store a ref reference. Assert.Fatal(action == null || !(o is ValueType), "Cannot bind object type refs on valuetypes"); return action; }
/// <summary> /// Sets the various properties associated with this action. /// </summary> /// <param name="aggFuncType">The type of aggregate this is.</param> /// <param name="aggFunc">The function to use to aggregate the data.</param> /// <param name="data">The list of objects to aggregate.</param> /// <param name="fieldOrProperty">The field or property to set.</param> /// <param name="targetInstance">The object whose field or property is to be set.</param> public ProcessAggregateAction(Type aggFuncType, string aggFunc, List<object> data, IFieldOrProperty fieldOrProperty, ref object targetInstance) { Assert.Fatal(data != null && data.Count > 0, "ProcessAggregateAction Constructor - Invalid data!"); Assert.Fatal(fieldOrProperty != null, "ProcessAggregateAction Constructor - Invalid field or property!"); Assert.Fatal(targetInstance != null, "ProcessAggregateAction Constructor - Invalid target!"); _aggFuncType = aggFuncType; _aggFunc = aggFunc; _data = data; _fieldOrProperty = fieldOrProperty; _targetInstance = targetInstance; }
/// <summary> /// Sets the various properties associated with this action. /// </summary> /// <param name="isObjTypeRef">True if the name is an object type, false if it is a named object.</param> /// <param name="nameRef">The name.</param> /// <param name="fieldOrProperty">The field or property instance to set.</param> /// <param name="targetInstance">The object whose property is being set.</param> /// <param name="d">The deserializer.</param> public BindNameRefAction(bool isObjTypeRef, string nameRef, IFieldOrProperty fieldOrProperty, ref object targetInstance, TorqueXmlDeserializer d) : base(d) { Assert.Fatal(nameRef != null && nameRef != string.Empty, "BindNameRefAction Constructor - Invalid name specified!"); Assert.Fatal(fieldOrProperty != null, "BindNameRefAction Constructor - Invalid field or property specified!"); Assert.Fatal(targetInstance != null, "BindNameRefAction Constructor - Invalid target instance specified!"); _lookup = new LookupNameRefAction(isObjTypeRef, nameRef, d); _fieldOrProperty = fieldOrProperty; _targetInstance = targetInstance; }