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

Юдин Павел #194

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Empty file added ObjectPrinting/AltConfig.cs
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
Empty file.
7 changes: 7 additions & 0 deletions ObjectPrinting/IPropertyPrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace ObjectPrinting
{
public interface IPropertyPrintingConfig<TOwner, TPropType>
{
PrintingConfig<TOwner> ParentConfig { get; }
}
}
3 changes: 2 additions & 1 deletion ObjectPrinting/ObjectPrinting.csproj
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<LangVersion>8</LangVersion>
<LangVersion>latest</LangVersion>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="NUnit" Version="3.12.0" />
</ItemGroup>
Expand Down
204 changes: 190 additions & 14 deletions ObjectPrinting/PrintingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,217 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace ObjectPrinting
{
public class PrintingConfig<TOwner>
{
private readonly List<Type> excludedTypes = new List<Type>();
private readonly List<PropertyInfo> excludedProperties = new List<PropertyInfo>();
private readonly Dictionary<Type, Delegate> serializedByType = new Dictionary<Type, Delegate>();

private readonly Dictionary<PropertyInfo, Delegate> serializedByPropertyInfo =
new Dictionary<PropertyInfo, Delegate>();

private readonly HashSet<object> printedObjects = new HashSet<object>();

public string PrintToString(TOwner obj)
{
return PrintToString(obj, 0);
}

public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>()
{
return new PropertyPrintingConfig<TOwner, TPropType>(this, null);
}

public PropertyPrintingConfig<TOwner, TPropType> Printing<TPropType>(
Expression<Func<TOwner, TPropType>> memberSelector)
{
return new PropertyPrintingConfig<TOwner, TPropType>(this, GetPropertyInfo(memberSelector));
}

public PrintingConfig<TOwner> Excluding<TPropType>(Expression<Func<TOwner, TPropType>> memberSelector)
{
excludedProperties.Add( GetPropertyInfo(memberSelector));
return this;
}
internal PrintingConfig<TOwner> Excluding<TPropType>()
{
excludedTypes.Add(typeof(TPropType));
return this;
}

private static PropertyInfo GetPropertyInfo<TPropType>(Expression<Func<TOwner, TPropType>> memberSelector)
{
if (memberSelector.Body is not MemberExpression memberExpression)
throw new ArgumentException($"Expression '{memberSelector}' refers to a method, not a property.");

if (memberExpression.Member is not PropertyInfo propInfo)
throw new ArgumentException($"Expression '{memberSelector}' refers to a field, not a property.");

return propInfo;
}

private static bool IsFinalType(Type type)
{
return type == typeof(string)
|| type.IsPrimitive
|| typeof(IFormattable).IsAssignableFrom(type);
}

private string PrintToString(object obj, int nestingLevel)
{
//TODO apply configurations
if (obj == null)
return "null" + Environment.NewLine;

var type = obj.GetType();
if (IsFinalType(type))
return obj + Environment.NewLine;

var sb = new StringBuilder();
if (printedObjects.Contains(obj))
return sb.AppendLine("(Cycle) " + type.FullName).ToString();

var finalTypes = new[]
sb.AppendLine(type.Name);
if (IsArrayOrList(type))
{
typeof(int), typeof(double), typeof(float), typeof(string),
typeof(DateTime), typeof(TimeSpan)
};
if (finalTypes.Contains(obj.GetType()))
return obj + Environment.NewLine;
return sb.Append(SerializeEnumerable(obj, nestingLevel)).ToString();
}

if (IsDictionary(type))
{
return sb.Append(SerializeEnumerable(obj, nestingLevel)).ToString();
}

printedObjects.Add(obj);

sb.Append(PrintProperties(obj, nestingLevel, type));

var identation = new string('\t', nestingLevel + 1);
return sb.ToString();
}

private string PrintProperties(object obj, int nestingLevel, Type type)
{
var indentation = new string('\t', nestingLevel + 1);
var sb = new StringBuilder();
var type = obj.GetType();
sb.AppendLine(type.Name);
foreach (var propertyInfo in type.GetProperties())
foreach (var propertyInfo in type.GetProperties().Where(prop => !IsExcluded(prop)))
{
sb.Append(identation + propertyInfo.Name + " = " +
PrintToString(propertyInfo.GetValue(obj),
nestingLevel + 1));
if (TrySerializeProperty(obj, propertyInfo, propertyInfo.PropertyType, out var serializedValue))
{
sb.AppendLine($"{indentation}{propertyInfo.Name} = {serializedValue}");
continue;
}

if (IsArrayOrList(propertyInfo.PropertyType))
{
sb.AppendLine(indentation + propertyInfo.Name + " = ");
sb.Append(SerializeEnumerable(propertyInfo.GetValue(obj), nestingLevel + 1));
}
else if (IsDictionary(propertyInfo.PropertyType))
{
sb.AppendLine(indentation + propertyInfo.Name + " = ");
sb.Append(SerializeDictionary(propertyInfo.GetValue(obj), nestingLevel + 1));
}
else
{
sb.Append(
$"{indentation}{propertyInfo.Name} = " +
$"{PrintToString(propertyInfo.GetValue(obj), nestingLevel + 1)}");
}
}

return sb.ToString();
}


private bool TrySerializeProperty(object obj, PropertyInfo propertyInfo, Type propertyType,
out string serializedValue)
{
serializedValue = null;
Delegate valueToUse = null;
if (serializedByPropertyInfo.TryGetValue(propertyInfo, out var propertyValue))
valueToUse = propertyValue;
if (serializedByType.TryGetValue(propertyType, out var typeValue))
valueToUse = typeValue;

return valueToUse != null && TrySerializeValue(valueToUse, propertyInfo.GetValue(obj), out serializedValue);
}

private static bool TrySerializeValue(Delegate serializer, object value, out string serializedValue)
{
try
{
serializedValue = serializer.DynamicInvoke(value)?.ToString();
return true;
}
catch
{
serializedValue = null;
return false;
}
}

private static bool IsArrayOrList(Type type)
{
return typeof(IList).IsAssignableFrom(type);
}

private static bool IsDictionary(Type type)
{
return typeof(IDictionary).IsAssignableFrom(type);
}

private string SerializeEnumerable(object obj, int nestingLevel)
{
var enumerable = (IEnumerable)obj;
var sb = new StringBuilder();
if (enumerable == null) return "null" + Environment.NewLine;
var indentation = new string('\t', nestingLevel + 1);
foreach (var value in enumerable)
{
sb.Append(indentation);
sb.Append(PrintToString(value, nestingLevel));
nestingLevel++;
}

return sb.ToString();
}

private string SerializeDictionary(object obj, int nestingLevel)
{
var dictionary = (IDictionary)obj;
var sb = new StringBuilder();
if (dictionary == null) return "null" + Environment.NewLine;
var indentation = new string('\t', nestingLevel + 1);
foreach (var keyVal in dictionary)
{
sb.Append(indentation);
var key = ((DictionaryEntry)keyVal).Key;
var value = ((DictionaryEntry)keyVal).Value;
sb.Append(PrintToString(key, nestingLevel) + " : " + PrintToString(value, nestingLevel));
}

return sb.ToString();
}

private bool IsExcluded(PropertyInfo propertyInfo)
{
return excludedProperties.Contains(propertyInfo) || excludedTypes.Contains(propertyInfo.PropertyType);
}

public void AddSerializeProperty<TPropType>(Func<TPropType, string> print, PropertyInfo propertyInfo)
{
serializedByPropertyInfo.Add(propertyInfo, print);
}

public void AddSerializeByType<TPropType>(Type type, Func<TPropType, string> print)
{
serializedByType.Add(type, print);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,39 @@
using System;
using System;
using System.Globalization;
using System.Reflection;

namespace ObjectPrinting.Solved
namespace ObjectPrinting
{
public class PropertyPrintingConfig<TOwner, TPropType> : IPropertyPrintingConfig<TOwner, TPropType>
{
private readonly PrintingConfig<TOwner> printingConfig;
private readonly PropertyInfo propertyInfo;

public PropertyPrintingConfig(PrintingConfig<TOwner> printingConfig)
public PropertyPrintingConfig(PrintingConfig<TOwner> printingConfig, PropertyInfo propertyInfo)
{
this.printingConfig = printingConfig;
this.propertyInfo = propertyInfo;
}

public PrintingConfig<TOwner> Using(Func<TPropType, string> print)
{
if (propertyInfo != null)
{
printingConfig.AddSerializeProperty(print, propertyInfo);
}
else
{
printingConfig.AddSerializeByType(typeof(TPropType), print);
}

return printingConfig;
}

public PrintingConfig<TOwner> Using(CultureInfo culture)
{
return printingConfig;
return Using(x =>((IConvertible)x).ToString(culture));
}

PrintingConfig<TOwner> IPropertyPrintingConfig<TOwner, TPropType>.ParentConfig => printingConfig;
}

public interface IPropertyPrintingConfig<TOwner, TPropType>
{
PrintingConfig<TOwner> ParentConfig { get; }
}
}
16 changes: 16 additions & 0 deletions ObjectPrinting/PropertyPrintingConfigExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;

namespace ObjectPrinting
{
public static class PropertyPrintingConfigExtensions
{
public static PrintingConfig<TOwner> TrimmedToLength<TOwner>(this PropertyPrintingConfig<TOwner, string> propConfig, int maxLen)
{
if (maxLen < 0)
{
throw new ArgumentException("Error: The length of the truncated string cannot be negative");
}
return propConfig.Using(x => x.Length > maxLen ? x[..maxLen] : x);
}
}
}
10 changes: 0 additions & 10 deletions ObjectPrinting/Solved/ObjectExtensions.cs

This file was deleted.

10 changes: 0 additions & 10 deletions ObjectPrinting/Solved/ObjectPrinter.cs

This file was deleted.

Loading