Skip to content

Commit

Permalink
new: allow providing custom assemblies to SimpleTypeBinder; move some…
Browse files Browse the repository at this point in the history
… tests over to the test project; also resolve SchemaFormatter in DynamicObjectFormatterResolver
  • Loading branch information
rikimaru0345 committed Feb 27, 2019
1 parent d64f3d6 commit c3a2521
Show file tree
Hide file tree
Showing 13 changed files with 647 additions and 701 deletions.
491 changes: 11 additions & 480 deletions samples/LiveTesting/Program.cs

Large diffs are not rendered by default.

30 changes: 7 additions & 23 deletions src/Ceras/CerasSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ internal static bool IsPrimitiveType(Type type)
return false;
}

static HashSet<Assembly> _frameworkAssemblies = new HashSet<Assembly>
internal static HashSet<Assembly> _frameworkAssemblies = new HashSet<Assembly>
{
typeof(object).Assembly, // mscorelib
typeof(Uri).Assembly, // System.dll
typeof(Enumerable).Assembly, // System.Core.dll
typeof(object).Assembly, // mscorelib
typeof(Uri).Assembly, // System.dll
typeof(Enumerable).Assembly, // System.Core.dll
};


Expand Down Expand Up @@ -181,7 +181,7 @@ public CerasSerializer(SerializerConfig config = null)
if (Config.Advanced.AotMode != AotMode.None && Config.VersionTolerance.Mode != VersionToleranceMode.Disabled)
throw new NotSupportedException("You can not use 'AotMode.Enabled' and version tolerance at the same time for now. If you would like this feature implemented, please open an issue on GitHub explaining your use-case, or join the Discord server.");

TypeBinder = Config.Advanced.TypeBinder ?? new NaiveTypeBinder();
TypeBinder = Config.Advanced.TypeBinder;
DiscardObjectMethod = Config.Advanced.DiscardObjectMethod;

_userResolvers = Config.OnResolveFormatter.ToArray();
Expand Down Expand Up @@ -618,23 +618,7 @@ IFormatter GetSpecificFormatter(Type type, TypeMetaData meta)
}
}


// 4.) Depending on the VersionTolerance we use different formatters
if (Config.VersionTolerance.Mode != VersionToleranceMode.Disabled)
{
if (!meta.IsFrameworkType && !meta.IsPrimitive && !meta.Type.IsArray)
{
// Create SchemaFormatter, it will automatically adjust itself to the schema when it's read
var formatterType = typeof(SchemaDynamicFormatter<>).MakeGenericType(type);
var schemaFormatter = (IFormatter)Activator.CreateInstance(formatterType, args: new object[] { this, meta.PrimarySchema });

meta.SpecificFormatter = schemaFormatter;
return schemaFormatter;
}
}


// 5.) Built-in
// 4.) Built-in
for (int i = 0; i < _resolvers.Count; i++)
{
var formatter = _resolvers[i].GetFormatter(type);
Expand All @@ -646,7 +630,7 @@ IFormatter GetSpecificFormatter(Type type, TypeMetaData meta)
}
}

// 6.) Dynamic
// 5.) Dynamic (optionally using Schema)
{
var formatter = _dynamicResolver.GetFormatter(type);
if (formatter != null)
Expand Down
5 changes: 3 additions & 2 deletions src/Ceras/Config/SerializerConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ public Action<TypeConfig> OnConfigNewType
bool IAdvancedConfigOptions.PersistTypeCache { get; set; } = false;
bool IAdvancedConfigOptions.SealTypesWhenUsingKnownTypes { get; set; } = true;
bool IAdvancedConfigOptions.SkipCompilerGeneratedFields { get; set; } = true;
ITypeBinder IAdvancedConfigOptions.TypeBinder { get; set; } = null;
ITypeBinder IAdvancedConfigOptions.TypeBinder { get; set; } = new SimpleTypeBinder();
DelegateSerializationFlags IAdvancedConfigOptions.DelegateSerialization { get; set; } = DelegateSerializationFlags.Off;
bool IAdvancedConfigOptions.UseReinterpretFormatter { get; set; } = true;
bool IAdvancedConfigOptions.RespectNonSerializedAttribute { get; set; } = true;
Expand Down Expand Up @@ -291,8 +291,9 @@ public interface IAdvancedConfigOptions
/// <para>Examples:</para>
/// <para>- Mapping server objects to client objects</para>
/// <para>- Shortening / abbreviating type-names to save space and performance</para>
/// The default type binder (NaiveTypeBinder) simply uses '.FullName'
/// See the readme on github for more information.
///
/// <para>Default: new SimpleTypeBinder()</para>
/// </summary>
ITypeBinder TypeBinder { get; set; }

Expand Down
2 changes: 1 addition & 1 deletion src/Ceras/Formatters/TypeFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public void Deserialize(byte[] buffer, ref int offset, ref Type type)
// Read construct full composite (example: Dictionary<string, object>)
var compositeProxy = typeCache.CreateDeserializationProxy();

type = _typeBinder.GetTypeFromBaseAndAgruments(baseType.FullName, genericArgs);
type = _typeBinder.GetTypeFromBaseAndArguments(baseType.FullName, genericArgs);
compositeProxy.Type = type; // make it available for future deserializations

if (_isSealed)
Expand Down
90 changes: 0 additions & 90 deletions src/Ceras/Helpers/NaiveTypeBinder.cs

This file was deleted.

99 changes: 99 additions & 0 deletions src/Ceras/Helpers/SimpleTypeBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
namespace Ceras
{
using System;
using System.Collections.Generic;
using System.Reflection;

/// <summary>
/// A type binder is simple. It is responsible to converts a type to a string and back.
/// For generic types it must do so by deconstructing the type though. So giving <see cref="List{int}"/> would return "System.Collections.List".
/// </summary>
public interface ITypeBinder
{
string GetBaseName(Type type);
Type GetTypeFromBase(string baseTypeName);
Type GetTypeFromBaseAndArguments(string baseTypeName, params Type[] genericTypeArguments);
}

/// <summary>
/// This simple type binder does two things:
/// <para>- does the basic ITypeBinder thing (converting types to names, and back)</para>
/// <para>- allows the user to add assemblies that will be searched for types</para>
/// </summary>
public class SimpleTypeBinder : ITypeBinder
{
readonly HashSet<Assembly> _searchAssemblies = new HashSet<Assembly>();

/// <summary>
/// Put your own assemblies in here for Ceras to discover them. If you don't and a type is not found, Ceras will have to look in all loaded assemblies (which is slow)
/// </summary>
public HashSet<Assembly> CustomSearchAssemblies { get; } = new HashSet<Assembly>();

public SimpleTypeBinder()
{
// Search in framework
foreach (var frameworkAsm in CerasSerializer._frameworkAssemblies)
_searchAssemblies.Add(frameworkAsm);

// Search in user code
_searchAssemblies.Add(Assembly.GetEntryAssembly());

_searchAssemblies.RemoveWhere(a => a == null);
}


public string GetBaseName(Type type)
{
if (type.IsGenericType)
return type.GetGenericTypeDefinition().FullName;

return type.FullName;
}

public Type GetTypeFromBase(string baseTypeName)
{
foreach (var a in _searchAssemblies)
{
var t = a.GetType(baseTypeName);
if (t != null)
return t;
}

foreach (var a in CustomSearchAssemblies)
{
if (_searchAssemblies.Contains(a))
continue; // We've already searched there

var t = a.GetType(baseTypeName);
if (t != null)
{
_searchAssemblies.Add(a);
return t;
}
}

// Oh no... did the user forget to add the right assembly??
// Lets search in everything that's loaded...
foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
{
if (_searchAssemblies.Contains(a) || CustomSearchAssemblies.Contains(a))
continue; // We've already searched there

var t = a.GetType(baseTypeName);
if (t != null)
{
_searchAssemblies.Add(a);
return t;
}
}

throw new Exception("Cannot find type " + baseTypeName + " after searching in all user provided assemblies and all loaded assemblies. Is the type in some plugin-module that was not yet loaded? Or did the assembly that contains the type change (ie the type got removed)?");
}

public Type GetTypeFromBaseAndArguments(string baseTypeName, params Type[] genericTypeArguments)
{
var baseType = GetTypeFromBase(baseTypeName);
return baseType.MakeGenericType(genericTypeArguments);
}
}
}
26 changes: 18 additions & 8 deletions src/Ceras/Resolvers/DynamicObjectFormatterResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
public class DynamicObjectFormatterResolver : IFormatterResolver
{
CerasSerializer _ceras;
TypeDictionary<IFormatter> _dynamicFormatters = new TypeDictionary<IFormatter>();
VersionToleranceMode _versionToleranceMode;

public DynamicObjectFormatterResolver(CerasSerializer ceras)
{
_ceras = ceras;
_versionToleranceMode = ceras.Config.VersionTolerance.Mode;
}

public IFormatter GetFormatter(Type type)
Expand All @@ -23,16 +24,25 @@ public IFormatter GetFormatter(Type type)
{
throw new InvalidOperationException($"No formatter for the Type '{type.FullName}' was found. Ceras is trying to fall back to the DynamicFormatter, but that formatter will never work in on AoT compiled platforms. Use the code generator tool to automatically generate a formatter for this type.");
}

var meta = _ceras.GetTypeMetaData(type);

if (meta.IsPrimitive)
throw new InvalidOperationException("DynamicFormatter is not allowed to serialize serialization-primitives.");

ref var formatter = ref _dynamicFormatters.GetOrAddValueRef(type);
if (formatter != null)
return formatter;

var dynamicFormatterType = typeof(DynamicFormatter<>).MakeGenericType(type);
formatter = (IFormatter)Activator.CreateInstance(dynamicFormatterType, _ceras);

return formatter;
if ((_versionToleranceMode == VersionToleranceMode.Standard && !meta.IsFrameworkType) ||
(_versionToleranceMode == VersionToleranceMode.Extended && meta.IsFrameworkType))
{
// SchemaFormatter will automatically adjust itself to the schema when it's read
var formatterType = typeof(SchemaDynamicFormatter<>).MakeGenericType(type);
return (IFormatter)Activator.CreateInstance(formatterType, args: new object[] { _ceras, meta.PrimarySchema });
}
else
{
var formatterType = typeof(DynamicFormatter<>).MakeGenericType(type);
return (IFormatter)Activator.CreateInstance(formatterType, _ceras);
}
}
}
}
Loading

0 comments on commit c3a2521

Please sign in to comment.