BizHawk/ExternalCoreProjects/Virtu/ExtraConverters.cs

306 lines
9.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Linq;
using System.Reflection;
namespace Jellyfish.Virtu
{
public class ArrayConverter : JsonConverter
{
// JSON.NET cannot, when reading, use PreserveReferencesHandling on arrays, although it fully supports it on writing.
// Doing so while being able to fully preserve circular references would require storing the length of the array,
// or reading ahead in the JSON to compute the length. For arrays that could contain reference types, we choose the latter.
// For arrays of primitive types, there is no issue.
// TODO: on serialization, the type of the object is available, but is the expected type (ie, the one that we'll be fed during deserialization) available?
// need this to at least detect covariance cases...
public override bool CanConvert(Type objectType)
{
if (!typeof(Array).IsAssignableFrom(objectType))
return false;
if (objectType.GetArrayRank() > 1)
throw new NotImplementedException();
return true;
}
public override bool CanRead { get { return true; } }
public override bool CanWrite { get { return true; } }
private JsonSerializer bareserializer = new JsonSerializer(); // full default settings, separate context
private static void ReadExpectType(JsonReader reader, JsonToken expected)
{
if (!reader.Read())
throw new InvalidOperationException();
if (reader.TokenType != expected)
throw new InvalidOperationException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
else if (reader.TokenType != JsonToken.StartObject)
throw new InvalidOperationException();
ReadExpectType(reader, JsonToken.PropertyName);
string prop = reader.Value.ToString();
ReadExpectType(reader, JsonToken.String);
string id = reader.Value.ToString();
if (prop == "$ref")
{
object ret = serializer.ReferenceResolver.ResolveReference(serializer, id);
ReadExpectType(reader, JsonToken.EndObject);
return ret;
}
else if (prop == "$id")
{
ReadExpectType(reader, JsonToken.PropertyName);
prop = reader.Value.ToString();
if (prop == "$length") // complex array
{
ReadExpectType(reader, JsonToken.Integer);
int length = Convert.ToInt32(reader.Value);
ReadExpectType(reader, JsonToken.PropertyName);
if (reader.Value.ToString() != "$values")
throw new InvalidOperationException();
Type elementType = objectType.GetElementType();
Array ret = Array.CreateInstance(elementType, length);
// must register reference before deserializing elements to handle possible circular references
serializer.ReferenceResolver.AddReference(serializer, id, ret);
int index = 0;
ReadExpectType(reader, JsonToken.StartArray);
while (true)
{
if (!reader.Read())
throw new InvalidOperationException();
if (reader.TokenType == JsonToken.EndArray)
break;
ret.SetValue(serializer.Deserialize(reader, elementType), index++);
}
ReadExpectType(reader, JsonToken.EndObject);
return ret;
}
else if (prop == "$values") // simple array
{
if (!reader.Read())
throw new InvalidOperationException();
object ret = bareserializer.Deserialize(reader, objectType);
// OK to add this after deserializing, as arrays of primitive types can't contain backrefs
serializer.ReferenceResolver.AddReference(serializer, id, ret);
ReadExpectType(reader, JsonToken.EndObject);
return ret;
}
else
{
throw new InvalidOperationException();
}
}
else
{
throw new InvalidOperationException();
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (serializer.ReferenceResolver.IsReferenced(serializer, value))
{
writer.WriteStartObject();
writer.WritePropertyName("$ref");
writer.WriteValue(serializer.ReferenceResolver.GetReference(serializer, value));
writer.WriteEndObject();
}
else
{
writer.WriteStartObject();
writer.WritePropertyName("$id");
writer.WriteValue(serializer.ReferenceResolver.GetReference(serializer, value));
var elementType = value.GetType().GetElementType();
if (elementType.IsPrimitive)
{
writer.WritePropertyName("$values");
bareserializer.Serialize(writer, value);
}
else
{
var array = (Array)value;
writer.WritePropertyName("$length");
writer.WriteValue(array.Length);
writer.WritePropertyName("$values");
writer.WriteStartArray();
foreach (object o in array)
{
serializer.Serialize(writer, o, elementType);
}
writer.WriteEndArray();
}
writer.WriteEndObject();
}
}
}
public class TypeTypeConverter : JsonConverter
{
// serialize and deserialize types, ignoring assembly entirely and only using namespace+typename
// all types, including generic type arguments to supplied types, must be in one of the declared assemblies (only checked on read!)
// the main goal here is to have something with a slight chance of working across versions
public TypeTypeConverter(IEnumerable<Assembly> ass)
{
assemblies = ass.ToList();
}
private List<Assembly> assemblies;
private Dictionary<string, Type> readlookup = new Dictionary<string, Type>();
public override bool CanConvert(Type objectType)
{
return typeof(Type).IsAssignableFrom(objectType);
}
public override bool CanRead { get { return true; } }
public override bool CanWrite { get { return true; } }
private Type GetType(string name)
{
Type ret;
if (!readlookup.TryGetValue(name, out ret))
{
ret = assemblies.Select(ass => ass.GetType(name, false)).Where(t => t != null).Single();
readlookup.Add(name, ret);
}
return ret;
}
private static string GetName(Type type)
{
return string.Format("{0}.{1}", type.Namespace, type.Name);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
else if (reader.TokenType == JsonToken.String)
{
return GetType(reader.Value.ToString());
}
else if (reader.TokenType == JsonToken.StartArray) // full generic
{
List<string> vals = serializer.Deserialize<List<string>>(reader);
return GetType(vals[0]).MakeGenericType(vals.Skip(1).Select(GetType).ToArray());
}
else
{
throw new InvalidOperationException();
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var type = (Type)value;
if (type.IsGenericType && !type.IsGenericTypeDefinition)
{
writer.WriteStartArray();
writer.WriteValue(GetName(type));
foreach (var t in type.GetGenericArguments())
{
writer.WriteValue(GetName(t));
}
writer.WriteEndArray();
}
else
{
writer.WriteValue(GetName(type));
}
}
}
public class DelegateConverter : JsonConverter
{
// caveats: if used on anonymous delegates and/or closures, brittle to name changes in the generated classes and methods
// brittle to type name changes in general
// must be serialized in tree with any real classes referred to by closures
// CAN NOT preserve reference equality of the delegates themselves, because the delegate must be created with
// target in one shot, with no possibility to change the target later. We preserve references to targets,
// and lose the ability to preserve references to delegates.
// TODO: much of this could be made somewhat smarter and more resilient
public override bool CanConvert(Type objectType)
{
return typeof(Delegate).IsAssignableFrom(objectType);
}
public override bool CanRead { get { return true; } }
public override bool CanWrite { get { return true; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var slug = serializer.Deserialize<Slug>(reader);
if (slug == null)
return null;
return slug.GetDelegate();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var slug = new Slug((Delegate)value);
serializer.Serialize(writer, slug);
}
private class Slug
{
public Type DelegateType;
public Type MethodDeclaringType;
public string MethodName;
public List<Type> MethodParameters;
public object Target;
public Delegate GetDelegate()
{
var mi = MethodDeclaringType.GetMethod(
MethodName,
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static,
null,
MethodParameters.ToArray(),
null);
return Delegate.CreateDelegate(DelegateType, Target, mi);
}
public Slug() { }
public Slug(Delegate d)
{
DelegateType = d.GetType();
MethodDeclaringType = d.Method.DeclaringType;
MethodName = d.Method.Name;
MethodParameters = d.Method.GetParameters().Select(p => p.ParameterType).ToList();
Target = d.Target;
}
}
}
}