using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; namespace OpenTap.Addin.Reader { public abstract class IObjectReader { internal const string TypeAttrName = "type"; public abstract object ReadObject(Type type, XElement element, Action postAction); } public class DefaultObjectReader { static readonly Dictionary SpecialReader = new Dictionary(); static DefaultObjectReader() { SpecialReader.Add(typeof(TestStepList), new TestStepListReader()); } static bool tryConvertNumber(Type type, string valueString, out object value) { switch (Type.GetTypeCode(type)) { case TypeCode.Byte: { byte oval; bool ok = byte.TryParse(valueString, NumberStyles.Any, CultureInfo.InvariantCulture, out oval); value = oval; return ok; } case TypeCode.SByte: { SByte oval; bool ok = SByte.TryParse(valueString, NumberStyles.Any, CultureInfo.InvariantCulture, out oval); value = oval; return ok; } case TypeCode.UInt16: { UInt16 oval; bool ok = UInt16.TryParse(valueString, NumberStyles.Any, CultureInfo.InvariantCulture, out oval); value = oval; return ok; } case TypeCode.UInt32: { UInt32 oval; bool ok = UInt32.TryParse(valueString, NumberStyles.Any, CultureInfo.InvariantCulture, out oval); value = oval; return ok; } case TypeCode.UInt64: { UInt64 oval; bool ok = UInt64.TryParse(valueString, NumberStyles.Any, CultureInfo.InvariantCulture, out oval); value = oval; return ok; } case TypeCode.Int16: { Int16 oval; bool ok = Int16.TryParse(valueString, NumberStyles.Any, CultureInfo.InvariantCulture, out oval); value = oval; return ok; } case TypeCode.Int32: { Int32 oval; bool ok = Int32.TryParse(valueString, NumberStyles.Any, CultureInfo.InvariantCulture, out oval); value = oval; return ok; } case TypeCode.Int64: { Int64 oval; bool ok = Int64.TryParse(valueString, NumberStyles.Any, CultureInfo.InvariantCulture, out oval); value = oval; return ok; } case TypeCode.Decimal: { value = BigFloat.Convert(valueString, CultureInfo.InvariantCulture).ConvertTo(typeof(decimal)); return true; } case TypeCode.Double: { Double oval; bool ok = Double.TryParse(valueString, NumberStyles.Any, CultureInfo.InvariantCulture, out oval); value = oval; return ok; } case TypeCode.Single: { Single oval; bool ok = Single.TryParse(valueString, NumberStyles.Any, CultureInfo.InvariantCulture, out oval); value = oval; return ok; } default: value = null; return false; } } static bool readContentInternal(Type propType, Func getvalueString, XElement elem, out object outvalue) { object value = null; bool ok; if (propType.IsEnum || propType.IsPrimitive || propType == typeof(string) || propType.IsValueType || propType == typeof(Type)) { ok = true; string valueString = getvalueString()?.Trim(); if (propType.IsEnum) { if (!string.IsNullOrEmpty(valueString)) { // legacy support: A flagged enum did not have ','s, but just spaces between. if (!valueString.Contains(',')) { var splitted = valueString.Split(' '); value = Enum.Parse(propType, string.Join(",", splitted)); } else value = Enum.Parse(propType, valueString); } } else if (propType == typeof(String) || propType == typeof(char)) { if (elem.HasElements) { // string contains Base64 if it has invalid XML chars. // elem.Element("Base64") fails if the attribute "xmlns" is set on the document. // In this case, "Base64" must be prepended with the value of xmlns in brackets. // If the namespace is not set, ns.GetName("Base64") will just evaluate to "Base64" var ns = elem.GetDefaultNamespace(); var encode = elem.Element(ns.GetName("Base64")); if (encode != null) { try { value = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(encode.Value)); } catch { value = encode.Value; } } } if (value == null) value = valueString; if (propType == typeof(char)) value = ((string)value)[0]; } else if (propType == typeof(TimeSpan) || propType == typeof(TimeSpan?)) { value = TimeSpan.Parse(valueString, CultureInfo.InvariantCulture); } else if (propType == typeof(Guid) || propType == typeof(Guid?)) { value = Guid.Parse(valueString); } else if (propType == typeof(Type)) { value = PluginManager.LocateType(valueString.Split(',').First()); } else if (tryConvertNumber(propType, valueString, out value)) { // Conversions done in tryConvertNumber } else if (typeof(IConvertible).IsAssignableFrom(propType)) { value = Convert.ChangeType(valueString, propType, CultureInfo.InvariantCulture); } else if (propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof(Nullable<>)) { // For nullable we recurse focusing on the underlying non-nullable type. if (valueString == null) { outvalue = null; return true; // null is a valid value for nullable types. } return readContentInternal(Nullable.GetUnderlyingType(propType), getvalueString, elem, out outvalue); } else { ok = false; } } else { ok = false; } outvalue = value; return ok; } public static object ReadObject(Type type, XElement element, Action postAction) { //var timer = Stopwatch.StartNew(); if (type == null || element == null) return null; object obj = null; if (SpecialReader.ContainsKey(type)) { obj = SpecialReader[type].ReadObject(type, element, postAction); //TestPlanReader.Log.Debug(timer, $"Create {obj}"); return obj; } else { var typeAttr = element.Attribute(IObjectReader.TypeAttrName); type = typeAttr == null ? type : Type.GetType(typeAttr.Value); if (type == null) { var td = TypeData.GetTypeData(typeAttr.Value); obj = td.CreateInstance(); type = obj.GetType(); } if (!readContentInternal(type, () => element.Value, element, out obj)) { try { obj = obj ?? Activator.CreateInstance(type); } catch (Exception ex) { throw ex; } if (typeof(IList).IsAssignableFrom(type) && type.IsGenericType) { var list = (IList)obj; var eles = element.Elements().ToArray(); var values = new object[eles.Length]; Parallel.For(0, eles.Length, i => values[i] = ReadObject(type.GetGenericArguments().First(), eles[i], postAction)); //var values = element.Elements().AsParallel().AsOrdered().Select(childElemennt => //{ // return ; //}).ToArray(); foreach (var val in values) { list.Add(val); } //foreach (var childElemennt in element.Elements()) //{ // var value = ReadObject(type.GetGenericArguments().First(), childElemennt, postAction); // list.Add(value); //} } else { var props = PropertyInfoCache.GetProps(type); foreach (var childElemennt in element.Elements()) { string childName = XmlConvert.DecodeName(childElemennt.Name.LocalName); if (!props.ContainsKey(childName)) { continue; } var prop = props[childName]; var value = ReadObject(prop.PropertyType, childElemennt, postAction); prop.SetValue(obj, value); } //List tasks = new List(); //foreach (var childElemennt in element.Elements()) //{ // string childName = XmlConvert.DecodeName(childElemennt.Name.LocalName); // if (!props.ContainsKey(childName)) // { // continue; // } // tasks.Add(Task.Run(() => // { // // })); //} //Task.WaitAll(tasks.ToArray()); //var eles = element.Elements().ToArray(); //Parallel.For(0, eles.Length, i => //{ // string childName = XmlConvert.DecodeName(eles[i].Name.LocalName); // if (!props.ContainsKey(childName)) // { // return; // } // var prop = props[childName]; // var value = ReadObject(prop.PropertyType, eles[i], postAction); // prop.SetValue(obj, value); //}); } //TestPlanReader.Log.Debug(timer, $"Create {obj}"); } if (postAction != null) postAction(obj); return obj; } } } }