#region Copyright
/*
* Copyright (c) 2005,2006,2007, OpenMI Association
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenMI Association nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY "OpenMI Association" ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL "OpenMI Association" BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#endregion
using System;
using System.ComponentModel;
using System.Reflection;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Xml;
using System.Xml.Schema;
namespace RTCTools.OpenMI.Sdk.DevelopmentSupport
{
///
/// Support class for reading and writing objects to xml.
/// Each object to be written or read is accessed in a generic way for the properties to write or read. This is done via an aggregate intermediate object.
/// Objects are written once in an xml file. If they are encountered more than once, references are written. References may refer to other files.
/// Writing and reading strings is always done and expected in neutral culture (English-US) in order to enable exchangability.
/// Needed meta info is retrieved from MetaInfo.
/// MetaInfo used by XmlFile
///
///
///
public class XmlFile
{
private static CultureInfo _culture = CultureInfo.CreateSpecificCulture ("");
private static Hashtable _fileList = new Hashtable();
private static ArrayList _readObjectsList = new ArrayList();
private static ArrayList _unreadObjectsList = new ArrayList();
private static Hashtable _rootList = new Hashtable();
private static Hashtable _rootKeyList = new Hashtable();
private static Hashtable _rootObjectList = new Hashtable();
private static Hashtable _aggregateTable = new Hashtable();
#region Public Read Methods
///
/// Reads an object from file
/// The file to read should have been registered with the object earlier by a reqad or write action
///
/// The object to be read
/// Cannot find file to read
/// Cannot resolve class type when an object for an xml element must be instantiated
/// Cannot find class type
/// Xml element holds a reference, but referenced object cannot be found
/// Schema cannot be found although it has been specified in MetaInfo
/// Validation error when xml file doesn't meet specified schema
public static void Read (object target)
{
string file = (string) _fileList[target];
if (file != null)
{
XmlFile.Read (target, new FileInfo(file));
}
}
///
/// Reads an object from file
///
/// The object to be read
/// The file containing the object's information
/// Cannot find file to read
/// Cannot resolve class type when an object for an xml element must be instantiated
/// Cannot find class type
/// Xml element holds a reference, but referenced object cannot be found
/// Schema cannot be found although it has been specified in MetaInfo
/// Validation error when xml file doesn't meet specified schema
public static void Read (object target, FileInfo file)
{
if (file.Exists)
{
XmlFile.CreateRegistration (target);
XmlReader reader = new XmlTextReader(file.FullName);
string xmlValidation = (string) MetaInfo.GetAttribute (target.GetType(), "XmlSchema");
string xmlNameSpace = (string) MetaInfo.GetAttribute (target.GetType(), "XmlNameSpace");
string xsdPackage = (string) MetaInfo.GetAttribute (target.GetType(), "XsdPackage");
if ((xmlValidation != null) && (xmlNameSpace != null))
{
Stream schemaStream = XmlFile.GetSchema (target.GetType(), xmlValidation, xsdPackage);
if (schemaStream != null)
{
XmlSchema schema = XmlSchema.Read(schemaStream, new ValidationEventHandler (ValidationCallBack));
// VS2005 fix - the following code is deprecated and ws replaced by new code - see below
//XmlSchemaCollection xsc = new XmlSchemaCollection();
//xsc.ValidationEventHandler += new ValidationEventHandler( ValidationCallBack );
//xsc.Add( schema ); //XSD schema
//reader = new XmlValidatingReader( reader );
//( (XmlValidatingReader) reader ).ValidationType = ValidationType.Schema;
//( (XmlValidatingReader) reader ).ValidationEventHandler += new ValidationEventHandler( ValidationCallBack );
//( (XmlValidatingReader) reader ).Schemas.Add( xsc );
XmlSchemaSet xss = new XmlSchemaSet();
xss.ValidationEventHandler += new ValidationEventHandler (ValidationCallBack);
xss.Add(schema); //XSD schema
XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.ValidationEventHandler += new ValidationEventHandler (ValidationCallBack);
settings.Schemas.Add( xss );
reader = XmlReader.Create( reader, settings );
}
else
{
throw new Exception ("Required schema " + xmlValidation + " not found for parsing " + file.FullName);
}
}
else
{
}
_fileList[target] = file.FullName;
_readObjectsList.Add (target);
_unreadObjectsList.Remove (target);
reader.MoveToContent();
XmlFile.Read (reader, target, null, null, target);
reader.Close();
}
else
{
_fileList[target] = file.FullName;
}
}
///
/// Reads and creates an object from a given file
///
/// The file containig the object to be read
/// Populated object
/// Cannot find file to read
/// Cannot resolve class type when an object for an xml element must be instantiated
/// Cannot find class type
/// Xml element holds a reference, but referenced object cannot be found
/// Schema cannot be found although it has been specified in MetaInfo
/// Validation error when xml file doesn't meet specified schema
public static object GetRead (FileInfo file)
{
return XmlFile.GetRead(file, null);
}
///
/// Creates and reads an object from a given file
///
/// The file containig the object to be read
/// The expected class type of the new object
/// The new populated object
/// Cannot find file to read
/// Cannot resolve class type when an object for an xml element must be instantiated
/// Cannot find class type
/// Xml element holds a reference, but referenced object cannot be found
/// Schema cannot be found although it has been specified in MetaInfo
/// Validation error when xml file doesn't meet specified schema
public static object GetRead (FileInfo file, Type objectType)
{
if (file.Exists)
{
object target = GetFileObject(file.FullName);
if (target == null)
{
XmlReader reader = new XmlTextReader(file.FullName);
reader.MoveToContent();
string classType = (string) MetaInfo.GetAttributeDefault(reader.GetAttribute ("Type"), "XmlTypeAlias", reader.GetAttribute ("Type"));
ObjectSupport.LoadAssembly (reader.GetAttribute ("Assembly"));
reader.Close();
if (classType != null)
{
// classType = (string) MetaInfo.GetAttributeDefault (ObjectSupport.GetType(ClassType), "XmlTypeAlias", classType);
target = ObjectSupport.GetInstance (classType);
}
if ((target == null) && (objectType != null))
{
target = ObjectSupport.GetInstance (objectType);
}
if (target == null)
{
if (classType != null)
{
throw new Exception("Could not find class type " + classType);
}
else if (objectType != null)
{
throw new Exception("Could not find class type " + objectType.FullName);
}
}
}
XmlFile.Read (target, file);
return target;
}
else
{
throw new Exception ("File doesn't exist: " + file.FullName);
}
}
#endregion
#region Xml Validation
///
/// Callback method used for validation against an xsd file
///
/// Sender of the method
/// Error details
/// Validation error when xml file doesn't meet specified schema
private static void ValidationCallBack (object sender, ValidationEventArgs args)
{
throw new Exception ("Validation error: " + args.Message);
}
///
/// Gets a stream containing an xsd file, which resides within an assembly.
///
/// If the xsdPackage isn't defined, type in the assembly in which the xsd file should reside
/// Name of the schema
/// The assembly in which the xsd file resides
/// The xsd stream, null if not found
private static Stream GetSchema (Type type, string xmlSchema, string xsdPackage)
{
Assembly assembly = Assembly.GetAssembly(type);
if ((xsdPackage != null) && (!xsdPackage.Trim().Equals("")))
{
// VS2005 fix
// this fix also needed to include full name to XsdPackage meta-info in XmlConfiguration.cs
//assembly = Assembly.LoadWithPartialName(xsdPackage);
assembly = Assembly.Load( xsdPackage );
}
Stream stream = assembly.GetManifestResourceStream(xmlSchema);
if (stream == null)
{
string assemblyName = assembly.GetName().Name;
stream = assembly.GetManifestResourceStream(assemblyName + "." + xmlSchema);
}
if (stream == null)
{
FileInfo assemblyFile = new FileInfo(assembly.Location);
FileInfo schema = FileSupport.ExpandRelativePath (assemblyFile.Directory, xmlSchema);
if (schema.Exists)
{
stream = schema.OpenRead();
}
}
return stream;
}
#endregion
#region Internal Read Methods
///
/// Reads the current xml element into an object.
/// Then moves the xml stream forward to the next element.
/// If the next element is one level deeper, that element is read by calling this method recursively.
/// Finally the current element is positioned on the first unread element.
/// By calling this method at the top level of an xml file, the whole xml file is read.
///
/// The xml stream
/// The object which will be populated with data within the current xml element
/// The parent of the object, i.e. the object to which the target will be assigned
/// The property of the parent
/// The root object of the xml file
/// Read only property has null value, but a property of this object should be set according to the xml file
private static void Read (XmlReader reader, object target, object parent, string targetProperty, object root)
{
// If it is a reference, the object has been read already and assigned to its parent
string Reference = XmlFile.GetKeyFromXml(target.GetType(), reader, true);
if (Reference != null)
{
XmlFile.MoveToNextElement(reader); // moves to next element
return;
}
bool inCurrentXmlFile = (reader.GetAttribute ("File") == null);
if ((reader.GetAttribute ("File") == null) && (Reference == null))
{
_rootList[target] = root;
}
XmlFile.RegisterObject (target, reader, root);
IAggregate aggregate = XmlFile.GetAggregate(target);
XmlFile.ReadAttributes (aggregate, reader, root);
string defaultProperty = MetaInfo.GetAttribute (target.GetType(), "XmlDefaultProperty") as string;
if (defaultProperty != null)
{
parent = target;
target = aggregate.GetValue(defaultProperty);
targetProperty = defaultProperty;
aggregate = XmlFile.GetAggregate(target);
}
int processLevel = reader.Depth;
string[] properties = aggregate.Properties;
// Special handling for lists
if (target is IList)
{
IList List = (IList) target;
List.Clear();
string listEntryClass = (string) MetaInfo.GetAttributeDefault (parent.GetType(), targetProperty, "XmlItemType", MetaInfo.GetAttribute (target.GetType(), "XmlItemType"));
if (!reader.IsEmptyElement)
{
XmlFile.MoveToNextElement(reader); // moves to first element
while ((reader.Depth > processLevel) && (reader.NodeType != XmlNodeType.EndElement))
{
object targetValue = XmlFile.GetObject (reader, ObjectSupport.GetType(listEntryClass), root);
if (targetValue != null)
{
XmlFile.Read (reader, targetValue, target, null, root);
List.Add (targetValue);
}
else
{
XmlFile.MoveToNextElement (reader);
}
}
}
else
{
XmlFile.MoveToNextElement(reader); // moves to next element
}
}
// Special handling for hashtables
else if (target is IDictionary)
{
IDictionary Dictionary = (IDictionary) target;
Dictionary.Clear();
if (!reader.IsEmptyElement)
{
XmlFile.MoveToNextElement(reader); // moves to first dictionary element
while ((reader.Depth > processLevel) && (reader.NodeType != XmlNodeType.EndElement))
{
XmlFile.MoveToNextElement(reader); // moves to key
object keyValue = XmlFile.GetObject (reader, null, root);
XmlFile.Read (reader, keyValue, target, null, root);
object targetValue = XmlFile.GetObject (reader, null, root);
if (targetValue != null)
{
XmlFile.Read (reader, targetValue, target, null, root);
}
else
{
XmlFile.MoveToNextElement (reader);
}
Dictionary.Add (keyValue, targetValue);
}
}
else
{
XmlFile.MoveToNextElement(reader); // moves to next element
}
}
else
{
if (!reader.IsEmptyElement)
{
XmlFile.MoveToNextElement(reader);
// Read all sub elements
while (!reader.EOF && (reader.Depth > processLevel))
{
string property = XmlFile.GetProperty(aggregate, reader.Name);
if (property != null)
{
if ((Boolean) MetaInfo.GetAttributeDefault (target.GetType(), property, "XmlSkipElement", false))
{
XmlFile.MoveToNextElement(reader);
while (reader.Depth > processLevel + 1)
{
XmlFile.MoveToNextElement(reader);
}
}
else
{
object targetValue;
if (!aggregate.CanWrite(property))
{
targetValue = aggregate.GetValue(property);
if (targetValue == null)
{
throw new Exception ("Cannot access readonly element " + property + " at tag " + reader.Name);
}
}
else
{
Type type = ObjectSupport.GetType((string) MetaInfo.GetAttributeDefault (target.GetType(), property, "XmlType", aggregate.GetType(property).FullName));
targetValue = XmlFile.GetObject (reader, type, root);
}
if (targetValue != null)
{
XmlFile.Read (reader, targetValue, target, property, root);
if (aggregate.CanWrite(property))
{
aggregate.SetValue (property, targetValue);
}
}
else
{
XmlFile.MoveToNextElement (reader);
}
}
}
else
{
XmlFile.MoveToNextElement(reader); // moves to next
}
}
}
else
{
XmlFile.MoveToNextElement(reader); // moves to next
}
}
// Ensures that UpdateSource is only called once per aggregate
if (inCurrentXmlFile)
{
aggregate.UpdateSource();
}
}
///
/// Gets the object associated with an xml element.
/// Different procedures are followed for the following cases:
/// 1) The file is mentioned. Then that file is read and the top object (a.k.a. the root) of that file is returned
/// 2) The file is mentioned and the xml element is defined as a reference. Then that file is read (if not before) and the root of the file is asked for a property with the specified reference.
/// 3) The file isn't mentioned and the xml element is defined as a reference. Then the object is retrieved from the registration.
/// 4) Otherwise the object is instantiated
///
/// The xml stream
/// The type to instantiate when no type is specified in the xml element
/// The top object of the xml stream
/// The object associated with the xml element
/// Cannot resolve class type when an object for an xml element must be instantiated
/// Cannot find class type
/// Cannot instantiate object for known class type
/// Xml element holds a reference, but referenced object cannot be found
private static object GetObject (XmlReader reader, Type defaultType, object root)
{
object targetValue = null;
string file = reader.GetAttribute ("File");
string classType = (string) MetaInfo.GetAttributeDefault(reader.GetAttribute ("Type"), "XmlTypeAlias", reader.GetAttribute ("Type"));
ObjectSupport.LoadAssembly (reader.GetAttribute ("Assembly"));
Type targetType = null;
if (classType != null)
{
// classType = (string) MetaInfo.GetAttributeDefault (ObjectSupport.GetType(classType), "XmlTypeAlias", classType);
targetType = ObjectSupport.GetType (classType);
}
if ((classType == null) && (defaultType != null))
{
targetType = defaultType;
}
string reference = XmlFile.GetKeyFromXml (targetType, reader, true);
if ((targetType == null) && (reference == null))
{
if (classType != null)
{
throw new Exception ("Cannot find type " + classType + " when reading element tag " + reader.Name);
}
else
{
throw new Exception ("Cannot resolve element tag " + reader.Name);
}
}
if (file != null)
{
string fullFile = FileSupport.ExpandRelativePath(XmlFile.GetRegisteredFile(root).Directory, file).FullName;
object targetRoot = GetFileObject (fullFile);
if ((reference != null) && (!reference.Equals("")))
{
if (File.Exists (fullFile))
{
if (targetRoot == null)
{
targetRoot = XmlFile.GetRead(new FileInfo(fullFile), targetType);
}
IAggregate aggregate = XmlFile.GetAggregate(targetRoot);
targetValue = aggregate.GetReferencedValue (reference);
if (targetValue == null)
{
throw new Exception ("Cannot find referenced element " + reference + " at tag " + reader.Name);
}
}
else
{
if (!((bool) MetaInfo.GetAttributeDefault (targetType, "XmlAllowFileMissing", false)))
{
throw new Exception ("Referenced file does not exist: " + fullFile);
}
}
}
else // File != null && Reference == null
{
targetValue = targetRoot;
if (File.Exists (fullFile))
{
if (targetValue == null)
{
targetValue = ObjectSupport.GetInstance (targetType);
_fileList[targetValue] = file;
XmlFile.Read (targetValue, new FileInfo(fullFile));
}
if (targetValue == null)
{
throw new Exception ("Cannot find referenced element in " + fullFile + " at tag " + reader.Name);
}
string identifier = reader.GetAttribute("Identifier");
if (identifier != null)
{
XmlFile.RegisterKey(root, identifier, targetValue);
}
}
else
{
if (!((bool) MetaInfo.GetAttributeDefault (targetType, "XmlAllowFileMissing", false)))
{
throw new Exception ("Referenced file does not exist: " + fullFile);
}
}
}
}
else // file == null
{
if (reference != null)
{
targetValue = XmlFile.GetRegisteredTarget(root, reference);
if (targetValue == null)
{
throw new Exception ("Cannot find referenced element " + reference + " at tag " + reader.Name);
}
}
else // File == null && Reference == null
{
string attributeValue = reader.GetAttribute ("Value");
if (attributeValue == null)
{
targetValue = ObjectSupport.GetInstance (targetType);
}
else if (targetType.Equals (typeof (FileInfo)))
{
targetValue = FileSupport.ExpandRelativePath (XmlFile.GetRegisteredFile(root).Directory, attributeValue);
}
else if (targetType.Equals (typeof (DirectoryInfo)))
{
targetValue = FileSupport.ExpandRelativeDirectory (XmlFile.GetRegisteredFile(root).Directory, attributeValue);
}
else
{
targetValue = ObjectSupport.GetInstance (targetType, attributeValue, _culture);
}
if (targetValue == null)
{
throw new Exception ("Cannot instantiate " + targetType.FullName + " at element tag " + reader.Name);
}
}
}
// if (targetValue == null)
// {
// throw new Exception ("Cannot resolve element tag " + reader.Name);
// }
return targetValue;
}
///
/// Reads the attributes of the current xml element and uses them to populute the target with
///
/// The object which is populated with the xml attributes
/// The xml stream
/// Top object of the xml stream
/// Cannot find class type
/// Cannot instantiate object for known class type
private static void ReadAttributes (IAggregate target, XmlReader reader, object root)
{
// Read all attributes
string[] properties = target.Properties;
for (int i = 0; i < properties.Length; i++)
{
string name = XmlFile.GetElementName (target.Source.GetType(), properties[i], false);
if (name != null)
{
string attributeValue = reader.GetAttribute(name);
if (attributeValue != null)
{
string classType = (string) MetaInfo.GetAttributeDefault (target.Source.GetType(), properties[i], "XmlType", target.GetType(properties[i]).FullName);
Type type = ObjectSupport.GetType(classType);
if (type == null)
{
throw new Exception("Could not find class type " + classType);
}
if (type.Equals(typeof(FileInfo)))
{
attributeValue = FileSupport.ExpandRelativePath (XmlFile.GetRegisteredFile(root).Directory, attributeValue).FullName;
}
if (type.Equals(typeof(DirectoryInfo)))
{
attributeValue = FileSupport.ExpandRelativeDirectory (XmlFile.GetRegisteredFile(root).Directory, attributeValue).FullName;
}
object targetValue = ObjectSupport.GetInstance (type, attributeValue, _culture);
if (targetValue == null)
{
throw new Exception("Could not instantiate class type " + type.FullName);
}
if (target.CanWrite(properties[i]))
{
target.SetValue(properties[i], targetValue);
}
}
}
}
}
///
/// Moves the xml stream forward to the next readable element
///
/// The xml stream
private static void MoveToNextElement(XmlReader reader)
{
reader.Read();
while (!reader.EOF && (reader.NodeType != XmlNodeType.Element))
{
reader.Read();
}
}
#endregion
#region Administration
///
/// Sets up registration tables for a root object
///
/// The root object
private static void CreateRegistration (object root)
{
if (_rootKeyList[root] == null)
{
_rootKeyList[root] = new Hashtable();
}
if (_rootObjectList[root] == null)
{
_rootObjectList[root] = new Hashtable();
}
}
///
/// Registers that an object is written to or read from the same file as a root object.
/// This method calls RegisterKey internally.
///
/// The object to register
/// The xml stream
/// The object at the top of an xml file
private static void RegisterObject (object target, XmlReader reader, object root)
{
string key = XmlFile.GetKeyFromXml (target.GetType(), reader, false);
// Register the target as being read from this xml file
if (key != null)
{
XmlFile.RegisterKey (root, key, target);
}
}
///
/// Registers an object with a unique key within the scope of the root.
///
/// The root object
/// The unique key
/// The object to register
private static void RegisterKey (object root, object identifier, object target)
{
Hashtable keys = (Hashtable) _rootKeyList[root];
keys[identifier] = target;
Hashtable objects = (Hashtable) _rootObjectList[root];
objects[target] = identifier;
}
///
/// Gets a key from registration given the object.
/// The key will only be found if RegisterKey or RegisterObject has been called before.
///
/// The root object, the scope where is searched for the object
/// The object to search for
/// The key, null if not found
private static object GetRegisteredKey (object root, object target)
{
Hashtable objects = (Hashtable) _rootObjectList[root];
if (objects != null)
{
return objects[target];
}
return null;
}
///
/// Gets an object from registration given the key.
/// The object will only be found if RegisterKey or RegisterObject has been called before.
///
/// The root object, the scope where is searched for the key
/// The key to search for
/// The associated object, null if not found
public static object GetRegisteredTarget (object root, object identifier)
{
Hashtable keys = (Hashtable) _rootKeyList[root];
if (keys != null)
{
return keys[identifier];
}
return null;
}
///
/// Gets the file associated with an object.
/// The object should be the root of the file, i.e. associated with the top xml element.
///
///
/// The file, null if the object isn't a root of a file
public static FileInfo GetRegisteredFile (object anObject)
{
if (_fileList[anObject] != null)
{
return new FileInfo((string) _fileList[anObject]);
}
return null;
}
///
/// Gets a list of all objects, whcih are known to be the root of a file.
/// The root of a file is the object associated with the top xml element.
///
///
public static object[] GetRegisteredObjects()
{
ArrayList list = new ArrayList(_fileList.Keys);
return list.ToArray();
}
///
/// Gets the root object of a file
///
/// Full name of the file
/// The root object, null if not found
private static object GetFileObject(string File)
{
IDictionaryEnumerator DictionaryEnumerator = _fileList.GetEnumerator();
while (DictionaryEnumerator.MoveNext())
{
if (DictionaryEnumerator.Value.Equals (File))
{
object TargetValue = DictionaryEnumerator.Key;
return TargetValue;
}
}
return null;
}
///
/// Gets the file registered with an object and if not found, a new file is created
///
/// The object an associated file is searched for
/// Directory in which a new file will be created, if no file exists
/// The associated file
private static FileInfo GetForcedRegisteredFile (object target, DirectoryInfo dir)
{
if (XmlFile.GetRegisteredFile(target) != null)
{
return XmlFile.GetRegisteredFile(target);
}
FileInfo newFile = new FileInfo(dir + "\\" + target.GetType().Name + "1.xml");
for (int i = 1; newFile.Exists; i++)
{
newFile = new FileInfo (dir + "\\" + target.GetType().Name + i.ToString() + ".xml");
}
_fileList[target] = newFile;
return newFile;
}
///
/// Removes an object of all registrations.
/// To be used for releasing memory
///
/// The object to be removed from registrations
public static void DisposeObject (object disposeObject)
{
_fileList[disposeObject] = null;
_readObjectsList.Remove(disposeObject);
_unreadObjectsList.Remove(disposeObject);
_rootList[disposeObject] = null;
foreach (object o in new ArrayList(_rootList.Keys))
{
if (_rootList[o] == disposeObject)
{
_rootList[o] = null;
}
}
_rootKeyList[disposeObject] = null;
_rootObjectList[disposeObject] = null;
}
#endregion
#region Aggregate
///
/// Gets the aggregate of an object.
/// The aggregate is an object, which can be used for querying the object in a generic way.
/// The MetaInfo attribute "ObjectAggregate" is used to identify the class type of the aggregate, if not present DefaultAggregate is used.
/// The same aggregate object is reused when the same object is passed.
///
///
///
/// The object an aggregate is asked for
/// The aggregate object
/// Cannot find aggregate class type
/// Cannot instantiate aggregate for known class type
private static IAggregate GetAggregate (object target)
{
IAggregate aggregate = (IAggregate) _aggregateTable[target];
if (aggregate == null)
{
string classType = (string)MetaInfo.GetAttributeDefault (target.GetType(), "ObjectAggregate", typeof (DefaultAggregate).FullName);
aggregate = (IAggregate) ObjectSupport.GetInstance (classType, target);
if (aggregate == null)
{
throw new Exception("Could not find class type " + classType);
}
_aggregateTable[target] = aggregate;
}
aggregate.UpdateAggregate();
return aggregate;
}
#endregion
#region Keys of objects
///
/// Gets a string from an xml file which uniquely identifies the object which is currently parsed
/// This string reflects the identification of the object, e.g. the ID of the object
///
///
/// The xml file stream
/// Indication whether the current xml element refers to a prior definition of the object
/// The identification string
private static string GetKeyFromXml(Type TargetType, XmlReader reader, bool referenced)
{
string key = null;
if (referenced)
{
string Reference = reader.GetAttribute ("Reference");
if (Reference != null)
{
//key = Reference + ";";
return Reference;
}
if (TargetType != null)
{
string[] property = MetaInfo.GetProperties(TargetType);
// Try all properties which are registered as key
for (int i = 0; i < property.Length; i++)
{
if ((Boolean) MetaInfo.GetAttributeDefault (TargetType, property[i], "XmlKey", false))
{
string KeyValue = reader.GetAttribute ((string) MetaInfo.GetAttributeDefault (TargetType, property[i], "XmlRefName", property[i]));
if (KeyValue != null)
{
key += KeyValue + ";";
}
}
}
}
}
else
{
string Identifier = reader.GetAttribute("Identifier");
if (Identifier != null)
{
//key = Identifier + ";";
return Identifier;
}
string[] property = MetaInfo.GetProperties(TargetType);
// Try all properties which are registered as key
for (int i = 0; i < property.Length; i++)
{
if ((Boolean) MetaInfo.GetAttributeDefault (TargetType, property[i], "XmlKey", false))
{
string KeyValue = reader.GetAttribute ((string) MetaInfo.GetAttributeDefault (TargetType, property[i], "XmlName", property[i]));
if (KeyValue != null)
{
key += KeyValue + ";";
}
}
}
}
if (key != null)
{
key += TargetType.FullName;
}
return key;
}
///
/// Gets the key from an object.
/// If the key isn't unique and the property allows for key generation, a unique key is generated.
///
///
///
///
private static string GetKeyFromObject(IAggregate target, object root)
{
string key = null;
int lastkey = 0;
string[] property = target.Properties;
int generatedProperty = -1;
bool generationPossible = true;
while (generationPossible)
{
key = null;
lastkey++;
generationPossible = false;
for (int i = 0; i < property.Length; i++)
{
if ((Boolean) MetaInfo.GetAttributeDefault (target.Source.GetType(), property[i], "XmlKey", false))
{
object KeyObject = target.GetValue(property[i]);
if ((KeyObject != null) && (!KeyObject.ToString().Trim().Equals("")))
{
key += KeyObject.ToString() + ";";
}
else if ((Boolean) MetaInfo.GetAttributeDefault (target.Source.GetType(), property[i], "XmlAllowGeneration", true))
{
generationPossible = true;
generatedProperty = i;
key += lastkey + ";";
}
}
}
if (key != null)
{
key += target.Source.GetType().FullName;
object registeredObject = XmlFile.GetRegisteredTarget(root, key);
if ((registeredObject == null) || (registeredObject == target))
{
if (generatedProperty != -1)
{
Type PropertyType = target.GetType(property[generatedProperty]);
if (PropertyType.Equals(typeof(string)))
{
target.SetValue(property[generatedProperty], Convert.ToString(lastkey));
}
else if (PropertyType.Equals(typeof(Int16)) || PropertyType.Equals(typeof(Int32)) || PropertyType.Equals(typeof(Int64)))
{
target.SetValue(property[generatedProperty], lastkey);
}
}
return key;
}
}
}
return key;
}
#endregion
#region Public Write Methods
///
/// Writes an object to an xml file
/// The registered file of the object will be used as xml file. This is the file
/// to which the object was written to or read from in an earlier stage
///
/// The object to be written
/// Cannot derive key for object which must be written as a reference. Probably MetaInfo is missing for the class and subject XmlKeyexception>
public static void Write (object target)
{
object fileobj = _fileList[target];
FileInfo file = null;
if (fileobj is FileInfo)
{
file = (FileInfo) fileobj;
}
if (fileobj is string)
{
file = new FileInfo((string)fileobj);
}
if (file != null)
{
XmlFile.Write (target, file);
}
}
///
/// Writes an object to an xml file
///
/// The object to write
/// The xml file
/// Cannot derive key for object which must be written as a reference. Probably MetaInfo is missing for the class and subject XmlKeyexception>
public static void Write (object target, FileInfo file)
{
XmlFile.CreateRegistration (target);
FileInfo targetFile = XmlFile.GetTempFile(file.Directory);
XmlTextWriter writer = new XmlTextWriter(targetFile.FullName, null);
_fileList[target] = file.FullName;
//Use automatic indentation for readability.
writer.Formatting = Formatting.Indented;
writer.WriteStartDocument();
string xmlStartElement = (string) MetaInfo.GetAttributeDefault (target.GetType(), "XmlStartElement", target.GetType().Name);
writer.WriteStartElement(xmlStartElement);
string xmlNameSpace = (string) MetaInfo.GetAttribute (target.GetType(), "XmlNameSpace");
if (xmlNameSpace != null)
{
writer.WriteAttributeString("xmlns", xmlNameSpace);
}
// Write the actual values
XmlFile.Write (writer, target, null, null, null, target, null, new ArrayList());
// end the root element
//Write the XML to file and close the writer
writer.WriteEndElement();
writer.Close();
file.Delete();
targetFile.MoveTo (file.FullName);
}
#endregion
#region Internal Write Methods
///
/// Writes an object to xml. This will write
/// 1) the type and assembly of the object to write
/// 2) xml attributes for all properties of the object which can be written as xml attributes
/// 3) xml elements for all other properties (this this method is called recursively)
///
/// The xml stream
/// The object to be written
/// The object in the xml stream, which is associated with the xml parent element
/// Type of the object
/// Type expected
/// Object associated with the top of the xml stream
/// Name of the xml parent tag
/// List of all object written so far in this xml stream
/// Cannot derive key for object which must be written as a reference. Probably MetaInfo is missing for the class and subject XmlKeyexception>
private static void Write (XmlTextWriter writer, object target, object parent, string targetProperty, Type expectedType, object root, string parentName, ArrayList writtenObjects)
{
if (target == null)
{
return;
}
bool reference = false;
if ((expectedType == null) || (!expectedType.Equals(target.GetType())))
{
string xmlTypeName = (string) MetaInfo.GetAttributeDefault (target.GetType().FullName, "XmlTypeName", target.GetType().FullName);
writer.WriteAttributeString("Type", xmlTypeName);
if (ObjectSupport.IsLoadedAssembly(target.GetType().Assembly) && !(bool) MetaInfo.GetAttributeDefault(target.GetType().Assembly, "XmlSkipElement", false))
{
if (target.GetType().Assembly.GlobalAssemblyCache)
{
writer.WriteAttributeString("Assembly", target.GetType().Assembly.FullName);
}
else
{
writer.WriteAttributeString("Assembly", target.GetType().Assembly.Location);
}
}
}
// Check whether this object has been written in this xml file already
// If so, just write the reference definition
if (CollectionSupport.ContainsObject (writtenObjects, target))
{
object key = XmlFile.GetRegisteredKey(root, target);
if (key == null)
{
throw new Exception ("Could not derive key for referenced object " + target.ToString());
}
if ((Boolean) MetaInfo.GetAttributeDefault (target.GetType(), "ObjectIndexAsKey", false))
{
writer.WriteAttributeString("Reference", key.ToString());
}
reference = true;
}
IAggregate aggregate = XmlFile.GetAggregate(target);
aggregate.UpdateAggregate();
// Find the parent under which the target will be written
/* string parentProperty = (string) MetaInfo.GetAttributeDefault (target.GetType(), "XmlParent", null);
if (parentProperty != null)
{
object parentValue = aggregate.GetValue (parentProperty);
if (parentValue != null)
{
object rootParent = _rootList[parentValue];
_rootList[target] = rootParent;
}
}*/
// Check whether the object will be written in another file
// This should occur when
// 1. The root of this object isn't the same as the root writing at this moment
// 2. The object is declared that it should be written in it's own file
// The root is the object at the top of the xml file
bool anotherFile = XmlFile.DifferentFiles (target, root);
if (!anotherFile)
{
if (target != root)
{
anotherFile = (Boolean) MetaInfo.GetAttributeDefault (target.GetType(), "XmlFile", false);
if (anotherFile)
{
_rootList[target] = target;
}
}
}
if (anotherFile)
{
if (_fileList[_rootList[target]] == null)
{
XmlFile.GetForcedRegisteredFile(_rootList[target], XmlFile.GetRegisteredFile(root).Directory);
XmlFile.Write (_rootList[target]);
}
writer.WriteAttributeString("File", FileSupport.GetRelativePath (XmlFile.GetRegisteredFile(root).Directory, new FileInfo((string) _fileList[_rootList[target]])));
if ((Boolean) MetaInfo.GetAttributeDefault (target.GetType(), "ObjectIndexAsKey", false))
{
writer.WriteAttributeString("Reference", XmlFile.GetRegisteredKey(_rootList[target], target).ToString());
}
reference = true;
}
DirectoryInfo baseDirectory = XmlFile.GetRegisteredFile(root).Directory;
if (XmlFile.IsAttribute(target.GetType()))
{
writer.WriteAttributeString("Value", XmlFile.ToString(target, baseDirectory));
return;
}
if (!reference)
{
_rootList[target] = root;
// Add the object to the written objects list
int identifier = writtenObjects.Count + 1;
writtenObjects.Add(target);
string identificationString = Convert.ToString(identifier);
if (!((Boolean) MetaInfo.GetAttributeDefault (target.GetType(), "ObjectIndexAsKey", false)))
{
identificationString = XmlFile.GetKeyFromObject(aggregate, root);
}
if (identificationString != null)
{
XmlFile.RegisterKey(root, identificationString, target);
}
if ((Boolean) MetaInfo.GetAttributeDefault (target.GetType(), "ObjectIndexAsKey", false))
{
writer.WriteAttributeString("Identifier", Convert.ToString(identifier));
}
}
// Write all xml attributes
if (!(target is IList) && !(target is IDictionary))
{
string[] property = XmlFile.GetSortedProperties(aggregate);
// Pass 1: Write all properties which can be represented as an attribute
for (int i = 0; i < property.Length; i++)
{
if (IsAttribute (aggregate.GetType(property[i])))
{
if (XmlFile.PropertyWrite (target.GetType(), property[i], reference))
{
bool skipElement = (Boolean) MetaInfo.GetAttributeDefault (target.GetType(), property[i], "XmlSkipElement", false);
if (!skipElement)
{
object targetValue = aggregate.GetValue(property[i]);
if (targetValue != null)
{
string name = XmlFile.GetElementName(target.GetType(), property[i], reference);
writer.WriteAttributeString(name, XmlFile.ToString(targetValue, baseDirectory));
}
}
}
}
}
}
// Move the target to the default property if any
string defaultProperty = MetaInfo.GetAttribute (target.GetType(), "XmlDefaultProperty") as string;
if (defaultProperty != null)
{
parent = target;
target = aggregate.GetValue (defaultProperty);
targetProperty = defaultProperty;
aggregate = XmlFile.GetAggregate(target);
aggregate.UpdateAggregate();
parentName = XmlFile.GetElementName (parent.GetType(), defaultProperty, false);
}
// Special handling for lists
if (target is IList)
{
if (parent == null)
{
parent = target;
}
string listEntryClass = (string) MetaInfo.GetAttributeDefault (parent.GetType(), targetProperty, "XmlItemType", MetaInfo.GetAttribute (target.GetType(), "XmlItemType"));
for (int j = 0; j < ((IList) target).Count; j++)
{
string name = XmlFile.GetSingleName(parentName);
if (CollectionSupport.ContainsObject(writtenObjects, ((IList) target)[j]))
{
name = (string) MetaInfo.GetAttributeDefault (parent.GetType(), targetProperty, "XmlRefItemName", MetaInfo.GetAttributeDefault (parent.GetType(), targetProperty, "XmlItemName", name));
}
else
{
name = (string) MetaInfo.GetAttributeDefault (parent.GetType(), targetProperty, "XmlItemName", name);
}
writer.WriteStartElement(name);
XmlFile.Write (writer, ((IList) target)[j], null, null, ObjectSupport.GetType(listEntryClass), root, name, writtenObjects);
writer.WriteEndElement(); // end Name
}
}
// Special handling for hashtables
else if (target is IDictionary)
{
if (parent == null)
{
parent = target;
}
IDictionaryEnumerator DictionaryEnumerator = ((IDictionary) target).GetEnumerator();
while (DictionaryEnumerator.MoveNext())
{
if (DictionaryEnumerator.Value != null)
{
string name = (string) MetaInfo.GetAttributeDefault (parent.GetType(), targetProperty, "XmlItemName", XmlFile.GetSingleName(parentName));
writer.WriteStartElement(name);
writer.WriteStartElement("Key");
XmlFile.Write (writer, DictionaryEnumerator.Key, null, null, null, root, null, writtenObjects);
writer.WriteEndElement(); // key
writer.WriteStartElement("Value");
XmlFile.Write (writer, DictionaryEnumerator.Value, null, null, null, root, null, writtenObjects);
writer.WriteEndElement(); // value
writer.WriteEndElement(); // property
}
}
}
// Normal write procedure
else
{
// Get all properties which must be written
string[] property = XmlFile.GetSortedProperties(aggregate);
// Pass 2: Write all properties which must be represented as a sub element
for (int i = 0; i < property.Length; i++)
{
if (!IsAttribute (aggregate.GetType(property[i])))
{
if (XmlFile.PropertyWrite (target.GetType(), property[i], reference))
{
bool skipElement = (Boolean) MetaInfo.GetAttributeDefault (target.GetType(), property[i], "XmlSkipElement", false);
if (!skipElement)
{
object targetValue = aggregate.GetValue(property[i]);
if (targetValue != null)
{
string name = XmlFile.GetElementName(target.GetType(), property[i], reference || CollectionSupport.ContainsObject(writtenObjects, targetValue) || XmlFile.DifferentFiles(targetValue, root));
//Get instance of the attribute.
writer.WriteStartElement(name);
Type type = ObjectSupport.GetType((string) MetaInfo.GetAttributeDefault (target.GetType(), property[i], "XmlType", aggregate.GetType(property[i]).FullName));
if (!aggregate.CanWrite(property[i]))
{
type = targetValue.GetType(); // this enforces that no type information is written; it cannot be used anyway, because the property is read only
}
XmlFile.Write (writer, targetValue, target, property[i], type, root, name, writtenObjects);
writer.WriteEndElement(); // end Name
}
}
}
}
}
}
}
///
/// Gets the string representing an object.
/// This string value is used for writing the object in an xml file.
///
/// The object a string value is requested for
/// Directory, which is used in case of files. Then the relative file path is returned
/// The string representation
private static string ToString (object target, DirectoryInfo directory)
{
if (target is FileInfo)
{
return FileSupport.GetRelativePath(directory, (FileInfo) target);
}
else if (target is DirectoryInfo)
{
return FileSupport.GetRelativePath(directory, (DirectoryInfo) target);
}
else if (target is IFormattable)
{
return ((IFormattable) target).ToString (null, _culture);
}
else if (target is bool)
{
return ((bool) target).ToString().ToLower();
}
else
{
return target.ToString();
}
}
///
/// Gets all properties of an aggregate in a sorted way.
/// MetaInfo attribute "XmlIndex" is used for the sorting.
///
/// The aggregate
/// Sorted property names
private static string[] GetSortedProperties (IAggregate aggregate)
{
SortedList list = new SortedList();
string[] properties = aggregate.Properties;
for (int i = 0; i < properties.Length; i++)
{
int index = (int)MetaInfo.GetAttributeDefault (aggregate.Source.GetType(), properties[i], "XmlIndex", 1000);
string listIndex = index.ToString("D9") + " " + properties[i].ToLower();
if (!list.Contains (listIndex))
{
list.Add (listIndex, properties[i]);
}
}
/* for (int i = 0; i < list.GetValueList()..Values.Count; i++)
{
propertyList.Add (list.GetByIndex(i));
}*/
ArrayList propertyList = new ArrayList(list.Values);
return (string[]) propertyList.ToArray(typeof(string));
}
///
/// Gets the xml element name for a specific property
///
/// The class type containing the property
/// The property to write
/// Identifies whether the property is to be written completely or just a reference, because the property has been written before
/// The xml element name
private static string GetElementName(Type type, string property, bool reference)
{
if (reference)
{
return (string) MetaInfo.GetAttributeDefault (type, property, "XmlRefName", MetaInfo.GetAttributeDefault (type, property, "XmlName", property));
}
else
{
return (string) MetaInfo.GetAttributeDefault (type, property, "XmlName", property);
}
}
///
/// Indicates whether two objects are written in the same file.
/// This should occur when 1) the root of this object isn't the same as the root writing at this moment
/// or 2) the object is declared that it should be written in it's own file.
/// The root is the object at the top of the xml file
///
/// First object
/// Second object
/// Indication same file
private static bool DifferentFiles (object target, object root)
{
// Find the parent under which the target will be written
IAggregate aggregate = XmlFile.GetAggregate(target);
if (_rootList[target] == null)
{
string parentProperty = (string) MetaInfo.GetAttributeDefault (target.GetType(), "XmlParent", null);
if (parentProperty != null)
{
object parentValue = aggregate.GetValue (parentProperty);
if (parentValue != null)
{
object rootParent = _rootList[parentValue];
_rootList[target] = rootParent;
}
}
}
return (_rootList[target] != null) && (_rootList[target] != root);
}
///
/// Indicates whether a property should be written
///
/// The class type of the object containing the property
/// The property to write
/// Identifies whether the property is to be written completely or just a reference, because the property has been written before
/// Boolean indicating whether the property is to be written
private static bool PropertyWrite (Type type, string property, bool reference)
{
bool allowed = !reference && ((Boolean) MetaInfo.GetAttributeDefault (type, property, "XmlElement", true));
allowed = allowed || (reference && (((Boolean) MetaInfo.GetAttributeDefault (type, property, "XmlRefElement", false)) || ((Boolean) MetaInfo.GetAttributeDefault (type, property, "XmlKey", false))));
return allowed;
}
///
/// Gets the single word given a plural word.
/// For example, plural word "variables" would return "variable".
///
/// The plural word
/// The single word, "Item" if nothing specific can be derived
private static string GetSingleName(string parentName)
{
if (parentName == null)
{
return "Item";
}
string singleName = "";
if (parentName.EndsWith("List") || parentName.EndsWith("list"))
{
singleName = parentName.Substring (0, parentName.Length - 4);
}
if (parentName.EndsWith("Set") || parentName.EndsWith("set"))
{
singleName = parentName.Substring (0, parentName.Length - 3);
}
if (parentName.EndsWith("Collection") || parentName.EndsWith("collection"))
{
singleName = parentName.Substring (0, parentName.Length - 10);
}
if (parentName.EndsWith("s"))
{
singleName = parentName.Substring (0, parentName.Length - 1);
}
if (parentName.EndsWith("ies"))
{
singleName = parentName.Substring (0, parentName.Length - 3) + "y";
}
if (singleName.Trim().Equals(""))
{
singleName = "Item";
}
return singleName;
}
///
/// Gets the property given a specific xml element name
///
/// The object which will contain the property
/// The xml element name
/// The property name, null if not found
private static string GetProperty (IAggregate target, string name)
{
string[] properties = target.Properties;
for (int i = 0; i < properties.Length; i++)
{
string elementName = XmlFile.GetElementName (target.Source.GetType(), properties[i], false);
if ((elementName != null) && (elementName.Equals(name)))
{
return properties[i];
}
elementName = XmlFile.GetElementName (target.Source.GetType(), properties[i], true);
if ((elementName != null) && (elementName.Equals(name)))
{
return properties[i];
}
}
return null;
}
///
/// Indicates whether an object can be written as an xml attribute (instead of an xml element)
///
/// The class type of the object which should be written
/// Indication of writable as xml attribute
private static bool IsAttribute (Type targetType)
{
return (targetType.IsPrimitive) || (targetType.IsEnum) || (targetType.Equals (typeof(string))) || (targetType.Equals (typeof(FileInfo))) || (targetType.Equals (typeof(DirectoryInfo))) || (targetType.Equals(typeof(DateTime)));
}
///
/// Gets a temporary file.
/// When writing an xml file, first a temporary file is used and later it is moved to the actually intended file.
/// In this way the original file is left untouched when an exception occurs.
///
/// The directory where the temp file should be located
/// The temporary file
private static FileInfo GetTempFile(DirectoryInfo TempDirectory)
{
int index = 0;
while (true)
{
FileInfo file = new FileInfo(TempDirectory.FullName + "\\temp" + index + ".xml");
if (!file.Exists)
{
return file;
}
index++;
}
}
#endregion
}
}