/// <summary> /// Specific to Graph-Viz (ver 2.38+) - produces the .gv file /// to be compiled by dot.exe resulting in a class diagram of /// the <see cref="assembly"/> /// </summary> /// <param name="assembly"></param> /// <param name="typeFullName"></param> /// <returns></returns> public static string GetClassDiagram(Assembly assembly, string typeFullName) { if(assembly == null) throw new ArgumentNullException(nameof(assembly)); if(string.IsNullOrWhiteSpace(typeFullName)) throw new ArgumentNullException(nameof(typeFullName)); var asmType = assembly.NfGetType(typeFullName); if (asmType == null) throw new RahRowRagee( $"This type '{typeFullName}' could not be found in the assembly '{assembly.GetName().Name}'"); var gv = new StringBuilder(); var graphName = NfTypeName.SafeDotNetIdentifier(assembly.GetName().Name); var topClass = GetCgOfType(assembly, typeFullName, false); var allCgTypeNames = topClass.AllPropertyTypes.Where( x => !string.Equals(x, typeFullName, StringComparison.OrdinalIgnoreCase) && assembly.GetType(x) != null).ToList(); var allCgTypes = allCgTypeNames.Where(x => !string.Equals(x, typeFullName, StringComparison.OrdinalIgnoreCase)) .Select(x => GetCgOfType(assembly, x, false)) .ToList(); foreach (var aCgType in allCgTypes) { aCgType.IsEnum = topClass.Properties.FirstOrDefault(x => x.TypeName == aCgType.FullName && x.IsEnum) != null; } //keep the diagram from exploding by multiples. var propertyManifold = allCgTypes.SelectMany(x => x.AllPropertyTypes) .Where( x => !string.Equals(x, typeFullName, StringComparison.OrdinalIgnoreCase) && !allCgTypeNames.Contains(x)) .Distinct() .ToList(); //determine which types require an empty class node var missingTypes = allCgTypes.SelectMany(x => x.AllPropertyTypes).Where(x => assembly.NfGetType(x) == null).Distinct().ToList(); missingTypes.AddRange( topClass.AllPropertyTypes.Where(x => assembly.NfGetType(x) == null && !missingTypes.Contains(x))); gv.Append($"digraph {graphName}Assembly"); gv.AppendLine("{"); gv.AppendLine("graph [rankdir=\"LR\"];"); gv.AppendLine("node [fontname=\"Consolas\"];"); gv.AppendLine(topClass.ToGraphVizNode()); foreach (var missing in missingTypes) gv.AppendLine(GraphVizExtensions.EmptyGraphVizClassNode(missing,topClass.EnumsValues(missing))); foreach (var far in propertyManifold) gv.AppendLine(GraphVizExtensions.EmptyGraphVizClassNode(far, topClass.EnumsValues(far))); foreach (var cg in allCgTypes.Where(x => !x.IsEnum)) gv.AppendLine(cg.ToGraphVizNode()); foreach ( var cg in allCgTypes.Where( x => x.IsEnum && !missingTypes.Contains(x.FullName) && !propertyManifold.Contains(x.FullName))) gv.AppendLine(GraphVizExtensions.EmptyGraphVizClassNode(cg.FullName, topClass.EnumsValues(cg.FullName))); gv.AppendLine(); gv.AppendLine(); gv.AppendLine(topClass.ToGraphVizEdge()); foreach (var cg in allCgTypes) gv.AppendLine(cg.ToGraphVizEdge()); gv.AppendLine("}"); return gv.ToString(); }
internal 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(NfTypeName.GetLastTypeNameFromArrayAndGeneric(info.PropertyType), s, StringComparison.OrdinalIgnoreCase) || (s == Constants.ENUM && NfTypeName.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 = NfTypeName.GetTypeNameWithoutNamespace(typeFullName) }); } var typeNamesList = currentType.GetProperties(NfConfig.DefaultFlags) .Where( x => (NfTypeName.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(NfConfig.DefaultFlags) .Where(x => !NfTypeName.IsValueTypeProperty(x))) { var typeIn = NfTypeName.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); currentDepth += 1; //time to go if (currentDepth >= maxDepth) return printList; //enum types being handled as limbo between value type and ref type string[] enumVals; if (displayEnums && NfTypeName.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; }
/// <summary> /// Get a custom type specific for Code Gen purposes based on the /// given <see cref="typeFullName"/> /// </summary> /// <param name="assembly">A reference to the assembly in which the type is located.</param> /// <param name="typeFullName">The type which will produce the cgObject.</param> /// <param name="valueTypeOnly">Will only export Fields and Properties whose base type extends System.ValueType</param> /// <param name="resolveDependencies">Switch to have the IL of the type parsed and all dependent calls Metadata tokens added.</param> /// <returns></returns> public static CgType GetCgOfType(Assembly assembly, string typeFullName, bool valueTypeOnly, bool resolveDependencies = false) { if (assembly == null || string.IsNullOrWhiteSpace(typeFullName)) return null; var asmType = assembly.NfGetType(typeFullName); var cgType = GetCgOfType(asmType, valueTypeOnly,resolveDependencies); return cgType; }
/// <summary> /// Intended for resolving method calls of types defined in another assembly. /// </summary> /// <param name="owningAsm"></param> /// <param name="tokenName">The name as drafted from <see cref="AssemblyAnalysis.ConvertToMetadataTokenName"/></param> /// <param name="mi"></param> /// <param name="msgOut">Inteneded for debug trace from the Console.</param> /// <returns></returns> internal bool TryResolveRtMemberInfo(Assembly owningAsm, string tokenName, out MemberInfo mi, StringBuilder msgOut = null) { //default the out variable mi = null; //expect token name to match naming given herein if (String.IsNullOrWhiteSpace(tokenName)) { if (msgOut != null) msgOut.Append(", Message:'the token name is null'"); return false; } if (!tokenName.Contains(Constants.TYPE_METHOD_NAME_SPLIT_ON)) { if (msgOut != null) msgOut.AppendFormat(", Message:'[{0}] does not contain {1}'", tokenName, Constants.TYPE_METHOD_NAME_SPLIT_ON); } if (owningAsm == null) { if (msgOut != null) msgOut.Append(", Message:'the owning assembly is null'"); return false; } var assemblyName = owningAsm.GetName().Name; var typeName = AssemblyAnalysis.ParseTypeNameFromTokenName(tokenName, assemblyName); if (String.IsNullOrWhiteSpace(typeName)) { if (msgOut != null) msgOut.Append(", Message:'could not parse type name'"); return false; } Type asmType = null; try { //framework throwing null-ref ex despite null checks asmType = owningAsm.NfGetType(typeName, false, _myProgram.LogFile); } catch { if (msgOut != null) msgOut.Append(", Message:'Assembly.GetType threw exception'"); return false; } if (asmType == null) { if (msgOut != null) msgOut.AppendFormat(", Message:'assembly {0} could not resolve {1}'", assemblyName, typeName); return false; } var methodName = AssemblyAnalysis.ParseMethodNameFromTokenName(tokenName); if (String.IsNullOrWhiteSpace(methodName)) { if (msgOut != null) msgOut.Append(", Message:'could not parse method name'"); return false; } MethodInfo methodInfo = null; //try easiest first try { methodInfo = asmType.NfGetMethod(methodName, NfConfig.DefaultFlags); } catch (AmbiguousMatchException) { }//is overloaded //try it the formal way if (methodInfo == null) { var args = AssemblyAnalysis.ParseArgsFromTokenName(tokenName).ToArray(); var argTypes = args.Length <= 0 ? Type.EmptyTypes : args.Select(Type.GetType).Where(x => x != null).ToArray(); //there must be a one-for-one match of string names to first-class types if (args.Length == argTypes.Length) methodInfo = asmType.NfGetMethod(methodName, NfConfig.DefaultFlags, null, argTypes, null, false, _myProgram.LogFile); } //try it the very slow but certian way if (methodInfo == null) { var methodInfos = asmType.NfGetMethods(NfConfig.DefaultFlags, false, _myProgram.LogFile); if (methodInfos.Length <= 0) { if (msgOut != null) { msgOut.AppendFormat(", Assembly:'{0}'\n", assemblyName); msgOut.AppendFormat(", Type:'{0}'\n", typeName); msgOut.Append(", Message:'does not have any methods'"); } return false; } foreach (var info in methodInfos) { var asTokenName = AssemblyAnalysis.ConvertToMetadataTokenName(info, _myProgram.AsmIndicies, IsIgnore); if (asTokenName == null || string.IsNullOrWhiteSpace(asTokenName.Name)) continue; if (string.Equals(asTokenName.Name, tokenName)) { methodInfo = info; break; } } } if (methodInfo == null) { if (msgOut != null) { msgOut.AppendFormat(", Assembly:'{0}'\n", assemblyName); msgOut.AppendFormat(", Type:'{0}'\n", typeName); msgOut.AppendFormat(", Method:'{0}'\n", methodName); msgOut.Append(", Message:'was not found'"); } return false; } mi = methodInfo; return true; }