/** * * C O P Y R I G H T N O T I C E * Copyright (c) 2001 by: * * The MicroArray Gene Expression Database group (MGED) * * Rosetta Inpharmatics * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation files * (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * * @author $Author: rhubley $ * @version $Revision: 1.2 $ * */ package org.biomage.tools.generate_classes ; /*--------------------------------------------------------------------------*/ /* Imported classes. */ /*--------------------------------------------------------------------------*/ import java.io.FileWriter; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Vector; import org.biomage.tools.helpers.StringOutputHelpers; import org.w3c.dom.*; /*--------------------------------------------------------------------------*/ /* CreateMageJavaClasses class. */ /*--------------------------------------------------------------------------*/ public class CreateMageClassFileList { /** * * * Description: * Constructor for the CreateMageJavaClasses object. * * * @param * * * @return * An instance of the object. * *

* @exception Exception *

* */ public CreateMageClassFileList() throws Exception { } // added packageList to do ordering of the association lists up front // --mm 05/22/02 protected Vector create( Element xmiElement, Element packageList ) throws Exception { // Setup information needed by all the class files // Grap information on documentation StringOutputHelpers.writeOutput("Obtain the documentation and misc info.", 0); Map id2extInfo = docInformation(xmiElement); // Grap the information on packages StringOutputHelpers.writeOutput("Obtain the package info.", 0); Map id2packages = packageInformation(xmiElement, id2extInfo); // Grap the information on data types StringOutputHelpers.writeOutput("Obtain the data type info.", 0); Map id2dataType = dataTypeInformation(xmiElement); // Grap the information on data types StringOutputHelpers.writeOutput("Obtain the constraint info.", 0); Map id2constraint = constraintInformation(xmiElement); // Now create the classes StringOutputHelpers.writeOutput("Obtain the class info.", 0); Vector createClassFileList = createClassFiles(xmiElement, id2extInfo, id2packages, id2dataType, id2constraint); // Set up a map for fast look up by a class of its base class or package of its classes Map id2classFiles = new HashMap(200); for (int i = 0; i < createClassFileList.size();i++) { id2classFiles.put(((CreateClassFile) createClassFileList.elementAt(i)).getID(), createClassFileList.elementAt(i)); } // Register the subclasses with their base class StringOutputHelpers.writeOutput("Register the subclasses with their base class.", 0); for (int i = 0; i < createClassFileList.size(); i++) { ((CreateFile) createClassFileList.get(i)).registerWithBaseClass(id2classFiles); } // Now create the package classes StringOutputHelpers.writeOutput("Obtain the package class info.", 0); Vector createPackageFileList = createPackageClassFiles(packageList, id2packages, id2extInfo, createClassFileList, id2classFiles); createClassFileList.addAll(createPackageFileList); // Now create the interfaces StringOutputHelpers.writeOutput("Obtain the interface info.", 0); Map id2classElement = classInformation(xmiElement); Map interfaceName2associationEndElement = interfaceInformation(xmiElement); Vector createInterfaceFileVector = createInterfaceFiles( interfaceName2associationEndElement, id2classElement, id2packages); createClassFileList.addAll(createInterfaceFileVector); // Create the class to hold the package classes StringOutputHelpers.writeOutput("Obtain the mage class info.", 0); createClassFileList.addElement(new CreateMAGEFile(createPackageFileList, packageList)); StringOutputHelpers.writeOutput("Done.", 0); return createClassFileList; } /** * Description: * Inner class to hold the extension information from the xml. */ public class ExtensionMechanisms { String tag; String value; ExtensionMechanisms( String tag, String value ) { this.tag = tag; this.value = value; } } /** * Description: * Obtains all the class information and puts it in a map keyed by id. * *

* @param xmiElement: the XMI tree. *

* *

* @return Map from id to class element. *

* */ private static final Map classInformation( Element xmiElement ) { // Create the map. Map id2classElement = new HashMap(256); // Get all class elements. NodeList classElementList = xmiElement.getElementsByTagName( "Foundation.Core.Class"); // Add all the class elements. for (int n = 0; n != classElementList.getLength(); ++n) { Element classElement = (Element)classElementList.item(n); id2classElement.put(classElement.getAttribute("xmi.id"), classElement); } return id2classElement; } /** * Description: * Obtains all the interface information and puts it in a map keyed by * interface name. * * We can construct the interface from the information stored in an * association end element, so that is all that is stored. Since each * association end sharing the same role name is verified for * consistency, we only store the first encountered. * *

* @param xmiElement: the XMI tree. *

* *

* @return Map from interface name to association end element. *

* */ private static final Map interfaceInformation( Element xmiElement ) throws Exception { // Create the map. Map interfaceName2associationEndElement = new HashMap(256); // Get all association elements. NodeList associationElementList = xmiElement.getElementsByTagName( "Foundation.Core.Association"); for (int n = 0; n != associationElementList.getLength(); ++n) { Element associationElement = (Element)associationElementList.item(n); // Get all association end elements. NodeList associationEndElementList = associationElement.getElementsByTagName( "Foundation.Core.AssociationEnd"); // Check that there are two association end elements. if (associationEndElementList.getLength() != 2) { throw new Exception("Association '" + XMIParseHelpers.getElementName(associationElement) + "' doesn't have two association ends."); } for (int nAssociationEndElementIndex = 0; nAssociationEndElementIndex != associationEndElementList.getLength(); ++nAssociationEndElementIndex) { Element associationEndElement = (Element)associationEndElementList.item( nAssociationEndElementIndex); if (!XMIParseHelpers.isAssociationEndNavigable(associationEndElement)) { // We don't care about non-navigable association ends. continue; } String sAssociationEndName = XMIParseHelpers.getElementName(associationEndElement); if (sAssociationEndName.length() == 0) { // We don't care about unnamed association ends. continue; } // At this point, we have decided to process this association // end into an interface. String sInterfaceName = CreateInterfaceFile.getInterfaceName(sAssociationEndName); // First, get some more information about this association end. int nAssociationEndMultiplicityRangeUpper = XMIParseHelpers.getAssociationEndMultiplicityRangeUpper( associationEndElement); String sClassifierID = XMIParseHelpers.getElementIDRef( XMIParseHelpers.getAssociationEndClassifier( associationEndElement)); if (interfaceName2associationEndElement.containsKey( sInterfaceName)) { // This interface has been previously processed, so we need // only ensure that it is consistent. Element previousAssociationEndElement = (Element) interfaceName2associationEndElement.get(sInterfaceName); // Get some more information about the previous association // end. int nPreviousAssociationEndMultiplicityRangeUpper = XMIParseHelpers.getAssociationEndMultiplicityRangeUpper( previousAssociationEndElement); String sPreviousClassifierID = XMIParseHelpers.getElementIDRef( XMIParseHelpers.getAssociationEndClassifier( previousAssociationEndElement)); // Compare current to previous information for consistency. if (nAssociationEndMultiplicityRangeUpper != nPreviousAssociationEndMultiplicityRangeUpper || !sClassifierID.equals(sPreviousClassifierID)) { throw new Exception("Association end '" + XMIParseHelpers.getElementName(associationEndElement) + "' is inconsistent."); } } else { // This interface has not been previously processed, so we // need only store it. interfaceName2associationEndElement.put(sInterfaceName, associationEndElement); } } } return interfaceName2associationEndElement; } /** * Description: * Obtains all the extension mechanism information and puts * it on a map keyed by the model element id. * *

* @param xmiElement: top node beneath which information for * the class can be found *

* *

* @return Map with the ExtensionMechanisms information keyed by id. *

* */ protected Map docInformation( Element xmiElement ) { HashMap id2extInfo = new HashMap(100); // get all the extension elements String extTagName = "Foundation.Extension_Mechanisms.TaggedValue"; NodeList extList = xmiElement.getElementsByTagName(extTagName); for (int i = 0; i < extList.getLength(); i++) { String tag = null; String value = null; String id = null; // NOTE: see below for why code is done this way. This code might be // fragile since I simply looked at the Xerces parser output and matched // what it did with added sanity checking. NodeList children = extList.item(i).getChildNodes(); for (int j = 0; j < children.getLength(); j++) { if (children.item(j).getNodeName().equals(extTagName + ".tag")) { NodeList gkids = children.item(j).getChildNodes(); for (int k = 0; k < gkids.getLength();k++) { if ( Node.TEXT_NODE == gkids.item(k).getNodeType() && 0 < gkids.item(k).getNodeValue().trim().length() ) { tag = gkids.item(k).getNodeValue(); } } } else if (children.item(j).getNodeName().equals(extTagName + ".value")) { NodeList gkids = children.item(j).getChildNodes(); for (int k = 0; k < gkids.getLength();k++) { if ( Node.TEXT_NODE == gkids.item(k).getNodeType() && 0 < gkids.item(k).getNodeValue().trim().length() ) { if (null == value) { value = gkids.item(k).getNodeValue(); } else { value += StringOutputHelpers.NEWLINE + gkids.item(k).getNodeValue(); } } } } else if (children.item(j).getNodeName().equals(extTagName + ".modelElement")) { NodeList gkids = children.item(j).getChildNodes(); for (int k = 0; k < gkids.getLength();k++) { if ( gkids.item(k).getNodeName().equals("Foundation.Core.ModelElement") ) { id = ((Element) gkids.item(k)).getAttribute("xmi.idref"); } } } } /* For this XML I expected the code below to work, but there are text nodes with no values except a line return in the child list (basically as every other one) so I went with the code above persistence transient String tag = children.item(0).getNodeValue(); String value = children.item(1).getNodeValue(); Node modelRef = children.item(2).getFirstChild(); Node ref = children.item(3).getFirstChild(); String id = ((Element) modelRef).getAttribute("idref"); */ if ( null == tag || null == value || null == id ) { StringOutputHelpers.writeOutput("WARNING: Extension Mechanism Tag not matched", 0); } // may have more than one tag/value pair for the model element Vector extensionMechanisms_list = (Vector)id2extInfo.get(id); if ( null == extensionMechanisms_list ) { // not on the map yet so create a vector to associate with the id extensionMechanisms_list = new Vector(); extensionMechanisms_list.addElement(new ExtensionMechanisms(tag,value)); id2extInfo.put(id, extensionMechanisms_list); } else { extensionMechanisms_list.addElement(new ExtensionMechanisms(tag,value)); } } return id2extInfo; } /** * Description: * Inner class to hold the package information from the xml. */ public class PackageInformation { String name; String documentation; Element packageNode; PackageInformation( String name, String documentation, Element packageNode ) { this.name = name; this.documentation = documentation; this.packageNode = packageNode; } } /** * Description: * Obtains the PackageInformation (name and documentation) and puts * it on a map keyed by the model element id. * *

* @param xmiElement: top node beneath which information for * the class can be found * @param id2extInfo: map where documentation for the package can be * found *

* *

* @return Map with the package information keyed by id. *

* */ protected Map packageInformation( Element xmiElement, Map id2extInfo ) { HashMap id2packages = new HashMap(15); // Create a map from the model id to PackageInfo NodeList packages = xmiElement.getElementsByTagName("Model_Management.Package"); String id = null; String name = null; Element packageNode = null; for (int i = 0; i < packages.getLength(); i++) { packageNode = (Element) packages.item(i); id = packageNode.getAttribute("xmi.id"); NodeList childNodes = packageNode.getChildNodes(); NodeList kids = null; for (int j = 0; j < childNodes.getLength(); j++) { if ( childNodes.item(j).getNodeName().trim().equals("Foundation.Core.ModelElement.name") ) { kids = childNodes.item(j).getChildNodes(); break; } } if( null != kids ) { for (int j = 0; j < kids.getLength(); j++) { if ( Node.TEXT_NODE == kids.item(j).getNodeType() && 0 < kids.item(j).getNodeValue().trim().length() ) { name = kids.item(j).getNodeValue(); break; } } } // Look up the documentation for this package String doc = null; Vector extMechs_list = (Vector) id2extInfo.get(id); if (null != extMechs_list) { for (int j = 0; j < extMechs_list.size(); j++) { ExtensionMechanisms extMechs = (ExtensionMechanisms) extMechs_list.elementAt(j); if (extMechs.tag.equals("documentation")) { doc = extMechs.value; } } } id2packages.put(id, new PackageInformation(name, doc, packageNode)); } return id2packages; } /** * Description: * Obtains the data type and puts * it on a map keyed by the model element id. * *

* @param xmiElement: top node beneath which information for * the class can be found *

* *

* @return Map with the data type keyed by id. *

* */ protected Map dataTypeInformation( Element xmiElement ) { HashMap id2dataType = new HashMap(25); // Create a map from the model id to PackageInfo NodeList dataTypes = xmiElement.getElementsByTagName("Foundation.Core.DataType"); for (int i = 0; i < dataTypes.getLength(); i++) { String id = ((Element) dataTypes.item(i)).getAttribute("xmi.id"); NodeList nameNodes = ((Element)dataTypes.item(i)).getElementsByTagName("Foundation.Core.ModelElement.name"); NodeList kids = nameNodes.item(0).getChildNodes(); String name = null; for (int j = 0; j < kids.getLength(); j++) { if ( Node.TEXT_NODE == kids.item(j).getNodeType() && 0 < kids.item(j).getNodeValue().trim().length() ) { name = kids.item(j).getNodeValue(); break; } } id2dataType.put(id, name); } return id2dataType; } /** * Description: * Obtains constraint information and puts * it on a map keyed by the association end element id. * *

* @param xmiElement: top node beneath which information for * the classes can be found *

* *

* @return Map with the strings from the constraint nodes keyed by id. *

* */ protected Map constraintInformation( Element xmiElement ) throws Exception { HashMap id2constraint = new HashMap(25); // Create a map from the association end ids to the constraint expression string NodeList constraints = xmiElement.getElementsByTagName("Foundation.Core.Constraint"); for (int i = 0; i < constraints.getLength(); i++) { String constraint = null; try { constraint = XMIParseHelpers.getConstraintString((Element) constraints.item(i)); } catch ( XMIParseHelpers.IDREFException e ) { // Because the constraint node is both a top-level node with an id, and a nested node in an // association end, this exception is expected continue; } // Many association ends share the same constraint StringOutputHelpers.writeOutput("\tConstraint: " + constraint + " for " + ((Element) constraints.item(i)).getAttribute("xmi.id"), 3); NodeList constraintRefs = XMIParseHelpers.getConstrainedElements((Element) constraints.item(i)); for (int j = 0; j < constraintRefs.getLength(); j++) { id2constraint.put(XMIParseHelpers.getElementIDRef((Element) constraintRefs.item(j)), constraint); } } return id2constraint; } /** * Description: * Inner class to hold the association and which association end is the "owner". * This is really only necessary for self-associations but otherwise there is no * way to distinguish them when being processd by CreateClassFile. */ public class AssociationInformation { Element associationNode; Element thisEndNode; Element otherEndNode; AssociationInformation( Element associationNode, Element thisEndNode, Element otherEndNode ) { this.associationNode = associationNode; this.thisEndNode = thisEndNode; this.otherEndNode = otherEndNode; } } /** * Description: * Allows a chain of associations to be built. */ protected class ExtendedAssociation { String startPackage; String startClass; String association; AssociationInformation nextAss; } /** * Description: * Obtains the association and the current end and places the * information on a map keyed by the model element id. * *

* @param xmiElement: top node beneath which information for * the class can be found *

* *

* @return Map with the associations keyed by id. *

* */ protected Map associationInformation( Element xmiElement ) throws Exception { HashMap id2associations = new HashMap(25); NodeList associationNodes = xmiElement.getElementsByTagName("Foundation.Core.Association"); for (int i = 0; i < associationNodes.getLength(); i++) { NodeList endNodes = ((Element) associationNodes.item(i)).getElementsByTagName("Foundation.Core.AssociationEnd"); if (2 != endNodes.getLength()) { throw new Exception("Unexpected number of end nodes."); } addToAssociationMap(id2associations, (Element) associationNodes.item(i), (Element) endNodes.item(0), (Element) endNodes.item(1)); addToAssociationMap(id2associations, (Element) associationNodes.item(i), (Element) endNodes.item(1), (Element) endNodes.item(0)); } return id2associations; } /** * Description: * Obtains the association and the current end and places the * information on a map keyed by the model element id. * *

* @param xmiElement: top node beneath which information for * the class can be found *

* *

* @return Map with the associations keyed by id. *

* */ protected void addToAssociationMap( Map map, Element associationNode, Element thisEndNode, Element otherEndNode ) throws Exception { NodeList endNodeRefID = thisEndNode.getElementsByTagName("Foundation.Core.Classifier"); String id = ((Element) endNodeRefID.item(0)).getAttribute("xmi.idref"); Vector associations = (Vector) map.get(id); AssociationInformation info = new AssociationInformation(associationNode, thisEndNode, otherEndNode); if ( null == associations ) { // not on the map yet so create a vector to associate with the id associations = new Vector(); associations.addElement(info); map.put(id, associations); } else { associations.addElement(info); } } /** * Description: * For each class in the XMI, will create a CreateClassFile object * and place it on the return Vector. * *

* @param xmiElement: top node beneath which information for * the class can be found. * @param id2extInfo: map where documentation for the class can be * found. * @param id2packages: map where the package for the class can be * found. * @param id2datatype: map where the datatype information can be found * by the datatype ID. * @param id2constraint: map where the constrait information can be * found by association end ID. *

* *

* @return Vector with the ClassFile information. *

* */ protected Vector createClassFiles( Element xmiElement, Map id2extInfo, Map id2packages, Map id2dataType, Map id2constraint ) throws Exception { NodeList classes = xmiElement.getElementsByTagName("Foundation.Core.Class"); // Create a map for all the ids to classes Map id2classes = new HashMap(200); for (int i = 0; i < classes.getLength(); i++) { id2classes.put(((Element) classes.item(i)).getAttribute("xmi.id"), classes.item(i)); } // Create a map for each class that maps to associations it is an end for Map id2associations = associationInformation(xmiElement); // For the CPP generation, it is necessary to know if the class is identifiable. // enumerateIdentifable adds information to the DOM nodes about each class even subclasses. // That information written into the Classes and associations for later use in Generation // of the CPP code. Vector tobeClassified = new Vector(); for ( int i = 0; i < classes.getLength(); i++ ) { Element tobeIdentified = (Element) classes.item(i); tobeClassified.addElement(tobeIdentified); } enumerateIdentifiable( tobeClassified, id2classes ); Vector classFiles = new Vector(); for (int i = 0; i < classes.getLength(); i++) { classFiles.addElement(new CreateClassFile(xmiElement, (Element) classes.item(i), id2classes, id2extInfo, id2packages, id2dataType, id2associations, id2constraint)); } return classFiles; } /** * Description: * This is a recursive function to determine abolutely whether or not an Object is * identifiable. * *

* @param theList -- list of classes still to be classified *

* @param id2classes - the list of classes needed to identify the parent class. */ protected void enumerateIdentifiable( Vector theList, Map id2classes) throws Exception { Vector tobeClassified = new Vector(); for ( int i = 0; i < theList.size(); i++ ) { Element tobeIdentified = (Element) theList.elementAt(i); String className = XMIParseHelpers.getElementName(tobeIdentified); if ( className.equals("Identifiable" ) ) { XMIParseHelpers.setIdentifiable(tobeIdentified, true); } else { // Obtain base class information for this class (will always have one unless it's Extendable) NodeList baseClass = tobeIdentified.getElementsByTagName("Foundation.Core.Generalization.parent"); NodeList baseClassID = (null == baseClass || null == baseClass.item(0)? null : ((Element) baseClass.item(0)).getElementsByTagName("Foundation.Core.GeneralizableElement")); if (null != baseClassID && null != baseClassID.item(0)) { String idelement = ((Element) baseClassID.item(0)).getAttribute("xmi.idref"); Element baseElement = (Element) id2classes.get(idelement); String baseClassName = XMIParseHelpers.getElementName(baseElement); String isIdable = baseElement.getAttribute(XMIParseHelpers.tobeIdentifiable); if ( isIdable != null && !isIdable.equals("") ) { XMIParseHelpers.setIdentifiable( tobeIdentified, isIdable ); } else { if ( baseClassName.equals("Identifiable") ) { XMIParseHelpers.setIdentifiable(tobeIdentified, true); } else { tobeClassified.addElement( tobeIdentified ); } } } else { XMIParseHelpers.setIdentifiable(tobeIdentified, false); } } } if ( tobeClassified.size() > 0 ) { enumerateIdentifiable( tobeClassified, id2classes ); } } /** * Description: * For each interface name, create a CreateInterfaceFile object. * *

* @param interfaceName2associationEndElement: map from interface name * to association end element. * * @param id2classElement: map from ID to class element. * * @param id2packageInformation: the map from ID to package information. *

* *

* @return Vector with the CreateInterfaceFile objects. *

* */ protected Vector createInterfaceFiles( Map interfaceName2associationEndElement, Map id2classElement, Map id2packageInformation ) throws Exception { Vector createInterfaceFileVector = new Vector(); Iterator isInterfaceName = interfaceName2associationEndElement.keySet().iterator(); while (isInterfaceName.hasNext()) { String sInterfaceName = (String)isInterfaceName.next(); createInterfaceFileVector.add(new CreateInterfaceFile( sInterfaceName, (Element)interfaceName2associationEndElement.get( sInterfaceName), id2classElement, id2packageInformation)); } return createInterfaceFileVector; } // added packageList to do ordering of the association lists up front // --mm 05/22/02 /** * Description: * For each class in the XMI, will create a CreateClassFile object * and place it on the return Vector. * *

* @param packageList: information on how to order the independent classes * in the packages. * @param id2packages: map where the package for the class can be * found. * @param id2extInfo: map where documentation for the class can be * found. * @param classFiles: list of the class file objects * @param id2classFiles: map of classFiles to provide information needed * by the package *

* *

* @return Vector with the ClassFile information. *

* */ protected Vector createPackageClassFiles( Element packageList, Map id2packages, Map id2extInfo, Vector classFiles, Map id2classFiles ) throws Exception { Vector packageFiles = new Vector(); NodeList packages = packageList.getElementsByTagName("package"); Map name2package = new HashMap(); for (int i = 0; i < packages.getLength(); i++) { Element curPackage = (Element) packages.item(i); name2package.put(curPackage.getAttribute("name"), curPackage); } String keys[] = (String[]) id2packages.keySet().toArray((String[]) new String[id2packages.size()]); for (int i = 0; i < keys.length;i++) { Element packageNode = ((PackageInformation) id2packages.get(keys[i])).packageNode; Element packageOrder = (Element) name2package.get(XMIParseHelpers.getElementName(packageNode)); packageFiles.add(new CreatePackageFile(packageNode,packageOrder,id2classFiles,id2extInfo)); } return packageFiles; } }