Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Горбатов Александр #187

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions ObjectPrinting/ObjectPrinter.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
namespace ObjectPrinting
namespace ObjectPrinting;

public static class ObjectPrinter
{
public class ObjectPrinter
public static PrintingConfig<T> For<T>()
{
public static PrintingConfig<T> For<T>()
{
return new PrintingConfig<T>();
}
return new PrintingConfig<T>();
}
}
4 changes: 2 additions & 2 deletions ObjectPrinting/ObjectPrinting.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<LangVersion>8</LangVersion>
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>11</LangVersion>
<TargetFramework>net7.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>

Expand Down
237 changes: 209 additions & 28 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,222 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace ObjectPrinting
namespace ObjectPrinting;

public class PrintingConfig<TOwner>
{
public class PrintingConfig<TOwner>
private readonly HashSet<Type> excludedTypes = new();
private readonly Dictionary<Type, Func<object, string>> customTypeSerialization = new();
private readonly Dictionary<Type, CultureInfo> specifiedCultureInfo = new();
protected readonly Dictionary<string, Func<object, string>> CustomMemberSerialization = new();
protected readonly Dictionary<string, int> MemberMaxStringLength = new();
protected readonly HashSet<string> ExcludedMembers = new();

private readonly Type[] finalTypes =
{
typeof(int), typeof(double), typeof(float), typeof(string),
typeof(DateTime), typeof(TimeSpan), typeof(Guid), typeof(uint),
typeof(decimal), typeof(long), typeof(ushort), typeof(ulong), typeof(short)
};

public PrintingConfig()
{
}

protected PrintingConfig(PrintingConfig<TOwner> printingConfig)
{
excludedTypes = printingConfig.excludedTypes;
customTypeSerialization = printingConfig.customTypeSerialization;
specifiedCultureInfo = printingConfig.specifiedCultureInfo;
CustomMemberSerialization = printingConfig.CustomMemberSerialization;
MemberMaxStringLength = printingConfig.MemberMaxStringLength;
ExcludedMembers = printingConfig.ExcludedMembers;
}

public string PrintToString(TOwner obj)
{
return PrintToString(obj, 0, obj.GetType().Name, new List<object>());
}

public PrintingConfig<TOwner> ExcludeType<T>()
{
excludedTypes.Add(typeof(T));
return this;
}

public PrintingConfig<TOwner> WithSerializationForType<T>(Func<T, string> serializationFunc)
{
var type = typeof(T);
customTypeSerialization[type] = obj => serializationFunc((T) obj);
return this;
}

public PrintingConfig<TOwner> SetCultureForType<T>(CultureInfo cultureInfo) where T : IFormattable
{
public string PrintToString(TOwner obj)
var type = typeof(T);
specifiedCultureInfo[type] = cultureInfo;
return this;
}

public PrintingStringMemberConfig<TOwner> SelectMember(Expression<Func<TOwner, string>> member)
{
PrintingConfigUtils.ValidateMemberExpression(member);
return new PrintingStringMemberConfig<TOwner>(this, member);
}

public PrintingMemberConfig<TOwner, T> SelectMember<T>(Expression<Func<TOwner, T>> member)
{
PrintingConfigUtils.ValidateMemberExpression(member);
return new PrintingMemberConfig<TOwner, T>(this, member);
}

private string PrintWithCultureIfExists(object obj, string initial)
{
if (obj is not IFormattable formattable ||
!specifiedCultureInfo.TryGetValue(obj.GetType(), out var cultureInfo))
return initial;

return formattable.ToString("", cultureInfo) + Environment.NewLine;
}

private string PrintToString(object obj, int nestingLevel, string memberPrefix, List<object> parents)
{
if (parents.Contains(obj))
return "CYCLIC_REFERENCE" + Environment.NewLine;

if (obj is null)
return "null" + Environment.NewLine;

if (excludedTypes.Contains(obj.GetType()) || ExcludedMembers.Contains(memberPrefix))
return "";

if (CustomMemberSerialization.TryGetValue(memberPrefix, out var memberSerialization))
return memberSerialization(obj) + Environment.NewLine;

if (customTypeSerialization.TryGetValue(obj.GetType(), out var typeSerialization))
return typeSerialization(obj) + Environment.NewLine;

if (MemberMaxStringLength.TryGetValue(memberPrefix, out var maxLength))
return CutString((string) obj, maxLength) + Environment.NewLine;

if (finalTypes.Contains(obj.GetType()))
return PrintWithCultureIfExists(obj, obj + Environment.NewLine);

if (obj is IEnumerable enumerable)
return PrintEnumerable(enumerable, nestingLevel, memberPrefix, parents);

return PrintObjectMembers(obj, nestingLevel, memberPrefix, parents);
}

private string PrintEnumerable(IEnumerable obj, int nestingLevel, string memberPrefix, List<object> parents)
{
var enumerator = obj.GetEnumerator();
using var disposable = enumerator as IDisposable;
if (!enumerator.MoveNext())
return "EMPTY_COLLECTION" + Environment.NewLine;

if (obj is IList indexed)
return PrintIndexed(indexed, nestingLevel, memberPrefix, parents);
if (obj is IDictionary dictionary)
return PrintDictionary(dictionary, nestingLevel, memberPrefix, parents);
return "NOT_SUPPORTED_COLLECTION" + Environment.NewLine;
}

private string PrintCollectionElement(string memberPrefix, string itemIdentificator, string indentation,
object element, int nestingLevel, List<object> parents)
{
var sb = new StringBuilder();
var elementName = memberPrefix + $".get_Item({itemIdentificator})";
sb.Append(indentation);
sb.Append(PrintToString(element, nestingLevel + 1, elementName, parents));
sb.Remove(sb.Length - Environment.NewLine.Length, Environment.NewLine.Length);
return sb.ToString();
}

private string PrintDictionary(IDictionary dictionary, int nestingLevel, string memberPrefix, List<object> parents)
{
var indentation = new string('\t', nestingLevel + 1);
var sb = new StringBuilder("{" + Environment.NewLine);
foreach (DictionaryEntry entry in dictionary)
{
return PrintToString(obj, 0);
sb.Append(PrintCollectionElement(memberPrefix, "DICTIONARY_ELEMENT", indentation, entry, nestingLevel, parents));
sb.AppendLine(",");
}

private string PrintToString(object obj, int nestingLevel)
sb.Remove(sb.Length - Environment.NewLine.Length - 1, Environment.NewLine.Length + 1);
sb.AppendLine();
sb.AppendLine(indentation[..^1] + "}");
return sb.ToString();
}

private string PrintIndexed(IList indexed, int nestingLevel, string memberPrefix, List<object> parents)
{
var indentation = new string('\t', nestingLevel + 1);
var sb = new StringBuilder("[" + Environment.NewLine);
for (var i = 0; i < indexed.Count; i++)
{
//TODO apply configurations
if (obj == null)
return "null" + Environment.NewLine;

var finalTypes = new[]
{
typeof(int), typeof(double), typeof(float), typeof(string),
typeof(DateTime), typeof(TimeSpan)
};
if (finalTypes.Contains(obj.GetType()))
return obj + Environment.NewLine;

var identation = new string('\t', nestingLevel + 1);
var sb = new StringBuilder();
var type = obj.GetType();
sb.AppendLine(type.Name);
foreach (var propertyInfo in type.GetProperties())
{
sb.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
}
return sb.ToString();
sb.Append(PrintCollectionElement(memberPrefix, i.ToString(), indentation, indexed[i], nestingLevel, parents));
if (i != indexed.Count - 1)
sb.AppendLine(",");
}

sb.AppendLine();
sb.AppendLine(indentation[..^1] + "]");
return sb.ToString();
}

private static string CutString(string s, int maxLength)
{
return s.Length > maxLength ? s[..maxLength] : s;
}

private string PrintObjectMembers(object obj, int nestingLevel, string memberPrefix, List<object> parents)
{
var indentation = new string('\t', nestingLevel + 1);
var objectType = obj.GetType();
var sb = new StringBuilder().AppendLine(objectType.Name);

if (memberPrefix != "")
memberPrefix += ".";

parents.Add(obj);
foreach (var propertyInfo in objectType.GetProperties())
{
sb.Append(ProcessMember(propertyInfo.PropertyType, propertyInfo.Name, propertyInfo.GetValue(obj),
indentation,
memberPrefix, nestingLevel, parents));
}

foreach (var fieldInfo in objectType.GetFields())
{
sb.Append(ProcessMember(fieldInfo.FieldType, fieldInfo.Name, fieldInfo.GetValue(obj), indentation,
memberPrefix,
nestingLevel, parents));
}

parents.Remove(parents.Count - 1);
return sb.ToString();
}

private string ProcessMember(Type type, string name, object value, string indentation, string memberPrefix,
int nestingLevel, List<object> parents)
{
var sb = new StringBuilder();

if (excludedTypes.Contains(type) || ExcludedMembers.Contains(memberPrefix + name))
return "";

sb.Append(indentation + name + " = ");

sb.Append(CustomMemberSerialization.TryGetValue(memberPrefix + name, out var serialization)
? serialization(value) + Environment.NewLine
: PrintToString(value, nestingLevel + 1, memberPrefix + name, parents));

return sb.ToString();
}
}
13 changes: 13 additions & 0 deletions ObjectPrinting/PrintingConfigUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Linq.Expressions;

namespace ObjectPrinting;

public static class PrintingConfigUtils
{
public static void ValidateMemberExpression<TOwner, T>(Expression<Func<TOwner, T>> member)
{
if (member.Body.NodeType != ExpressionType.MemberAccess)
throw new MissingMemberException("Type's member has to be selected.");
}
}
29 changes: 29 additions & 0 deletions ObjectPrinting/PrintingMemberConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Linq.Expressions;

namespace ObjectPrinting;

public class PrintingMemberConfig<TOwner, T> : PrintingConfig<TOwner>
{
protected readonly Expression<Func<TOwner, T>> Member;

public PrintingMemberConfig(PrintingConfig<TOwner> printingConfig, Expression<Func<TOwner, T>> member)
: base(printingConfig)
{
Member = member;
}

public PrintingMemberConfig<TOwner, T> ExcludeMember()
{
var memberName = PrintingMemberConfigUtils.GetFullMemberName(Member);
ExcludedMembers.Add(memberName);
return this;
}

public PrintingMemberConfig<TOwner, T> WithSerialization(Func<T, string> serializationFunc)
{
var memberName = PrintingMemberConfigUtils.GetFullMemberName(Member);
CustomMemberSerialization[memberName] = obj => serializationFunc((T)obj);
return this;
}
}
38 changes: 38 additions & 0 deletions ObjectPrinting/PrintingMemberConfigUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace ObjectPrinting;

public static class PrintingMemberConfigUtils
{
public static string GetFullMemberName<TOwner, T>(Expression<Func<TOwner, T>> expression)
{
PrintingConfigUtils.ValidateMemberExpression(expression);
var expressionBody = expression.Body;

var memberNames = new List<string>();
while (expressionBody != null)
{
switch (expressionBody)
{
case MemberExpression memberExpression:
memberNames.Add(memberExpression.Member.Name);
expressionBody = memberExpression.Expression;
continue;
case MethodCallExpression {Method.Name: "get_Item"} methodCallExpression:
memberNames.Add($"get_Item({methodCallExpression.Arguments[0]})");
expressionBody = methodCallExpression.Object;
continue;
default:
expressionBody = null;
break;
}
}

memberNames.Reverse();
memberNames.Insert(0, typeof(TOwner).Name);

return string.Join(".", memberNames);
}
}
21 changes: 21 additions & 0 deletions ObjectPrinting/PrintingStringMemberConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Linq.Expressions;

namespace ObjectPrinting;

public class PrintingStringMemberConfig<TOwner> : PrintingMemberConfig<TOwner, string>
{
public PrintingStringMemberConfig(PrintingConfig<TOwner> printingConfig, Expression<Func<TOwner, string>> member) : base(printingConfig, member)
{
}

public PrintingStringMemberConfig<TOwner> SetStringMaxLength(int length)
{
if (length < 0)
throw new ArgumentException("Provided string length cannot be negative");

var memberName = PrintingMemberConfigUtils.GetFullMemberName(Member);
MemberMaxStringLength[memberName] = length;
return this;
}
}
Loading