// 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); } } }