public ObjectTextDumper Dump( object value, Type dumpMetadata = null, DumpAttribute dumpAttribute = null) { var originalIndentLevel = _indentLevel; var reflectionPermission = new ReflectionPermission(PermissionState.Unrestricted); var revertPermission = false; try { // assert the permission and dump reflectionPermission.Demand(); revertPermission = true; // assert the permission and dump reflectionPermission.Assert(); _maxDepth = int.MinValue; DumpObject(value, dumpMetadata, dumpAttribute); } catch (SecurityException) { Writer.WriteLine(); Writer.Write(Resources.CallerDoesNotHavePermissionFormat, value?.ToString() ?? DumpUtilities.Null); } catch (Exception x) { var message = $"\n\nATTENTION:\nThe TextDumper threw an exception:\n{x.ToString()}"; Writer.WriteLine(message); Debug.WriteLine(message); _indentLevel = originalIndentLevel; } finally { // revert the permission assert if (revertPermission) { CodeAccessPermission.RevertAssert(); } // clear the dumped objects register DumpedObjects.Clear(); // prepare our writer for a new dump if (_isDumpWriter) { ((DumpTextWriter)Writer).Reset(); } } return(this); }
static void AddClassDumpData( Type type, Type buddy, DumpAttribute dumpAttribute, bool replace) { if (type == null) { throw new ArgumentNullException(nameof(type)); } AddClassDumpData(type, new ClassDumpData(buddy, dumpAttribute), replace); }
/// <summary> /// Registers the dump metadata and <see cref="DumpAttribute" /> instance related to the specified type. /// </summary> /// <param name="type">The type for which the metadata is being registered.</param> /// <param name="metadataType">The dump metadata type.</param> /// <param name="dumpAttribute">The dump attribute.</param> /// <param name="replace"> /// If set to <see langword="false" /> and there is already dump metadata associated with the <paramref name="type"/> /// the method will throw exception of type <see cref="InvalidOperationException"/>; /// otherwise it will silently override the existing metadata with <paramref name="metadataType"/> and <paramref name="dumpAttribute"/>. /// </param> /// <returns>The current instance of ClassMetadataRegistrar.</returns> /// <exception cref="T:System.ArgumentNullException">Thrown if <paramref name="type" /> is <see langword="null" />.</exception> /// <exception cref="InvalidOperationException"> /// Thrown if <paramref name="replace"/> is <see langword="false"/> and there is already metadata associated with the <paramref name="type"/>. /// </exception> public ClassMetadataRegistrar Register( Type type, Type metadataType, DumpAttribute dumpAttribute = null, bool replace = false) { if (type == null) { throw new ArgumentNullException(nameof(type)); } ClassMetadataResolver.SetClassDumpData(type, metadataType, dumpAttribute, replace); return(this); }
public static bool DumpedBasicNullable( this TextWriter writer, object value, DumpAttribute dumpAttribute) { if (writer == null) { throw new ArgumentNullException(nameof(writer)); } if (value == null) { writer.Write(DumpUtilities.Null); return(true); } var type = value.GetType(); if (type.IsBasicType()) { return(writer.DumpedBasicValue(value, dumpAttribute)); } if (!type.IsGenericType || type.GetGenericTypeDefinition() != typeof(Nullable <>) || !type.GetGenericArguments()[0].IsBasicType()) { return(false); } var hasValue = (bool)type.InvokeMember(nameof(Nullable <int> .HasValue), BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty, null, value, new object[] { }); if (!hasValue) { writer.Write(DumpUtilities.Null); return(true); } var val = type.InvokeMember(nameof(Nullable <int> .Value), BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty, null, value, new object[] { }); return(writer.DumpedBasicValue(val, dumpAttribute)); }
public ClassDumpData( Type metadata, DumpAttribute dumpAttribute = null) { Metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); if (dumpAttribute != null) { DumpAttribute = dumpAttribute; } else if (Attribute.IsDefined(metadata, typeof(DumpAttribute))) { DumpAttribute = metadata.GetCustomAttribute <DumpAttribute>(); } else { DumpAttribute = DumpAttribute.Default; } }
/// <summary> /// Adds buddy type and dump attribute for classes which we do not have access to, e.g. Exception. /// </summary> /// <param name="type">The type for which to set buddy type and dump attribute.</param> /// <param name="metadata">The metadata type (buddy class).</param> /// <param name="dumpAttribute">The dump attribute.</param> /// <param name="replace"> /// If set to <see langword="false" /> and there is already dump metadata associated with the <paramref name="type"/> /// the method will throw exception of type <see cref="InvalidOperationException"/>; /// otherwise it will silently override the existing metadata with <paramref name="metadata"/> and <paramref name="dumpAttribute"/>. /// </param> /// <exception cref="InvalidOperationException"> /// Thrown if <paramref name="replace"/> is <see langword="false"/> and there is already metadata associated with the <paramref name="type"/>. /// </exception> public static void SetClassDumpData( Type type, Type metadata = null, DumpAttribute dumpAttribute = null, bool replace = false) { if (type == null) { throw new ArgumentNullException(nameof(type)); } if (metadata == null) { var attribute = type.GetCustomAttribute <MetadataTypeAttribute>(); metadata = attribute != null ? attribute.MetadataClassType : type; } AddClassDumpData(type, metadata, dumpAttribute, replace); }
public ClassMetadataRegistrar Register <T>( DumpAttribute dumpAttribute, bool replace = false) => Register(typeof(T), null, dumpAttribute, replace);
internal void DumpObject( object obj, Type dumpMetadata = null, DumpAttribute dumpAttribute = null, DumpState parentState = null) { if (Writer.DumpedBasicValue(obj, dumpAttribute) || Writer.DumpedBasicNullable(obj, dumpAttribute)) // incl. null { return; } // resolve the class metadata and the dump attribute ClassDumpData classDumpData; var objectType = obj.GetType(); if (dumpMetadata == null) { classDumpData = ClassMetadataResolver.GetClassDumpData(objectType); if (dumpAttribute != null) { classDumpData.DumpAttribute = dumpAttribute; } } else { classDumpData = new ClassDumpData(dumpMetadata, dumpAttribute); } // if we're too deep - stop here. if (_maxDepth == int.MinValue) { _maxDepth = classDumpData.DumpAttribute.MaxDepth; } if (_maxDepth < 0) { Writer.Write(Resources.DumpReachedMaxDepth); return; } // save here the IsSubExpression flag as it will change if obj is Expression and IsSubExpression==false. // but in the end of this method restore the value from this local variable. See below. var isSubExpressionStore = IsSubExpression; // slow dump vs. run script? bool isTopLevelObject = parentState == null; var buildScript = UseDumpScriptCache && !obj.IsDynamicObject(); Script script = null; using (var state = new DumpState(this, obj, classDumpData, buildScript)) { if (buildScript) { // does the script exist or is it in process of building if (DumpScriptCache.TryFind(this, obj, classDumpData, out script)) { if (script != null) { if (isTopLevelObject) { Writer.Indent(_indentLevel, _indentSize) .WriteLine(); } script(obj, classDumpData, this, state); } return; } DumpScriptCache.BuildingScriptFor(this, objectType, classDumpData); } else { if (isTopLevelObject) { Writer.Indent(_indentLevel, _indentSize) .WriteLine(); } } if (!state.DumpedAlready()) // the object has been dumped already (block circular and repeating references) { // this object will be dumped below. // Add it to the dumped objects now so that if nested property refers back to it, it won't be dumped in an infinite recursive chain. DumpedObjects.Add(new DumpedObject(obj, objectType)); if (!state.DumpedCollection(classDumpData.DumpAttribute, false)) // custom collections are dumped after dumping all other properties (see below * ) { Stack <DumpState> statesWithRemainingProperties = new Stack <DumpState>(); Queue <DumpState> statesWithTailProperties = new Queue <DumpState>(); // recursively dump all properties with non-negative order in class inheritance descending order (base classes' properties first) // and if there are more properties to be dumped put them in the stack if (!DumpedTopProperties(state, statesWithRemainingProperties)) { // dump all properties with negative order in class ascending order (derived classes' properties first) DumpRemainingProperties(statesWithRemainingProperties, statesWithTailProperties); // dump all properties with Order=int.MinValue in ascending order (derived classes' properties first) DumpTailProperties(statesWithTailProperties); // * if the object implements IEnumerable and the state allows it - dump the elements. state.DumpedCollection(classDumpData.DumpAttribute, true); } // we are done dumping state.Unindent(); } } if (buildScript && state.DumpScript != null) { script = DumpScriptCache.Add(this, objectType, classDumpData, state.DumpScript); } if (!buildScript) { if (isTopLevelObject) { Writer.Unindent(_indentLevel, _indentSize); } } else { if (script != null) { if (isTopLevelObject) { Writer.Indent(_indentLevel, _indentSize) .WriteLine(); } script(obj, classDumpData, this, state); } } // restore here the IsSubExpression flag as it have changed if obj is Expression and IsSubExpression==false. IsSubExpression = isSubExpressionStore; } }
public static bool DumpedCollection( this TextWriter writer, IEnumerable sequence, DumpAttribute dumpAttribute, Action <object> dumpObject, Action indent, Action unindent) { if (writer == null) { throw new ArgumentNullException(nameof(writer)); } if (sequence == null) { throw new ArgumentNullException(nameof(sequence)); } if (dumpAttribute == null) { throw new ArgumentNullException(nameof(dumpAttribute)); } if (dumpObject == null) { throw new ArgumentNullException(nameof(dumpObject)); } if (indent == null) { throw new ArgumentNullException(nameof(indent)); } if (unindent == null) { throw new ArgumentNullException(nameof(unindent)); } var sequenceType = sequence.GetType(); var elementsType = sequenceType.IsArray ? new Type[] { sequenceType.GetElementType() } : sequenceType.IsGenericType ? sequenceType.GetGenericArguments() : new Type[] { typeof(object) }; int count = 0; if (sequenceType.IsArray) { var piLength = sequenceType.GetProperty(nameof(Array.Length), BindingFlags.Instance | BindingFlags.Public); count = (int)piLength.GetValue(sequence); } else { var piCount = sequenceType.GetProperty(nameof(ICollection.Count), BindingFlags.Instance | BindingFlags.Public); if (piCount != null) { count = (int)piCount.GetValue(sequence); } else { count = int.MaxValue; } } // how many items to dump max? var max = dumpAttribute.GetMaxToDump(count); writer.Write( DumpFormat.SequenceTypeName, sequenceType.IsArray ? elementsType[0].GetTypeName() : sequenceType.GetTypeName(), count > 0 && count < int.MaxValue ? count.ToString(CultureInfo.InvariantCulture) : string.Empty); if (sequence is byte[] bytes) { // dump no more than max elements from the sequence: writer.Write(BitConverter.ToString(bytes, 0, max)); if (max < bytes.Length) { writer.Write(DumpFormat.SequenceDumpTruncated, max, count); } return(true); } writer.Write( DumpFormat.SequenceType, sequenceType.GetTypeName(), sequenceType.Namespace, sequenceType.AssemblyQualifiedName); // stop the recursion if dump.Recurse is false if (dumpAttribute.RecurseDump != ShouldDump.Skip) { var n = 0; indent(); foreach (var item in sequence) { writer.WriteLine(); if (n++ >= max) { writer.Write(DumpFormat.SequenceDumpTruncated, max, count); break; } dumpObject(item); } unindent(); } return(true); }
public static bool DumpedDictionary( this TextWriter writer, object sequence, DumpAttribute dumpAttribute, Action <object> dumpObject, Action indent, Action unindent) { if (writer == null) { throw new ArgumentNullException(nameof(writer)); } if (sequence == null) { throw new ArgumentNullException(nameof(sequence)); } if (dumpAttribute == null) { throw new ArgumentNullException(nameof(dumpAttribute)); } if (dumpObject == null) { throw new ArgumentNullException(nameof(dumpObject)); } if (indent == null) { throw new ArgumentNullException(nameof(indent)); } if (unindent == null) { throw new ArgumentNullException(nameof(unindent)); } var sequenceType = sequence.GetType(); var typeArguments = sequenceType.DictionaryTypeArguments(); if (typeArguments == null) { return(false); } Debug.Assert(typeArguments.Length == 2); var keyType = typeArguments[0]; var valueType = typeArguments[1]; int count = 0; var piCount = sequenceType.GetProperty(nameof(ICollection.Count), BindingFlags.Instance | BindingFlags.Public); if (piCount != null) { count = (int)piCount.GetValue(sequence); } var keyValueType = typeof(KeyValuePair <,>).MakeGenericType(keyType, valueType); writer.Write( DumpFormat.SequenceTypeName, sequenceType.GetTypeName(), count.ToString(CultureInfo.InvariantCulture)); writer.Write( DumpFormat.SequenceType, sequenceType.GetTypeName(), sequenceType.Namespace, sequenceType.AssemblyQualifiedName); // stop the recursion if dump.Recurse is false if (dumpAttribute.RecurseDump == ShouldDump.Skip) { return(true); } // how many items to dump max? var max = dumpAttribute.GetMaxToDump(count); var n = 0; writer.WriteLine(); writer.Write("{"); indent(); foreach (var kv in (IEnumerable)sequence) { Debug.Assert(kv.GetType() == keyValueType); writer.WriteLine(); if (n++ >= max) { writer.Write(DumpFormat.SequenceDumpTruncated, max, count); break; } var key = keyValueType.GetProperty("Key").GetValue(kv, null); var value = keyValueType.GetProperty("Value").GetValue(kv, null); writer.Write("["); dumpObject(key); writer.Write("] = "); dumpObject(value); } unindent(); writer.WriteLine(); writer.Write("}"); return(true); }
/// <summary> /// Dumps the value of basic types (all primitive types plus <see cref="String" />, <see cref="DateTime" />, <see cref="DateTimeOffset" />, /// <see cref="TimeSpan" />, <see cref="Decimal" /> enum-s, <see cref="Guid" />, Uri). /// </summary> /// <param name="writer">The writer.</param> /// <param name="value">The value.</param> /// <param name="dumpAttribute">The dump attribute associated with the value (e.g. property attribute).</param> /// <returns><c>true</c> if the value was dumped; otherwise <c>false</c> (e.g. the value is struct)</returns> public static bool DumpedBasicValue( this TextWriter writer, object value, DumpAttribute dumpAttribute) { if (writer == null) { throw new ArgumentNullException(nameof(writer)); } if (value == null) { writer.Write(DumpUtilities.Null); return(true); } if (!value.GetType().IsBasicType()) { return(false); } // store the max dump length here var dumpMaxLength = int.MaxValue; if (dumpAttribute != null) { // should we mask it - dump the mask and return if (dumpAttribute.Mask) { writer.Write(dumpAttribute.MaskValue); return(true); } // special formatting - dump blindly and return if (dumpAttribute.ValueFormat != DumpFormat.Value) { if (dumpAttribute.ValueFormat == Resources.ValueFormatToString) { writer.Write(value.ToString()); } else { writer.Write(dumpAttribute.ValueFormat, value); } return(true); } // get the max dump length if (dumpAttribute.MaxLength > 0) { dumpMaxLength = dumpAttribute.MaxLength; } } var type = value.GetType(); if (!_dumpBasicValues.TryGetValue(type, out var dump)) { if (type.IsEnum) { dump = DumpEnumValue; } else { dump = (w, v, max) => w.Write($"[{type.Name} value]"); } } dump(writer, value, dumpMaxLength); return(true); }
/// <summary> /// Implements the instance over type priority rule for the <see cref="DumpAttribute.RecurseDump"/> property. /// </summary> /// <param name="instanceAttribute">The instance associated attribute.</param> /// <returns> /// The property value that should be in effect. Never returns <see cref="ShouldDump.Default"/>. /// </returns> public ShouldDump RecurseDump(DumpAttribute instanceAttribute = null) => instanceAttribute != null && instanceAttribute.RecurseDump != ShouldDump.Default ? instanceAttribute.RecurseDump : DumpAttribute.RecurseDump != ShouldDump.Default ? DumpAttribute.RecurseDump : ShouldDump.Dump;
/// <summary> /// Implements the instance over type priority rule for the <see cref="DumpAttribute.DefaultProperty"/> property. /// </summary> /// <param name="instanceAttribute">The attribute associated with the instance.</param> /// <returns> /// The property value that should be in effect. /// </returns> public string DefaultProperty(DumpAttribute instanceAttribute) => instanceAttribute != null && !instanceAttribute.DefaultProperty.IsNullOrWhiteSpace() ? instanceAttribute.DefaultProperty : DumpAttribute.DefaultProperty;
/// <summary> /// Implements the instance over type priority rule for the <see cref="DumpAttribute.DumpNullValues"/> property. /// </summary> /// <param name="instanceAttribute">The instance associated attribute.</param> /// <returns> /// The property value that should be in effect. /// </returns> public ShouldDump DumpNullValues(DumpAttribute instanceAttribute) => instanceAttribute != null && instanceAttribute.DumpNullValues != ShouldDump.Default ? instanceAttribute.DumpNullValues : DumpAttribute.DumpNullValues == ShouldDump.Default ? ShouldDump.Dump : DumpAttribute.DumpNullValues;