Konstantin Koch cb01231e7d implemented Intermediate.Serializer namespace in Content Pipeline.
removed the old .tfignore file.
2015-03-29 18:16:03 +02:00

479 lines
23 KiB
C#

#region Using Statements
using ANX.Framework.Content.Pipeline.Helpers;
using ANX.Framework.NonXNA.Development;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
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 an implementation of many of the methods of IntermediateSerializer.
/// Deserializes and tracks state for shared resources and external references.
/// </summary>
[Developer("KorsarNek")]
[TestState(TestStateAttribute.TestState.InProgress)]
[PercentageComplete(100)]
public sealed class IntermediateReader
{
private struct ExternalReferenceFixup
{
public string ID;
public Type TargetType;
public Action<string> Fixup;
}
private string basePath;
private Dictionary<string, List<Action<object>>> sharedResourceFixups = new Dictionary<string, List<Action<object>>>();
private List<IntermediateReader.ExternalReferenceFixup> externalReferenceFixups = new List<IntermediateReader.ExternalReferenceFixup>();
private XmlTypeNameReader xmlTypeNameReader;
/// <summary>
/// Gets the parent serializer.
/// </summary>
public IntermediateSerializer Serializer
{
get;
private set;
}
/// <summary>
/// Gets the XML input stream.
/// </summary>
public IntermediateXmlReader Xml
{
get;
private set;
}
internal string BasePath
{
get { return this.basePath; }
}
internal IntermediateReader(IntermediateSerializer serializer, IntermediateXmlReader xmlReader, string basePath, XmlTypeNameContainer container)
{
this.Serializer = serializer;
this.Xml = xmlReader;
this.xmlTypeNameReader = new XmlTypeNameReader(container, xmlReader);
if (basePath != null)
{
basePath = Path.GetFullPath(basePath);
}
this.basePath = basePath;
}
/// <summary>
/// Reads a single object from the input XML stream.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="format">The format expected by the type serializer.</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>
/// <exception cref="ANX.Framework.Content.Pipeline.InvalidContentException"></exception>
public T ReadObject<T>(ContentSerializerAttribute format)
{
return this.ReadObjectInternal<T>(format, this.Serializer.GetTypeSerializer(typeof(T)), null);
}
/// <summary>
/// Reads a single object from the input XML stream, optionally specifying an existing instance to receive the data.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="format">The format of the XML.</param>
/// <param name="existingInstance">The object receiving the data, or null if a new instance should be created.</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>
/// <exception cref="ANX.Framework.Content.Pipeline.InvalidContentException"></exception>
public T ReadObject<T>(ContentSerializerAttribute format, T existingInstance)
{
return this.ReadObjectInternal<T>(format, this.Serializer.GetTypeSerializer(typeof(T)), existingInstance);
}
/// <summary>
/// Reads a single object from the input XML stream, using the specified type hint.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="format">The format expected by the type serializer.</param>
/// <param name="typeSerializer">The type serializer.</param>
/// <returns></returns>
/// <exception cref="System.ArgumentNullException"></exception>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="ANX.Framework.Content.Pipeline.InvalidContentException"></exception>
public T ReadObject<T>(ContentSerializerAttribute format, ContentTypeSerializer typeSerializer)
{
return this.ReadObjectInternal<T>(format, typeSerializer, null);
}
/// <summary>
/// Reads a single object from the input XML stream using the specified type hint, optionally specifying an existing instance to receive the data.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="format">The format of the XML.</param>
/// <param name="typeSerializer">The type serializer.</param>
/// <param name="existingInstance">The object receiving the data, or null if a new instance should be created.</param>
/// <returns></returns>
/// <exception cref="System.ArgumentNullException"></exception>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="ANX.Framework.Content.Pipeline.InvalidContentException"></exception>
public T ReadObject<T>(ContentSerializerAttribute format, ContentTypeSerializer typeSerializer, T existingInstance)
{
return this.ReadObjectInternal<T>(format, typeSerializer, existingInstance);
}
private T ReadObjectInternal<T>(ContentSerializerAttribute format, ContentTypeSerializer typeSerializer, object existingInstance)
{
if (typeSerializer == null)
throw new ArgumentNullException("typeSerializer");
if (format == null)
{
throw new ArgumentNullException("format");
}
if (!format.FlattenContent)
{
if (!this.Xml.CheckForElement(format.ElementName))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML element \"{0}\" not found.", format.ElementName));
string attribute = this.Xml.GetAttribute("Null");
if (attribute != null && XmlConvert.ToBoolean(attribute))
{
if (!format.AllowNull)
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML element \"{0}\" is not allowed to be null.", format.ElementName));
this.Xml.Skip();
return default(T);
}
else if (this.Xml.MoveToAttribute("Type"))
{
Type type = this.ReadTypeName();
if (!typeSerializer.TargetType.IsAssignableFrom(type))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML \"Type\" attribute is invalid. Expecting a subclass of {0}, but XML contains {1}.", typeSerializer.TargetType, type));
typeSerializer = this.Serializer.GetTypeSerializer(type);
this.Xml.MoveToElement();
}
else if (typeSerializer.TargetType == typeof(object))
{
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, "XML is missing a \"Type\" attribute.");
}
}
return this.ReadRawObjectInternal<T>(format, typeSerializer, existingInstance);
}
/// <summary>
/// Reads a single object from the input XML stream as an instance of the specified type, optionally specifying an existing instance to receive the data.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="format">The format of the XML.</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>
/// <exception cref="ANX.Framework.Content.Pipeline.InvalidContentException"></exception>
public T ReadRawObject<T>(ContentSerializerAttribute format)
{
return this.ReadRawObjectInternal<T>(format, this.Serializer.GetTypeSerializer(typeof(T)), null);
}
/// <summary>
/// Reads a single object from the input XML stream, as an instance of the specified type.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="format">The format of the XML.</param>
/// <param name="existingInstance">The object receiving the data, or null if a new instance should be created.</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>
/// <exception cref="ANX.Framework.Content.Pipeline.InvalidContentException"></exception>
public T ReadRawObject<T>(ContentSerializerAttribute format, T existingInstance)
{
return this.ReadRawObjectInternal<T>(format, this.Serializer.GetTypeSerializer(typeof(T)), existingInstance);
}
/// <summary>
/// Reads a single object from the input XML stream as an instance of the specified type using the specified type hint.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="format">The format of the XML.</param>
/// <param name="typeSerializer">The type serializer.</param>
/// <returns></returns>
/// <exception cref="System.ArgumentNullException"></exception>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="ANX.Framework.Content.Pipeline.InvalidContentException"></exception>
public T ReadRawObject<T>(ContentSerializerAttribute format, ContentTypeSerializer typeSerializer)
{
return this.ReadRawObjectInternal<T>(format, typeSerializer, null);
}
/// <summary>
/// Reads a single object from the input XML stream as an instance of the specified type using the specified type hint, optionally specifying an existing instance to receive the data.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="format">The format of the XML.</param>
/// <param name="typeSerializer">The type serializer.</param>
/// <param name="existingInstance">The object receiving the data, or null if a new instance should be created.</param>
/// <returns></returns>
/// <exception cref="System.ArgumentNullException"></exception>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="ANX.Framework.Content.Pipeline.InvalidContentException"></exception>
public T ReadRawObject<T>(ContentSerializerAttribute format, ContentTypeSerializer typeSerializer, T existingInstance)
{
return this.ReadRawObjectInternal<T>(format, typeSerializer, existingInstance);
}
private T ReadRawObjectInternal<T>(ContentSerializerAttribute format, ContentTypeSerializer typeSerializer, object existingInstance)
{
if (typeSerializer == null)
throw new ArgumentNullException("typeSerializer");
if (format == null)
throw new ArgumentNullException("format");
object obj;
if (format.FlattenContent)
{
this.Xml.MoveToContent();
obj = typeSerializer.Deserialize(this, format, existingInstance);
}
else
{
if (!this.Xml.CheckForElement(format.ElementName))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML element \"{0}\" not found.", format.ElementName));
IntermediateXmlReader xmlReader = this.Xml;
//When reading an empty element, XNA was giving a fake reader where it doesn't matter what you do.
if (this.Xml.IsEmptyElement)
{
this.Xml = EmptyElementReader.Instance;
}
xmlReader.ReadStartElement();
obj = typeSerializer.Deserialize(this, format, existingInstance);
if (this.Xml == xmlReader)
{
this.Xml.ReadEndElement();
}
else
{
this.Xml = xmlReader;
}
}
if (obj == null)
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("Intermediate ContentTypeSerializer {0} (handling type {1}) returned a null value from its Deserialize method.", typeSerializer.GetType(), typeSerializer.TargetType));
if (existingInstance != null && obj != existingInstance)
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("Intermediate ContentTypeSerializer {0} (handling type {1}) returned a new object instance from its Deserialize method. This should have loaded data into the existingInstance parameter.", typeSerializer.GetType(), typeSerializer.TargetType));
if (!(obj is T))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML \"Type\" attribute is invalid. Expecting a subclass of {0}, but XML contains {1}.", typeof(T), obj.GetType()));
return (T)obj;
}
/// <summary>
/// Reads a shared resource ID and records it for subsequent operations.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="format">The format of the XML.</param>
/// <param name="fixup">The fixup operation to perform.</param>
/// <exception cref="System.ArgumentNullException"></exception>
/// <exception cref="ANX.Framework.Content.Pipeline.InvalidContentException"></exception>
public void ReadSharedResource<T>(ContentSerializerAttribute format, Action<T> fixup)
{
if (format == null)
throw new ArgumentNullException("format");
if (fixup == null)
throw new ArgumentNullException("fixup");
string text;
if (format.FlattenContent)
{
text = this.Xml.ReadContentAsString();
}
else
{
if (!this.Xml.CheckForElement(format.ElementName))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML element \"{0}\" not found.", format.ElementName));
text = this.Xml.ReadElementContentAsString();
}
if (string.IsNullOrEmpty(text))
{
if (!format.AllowNull)
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML element \"{0}\" is not allowed to be null.", format.ElementName));
}
else
{
if (!this.sharedResourceFixups.ContainsKey(text))
{
this.sharedResourceFixups.Add(text, new List<Action<object>>());
}
this.sharedResourceFixups[text].Add((value) =>
{
if (!(value is T))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML specifies invalid type for shared resource. Expecting a subclass of \"{0}\", but XML contains \"{1}\".", typeof(T), value.GetType()));
fixup((T)value);
});
}
}
internal void ReadSharedResources()
{
if (this.Xml.CheckForElement("Resources"))
{
Dictionary<string, object> dictionary = new Dictionary<string, object>();
ContentSerializerAttribute contentSerializerAttribute = new ContentSerializerAttribute();
contentSerializerAttribute.ElementName = "Resource";
this.Xml.ReadStartElement();
while (this.Xml.CheckForElement("Resource"))
{
string id = this.Xml.GetAttribute("ID");
if (string.IsNullOrEmpty(id))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML attribute \"{0}\" was not found.", "ID"));
if (dictionary.ContainsKey(id))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("Duplicate XML ID attribute \"{0}\".", id));
object value = this.ReadObject<object>(contentSerializerAttribute);
dictionary.Add(id, value);
}
this.Xml.ReadEndElement();
foreach (var fixup in this.sharedResourceFixups)
{
object obj;
if (!dictionary.TryGetValue(fixup.Key, out obj))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("Missing shared resource \"{0}\".", fixup.Key));
foreach (var action in fixup.Value)
{
action(obj);
}
}
}
else if (this.sharedResourceFixups.Count > 0)
{
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML element \"{0}\" not found.", "Resources"));
}
}
/// <summary>
/// Reads an external reference ID and records it for subsequent operations.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="existingInstance">The object receiving the data, or null if a new instance of the object should be created.</param>
/// <exception cref="System.ArgumentNullException"></exception>
public void ReadExternalReference<T>(ExternalReference<T> existingInstance)
{
if (existingInstance == null)
throw new ArgumentNullException("existingInstance");
if (!this.Xml.CheckForElement("Reference"))
{
return;
}
string text = this.Xml.ReadElementContentAsString();
if (!string.IsNullOrEmpty(text))
{
IntermediateReader.ExternalReferenceFixup item;
item.ID = text;
item.TargetType = typeof(T);
item.Fixup = (x) => existingInstance.Filename = x;
this.externalReferenceFixups.Add(item);
}
}
internal void ReadExternalReferences()
{
if (this.Xml.CheckForElement("ExternalReferences"))
{
Dictionary<string, Type> idTypeDictionary = new Dictionary<string, Type>();
Dictionary<string, string> idFilenameDictionary = new Dictionary<string, string>();
this.Xml.ReadStartElement();
while (this.Xml.CheckForElement("ExternalReference"))
{
string id = this.Xml.GetAttribute("ID");
if (string.IsNullOrEmpty(id))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML attribute \"{0}\" was not found.", "ID"));
if (idTypeDictionary.ContainsKey(id))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("Duplicate XML ID attribute \"{0}\".", id));
if (!this.Xml.MoveToAttribute("TargetType"))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML attribute \"{0}\" was not found.", "TargetType"));
idTypeDictionary.Add(id, this.ReadTypeName());
this.Xml.MoveToElement();
string filename = this.Xml.ReadElementString();
string absolutePath = Path.Combine(this.basePath, filename);
idFilenameDictionary.Add(id, absolutePath);
}
this.Xml.ReadEndElement();
foreach (var refFixup in externalReferenceFixups)
{
if (!idTypeDictionary.ContainsKey(refFixup.ID))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("Missing external reference \"{0}\".", refFixup.ID));
if (idTypeDictionary[refFixup.ID] != refFixup.TargetType)
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML specifies wrong type for external reference \"{0}\".", refFixup.ID));
refFixup.Fixup(idFilenameDictionary[refFixup.ID]);
}
}
else if (this.externalReferenceFixups.Count > 0)
{
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML element \"{0}\" not found.", "ExternalReferences"));
}
}
/// <summary>
/// Reads and decodes a type descriptor from the XML input stream.
/// </summary>
/// <returns></returns>
public Type ReadTypeName()
{
string typeName = this.Xml.ReadContentAsString();
return this.xmlTypeNameReader.GetTypeFromXmlName(typeName);
}
/// <summary>
/// Moves to the specified element if the element name exists.
/// </summary>
/// <param name="elementName">The element name.</param>
/// <returns></returns>
public bool MoveToElement(string elementName)
{
return this.Xml.CheckForElement(elementName);
}
}
}