View Javadoc

1   package org.lcsim.material;
2   
3   import java.io.IOException;
4   import java.util.HashMap;
5   import java.util.LinkedHashMap;
6   import java.util.List;
7   import java.util.Map;
8   
9   import org.jdom.Document;
10  import org.jdom.Element;
11  import org.jdom.JDOMException;
12  import org.jdom.input.SAXBuilder;
13  
14  /**
15   * This class loads GDML data from a materials XML section into the {@link MaterialManager}.  
16   * 
17   * It is also used to keep track of compact material data for the conversion to/from
18   * compact descriptions.
19   * 
20   * To distinguish between XML and chemical "elements" in the code, I sometimes use the 
21   * term "node" to refer to an XML element.
22   * 
23   * @author jeremym
24   * @version $Id: XMLMaterialManager.java,v 1.23 2011/04/21 19:42:37 jeremy Exp $
25   */
26  // TODO Refactor spaghetti/recursion code that does reference resolution.
27  // TODO Replace parent/child structure of managers with a single manager or utility class.
28  // TODO Move most of the logic for loading materials to the CompactReader class.
29  // TODO Simplify the find/get methods for looking up XML elements.  There are too many similar methods.
30  // TODO Where possible, move additional methods into LCDDMaterialHelper class.
31  // FIXME Should not keep references to XML nodes.  To copy from compact to LCDD,
32  // only the MaterialManager should be used to resolve references.  LCDD material XML 
33  // can be constructed from Material and MaterialElement objects on the fly rather than cloned.
34  // FIXME Deal with multiple unnecessary calls to setup() from clients.
35  public class XMLMaterialManager
36  {    
37      // Location of elements resource in jar relative to org.lcsim.material pkg.
38      private static final String elementResource = "elements.xml";
39  
40      /// Location of materials resource in jar relative to org.lcsim.material pkg.
41      private static final String materialResource = "materials.xml";
42      
43      // Instance of MaterialManager.
44      private static MaterialManager mgr;
45              
46      // Static instance containing elements data.
47      private static XMLMaterialManager elements;
48  
49      // Static instance containing materials data.
50      private static XMLMaterialManager materials;
51  
52      // Parent manager of this one. 
53      private XMLMaterialManager parent;
54  
55      // Current XML document known by this manager.
56      private Document currentDoc;
57  
58      // Map of names to material XML nodes known by this manager.
59      private HashMap<String, org.jdom.Element> materialMap = new LinkedHashMap<String, Element>();
60  
61      // Map of names to material element XML nodes known by this manager.
62      private HashMap<String, org.jdom.Element> elementMap = new LinkedHashMap<String, Element>();
63      
64      /**
65       * Setup static material data.  This can be called to reset material data between runs
66       * with different Detectors that are run in the same Java process, such as a TestCase.  
67       * This method must be called before the XMLMaterialManager is created for a compact document.
68       */    
69      public static void setup()
70      {        
71          // Clear the global material store.
72          MaterialManager.instance().clear();
73          mgr = MaterialManager.instance();
74                          
75          // Rebuild the default material data stores.
76          elements = createMaterialElements();
77          materials = createMaterials();        
78      }
79      
80      /**
81       * Ctor for a manager with the given root XML node, probably pointing to the
82       * materials section of a compact detector description.  Its parent is the default 
83       * material manager.
84       * @param materialsRoot The XML element pointing to a materials section.
85       */
86      public XMLMaterialManager(Element materialsRoot)
87      {
88          // Bootstrap if static data is not setup yet.
89          if (elements == null)
90          {
91              setup();
92          }
93          parent = getDefaultMaterialManager();
94          setMaterialsRoot(materialsRoot);
95          loadMaterialsFromXML(materialsRoot);
96      }
97                 
98      /** 
99       * Get manager with default materials defined in the embedded resource file. 
100      * @return The manager with references to the default material manager.    
101      **/        
102     public static XMLMaterialManager getDefaultMaterialManager()
103     {
104         // Bootstrap if static data is not setup yet.
105         if (elements == null)
106         {
107             setup();
108         }
109         return materials;
110     }    
111     
112     /** 
113      * Get the manager with chemical element data.
114      * @return The manager with default chemical elements data.    
115      **/        
116     public static XMLMaterialManager getElementsManager()
117     {
118         // Bootstrap if static data is not setup yet.
119         if (elements == null)
120         {
121             setup();
122         }
123         return elements;
124     }
125          
126     /**
127      * Get the map of XML material nodes known by this manager.
128      * @return The map of names to material nodes.
129      */
130     public Map<String, Element> getMaterialXMLMap()
131     {
132         return materialMap;
133     }
134     
135     /**
136      * Get the map of XML chemical element nodes known by this manager.
137      * @return The map of names to chemical element nodes.
138      */
139     public Map<String, Element> getElementXMLMap()
140     {
141         return elementMap;
142     }
143     
144     /** 
145      * Add a material XML node to the lookup map.  
146      * Does not create a Material object.
147      * @param e The XML node to add. 
148      **/
149     public void addMaterialRef(org.jdom.Element e)
150     {
151         materialMap.put(e.getAttributeValue("name"), e);     
152     }
153 
154     /** 
155      * Add a material element XML node to the lookup map.
156      * Does not create a MaterialElement.
157      * @param e The XML ndoe to add. 
158      **/
159     public void addMaterialElementRef(org.jdom.Element e)
160     {
161         elementMap.put(e.getAttributeValue("name"), e);
162     }
163 
164     /** 
165      * Find the XML element for a material.
166      * @return The XML element with name <code>matName</code> or null if doesn't exist.    
167      */
168     // FIXME Move to LCDDMatHelp
169     public org.jdom.Element getMaterialXML(String matName)
170     {
171         org.jdom.Element m = materialMap.get(matName);
172 
173         if (m == null)
174         {
175             if (hasParentManager())
176             {
177                 m = parent.getMaterialXML(matName);
178             }
179         }
180 
181         return m;
182     }    
183 
184     /** 
185      * Find the XML node for a chemical element.
186      * @param elemName The name of the chemical element.
187      * @return The XML element with name <code>elemName</code> or null if doesn't exist.    
188      */
189     public org.jdom.Element getMaterialElementXML(String elemName)
190     {
191         org.jdom.Element e = elementMap.get(elemName);
192 
193         if (e == null)
194         {
195             if (hasParentManager())
196             {
197                 e = parent.getMaterialElementXML(elemName);
198             }
199         }
200 
201         return e;
202     }
203 
204     /** 
205      * Find a chemical element in a document.
206      * @return The XML node of the chemical element called <code>elemName</code>. 
207      **/
208     public Element findMaterialElementXML(String elemName, Document d)
209     {        
210         org.jdom.Element me = getMaterialsRoot(d);
211         org.jdom.Element fnd = null;
212         for (Object o : me.getChildren("element"))
213         {
214             org.jdom.Element curr = (org.jdom.Element)o;
215             if (curr.getAttributeValue("name").equals(elemName))
216             {
217                 fnd = curr;
218                 break;
219             }
220         }
221 
222         // Try the parent.
223         if (fnd == null && hasParentManager())
224         {
225             fnd = parent.findMaterialElementXML(elemName);
226         }
227 
228         return fnd;
229     }
230     
231     /**
232      *  Get the current XML document known by this manager.
233      *  @return The current XML document. 
234      **/
235     public Document getCurrentDocument()
236     {
237         return currentDoc;
238     }
239     
240     /** 
241      * Find a material in an XML document.
242      * @param matName The name of the material.
243      * @param d The document to search.
244      * @return The node for the material called <code>matName</code> or null if not found. 
245      **/
246     public org.jdom.Element findMaterialXML(String matName, Document d)
247     {
248         org.jdom.Element me = getMaterialsRoot(d);
249 
250         org.jdom.Element fnd = null;
251         for (Object o : me.getChildren("material"))
252         {
253             org.jdom.Element curr = (org.jdom.Element)o;
254             if (curr.getAttributeValue("name").equals(matName))
255             {
256                 fnd = curr;
257                 break;
258             }
259         }
260 
261         // Look in parent manager.
262         if (fnd == null && hasParentManager())
263         {
264             fnd = parent.findMaterialXML(matName);
265         }
266 
267         return fnd;
268     }
269             
270     /**
271      * Create the static instance of the elements manager.
272      * @return The manager containing elements data.
273      */
274     static private XMLMaterialManager createMaterialElements()
275     {
276         elements = new XMLMaterialManager();
277         elements.loadElementsFromResource();
278         return elements; 
279     }
280     
281     /**
282      * Create the static instance of the material manager.
283      * @return The manager containing default materials data.
284      */
285     static private XMLMaterialManager createMaterials()
286     {
287         materials = new XMLMaterialManager(elements);
288         materials.loadMaterialsFromResource(materialResource);
289         return materials;
290     }
291               
292     /**
293      * The default ctor should not be used.  Any "user" managers
294      * must have a parent and/or a node pointing to a materials section.
295      */
296     private XMLMaterialManager()
297     {}
298           
299     /** 
300      * Ctor for XMLMaterialFactory with given parent and no initial material data.
301      * @param p The parent manager. 
302      **/
303     private XMLMaterialManager(XMLMaterialManager p)
304     {
305         parent = p;
306     }
307    
308     /** 
309      * Set current document source.
310      * @param d The Document with a materials node as its child.
311      **/    
312     private void setCurrentDocument(Document d)
313     {
314         currentDoc = d;
315     }
316 
317     /** 
318      * Check if this object has a parent manager.
319      * @return True if this manager has a parent; false if not. 
320      **/
321     protected boolean hasParentManager()
322     {
323         return (parent != null);
324     }
325 
326     /**
327      * Attempt to find the materials section in a document using two possible cases.
328      * 
329      * 1) The materials section is the root element. 
330      * 2) The materials section is a child of the root element.
331      * 
332      * @param d Document to search for materials element.     
333      */           
334     // FIXME This should be removed.  Callers should have the materials node in hand.
335     private static org.jdom.Element getMaterialsRoot(Document d)
336     {        
337         org.jdom.Element m = null;
338 
339         if (d.getRootElement().getName() == "materials")
340         {
341             m = d.getRootElement();
342         }
343         else
344         {
345             m = d.getRootElement().getChild("materials");
346         }
347 
348         return m;
349     }
350  
351     /**
352      * Find the XML node for the material or element called <code>elemName</code>
353      * in the current Document. 
354      * @param elemName The name attribute value of the node.
355      * @return The XML node with attribute name of <code>elemName</code> or null if not found.
356      * @throws JDOMException
357      */
358     private org.jdom.Element findMaterialElementXML(String elemName)
359     {
360         return findMaterialElementXML(elemName, currentDoc);
361     }
362    
363     /** 
364      * Find a material in the manager's document.
365      * @param matName The name of the material.
366      * @return The node for the material called <code>matName</code> or null if not found. 
367      **/
368     private org.jdom.Element findMaterialXML(String matName)
369     {
370         return findMaterialXML(matName, currentDoc);
371     } 
372      
373     /**
374      * Create a new document from a materials section.
375      * @param e The XML element of the materials section.
376      * @return The document containing the materials section.
377      */
378     private Document cloneMaterialsRoot(org.jdom.Element e)
379     {
380         org.jdom.Element matRoot = new org.jdom.Element("materials");
381         matRoot.setContent(e.cloneContent());
382         Document d = new Document();
383         d.setRootElement(matRoot);
384         setCurrentDocument(d);
385         return currentDoc;
386     }
387 
388     /**
389      * Set the XML element containing the materials section for this manager.
390      * @param e The XML element pointing to a materials section.
391      */
392     public void setMaterialsRoot(org.jdom.Element e)
393     {
394         setCurrentDocument(cloneMaterialsRoot(e));
395     }
396                
397     /**
398      * Load the chemical elements data from the embedded resource file into this manager.
399      */
400     // FIXME: Duplicates some code for creating materials in makeMaterialFromXML().
401     private void loadElementsFromResource()
402     { 
403         SAXBuilder builder = new SAXBuilder();
404         Document doc = null;
405         try
406         {
407             doc = builder.build(XMLMaterialManager.class.getResourceAsStream(elementResource));
408             setCurrentDocument(doc);
409         }
410         catch (JDOMException e)
411         {
412             throw new RuntimeException(e);
413         }
414         catch (IOException e)
415         {
416             throw new RuntimeException(e);
417         }
418 
419         Element root = doc.getRootElement();
420         
421         setMaterialsRoot(root);
422 
423         Map<String, Element> elementNodes = new HashMap();
424 
425         // Loop over materials and elements.
426         for (Object x : root.getChildren())
427         {
428             Element node = (Element)x;
429 
430             // Don't actually build elements but keep references for corresponding materials.
431             if (node.getName() == "element")
432             {
433                 elementNodes.put(node.getAttributeValue("name"), node);
434                 
435                 // Add element to lookup map in this manager.
436                 addMaterialElementRef(node);
437             }
438             // This code block handles the simple case of a material element
439             // with a corresponding material. 
440             if (node.getName() == "material")
441             {
442                 // Add material to lookup map.
443                 addMaterialRef(node);
444 
445                 // FIXME: Rest of method duplicates code in makeMaterialFromXML().
446                 Element comp = node.getChild("composite");
447                 String elemRef = comp.getAttributeValue("ref");
448                 Element elemNode = elementNodes.get(elemRef);
449 
450                 if (elemNode == null)
451                     throw new RuntimeException("Could not find element " + elemRef + " in map.");
452 
453                 String formula = elemNode.getAttributeValue("name");
454                 double Z = Double.valueOf(elemNode.getAttributeValue("Z"));
455                 Element anode = elemNode.getChild("atom");
456                 double A = Double.valueOf(anode.getAttributeValue("value"));
457 
458                 Element radlenNode = node.getChild("RL");
459                 double radlen = Double.valueOf(radlenNode.getAttributeValue("value"));
460                 Element nilNode = node.getChild("NIL");
461                 double nil = Double.valueOf(nilNode.getAttributeValue("value"));
462 
463                 Element densNode = node.getChild("D");
464                 double density = Double.valueOf(densNode.getAttributeValue("value"));
465 
466                 String fullName = node.getAttributeValue("name");
467 
468                 MaterialState state = MaterialState.fromString(node.getAttributeValue("state"));
469  
470                 new org.lcsim.material.Material(fullName, formula, Z, A, density, radlen, nil, state);
471             }
472         }               
473     }  
474     
475     /**
476      * Load materials from an embedded resource in the jar file.
477      * @param resource Resource location string.
478      */
479     private void loadMaterialsFromResource(String resource)
480     {
481         // Build the materials document.
482         SAXBuilder builder = new SAXBuilder();
483         Document doc = null;
484         try
485         {
486             doc = builder.build(XMLMaterialManager.class.getResourceAsStream(resource));
487         }
488         catch (JDOMException e)
489         {
490             throw new RuntimeException(e);
491         }
492         catch (IOException e)
493         {
494             throw new RuntimeException(e);
495         }
496         
497         // Set the root materials element.
498         Element root = doc.getRootElement();
499         setMaterialsRoot(root);
500         
501         // Load the Materials from the materials section.
502         loadMaterialsFromXML(root);
503     }  
504     
505     /**    
506      * Creates lcsim materials from an XML element pointing to a GDML materials section.      
507      * @param materialsRoot The GDML materials section.
508      */
509     private void loadMaterialsFromXML(Element materialsRoot)
510     {          
511         for (Object o : materialsRoot.getChildren("material"))
512         {
513             makeMaterialFromXML((Element)o);
514         }        
515     }
516 
517     /**
518      * Create an lcsim Material from XML.
519      * @param matNode The XML Element pointing to a material node.
520      */
521     private void makeMaterialFromXML(Element matNode)
522     {                
523         // Get the name of the material.
524         String name = matNode.getAttributeValue("name");
525                                         
526         // Check if material with this name already exists.
527         if (mgr.getMaterial(name) != null)
528         {                       
529             // This could be okay, e.g. if a material was moved into default material file
530             // and not removed from compact file.  Print a warning in case something strange is happening.
531             System.out.println("WARNING: Ignoring attempt to add material " + name + " that already exists!");
532             return;
533             //throw new RuntimeException("Attempting to add material " + name + " that already exists!");
534         }
535         
536         // Add material reference in this XMLMatMgr.
537         addMaterialRef(matNode);
538         
539         // Get the density element and convert it.
540         Element densNode = matNode.getChild("D");
541         if (densNode == null)
542         {
543             throw new RuntimeException("Missing required D element in material " + name + ".");
544         }        
545         double density = Double.valueOf(densNode.getAttributeValue("value"));
546         
547         // Get the state element and convert it.
548         MaterialState state = MaterialState.UNKNOWN;
549         if (matNode.getAttribute("state") != null)
550         {
551             String stateStr = matNode.getAttributeValue("state");
552             state = MaterialState.fromString(stateStr);
553         }
554         
555         // Check for either list of mass fractions or composites.
556         List fractions = matNode.getChildren("fraction");
557         List composites = matNode.getChildren("composite");
558         
559         // Reference to new material that will be created.
560         org.lcsim.material.Material mat = null;
561         
562         // Figure out number of components depending on the type of definition.
563         int ncomp = 0;
564         if (fractions.size() > 0 && composites.size() > 0)
565         {
566             throw new RuntimeException("Cannot mix fraction and composite tags in material " + name + ".");
567         }
568         else if (fractions.size() > 0)
569         {
570             ncomp = fractions.size();
571         }
572         else
573         {
574             ncomp = composites.size();
575         }   
576         
577         // Define the new material.
578         mat = new org.lcsim.material.Material(name, ncomp, density, state);
579         
580         // Defined as fractions of material adding to 1.0.
581         if (matNode.getChildren("fraction").size() != 0)
582         {                                                
583             // Add the mass fractions to the Material.
584             for (Object oo : fractions)
585             {
586                 Element fracNode = (Element)oo;
587                 double frac = Double.valueOf(fracNode.getAttributeValue("n"));
588                 String ref = fracNode.getAttributeValue("ref");
589                 MaterialElement elem = mgr.getElement(ref);
590                 if (elem != null)
591                 {
592                     mat.addElement(elem, frac);
593                 }
594                 else
595                 {
596                     org.lcsim.material.Material matFrac = mgr.getMaterial(ref);
597                     if (matFrac == null)
598                     {
599                         throw new RuntimeException("Could not resolve ref to " + ref + ".");
600                     }
601                     mat.addMaterial(matFrac, frac);
602                 }                        
603             }
604         }
605         // Defined as composite by number of atoms of chemical elements.
606         else if (matNode.getChildren("composite").size() != 0)
607         {
608             // Add the composites to the Material.
609             for (Object oo : composites)
610             {
611                 Element compNode = (Element)oo;
612                 int n = Integer.valueOf(compNode.getAttributeValue("n"));
613                 String ref = compNode.getAttributeValue("ref");
614                 MaterialElement elem = mgr.getElement(ref);
615                 if (elem == null)
616                 {
617                     throw new RuntimeException("Could not find referenced element " + ref + ".");
618                 }
619                 mat.addElement(elem, n);
620             }
621         }
622         // The Material is missing required fraction or composite elements.
623         else
624         {
625             throw new RuntimeException("Missing at least one fraction or composite element in material " + name + ".");
626         }
627     }    
628 
629     // !!!!!!!!!!!
630     public void clearMaterialMap()
631     {
632         this.materialMap.clear();
633     }
634 }