// MonoGame - Copyright (C) The MonoGame Team
// This file is subject to the terms and conditions defined in
// file 'LICENSE.txt', which is part of this source code package.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml;
namespace Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate
{
// The intermediate serializer implementation is based on testing XNA behavior and the following sources:
//
// http://msdn.microsoft.com/en-us/library/Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate.aspx
// http://blogs.msdn.com/b/shawnhar/archive/2008/08/12/everything-you-ever-wanted-to-know-about-intermediateserializer.aspx
// http://blogs.msdn.com/b/shawnhar/archive/2008/08/26/customizing-intermediateserializer-part-1.aspx
// http://blogs.msdn.com/b/shawnhar/archive/2008/08/26/customizing-intermediateserializer-part-2.aspx
// http://blogs.msdn.com/b/shawnhar/archive/2008/08/27/why-intermediateserializer-control-attributes-are-not-part-of-the-content-pipeline.aspx
//
public class IntermediateSerializer
{
///
/// According to the examples on Sean Hargreaves' blog, explicit types
/// can also specify the type aliases from C#. This maps those names
/// to the actual .NET framework types for parsing.
///
private static readonly Dictionary _typeAliases = new Dictionary
{
{ "bool", typeof(bool) },
{ "byte", typeof(byte) },
{ "sbyte", typeof(sbyte) },
{ "char", typeof(char) },
{ "decimal",typeof(decimal) },
{ "double", typeof(double) },
{ "float", typeof(float) },
{ "int", typeof(int) },
{ "uint", typeof(uint) },
{ "long", typeof(long) },
{ "ulong", typeof(ulong) },
{ "object", typeof(object) },
{ "short", typeof(short) },
{ "ushort", typeof(ushort) },
{ "string", typeof(string) }
};
private static readonly Dictionary _typeAliasesReverse;
static IntermediateSerializer()
{
_typeAliasesReverse = _typeAliases.ToDictionary(x => x.Value, x => x.Key);
}
private IntermediateSerializer()
{
_scannedObjects = new List();
_namespaceAliasHelper = new NamespaceAliasHelper(this);
}
///
/// Maps "ShortName:" -> "My.Namespace.LongName." for type lookups.
///
private Dictionary _namespaceLookup;
private Dictionary _serializers;
private Dictionary _collectionHelpers;
private Dictionary _genericSerializerTypes;
private readonly NamespaceAliasHelper _namespaceAliasHelper;
private readonly List _scannedObjects;
public static T Deserialize(XmlReader input, string referenceRelocationPath)
{
var serializer = new IntermediateSerializer();
var reader = new IntermediateReader(serializer, input, referenceRelocationPath);
var asset = default(T);
try
{
if (!reader.MoveToElement("XnaContent"))
throw new InvalidContentException(string.Format("Could not find XnaContent element in '{0}'.",
referenceRelocationPath));
// Initialize the namespace lookups from
// the attributes on the XnaContent element.
serializer.CreateNamespaceLookup(input);
// Move past the XnaContent.
input.ReadStartElement();
// Read the asset.
var format = new ContentSerializerAttribute {ElementName = "Asset"};
asset = reader.ReadObject(format);
// Process the shared resources and external references.
reader.ReadSharedResources();
reader.ReadExternalReferences();
// Move past the closing XnaContent element.
input.ReadEndElement();
}
catch (XmlException xmlException)
{
throw reader.NewInvalidContentException(xmlException, "An error occured parsing.");
}
return asset;
}
public ContentTypeSerializer GetTypeSerializer(Type type)
{
// Create the known serializers if we haven't already.
if (_serializers == null)
{
_serializers = new Dictionary();
_genericSerializerTypes = new Dictionary();
var types = ContentTypeSerializerAttribute.GetTypes();
foreach (var t in types)
{
if (t.IsGenericType)
{
var genericType = t.BaseType.GetGenericArguments()[0];
_genericSerializerTypes.Add(genericType.GetGenericTypeDefinition(), t);
}
else
{
var cts = Activator.CreateInstance(t) as ContentTypeSerializer;
cts.Initialize(this);
_serializers.Add(cts.TargetType, cts);
}
}
}
// Look it up.
ContentTypeSerializer serializer;
if (_serializers.TryGetValue(type, out serializer))
return serializer;
Type serializerType;
if (type.IsArray)
{
if (type.GetArrayRank() != 1)
throw new RankException("We only support single dimension arrays.");
var arrayType = typeof(ArraySerializer<>).MakeGenericType(new[] { type.GetElementType() });
serializer = (ContentTypeSerializer)Activator.CreateInstance(arrayType);
}
else if (type.IsGenericType && _genericSerializerTypes.TryGetValue(type.GetGenericTypeDefinition(), out serializerType))
{
serializerType = serializerType.MakeGenericType(type.GetGenericArguments());
serializer = (ContentTypeSerializer)Activator.CreateInstance(serializerType);
}
else if (type.IsEnum)
{
serializer = new EnumSerializer(type);
}
else if (typeof(IList).IsAssignableFrom(type) && !GenericCollectionHelper.IsGenericCollectionType(type, true))
{
// Special handling for non-generic IList types. By the time we get here,
// generic collection types will already have been handled by one of the known serializers.
serializer = new NonGenericIListSerializer(type);
}
else
{
// The reflective serializer is not for primitive types!
if (type.IsPrimitive)
throw new NotImplementedException(string.Format("Unhandled primitive type `{0}`!", type.FullName));
// We still don't have a serializer then we
// fallback to the reflection based serializer.
serializer = new ReflectiveSerializer(type);
}
Debug.Assert(serializer.TargetType == type, "Target type mismatch!");
// We cache the serializer before we initialize it to
// avoid a stack overflow on recursive types.
_serializers.Add(type, serializer);
serializer.Initialize(this);
return serializer;
}
internal GenericCollectionHelper GetCollectionHelper(Type type)
{
if (_collectionHelpers == null)
_collectionHelpers = new Dictionary();
GenericCollectionHelper result;
if (!_collectionHelpers.TryGetValue(type, out result))
{
result = new GenericCollectionHelper(this, type);
_collectionHelpers.Add(type, result);
}
return result;
}
public static void Serialize(XmlWriter output, T value, string referenceRelocationPath)
{
var serializer = new IntermediateSerializer();
var writer = new IntermediateWriter(serializer, output, referenceRelocationPath);
output.WriteStartElement("XnaContent");
serializer._namespaceAliasHelper.WriteNamespaces(output, value);
// Write the asset.
var format = new ContentSerializerAttribute { ElementName = "Asset" };
writer.WriteObject(value, format);
// Process the shared resources and external references.
writer.WriteSharedResources();
writer.WriteExternalReferences();
// Close the XnaContent element.
output.WriteEndElement();
}
///
/// Builds a lookup table from a short name to the full namespace.
///
private void CreateNamespaceLookup(XmlReader reader)
{
_namespaceLookup = new Dictionary();
for (var i=0; i < reader.AttributeCount; i++)
{
reader.MoveToAttribute(i);
if (reader.Prefix != "xmlns")
continue;
_namespaceLookup.Add(reader.LocalName + ":", reader.Value + ".");
}
}
///
/// Finds the type in any assembly loaded into the AppDomain.
///
internal Type FindType(string typeName)
{
Type foundType;
typeName = typeName.Trim();
// Shortcut for friendly C# names
if (_typeAliases.TryGetValue(typeName, out foundType))
return foundType;
// If this is an array then handle it separately.
if (typeName.EndsWith("[]"))
{
var arrayType = typeName.Substring(0, typeName.Length - 2);
foundType = FindType(arrayType);
return foundType == null ? null : foundType.MakeArrayType();
}
// Expand any namespaces in the asset type
foreach (var pair in _namespaceLookup)
typeName = typeName.Replace(pair.Key, pair.Value);
var expandedName = typeName;
// If this a generic type, handle it separately.
if (typeName.EndsWith("]"))
{
var openBracketIndex = typeName.IndexOf("[");
var typeNameWithoutArguments = typeName.Substring(0, openBracketIndex);
var genericArgumentsString = typeName.Substring(openBracketIndex + 1, typeName.Length - openBracketIndex - 2);
var genericArgumentsArray = genericArgumentsString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var genericArguments = genericArgumentsArray.Select(FindType).ToArray();
foundType = FindType(typeNameWithoutArguments + "`" + genericArguments.Length);
return (foundType == null) ? null : foundType.MakeGenericType(genericArguments);
}
foundType = (from assembly in AppDomain.CurrentDomain.GetAssemblies()
from type in assembly.GetTypes()
where type.FullName == typeName || type.Name == typeName
select type).FirstOrDefault();
if (foundType == null)
foundType = Type.GetType(expandedName, false, true);
return foundType;
}
///
/// Gets the (potentially) aliased name for any type.
///
internal string GetFullTypeName(Type type)
{
string typeName;
// Shortcut for friendly C# names
if (_typeAliasesReverse.TryGetValue(type, out typeName))
return typeName;
// Look for aliased namespace.
if (_namespaceAliasHelper.TryGetAliasedTypeName(type, out typeName))
return typeName;
// Fallback to full type name.
var typeNamespace = type.Namespace;
if (!string.IsNullOrEmpty(typeNamespace))
typeName = typeNamespace + ".";
typeName += GetTypeName(type);
return typeName;
}
///
/// Returns the name of the type, without the namespace.
/// For generic types, we add the type parameters in square brackets.
/// i.e. List<int> becomes List[int]
///
internal string GetTypeName(Type type)
{
if (type.IsGenericType)
{
var typeName = type.Name;
int genericBacktickIndex = typeName.IndexOf("`");
if (genericBacktickIndex >= 0)
typeName = typeName.Substring(0, genericBacktickIndex);
var result = typeName + "[";
result += string.Join(",", type.GetGenericArguments().Select(GetFullTypeName));
result += "]";
return result;
}
if (type.IsArray)
return GetTypeName(type.GetElementType()) + "[]";
if (type.IsNested)
return type.DeclaringType.Name + "+" + type.Name;
return type.Name;
}
internal bool AlreadyScanned(object value)
{
if (_scannedObjects.Contains(value))
return true;
_scannedObjects.Add(value);
return false;
}
internal bool HasTypeAlias(Type type)
{
return _typeAliasesReverse.ContainsKey(type);
}
}
}