409 lines
19 KiB
C#
409 lines
19 KiB
C#
#region Using Statements
|
|
using ANX.Framework.Content.Pipeline.Helpers;
|
|
using ANX.Framework.Content.Pipeline.Serialization.Intermediate.SystemTypeSerializers;
|
|
using ANX.Framework.NonXNA.Development;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Xml;
|
|
#endregion
|
|
|
|
// This file is part of the ANX.Framework created by the
|
|
// "ANX.Framework developer group" and released under the Ms-PL license.
|
|
// For details see: http://anxframework.codeplex.com/license
|
|
|
|
namespace ANX.Framework.Content.Pipeline.Serialization.Intermediate
|
|
{
|
|
/// <summary>
|
|
/// Provides methods for reading and writing intermediate XML format.
|
|
/// </summary>
|
|
[Developer("KorsarNek")]
|
|
[TestState(TestStateAttribute.TestState.Tested)]
|
|
[PercentageComplete(100)]
|
|
public class IntermediateSerializer
|
|
{
|
|
private Dictionary<Type, Type> genericSerializerTypeHandler = new Dictionary<Type, Type>();
|
|
private Dictionary<Type, ContentTypeSerializer> serializerInstances = new Dictionary<Type, ContentTypeSerializer>();
|
|
private XmlTypeNameContainer xmlTypeNameContainer = new XmlTypeNameContainer();
|
|
private string contentTagName = "AnxContent";
|
|
|
|
//Used for the option to rename the anx namespaces to xna.
|
|
private const string XnaFrameworkNamespace = "Microsoft.Xna.Framework";
|
|
private const string AnxFrameworkNamespace = "ANX.Framework";
|
|
|
|
private static readonly ContentSerializerAttribute assetAttribute = new ContentSerializerAttribute() { ElementName = "Asset" };
|
|
|
|
private static IntermediateSerializer singleton;
|
|
|
|
internal static IntermediateSerializer Singleton
|
|
{
|
|
get
|
|
{
|
|
if (singleton == null)
|
|
singleton = new IntermediateSerializer(true, false);
|
|
|
|
return singleton;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new intermediate serialzier with the option to use xna namespaces and search <see cref="ContentTypeSerializer"/>s.
|
|
/// </summary>
|
|
/// <param name="searchSerializers"></param>
|
|
/// <param name="changeToXnaNamespaces"></param>
|
|
/// <exception cref="System.ArgumentException">Will be thrown if <see cref="ContentTypeSerializer"/>s are loaded that conform to the requirements.</exception>
|
|
/// <exception cref="System.MissingMethodException">Will be thrown if no parameterless constructor for a <see cref="ContentTypeSerializer"/> can be found.</exception>
|
|
#if XNAEXT
|
|
public IntermediateSerializer(bool searchSerializers = true, bool changeToXnaNamespaces = false)
|
|
#else
|
|
internal IntermediateSerializer(bool searchSerializers = true, bool changeToXnaNamespaces = false)
|
|
#endif
|
|
{
|
|
if (searchSerializers)
|
|
{
|
|
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
|
|
{
|
|
if (!AssemblyHelper.IsValidForPipeline(assembly.GetName()))
|
|
continue;
|
|
|
|
this.AddContentTypeSerializerAssembly(assembly);
|
|
}
|
|
}
|
|
|
|
if (changeToXnaNamespaces)
|
|
{
|
|
this.ChangeToXnaNamespaces();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The name of the main element around the <Assset> element.
|
|
/// If the value is empty or null, no element is used.
|
|
/// </summary>
|
|
public string ContentElementName
|
|
{
|
|
get { return contentTagName; }
|
|
set { contentTagName = value; }
|
|
}
|
|
|
|
private void AddContentTypeSerializerAssembly(Assembly assembly)
|
|
{
|
|
foreach (Type type in assembly.GetTypes())
|
|
{
|
|
ContentTypeSerializerAttribute[] value = (ContentTypeSerializerAttribute[])type.GetCustomAttributes(typeof(ContentTypeSerializerAttribute), true);
|
|
if (value.Length > 0)
|
|
{
|
|
if (!typeof(ContentTypeSerializer).IsAssignableFrom(type))
|
|
continue;
|
|
|
|
AddTypeSerializer(type);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static Type ValidateGenericSerializer(Type type)
|
|
{
|
|
//The only instance when we can know the type of T is when T is also used as the Parameter for the generic ContentTypeSerializer<T>.
|
|
Type baseType = type.BaseType;
|
|
while (!baseType.IsGenericType || baseType.GetGenericTypeDefinition() != typeof(ContentTypeSerializer<>))
|
|
{
|
|
baseType = baseType.BaseType;
|
|
if (baseType == null)
|
|
{
|
|
throw new ArgumentException(string.Format("Intermediate {0} {1} is an invalid generic. It should be in the form My{0}<T> : {0}<MyType<T>> .", typeof(ContentTypeSerializer<>).Name, type));
|
|
}
|
|
}
|
|
|
|
//The generic argument must be itself generic, that way we can distinguish which of the generic handlers to use.
|
|
//MySerializer<T> : ContentTypeSerializer<MyGeneric<T>> When ever we meet a version of MyGeneric, we can find the responsible serializer.
|
|
Type genericArgument = baseType.GetGenericArguments()[0];
|
|
if (!genericArgument.IsGenericType)
|
|
{
|
|
throw new ArgumentException(string.Format("Intermediate {0} {1} is an invalid generic. It should be in the form My{0}<T> : {0}<MyType<T>> .", typeof(ContentTypeSerializer<>).Name, type));
|
|
}
|
|
|
|
if (genericArgument.IsByRef || genericArgument.IsPointer)
|
|
{
|
|
throw new ArgumentException(string.Format("Cannot serialize type {0}. Pointers and references are not supported.", type));
|
|
}
|
|
|
|
if (genericArgument.IsSubclassOf(typeof(Delegate)))
|
|
{
|
|
throw new ArgumentException(string.Format("Cannot serialize type {0}. Delegates are not supported.", type));
|
|
}
|
|
|
|
if (!type.GetGenericArguments().SequenceEqual(genericArgument.GetGenericArguments()))
|
|
{
|
|
throw new ArgumentException(string.Format("Intermediate {0} {1} is an invalid generic. It should be in the form My{0}<T> : {0}<MyType<T>> .", typeof(ContentTypeSerializer<>).Name, type));
|
|
}
|
|
|
|
return genericArgument;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a <see cref="ContentTypeSerializer"/> to this intermediate serializer instance.
|
|
/// </summary>
|
|
/// <param name="type"></param>
|
|
/// <exception cref="System.ArgumentNullException"></exception>
|
|
/// <exception cref="System.ArgumentException"></exception>
|
|
/// <exception cref="System.InvalidOperationException"></exception>
|
|
/// <exception cref="System.MissingMethodException">Will be thrown if no parameterless constructor can be found.</exception>
|
|
public void AddTypeSerializer(Type type)
|
|
{
|
|
if (type == null)
|
|
throw new ArgumentNullException("type");
|
|
|
|
if (type.IsByRef || type.IsPointer)
|
|
throw new ArgumentException(string.Format("Intermediate serializer type {0}. Pointers and references are not supported.", type));
|
|
|
|
if (!typeof(ContentTypeSerializer).IsAssignableFrom(type))
|
|
throw new ArgumentException(string.Format("\"{0}\" must inherit from {1}.", type.FullName, typeof(ContentTypeSerializer).Name));
|
|
|
|
//If it contains open generic parameters like MySerializer<T>. We can't create the instance until we know T.
|
|
if (type.IsGenericTypeDefinition)
|
|
{
|
|
Type genericArgument = ValidateGenericSerializer(type);
|
|
|
|
Type genericTypeDefinition = genericArgument.GetGenericTypeDefinition();
|
|
if (genericSerializerTypeHandler.ContainsKey(genericTypeDefinition))
|
|
{
|
|
throw new InvalidOperationException(string.Format("Intermediate {0} \"{1}\" conflicts with existing handler \"{2}\" for {3}.",
|
|
typeof(ContentTypeSerializer<>).Name,
|
|
type.AssemblyQualifiedName,
|
|
genericSerializerTypeHandler[genericTypeDefinition].AssemblyQualifiedName,
|
|
genericTypeDefinition));
|
|
}
|
|
genericSerializerTypeHandler.Add(genericTypeDefinition, type);
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
AddTypeSerializer((ContentTypeSerializer)Activator.CreateInstance(type));
|
|
}
|
|
catch (MissingMethodException exc)
|
|
{
|
|
throw new ArgumentException(string.Format("\"{0}\" doesn't have a public parameterless constructor.", type.AssemblyQualifiedName), exc);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void AddTypeSerializer(ContentTypeSerializer serializer)
|
|
{
|
|
if (serializerInstances.ContainsKey(serializer.TargetType))
|
|
throw new InvalidOperationException(string.Format("Intermediate {0} \"{1}\" conflicts with existing handler \"{2}\" for {3}.", typeof(ContentTypeSerializer).Name, serializer.GetType().AssemblyQualifiedName, this.serializerInstances[serializer.TargetType].GetType().AssemblyQualifiedName, serializer.TargetType));
|
|
|
|
if (serializer.XmlTypeName != null)
|
|
{
|
|
this.xmlTypeNameContainer.AddSerializer(serializer);
|
|
}
|
|
serializerInstances.Add(serializer.TargetType, serializer);
|
|
|
|
serializer.Initialize(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes an object into an intermediate XML file.
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <param name="output">The output XML stream.</param>
|
|
/// <param name="value">The object to be serialized.</param>
|
|
/// <param name="referenceRelocationPath">Final name of the output file, used to relative encode external reference filenames.</param>
|
|
/// <exception cref="System.ArgumentNullException"></exception>
|
|
/// <exception cref="System.ArgumentException"></exception>
|
|
/// <exception cref="System.RankException">Will be thrown if a multidimensional array is passed.</exception>
|
|
/// <exception cref="System.MissingMethodException">Will be thrown if no parameterless constructor for the corresponding <see cref="ContentTypeSerializer"/> can be found.</exception>
|
|
public virtual void SerializeObject<T>(XmlWriter output, T value, string referenceRelocationPath)
|
|
{
|
|
if (output == null)
|
|
throw new ArgumentNullException("output");
|
|
|
|
if (value == null)
|
|
throw new ArgumentNullException("value");
|
|
|
|
bool expectsContentTag = !string.IsNullOrWhiteSpace(ContentElementName);
|
|
|
|
IntermediateWriter intermediateWriter = new IntermediateWriter(this, new IntermediateXmlWriter(output), referenceRelocationPath, xmlTypeNameContainer, expectsContentTag);
|
|
|
|
if (expectsContentTag)
|
|
{
|
|
output.WriteStartElement(ContentElementName);
|
|
}
|
|
|
|
intermediateWriter.WriteObject<object>(value, assetAttribute);
|
|
intermediateWriter.WriteSharedResources();
|
|
intermediateWriter.WriteExternalReferences();
|
|
|
|
if (expectsContentTag)
|
|
{
|
|
intermediateWriter.WriteUsedNamespaces();
|
|
intermediateWriter.ComposeXml();
|
|
output.WriteEndElement();
|
|
}
|
|
else
|
|
{
|
|
intermediateWriter.ComposeXml();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes an object into an intermediate ANX XML file.
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <param name="output">The output XML stream.</param>
|
|
/// <param name="value">The object to be serialized.</param>
|
|
/// <param name="referenceRelocationPath">Final name of the output file, used to relative encode external reference filenames.</param>
|
|
/// <exception cref="System.ArgumentNullException"></exception>
|
|
/// <exception cref="System.ArgumentException"></exception>
|
|
/// <exception cref="System.RankException">Will be thrown if a multidimensional array is passed.</exception>
|
|
/// <exception cref="System.MissingMethodException">Will be thrown if no parameterless constructor for the corresponding <see cref="ContentTypeSerializer"/> can be found.</exception>
|
|
public static void Serialize<T>(XmlWriter output, T value, string referenceRelocationPath)
|
|
{
|
|
Singleton.SerializeObject<T>(output, value, referenceRelocationPath);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deserializes an intermediate XML file into a managed object.
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <param name="input">Intermediate XML file.</param>
|
|
/// <param name="referenceRelocationPath">Final name of the output file used to relative encode external reference filenames.</param>
|
|
/// <returns></returns>
|
|
/// <exception cref="System.ArgumentNullException"></exception>
|
|
/// <exception cref="System.MissingMethodException">Will be thrown if no parameterless constructor for the corresponding <see cref="ContentTypeSerializer"/> can be found.</exception>
|
|
public virtual T DeserializeObject<T>(XmlReader input, string referenceRelocationPath)
|
|
{
|
|
if (input == null)
|
|
throw new ArgumentNullException("input");
|
|
|
|
bool expectsContentTag = !string.IsNullOrWhiteSpace(ContentElementName);
|
|
if (expectsContentTag)
|
|
{
|
|
if (!input.CheckForElement(ContentElementName))
|
|
throw ExceptionHelper.CreateInvalidContentException(input, referenceRelocationPath, null, string.Format("XML is not in the intermediate format. Missing {0} root element.", this.ContentElementName));
|
|
}
|
|
|
|
input.ReadStartElement();
|
|
|
|
IntermediateReader intermediateReader = new IntermediateReader(this, new IntermediateXmlReader(input), referenceRelocationPath, xmlTypeNameContainer);
|
|
|
|
T resultObject = intermediateReader.ReadObject<T>(assetAttribute);
|
|
|
|
intermediateReader.ReadSharedResources();
|
|
intermediateReader.ReadExternalReferences();
|
|
|
|
if (expectsContentTag)
|
|
{
|
|
input.ReadEndElement();
|
|
}
|
|
|
|
return resultObject;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deserializes an intermediate ANX XML file into a managed object.
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <param name="input">Intermediate XML file.</param>
|
|
/// <param name="referenceRelocationPath">Final name of the output file used to relative encode external reference filenames.</param>
|
|
/// <returns></returns>
|
|
/// <exception cref="System.ArgumentNullException"></exception>
|
|
/// <exception cref="System.MissingMethodException">Will be thrown if no parameterless constructor for the corresponding <see cref="ContentTypeSerializer"/> can be found.</exception>
|
|
public static T Deserialize<T>(XmlReader input, string referenceRelocationPath)
|
|
{
|
|
return Singleton.DeserializeObject<T>(input, referenceRelocationPath);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the worker serializer for a specified type.
|
|
/// </summary>
|
|
/// <param name="type">The type.</param>
|
|
/// <returns></returns>
|
|
/// <exception cref="System.ArgumentNullException"></exception>
|
|
/// <exception cref="System.ArgumentException"></exception>
|
|
/// <exception cref="System.RankException">Will be triggered if a multidimensional array is passed.</exception>
|
|
/// <exception cref="System.MissingMethodException">Will be thrown if no parameterless constructor for the corresponding <see cref="ContentTypeSerializer"/> can be found.</exception>
|
|
public virtual ContentTypeSerializer GetTypeSerializer(Type type)
|
|
{
|
|
if (type == null)
|
|
throw new ArgumentNullException("type");
|
|
|
|
if (type.IsByRef || type.IsPointer)
|
|
throw new ArgumentException(string.Format("Cannot serialize type {0}. Pointers and references are not supported.", type));
|
|
|
|
if (type.ContainsGenericParameters)
|
|
throw new ArgumentException(string.Format("Type {0} cannot be serialized because not all the generic type parameters have been specified.", type));
|
|
|
|
if (type.IsSubclassOf(typeof(Delegate)))
|
|
throw new ArgumentException(string.Format("Cannot serialize type {0}. Delegates are not supported.", type));
|
|
|
|
ContentTypeSerializer contentTypeSerializer = null;
|
|
if (serializerInstances.TryGetValue(type, out contentTypeSerializer))
|
|
{
|
|
return contentTypeSerializer;
|
|
}
|
|
|
|
Type genericTypeHandler;
|
|
if (type.IsGenericType && genericSerializerTypeHandler.TryGetValue(type.GetGenericTypeDefinition(), out genericTypeHandler))
|
|
{
|
|
contentTypeSerializer = BuildGenericSerializerVariant(genericTypeHandler, type);
|
|
}
|
|
else if (type.IsArray)
|
|
{
|
|
if (type.GetArrayRank() != 1)
|
|
throw new RankException("Can't serialize multidimensional arrays.");
|
|
|
|
contentTypeSerializer = (ContentTypeSerializer)Activator.CreateInstance(typeof(ArraySerializer<>).MakeGenericType(type.GetElementType()));
|
|
}
|
|
else if (type.IsEnum)
|
|
{
|
|
contentTypeSerializer = new EnumSerializer(type);
|
|
}
|
|
else
|
|
{
|
|
contentTypeSerializer = new ReflectiveSerializer(type);
|
|
}
|
|
|
|
AddTypeSerializer(contentTypeSerializer);
|
|
return contentTypeSerializer;
|
|
}
|
|
|
|
private ContentTypeSerializer BuildGenericSerializerVariant(Type genericTypeHandler, Type targetType)
|
|
{
|
|
Type[] targetArguments = targetType.GetGenericArguments();
|
|
Type genericHandlerDefinition = genericTypeHandler.GetGenericTypeDefinition();
|
|
|
|
return (ContentTypeSerializer)Activator.CreateInstance(genericHandlerDefinition.MakeGenericType(targetArguments));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changes the ContentElementName to XnaContent and changes the namespaces for the ANX classes to match the name for the XNA classes.
|
|
/// </summary>
|
|
/// <exception cref="System.Reflection.ReflectionTypeLoadException"></exception>
|
|
private void ChangeToXnaNamespaces()
|
|
{
|
|
foreach (Type type in Assembly.GetAssembly(typeof(Game)).GetTypes())
|
|
{
|
|
if (type.IsPublic && type.Namespace.StartsWith(AnxFrameworkNamespace) && !type.Namespace.Contains("NonXNA"))
|
|
{
|
|
this.xmlTypeNameContainer.SetNamespaceRename(type, XnaFrameworkNamespace + type.Namespace.Substring(AnxFrameworkNamespace.Length));
|
|
}
|
|
}
|
|
|
|
foreach (Type type in Assembly.GetAssembly(typeof(IntermediateSerializer)).GetTypes())
|
|
{
|
|
if (type.IsPublic && type.Namespace.StartsWith(AnxFrameworkNamespace) && !type.Namespace.Contains("NonXNA"))
|
|
{
|
|
this.xmlTypeNameContainer.SetNamespaceRename(type, XnaFrameworkNamespace + type.Namespace.Substring(AnxFrameworkNamespace.Length));
|
|
}
|
|
}
|
|
|
|
ContentElementName = "XnaContent";
|
|
}
|
|
}
|
|
}
|