/// <summary> /// Attempts to assign all the property's values on <see cref="fromObj"/> to /// some property on <see cref="toObj"/> based on the closest matching name. /// </summary> /// <param name="fromObj"></param> /// <param name="toObj"></param> /// <returns>1.0 when it was a perfect match</returns> /// <remarks> /// Use the <see cref="GetAssignPropertiesData"/> to see what it ended up choosing. /// </remarks> public static double TryAssignProperties(object fromObj, object toObj) { if (fromObj == null || toObj == null) { return(0); } var rScores = new List <Tuple <int, int> >(); var prevActions = new Dictionary <string, int>(); try { //remove any previous records. assignPiLog.Clear(); //only fromObj's value-type props rScores.AddRange(TryAssignValueTypeProperties(fromObj, toObj, prevActions)); //only fromObj's ref-type props foreach (var fromPi in fromObj.GetType().GetProperties(DefaultFlags)) { if (fromPi == null || !fromPi.CanRead) { continue; } if (NfReflect.IsValueTypeProperty(fromPi, true)) { continue; } var fromPv = fromPi.GetValue(fromObj); if (fromPv == null && TryGetInstanceOfPiType(fromPi, out fromPv)) { fromPi.SetValue(fromObj, fromPv); } rScores.AddRange(TryAssignValueTypeProperties(fromPv, toObj, prevActions, fromPi.Name)); } } catch (Exception ex) { assignPiLog.Add(new[] { ERROR_PREFIX, ex.Message, ex.StackTrace }); return(0); } var num = rScores.Select(x => (double)x.Item2).Sum(); var den = rScores.Select(x => (double)x.Item1).Sum(); var ratio = den == 0.0D ? 0.0D : num / den; return(1D - ratio); }
internal static Action GetSimpleAssignment(PropertyInfo toPi, object toObj, PropertyInfo fromPi, object fromObj, out string logInfo) { Action noop = () => { }; logInfo = null; if (toPi == null || toObj == null || fromPi == null || fromObj == null) { return(noop); } //only deal in value types to value types if (!NfReflect.IsValueTypeProperty(toPi, true) || !NfReflect.IsValueTypeProperty(fromPi, true)) { return(noop); } //enums require alot of special handling especially when wrapped in Nullable`1[ var cpiIsEnum = NfReflect.IsPropertyEnum(toPi); var fromPiIsEnum = NfReflect.IsPropertyEnum(fromPi); //get each pi's type var cpiType = cpiIsEnum ? NfReflect.GetEnumType(toPi) : NfReflect.GetPropertyValueType(toPi); var fromPiType = fromPiIsEnum ? NfReflect.GetEnumType(fromPi) : NfReflect.GetPropertyValueType(fromPi); //get each pi's type's full name var cpiTypeFullname = cpiType.FullName; var fromPiTypeFullname = fromPiType.FullName; logInfo = String.Join("->", fromPiTypeFullname, cpiTypeFullname); //easy assignment for same types if (cpiTypeFullname == fromPiTypeFullname) { return(() => toPi.SetValue(toObj, fromPi.GetValue(fromObj))); } //going from Enum to a string if (fromPiIsEnum && cpiTypeFullname == STR_FN) { //take enum value and get its name return(() => { var enumName = Enum.GetName(fromPiType, fromPi.GetValue(fromObj)); if (enumName != null) { toPi.SetValue(toObj, enumName); } }); } //going from a string to an enum if (cpiIsEnum && fromPiTypeFullname == STR_FN) { return(() => { var val = fromPi.GetValue(fromObj); if (val != null && !String.IsNullOrWhiteSpace(val.ToString()) && Enum.GetNames(cpiType) .Any(x => String.Equals(x, val.ToString(), StringComparison.OrdinalIgnoreCase))) { var enumVal = Enum.Parse(cpiType, val.ToString(), true); toPi.SetValue(toObj, enumVal); } }); } //going from enum to enum if (fromPiIsEnum && cpiIsEnum) { //will this require any cast? return(() => { toPi.SetValue(toObj, fromPi.GetValue(fromObj)); }); } //going from some value-type to a string if (cpiTypeFullname == STR_FN) { return(() => toPi.SetValue(toObj, fromPi.GetValue(fromObj).ToString())); } //going from something to value-type switch (cpiTypeFullname) { case "System.Byte": return(() => { byte bout; var bpiv = fromPi.GetValue(fromObj); var byteString = bpiv == null ? "0" : bpiv.ToString(); if (Byte.TryParse(byteString, out bout)) { toPi.SetValue(toObj, bout); } }); case "System.Int16": return(() => { short vout; var piv = fromPi.GetValue(fromObj); var vStr = piv == null ? "0" : piv.ToString(); if (Int16.TryParse(vStr, out vout)) { toPi.SetValue(toObj, vout); } }); case "System.Int32": return(() => { int vout; var piv = fromPi.GetValue(fromObj); var vStr = piv == null ? "0" : piv.ToString(); if (Int32.TryParse(vStr, out vout)) { toPi.SetValue(toObj, vout); } }); case "System.DateTime": return(() => { DateTime vout; var piv = fromPi.GetValue(fromObj); var vStr = piv == null ? "0" : piv.ToString(); if (DateTime.TryParse(vStr, out vout)) { toPi.SetValue(toObj, vout); } }); case "System.Decimal": return(() => { decimal vout; var piv = fromPi.GetValue(fromObj); var vStr = piv == null ? "0" : piv.ToString(); if (Decimal.TryParse(vStr, out vout)) { toPi.SetValue(toObj, vout); } }); case "System.Single": return(() => { float vout; var piv = fromPi.GetValue(fromObj); var vStr = piv == null ? "0" : piv.ToString(); if (Single.TryParse(vStr, out vout)) { toPi.SetValue(toObj, vout); } }); case "System.Double": return(() => { double vout; var piv = fromPi.GetValue(fromObj); var vStr = piv == null ? "0" : piv.ToString(); if (Double.TryParse(vStr, out vout)) { toPi.SetValue(toObj, vout); } }); } //default out to no operation return(noop); }
internal static List <Tuple <int, int> > TryAssignValueTypeProperties(object fromObj, object toObj, Dictionary <string, int> prevActions, string contextName = null) { var df = new List <Tuple <int, int> > { new Tuple <int, int>(Int32.MaxValue, 0) }; if (fromObj == null || toObj == null) { return(df); } var rScores = new List <Tuple <int, int> >(); try { var fromObjTypeName = fromObj.GetType().FullName; var fromObjPropertyNames = fromObj.GetType().GetProperties(DefaultFlags).Select(p => p.Name).ToArray(); //if still nothing found - just leave if (!fromObjPropertyNames.Any()) { return(df); } var toPis = toObj.GetType().GetProperties(DefaultFlags); prevActions = prevActions ?? new Dictionary <string, int>(); foreach (var pn in fromObjPropertyNames) { var fromPi = fromObj.GetType().GetProperty(pn); if (fromPi == null || !fromPi.CanRead || !NfReflect.IsValueTypeProperty(fromPi, true)) { continue; } //this will get us those closest on name only var closestMatches = GetClosestMatch(fromPi, fromObj, toPis, toObj); if (closestMatches == null || !Enumerable.Any <Tuple <Action, int, string, string> >(closestMatches)) { continue; } //have to decide how to break a tie if (closestMatches.Count > 1) { closestMatches = Enumerable.Where <Tuple <Action, int, string, string> >(closestMatches, x => x.Item3.Contains(pn)).ToList(); } foreach (var cm in closestMatches) { //its possiable that two different pi names are both attempting to write to the exact same target pi in toObj if (prevActions.ContainsKey(cm.Item3) && cm.Item2 >= prevActions[cm.Item3]) { //we only want the one with the shortest distance continue; } //exec the assignment on the target cm.Item1(); //get this distance as a ratio to the possible max distance rScores.Add(new Tuple <int, int>(cm.Item2, pn.Length)); //add this to the log var logPn = !String.IsNullOrWhiteSpace(contextName) ? String.Join(".", contextName, pn) : pn; var logPath = !String.IsNullOrWhiteSpace(contextName) ? String.Join("`", fromObjTypeName, cm.Item4) : cm.Item4; assignPiLog.Add(new[] { logPn, cm.Item3, logPath, cm.Item2.ToString() }); prevActions.Add(cm.Item3, cm.Item2); } } } catch (Exception ex) { assignPiLog.Add(new[] { ERROR_PREFIX, ex.Message, ex.StackTrace }); return(df); } //return average return(rScores); }
internal static List <Tuple <Action, int, string, string> > GetClosestMatch(PropertyInfo fromPi, object fromObj, PropertyInfo[] toPis, object toObj, string namePrefix = null, int minLen = 2) { if (fromPi == null || toPis == null || !toPis.Any()) { return(new List <Tuple <Action, int, string, string> >()); } //we want to map a whole assignment expression to a distance on name var ops2Score = new List <Tuple <Action, int, string, string> >(); Func <PropertyInfo, string, bool, string> getMeasureableName = (gmnPi, prefix, inReverse) => { if (gmnPi.Name.Length <= minLen) { return(inReverse ? String.Join("", gmnPi.Name, prefix) : String.Join("", prefix, gmnPi.Name)); } return(gmnPi.Name); }; foreach (var toPi in toPis) { if (NfReflect.IsValueTypeProperty(toPi, true)) { string toFromTns; Action simpleAssignment = GetSimpleAssignment(toPi, toObj, fromPi, fromObj, out toFromTns); if (String.IsNullOrWhiteSpace(namePrefix)) { //for simple value types -to- value types where dest has a very short name namePrefix = toObj.GetType().Name; } var fromPiName = getMeasureableName(fromPi, namePrefix, false); var cpiName = getMeasureableName(toPi, namePrefix, false); var fromPiReverseName = getMeasureableName(fromPi, namePrefix, true); var cpiReverseName = getMeasureableName(toPi, namePrefix, true); var score = (int)NfString.LevenshteinDistance(fromPiName, cpiName); //with short property names, prehaps a better score is when prefix is a suffix instead if (fromPiName != fromPiReverseName || cpiName != cpiReverseName) { var revScore = (int)NfString.LevenshteinDistance(fromPiReverseName, cpiReverseName); if (revScore < score) { score = revScore; } } ops2Score.Add(new Tuple <Action, int, string, string>(simpleAssignment, score, toPi.Name, toFromTns)); } else { var toInnerPi = toPi.PropertyType.GetProperties(DefaultFlags); if (!toInnerPi.Any()) { continue; } var toInnerObj = toPi.GetValue(toObj) ?? Activator.CreateInstance(toPi.PropertyType); var innerMeasurements = GetClosestMatch(fromPi, fromObj, toInnerPi, toInnerObj, toPi.Name, minLen); if (!innerMeasurements.Any()) { continue; } //these will by def, be the shortest distance foreach (var innerM in innerMeasurements) { //we will chain-link these actions Action complexAction = () => { innerM.Item1(); if (toPi.GetValue(toObj) == null) { toPi.SetValue(toObj, toInnerObj); } }; var actionId = String.Join(".", toPi.Name, innerM.Item3); ops2Score.Add(new Tuple <Action, int, string, string>(complexAction, innerM.Item2, actionId, String.Join("`", toPi.PropertyType.FullName, innerM.Item4))); } } } var totalMin = ops2Score.Min(x => x.Item2); var closest = ops2Score.Where(x => x.Item2 == totalMin).ToList(); return(closest); }
//[EditorBrowsable(EditorBrowsableState.Never)] public static List <FlattenedLine> FlattenType(Assembly assembly, string typeFullName, ref int currentDepth, int maxDepth, string limitOnValueType, bool displayEnums, Stack <FlattenedItem> fiValueTypes, Stack typeStack) { var printList = new List <FlattenedLine>(); if (string.IsNullOrWhiteSpace(typeFullName)) { return(printList); } Func <PropertyInfo, string, bool> limitOnPi = (info, s) => string.IsNullOrWhiteSpace(s) || string.Equals($"{info.PropertyType}", s, StringComparison.OrdinalIgnoreCase) || string.Equals(NfReflect.GetLastTypeNameFromArrayAndGeneric(info.PropertyType), s, StringComparison.OrdinalIgnoreCase) || (s == Constants.ENUM && NfReflect.IsEnumType(info.PropertyType)); var currentType = assembly.NfGetType(typeFullName); if (currentType == null) { return(printList); } //top frame of recursive calls will perform this if (fiValueTypes == null) { if (maxDepth <= 0) { maxDepth = 16; } typeStack = new Stack(); typeStack.Push(typeFullName); fiValueTypes = new Stack <FlattenedItem>(); fiValueTypes.Push(new FlattenedItem(currentType) { FlName = NfReflect.GetTypeNameWithoutNamespace(typeFullName) }); } var typeNamesList = currentType.GetProperties(NfSettings.DefaultFlags) .Where( x => (NfReflect.IsValueTypeProperty(x) && limitOnPi(x, limitOnValueType) || (limitOnValueType == Constants.ENUM && limitOnPi(x, limitOnValueType))) //more limbo branching for enums ) .Select(p => new Tuple <Type, string>(p.PropertyType, p.Name)) .ToList(); foreach (var typeNamePair in typeNamesList) { var pVtype = typeNamePair.Item1; var pVname = typeNamePair.Item2; fiValueTypes.Push(new FlattenedItem(pVtype) { FlName = pVname }); var fiItems = fiValueTypes.ToList(); fiItems.Reverse(); printList.Add(new FlattenedLine(fiItems.Distinct(new FlattenedItemComparer()).ToList()) { ValueType = $"{typeNamePair.Item1}" }); fiValueTypes.Pop(); } //then recurse the object types foreach ( var p in currentType.GetProperties(NfSettings.DefaultFlags) .Where(x => !NfReflect.IsValueTypeProperty(x))) { currentDepth += 1; //time to go if (currentDepth >= maxDepth) { return(printList); } var typeIn = NfReflect.GetLastTypeNameFromArrayAndGeneric(p.PropertyType); if (typeIn == null || typeStack.Contains(typeIn)) { continue; } var fi = new FlattenedItem(p.PropertyType) { FlName = p.Name }; if (fiValueTypes.ToList().Any(x => x.FlType == p.PropertyType)) { continue; } fiValueTypes.Push(fi); typeStack.Push(typeIn); //enum types being handled as limbo between value type and ref type string[] enumVals; if (displayEnums && NfReflect.IsEnumType(p.PropertyType, out enumVals)) { foreach (var ev in enumVals) { fiValueTypes.Push(new FlattenedItem(typeof(Enum)) { FlName = ev }); var fiItems = fiValueTypes.ToList(); fiItems.Reverse(); printList.Add(new FlattenedLine(fiItems.Distinct(new FlattenedItemComparer()).ToList()) { ValueType = String.Empty }); fiValueTypes.Pop(); } } else { printList.AddRange(FlattenType(assembly, fi.TypeFullName, ref currentDepth, maxDepth, limitOnValueType, displayEnums, fiValueTypes, typeStack)); } fiValueTypes.Pop(); typeStack.Pop(); currentDepth -= 1; } return(printList); }