From: Michael D. <mik...@us...> - 2005-02-21 19:19:33
|
Update of /cvsroot/nhibernate/nhibernate/src/NHibernate/Cfg In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv2694/NHibernate/Cfg Modified Files: Configuration.cs Added Files: AssemblyHbmOrderer.cs Log Message: NH-178: AddAssembly now orders the hbm.xml files before processing. --- NEW FILE: AssemblyHbmOrderer.cs --- using System; using System.Collections; using System.Collections.Specialized; using System.IO; using System.Reflection; using System.Xml; namespace NHibernate.Cfg { /// <summary> /// Analyzes the contents of the <c>hbm.xml</c> files embedded in the /// <see cref="Assembly"/> for their dependency order. /// </summary> /// <remarks> /// This solves the problem caused when you have embedded <c>hbm.xml</c> files /// that contain subclasses/joined-subclasses that make use of the <c>extends</c> /// attribute. This ensures that those subclasses/joined-subclasses will not be /// processed until after the class they extend is processed. /// </remarks> internal class AssemblyHbmOrderer { /* * It almost seems like a better way to handle this would be to have * Binder.BindRoot throw a different exception when a subclass/joined-subclass * couldn't find their base class mapping. The AddAssembly method could * "queue" up the problem hbm.xml files and run through them at the end. * This method solves the problem WELL enough to get by with for now. */ Assembly _assembly; /// <summary> /// An unordered <see cref="IList"/> of all the mapped classes contained /// in the assembly. /// </summary> ArrayList _classes; /// <summary> /// An <see cref="IList"/> of all the <c>hbm.xml</c> resources found /// in the assembly. /// </summary> ArrayList _hbmResources; /// <summary> /// Creates a new instance of <see cref="AssemblyHbmOrderer"/> /// </summary> /// <param name="assembly">The <see cref="Assembly"/> to order the <c>hbm.xml</c> files in.</param> public AssemblyHbmOrderer(Assembly assembly) { _assembly = assembly; _classes = new ArrayList(); _hbmResources = new ArrayList(); foreach( string fileName in assembly.GetManifestResourceNames() ) { if( fileName.EndsWith( ".hbm.xml" ) ) { _hbmResources.Add( fileName ); } } } /// <summary> /// Gets an <see cref="IList"/> of <c>hbm.xml</c> resources in the correct order. /// </summary> /// <returns> /// An <see cref="IList"/> of <c>hbm.xml</c> resources in the correct order. /// </returns> public IList GetHbmFiles() { // tracks if any hbm.xml files make use of the "extends" attribute bool containsExtends = false; foreach( string fileName in _hbmResources ) { Stream xmlInputStream = null; XmlReader xmlReader = null; try { xmlInputStream = _assembly.GetManifestResourceStream( fileName ); xmlReader = new XmlTextReader( xmlInputStream ); while( xmlReader.Read() ) { if( xmlReader.NodeType!=XmlNodeType.Element ) { continue; } if( xmlReader.Name=="class" ) { xmlReader.MoveToAttribute("name"); string className = xmlReader.Value; ClassEntry ce = new ClassEntry( null, className, fileName ); _classes.Add(ce); } else if( xmlReader.Name=="joined-subclass" || xmlReader.Name=="subclass" ) { xmlReader.MoveToAttribute("name"); string className = xmlReader.Value; if( xmlReader.MoveToAttribute("extends") ) { containsExtends = true; string baseClassName = xmlReader.Value; ClassEntry ce = new ClassEntry( baseClassName, className, fileName ); _classes.Add(ce); } } } } finally { if( xmlReader!=null ) { xmlReader.Close(); } if( xmlInputStream!=null ) { xmlInputStream.Close(); } } } // only bother to do the sorting if one of the hbm files uses 'extends' - // the sorting does quite a bit of looping through collections so if we don't // need to spend the time doing that then don't bother. if( containsExtends ) { return OrderedHbmFiles( _classes ); } else { return _hbmResources; } } /// <summary> /// Returns an <see cref="IList"/> of <c>hbm.xml</c> files in the order that ensures /// base classes are loaded before their subclass/joined-subclass. /// </summary> /// <param name="unorderedClasses">An <see cref="IList"/> of <see cref="ClassEntry"/> objects.</param> /// <returns> /// An <see cref="IList"/> of <see cref="String"/> objects that contain the <c>hbm.xml</c> file names. /// </returns> private IList OrderedHbmFiles(IList unorderedClasses) { // Make sure joined-subclass mappings are loaded after base class ArrayList sortedList = new ArrayList(); foreach( ClassEntry ce in unorderedClasses ) { // this class extends nothing - so put it at the front of // the list because it is safe to process at any time. if( ce.BaseClassName==null ) { sortedList.Insert( 0, ce ); } else { int insertIndex = -1; // try to find this classes base class in the list already for( int i=0; i<sortedList.Count; i++ ) { ClassEntry sce = (ClassEntry)sortedList[i]; // base class was found - insert at the index // immediately following it if( sce.ClassName==ce.BaseClassName ) { insertIndex = i + 1; break; } } // This Classes' baseClass was not found in the list so we still don't // know where to insert it. Check to see if any of the classes that // have already been sorted derive from this class. if( insertIndex==-1 ) { for( int j=0; j<sortedList.Count; j++ ) { ClassEntry sce = (ClassEntry)sortedList[j]; // A class already in the sorted list derives from this class so // insert this class before the class deriving from it. if( sce.BaseClassName == ce.ClassName ) { insertIndex = j; break; } } } // could not find any classes that were subclasses of this one or // that this class was a subclass of so it should be inserted at // then end. if( insertIndex==-1 ) { // Insert at end insertIndex = sortedList.Count; } sortedList.Insert( insertIndex, ce ); } } // now that we know the order the classes should be loaded - order the // hbm.xml files those classes are contained in. StringCollection loadedFiles = new StringCollection(); foreach( ClassEntry ce in sortedList ) { // Check if this file already loaded (this can happen if // we have mappings for multiple classes in one file). if (loadedFiles.Contains( ce.FileName )==false ) { loadedFiles.Add( ce.FileName ); } } return loadedFiles; } /// <summary> /// Holds information about mapped classes found in the <c>hbm.xml</c> files. /// </summary> internal class ClassEntry { private string _baseClassName; private string _className; private string _fileName; public ClassEntry(string baseClassName, string className, string fileName) { _baseClassName = baseClassName; _className = className; _fileName = fileName; } /// <summary> /// Gets the name of the Class that this Class inherits from, or <c>null</c> /// if this does not inherit from any mapped Class. /// </summary> public string BaseClassName { get { return _baseClassName; } } /// <summary> /// Gets the name of this Class. /// </summary> public string ClassName { get { return _className; } } /// <summary> /// Gets the name of the <c>hbm.xml</c> file this class was found in. /// </summary> public string FileName { get { return _fileName; } } } } } Index: Configuration.cs =================================================================== RCS file: /cvsroot/nhibernate/nhibernate/src/NHibernate/Cfg/Configuration.cs,v retrieving revision 1.30 retrieving revision 1.31 diff -C2 -d -r1.30 -r1.31 *** Configuration.cs 21 Feb 2005 14:27:36 -0000 1.30 --- Configuration.cs 21 Feb 2005 19:17:39 -0000 1.31 *************** *** 148,152 **** /// creating Mapping objects from the Mapping Xml. /// </summary> ! /// <param name="doc">The validated XmlDocument that contains the Mappings.</param> private void Add( XmlDocument doc ) { --- 148,152 ---- /// creating Mapping objects from the Mapping Xml. /// </summary> ! /// <param name="doc">The <b>validated</b> XmlDocument that contains the Mappings.</param> private void Add( XmlDocument doc ) { *************** *** 197,203 **** /// <param name="assembly">The loaded Assembly.</param> /// <returns>This Configuration object.</returns> public Configuration AddAssembly( Assembly assembly ) { ! foreach( string fileName in assembly.GetManifestResourceNames() ) { if( fileName.EndsWith( ".hbm.xml" ) ) --- 197,245 ---- /// <param name="assembly">The loaded Assembly.</param> /// <returns>This Configuration object.</returns> + /// <remarks> + /// This assumes that the <c>hbm.xml</c> files in the Assembly need to be put + /// in the correct order by NHibernate. See <see cref="AddAssembly(Assembly, bool)"> + /// AddAssembly(Assembly assembly, bool skipOrdering)</see> + /// for the impacts and reasons for letting NHibernate order the + /// <c>hbm.xml</c> files. + /// </remarks> public Configuration AddAssembly( Assembly assembly ) { ! // assume ordering is needed because that is the ! // safest way to handle it. ! return AddAssembly( assembly, false ); ! } ! ! /// <summary> ! /// Adds all of the Assembly's Resource files that end with "hbm.xml" ! /// </summary> ! /// <param name="assembly">The loaded Assembly.</param> ! /// <param name="skipOrdering"> ! /// A <see cref="Boolean"/> indicating if the ordering of hbm.xml files can be skipped. ! /// </param> ! /// <returns>This Configuration object.</returns> ! /// <remarks> ! /// <p> ! /// The order of <c>hbm.xml</c> files only matters if the attribute "extends" is used. ! /// The ordering should only be done when needed because it takes extra time ! /// to read the Xml files to find out the order the files should be passed to the Binder. ! /// If you don't use the "extends" attribute then it is reccommended to call this ! /// with <c>skipOrdering=true</c>. ! /// </p> ! /// </remarks> ! public Configuration AddAssembly(Assembly assembly, bool skipOrdering) ! { ! IList resources = null; ! if( skipOrdering ) ! { ! resources = assembly.GetManifestResourceNames(); ! } ! else ! { ! AssemblyHbmOrderer orderer = new AssemblyHbmOrderer( assembly ); ! resources = orderer.GetHbmFiles(); ! } ! ! foreach( string fileName in resources ) { if( fileName.EndsWith( ".hbm.xml" ) ) |