Example #1
0
        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);
        }
Example #2
0
        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);
        }
Example #3
0
        /// <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);
        }
Example #4
0
        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));
        }
Example #5
0
        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;
            }
        }
Example #6
0
        /// <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);
        }
Example #7
0
 public ClassMetadataRegistrar Register <T>(
     DumpAttribute dumpAttribute,
     bool replace = false)
 => Register(typeof(T), null, dumpAttribute, replace);
Example #8
0
        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;
            }
        }
Example #9
0
        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);
        }
Example #10
0
        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);
        }
Example #11
0
        /// <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);
        }
Example #12
0
 /// <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;
Example #13
0
 /// <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;
Example #14
0
 /// <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;