// TODO: make this function and its overloads in nongeneric Reflector class simpler by introducing new class storing parameters private static IEnumerator <DiffNode> EnumerateDiffNodes(ObjectInfo <object> objectInfo, Property thisProperty, SrmDocument.DOCUMENT_TYPE defaultDocumentType, bool expand, PropertyPath propertyPath, object elementKey, IList <object> defaults, Func <DiffNode, bool> nodeSelector, DiffNode resultNode, int stackDepth) { var docType = objectInfo.NewObject is SrmDocument document ? document.DocumentType : objectInfo.NewParentObject is SrmDocument documentp ? documentp.DocumentType : objectInfo.NewRootObject is SrmDocument documentr ? documentr.DocumentType : SrmDocument.DOCUMENT_TYPE.none; if (docType == SrmDocument.DOCUMENT_TYPE.none) { docType = defaultDocumentType; } nodeSelector = nodeSelector ?? (n => true); if (objectInfo.ObjectPair.ReferenceEquals() && !expand) { yield break; } resultNode = resultNode ?? (thisProperty.IsCollectionElement ? new ElementPropertyDiffNode(thisProperty, propertyPath, objectInfo.ObjectPair, elementKey, docType, null, expand) : new PropertyDiffNode(thisProperty, propertyPath, objectInfo.ObjectPair, docType, null, expand)); var expandAnyways = false; var auditObjPair = objectInfo.ObjectPair.Transform(AuditLogObject.GetAuditLogObject); var nameChanged = auditObjPair.OldObject.IsName && auditObjPair.NewObject.IsName && auditObjPair.OldObject.AuditLogText != auditObjPair.NewObject.AuditLogText; if (ReferenceEquals(objectInfo.OldObject, null) && !ReferenceEquals(objectInfo.NewObject, null) && auditObjPair.NewObject.IsName || nameChanged && // IIdentiyContainer name changes are actually displayed, since we match IIdentiyContainers by their global indices !typeof(IIdentiyContainer).IsAssignableFrom(thisProperty.GetPropertyType(objectInfo.ObjectPair))) { if (!thisProperty.IsRoot) { if (!expand) { resultNode.IsFirstExpansionNode = true; } expandAnyways = expand = true; } } // If the object is an IAuditLogObject but not a name one, we don't care about subproperties //if (expand && typeof(IAuditLogObject).IsAssignableFrom(thisProperty.GetPropertyType(objectInfo.ObjectPair)) && // !auditObjPair.OldObject.IsName && !auditObjPair.NewObject.IsName) // expand = false; defaults = defaults ?? new List <object>(); if (expand && Reflector.ProcessDefaults(objectInfo, thisProperty, ref defaults, out var ignore)) { // Don't expand if we changed to a default object if (expandAnyways && !ignore && nodeSelector(resultNode)) // Only show this object if it shouldn't be fully ignored if it's a default object (for instance: small molecule only properties) { yield return(resultNode); } yield break; } // We only compare sub properties if both objects are non null, unless we're expanding // and only the old object is null if (ReferenceEquals(objectInfo.OldObject, null) && !expand || ReferenceEquals(objectInfo.NewObject, null)) { if (nodeSelector(resultNode)) { yield return(resultNode); } yield break; } // Deal with Collections var collection = Reflector.GetCollectionInfo(thisProperty.GetPropertyType(objectInfo.ObjectPair), objectInfo.ObjectPair); if (collection != null) { var nodeIter = Reflector.EnumerateCollectionDiffNodes(objectInfo.ChangeObjectPair(collection.Collections), collection, thisProperty, propertyPath, docType, expand, defaults, nodeSelector, resultNode, stackDepth); while (nodeIter.MoveNext()) { yield return(nodeIter.Current); } yield break; } // Properties that should directly be checked for equality -- stop recursion if (!thisProperty.DiffProperties) { // Properties that are not TrackParents and not collections are simply compared // Also make sure that new val doesn't equal any of the default objects if (nodeSelector(resultNode) && (!objectInfo.ObjectPair.Equals() || expand) && (!expand || thisProperty.IgnoreDefaultParent || !defaults.Any(d => Equals(d, objectInfo.ObjectPair.NewObject)))) { yield return(resultNode); } yield break; } objectInfo = objectInfo.ChangeParentPair(objectInfo.ObjectPair); // Compare properties foreach (var property in _properties) { var newPropertyPath = property.AddProperty(propertyPath); var newObjectInfo = objectInfo .ChangeOldObject(ReferenceEquals(objectInfo.OldObject, null) ? null : property.GetValue(objectInfo.OldObject)) .ChangeNewObject(property.GetValue(objectInfo.NewObject)); var valType = property.GetPropertyType(newObjectInfo.ObjectPair); var nodeIter = Reflector.EnumerateDiffNodes(valType, newObjectInfo, property, docType, expand, newPropertyPath, null, defaults, nodeSelector, null, stackDepth); DiffNode current = null; while (nodeIter.MoveNext()) { current = nodeIter.Current; yield return(current); } if (current != null) { resultNode.Nodes.Add(current); } } // If there are child nodes, we always want to return the parent node, // but if it's just the root node or a diffparent (meaning only the object reference changed) // we return null (meaning the objects are equivalent) if (resultNode.Nodes.Count == 0 && thisProperty.DiffProperties && !expandAnyways) { yield break; } if (nodeSelector(resultNode)) { yield return(resultNode); } }
/// <summary> /// Converts the given object to a string, showing each of its properties values. /// </summary> /// <param name="rootPair">old and new document, can be null</param> /// <param name="docType">May determine whether human readable version requires "peptide"->"molecule" translation</param> // CONSIDER: does this belong in ToStringState? /// <param name="rootNode">diff node describing root object change</param> /// <param name="state">describes how to format the string</param> /// <returns>String representation</returns> public static string ToString(ObjectPair <object> rootPair, SrmDocument.DOCUMENT_TYPE docType, DiffNode rootNode, ToStringState state) { return(Reflector.ToString(rootPair, docType, rootNode, null, state).Trim()); }