/* * This file is part of JGAP. * * JGAP offers a dual license model containing the LGPL as well as the MPL. * * For licensing information please see the file license.txt included with JGAP * or have a look at the top of class org.jgap.Chromosome which representatively * includes the JGAP license policy applicable for any file delivered with JGAP. */ package org.jgap.xml; import java.io.*; import java.lang.reflect.*; import java.util.*; import javax.xml.parsers.*; import javax.xml.transform.*; import javax.xml.transform.dom.*; import javax.xml.transform.stream.*; import junitx.util.PrivateAccessor; import org.jgap.*; import org.w3c.dom.*; /** * The XMLManager performs marshalling of genetic entity instances * (such as Chromosomes and Genotypes) to XML representations of those * entities, as well as unmarshalling. All of the methods in this class are * static, so no construction is required (or allowed). * * @author Neil Rotstan * @author Klaus Meffert * @since 1.0 */ public class XMLManager { /** String containing the CVS revision. Read out via reflection!*/ private final static String CVS_REVISION = "$Revision: 1.21 $"; /** * Constant representing the name of the genotype XML element tag. */ private static final String GENOTYPE_TAG = "genotype"; /** * Constant representing the name of the chromosome XML element tag. */ private static final String CHROMOSOME_TAG = "chromosome"; /** * Constant representing the name of the gene XML element tag. */ private static final String GENES_TAG = "genes"; /** * Constant representing the name of the gene XML element tag. */ private static final String GENE_TAG = "gene"; private static final String ALLELE_TAG = "allele"; /** * Constant representing the name of the size XML attribute that is * added to genotype and chromosome elements to describe their size. */ private static final String SIZE_ATTRIBUTE = "size"; /** * Constant representing the fully-qualified name of the concrete * Gene class that was marshalled. */ private static final String CLASS_ATTRIBUTE = "class"; /** * Shared DocumentBuilder, which is used to create new DOM Document * instances. */ private static final DocumentBuilder m_documentCreator; /** * Shared lock object used for synchronization purposes. */ private static final Object m_lock = new Object(); /** * @author Neil Rotstan * @since 1.0 */ static { try { m_documentCreator = DocumentBuilderFactory.newInstance().newDocumentBuilder(); } catch (ParserConfigurationException parserError) { throw new RuntimeException( "XMLManager: Unable to setup DocumentBuilder: " + parserError.getMessage()); } } /** * Private constructor. All methods in this class are static, so no * construction is allowed. */ private XMLManager() { } /** * Marshall a Chromosome instance to an XML Document representation, * including its contained Gene instances. * * @param a_subject the chromosome to represent as an XML document * @return a Document object representing the given Chromosome * * @author Neil Rotstan * @since 1.0 * @deprecated use XMLDocumentBuilder instead */ public static Document representChromosomeAsDocument(final IChromosome a_subject) { // DocumentBuilders do not have to be thread safe, so we have to // protect creation of the Document with a synchronized block. // ------------------------------------------------------------- Document chromosomeDocument; synchronized (m_lock) { chromosomeDocument = m_documentCreator.newDocument(); } Element chromosomeElement = representChromosomeAsElement(a_subject, chromosomeDocument); chromosomeDocument.appendChild(chromosomeElement); return chromosomeDocument; } /** * Marshall a Genotype to an XML Document representation, including its * population of Chromosome instances. * * @param a_subject the genotype to represent as an XML document * @return a Document object representing the given Genotype * * @author Neil Rotstan * @since 1.0 * @deprecated use XMLDocumentBuilder instead */ public static Document representGenotypeAsDocument(final Genotype a_subject) { // DocumentBuilders do not have to be thread safe, so we have to // protect creation of the Document with a synchronized block. // ------------------------------------------------------------- Document genotypeDocument; synchronized (m_lock) { genotypeDocument = m_documentCreator.newDocument(); } Element genotypeElement = representGenotypeAsElement(a_subject, genotypeDocument); genotypeDocument.appendChild(genotypeElement); return genotypeDocument; } /** * Marshall an array of Genes to an XML Element representation. * * @param a_geneValues the genes to represent as an XML element * @param a_xmlDocument a Document instance that will be used to create * the Element instance. Note that the element will NOT be added to the * document by this method * @return an Element object representing the given genes * * @author Neil Rotstan * @author Klaus Meffert * @since 1.0 * @deprecated use XMLDocumentBuilder instead */ public static Element representGenesAsElement(final Gene[] a_geneValues, final Document a_xmlDocument) { // Create the parent genes element. // -------------------------------- Element genesElement = a_xmlDocument.createElement(GENES_TAG); // Now add gene sub-elements for each gene in the given array. // ----------------------------------------------------------- Element geneElement; for (int i = 0; i < a_geneValues.length; i++) { // Create the allele element for this gene. // ---------------------------------------- geneElement = a_xmlDocument.createElement(GENE_TAG); // Add the class attribute and set its value to the class // name of the concrete class representing the current Gene. // --------------------------------------------------------- geneElement.setAttribute(CLASS_ATTRIBUTE, a_geneValues[i].getClass().getName()); // Create a text node to contain the string representation of // the gene's value (allele). // ---------------------------------------------------------- Element alleleRepresentation = representAlleleAsElement(a_geneValues[i], a_xmlDocument); // And now add the text node to the gene element, and then // add the gene element to the genes element. // ------------------------------------------------------- geneElement.appendChild(alleleRepresentation); genesElement.appendChild(geneElement); } return genesElement; } /** * * @param a_gene Gene * @param a_xmlDocument Document * @return Element * * @author Klaus Meffert * @since 2.0 */ private static Element representAlleleAsElement(final Gene a_gene, final Document a_xmlDocument) { Element alleleElement = a_xmlDocument.createElement(ALLELE_TAG); alleleElement.setAttribute("class", a_gene.getClass().getName()); alleleElement.setAttribute("value", a_gene.getPersistentRepresentation()); return alleleElement; } /** * Marshall a Chromosome instance to an XML Element representation, * including its contained Genes as sub-elements. This may be useful in * scenarios where representation as an entire Document is undesirable, * such as when the representation of this Chromosome is to be combined * with other elements in a single Document. * * @param a_subject the chromosome to represent as an XML element * @param a_xmlDocument a Document instance that will be used to create * the Element instance. Note that the element will NOT be added to the * document by this method * @return an Element object representing the given Chromosome * * @author Neil Rotstan * @since 1.0 * @deprecated use XMLDocumentBuilder instead */ public static Element representChromosomeAsElement(final IChromosome a_subject, final Document a_xmlDocument) { // Start by creating an element for the chromosome and its size // attribute, which represents the number of genes in the chromosome. // ------------------------------------------------------------------ Element chromosomeElement = a_xmlDocument.createElement(CHROMOSOME_TAG); chromosomeElement.setAttribute(SIZE_ATTRIBUTE, Integer.toString(a_subject.size())); // Next create the genes element with its nested gene elements, // which will contain string representations of the alleles. // -------------------------------------------------------------- Element genesElement = representGenesAsElement(a_subject.getGenes(), a_xmlDocument); // Add the new genes element to the chromosome element and then // return the chromosome element. // ------------------------------------------------------------- chromosomeElement.appendChild(genesElement); return chromosomeElement; } /** * Marshall a Genotype instance into an XML Element representation, * including its population of Chromosome instances as sub-elements. * This may be useful in scenarios where representation as an * entire Document is undesirable, such as when the representation * of this Genotype is to be combined with other elements in a * single Document. * * @param a_subject the genotype to represent as an XML element * @param a_xmlDocument a Document instance that will be used to create * the Element instance. Note that the element will NOT be added to the * document by this method * @return an Element object representing the given Genotype * * @author Neil Rotstan * @since 1.0 * @deprecated use XMLDocumentBuilder instead */ public static Element representGenotypeAsElement(final Genotype a_subject, final Document a_xmlDocument) { Population population = a_subject.getPopulation(); // Start by creating the genotype element and its size attribute, // which represents the number of chromosomes present in the // genotype. // -------------------------------------------------------------- Element genotypeTag = a_xmlDocument.createElement(GENOTYPE_TAG); genotypeTag.setAttribute(SIZE_ATTRIBUTE, Integer.toString(population.size())); // Next, add nested elements for each of the chromosomes in the // genotype. // ------------------------------------------------------------ for (int i = 0; i < population.size(); i++) { Element chromosomeElement = representChromosomeAsElement(population.getChromosome(i), a_xmlDocument); genotypeTag.appendChild(chromosomeElement); } return genotypeTag; } /** * Unmarshall a Chromosome instance from a given XML Element * representation. * * @param a_activeConfiguration current Configuration object * @param a_xmlElement the XML Element representation of the Chromosome * @return a new Chromosome instance setup with the data from the XML Element * representation * * @throws ImproperXMLException if the given Element is improperly * structured or missing data * @throws UnsupportedRepresentationException if the actively configured * Gene implementation does not support the string representation of the * alleles used in the given XML document * @throws GeneCreationException if there is a problem creating or populating * a Gene instance * * @author Neil Rotstan * @since 1.0 */ public static Gene[] getGenesFromElement( Configuration a_activeConfiguration, Element a_xmlElement) throws ImproperXMLException, UnsupportedRepresentationException, GeneCreationException { // Do some sanity checking. Make sure the XML Element isn't null and // that it in fact represents a set of genes. // ----------------------------------------------------------------- if (a_xmlElement == null || ! (a_xmlElement.getTagName().equals(GENES_TAG))) { throw new ImproperXMLException( "Unable to build Chromosome instance from XML Element: " + "given Element is not a 'genes' element."); } List genes = Collections.synchronizedList(new ArrayList()); // Extract the nested gene elements. // --------------------------------- NodeList geneElements = a_xmlElement.getElementsByTagName(GENE_TAG); if (geneElements == null) { throw new ImproperXMLException( "Unable to build Gene instances from XML Element: " + "'" + GENE_TAG + "'" + " sub-elements not found."); } // For each gene, get the class attribute so we know what class // to instantiate to represent the gene instance, and then find // the child text node, which is where the string representation // of the allele is located, and extract the representation. // ------------------------------------------------------------- int numberOfGeneNodes = geneElements.getLength(); for (int i = 0; i < numberOfGeneNodes; i++) { Element thisGeneElement = (Element) geneElements.item(i); thisGeneElement.normalize(); // Fetch the class attribute and create an instance of that // class to represent the current gene. // -------------------------------------------------------- String geneClassName = thisGeneElement.getAttribute(CLASS_ATTRIBUTE); Gene thisGeneObject; Class geneClass = null; try { geneClass = Class.forName(geneClassName); try { Constructor constr = geneClass.getConstructor(new Class[] { Configuration.class}); thisGeneObject = (Gene) constr.newInstance(new Object[] { a_activeConfiguration}); } catch (NoSuchMethodException nsme) { // Try it by calling method newGeneInternal. // ----------------------------------------- Constructor constr = geneClass.getConstructor(new Class[] {}); thisGeneObject = (Gene) constr.newInstance(new Object[] {}); thisGeneObject = (Gene) PrivateAccessor.invoke(thisGeneObject, "newGeneInternal", new Class[] {}, new Object[] {}); } } catch (Throwable e) { throw new GeneCreationException(geneClass, e); } // Find the text node and fetch the string representation of // the allele. // --------------------------------------------------------- NodeList children = thisGeneElement.getChildNodes(); int childrenSize = children.getLength(); String alleleRepresentation = null; for (int j = 0; j < childrenSize; j++) { Element alleleElem = (Element) children.item(j); if (alleleElem.getTagName().equals(ALLELE_TAG)) { alleleRepresentation = alleleElem.getAttribute("value"); } if (children.item(j).getNodeType() == Node.TEXT_NODE) { // We found the text node. Extract the representation. // --------------------------------------------------- alleleRepresentation = children.item(j).getNodeValue(); break; } } // Sanity check: Make sure the representation isn't null. // ------------------------------------------------------ if (alleleRepresentation == null) { throw new ImproperXMLException( "Unable to build Gene instance from XML Element: " + "value (allele) is missing representation."); } // Now set the value of the gene to that reflect the // string representation. // ------------------------------------------------- try { thisGeneObject.setValueFromPersistentRepresentation( alleleRepresentation); } catch (UnsupportedOperationException e) { throw new GeneCreationException( "Unable to build Gene because it does not support the " + "setValueFromPersistentRepresentation() method."); } // Finally, add the current gene object to the list of genes. // ---------------------------------------------------------- genes.add(thisGeneObject); } return (Gene[]) genes.toArray(new Gene[genes.size()]); } /** * Unmarshall a Chromosome instance from a given XML Element * representation. * * @param a_activeConfiguration the current active Configuration object * that is to be used during construction of the Chromosome * @param a_xmlElement the XML Element representation of the Chromosome * @return a new Chromosome instance setup with the data from the XML * Element representation * * @throws ImproperXMLException if the given Element is improperly * structured or missing data * @throws InvalidConfigurationException if the given Configuration is in * an inconsistent state * @throws UnsupportedRepresentationException if the actively configured * Gene implementation does not support the string representation of the * alleles used in the given XML document * @throws GeneCreationException if there is a problem creating or populating * a Gene instance * * @author Neil Rotstan * @since 1.0 */ public static Chromosome getChromosomeFromElement( Configuration a_activeConfiguration, Element a_xmlElement) throws ImproperXMLException, InvalidConfigurationException, UnsupportedRepresentationException, GeneCreationException { // Do some sanity checking. Make sure the XML Element isn't null and // that in fact represents a chromosome. // ----------------------------------------------------------------- if (a_xmlElement == null || ! (a_xmlElement.getTagName().equals(CHROMOSOME_TAG))) { throw new ImproperXMLException( "Unable to build Chromosome instance from XML Element: " + "given Element is not a 'chromosome' element."); } // Extract the nested genes element and make sure it exists. // --------------------------------------------------------- Element genesElement = (Element) a_xmlElement.getElementsByTagName(GENES_TAG).item(0); if (genesElement == null) { throw new ImproperXMLException( "Unable to build Chromosome instance from XML Element: " + "'genes' sub-element not found."); } // Construct the genes from their representations. // ----------------------------------------------- Gene[] geneAlleles = getGenesFromElement(a_activeConfiguration, genesElement); // Construct the new Chromosome with the genes and return it. // ---------------------------------------------------------- return new Chromosome(a_activeConfiguration, geneAlleles); } /** * Unmarshall a Genotype instance from a given XML Element representation. * Its population of Chromosomes will be unmarshalled from the Chromosome * sub-elements. * * @param a_activeConfiguration the current active Configuration object * that is to be used during construction of the Genotype and Chromosome * instances * @param a_xmlElement the XML Element representation of the Genotype * @return a new Genotype instance, complete with a population of Chromosomes, * setup with the data from the XML Element representation * * @throws ImproperXMLException if the given Element is improperly structured * or missing data * @throws InvalidConfigurationException if the given Configuration is in an * inconsistent state * @throws UnsupportedRepresentationException if the actively configured * Gene implementation does not support the string representation of the * alleles used in the given XML document * @throws GeneCreationException if there is a problem creating or populating * a Gene instance * * @author Neil Rotstan * @author Klaus Meffert * @since 1.0 */ public static Genotype getGenotypeFromElement( Configuration a_activeConfiguration, Element a_xmlElement) throws ImproperXMLException, InvalidConfigurationException, UnsupportedRepresentationException, GeneCreationException { // Sanity check. Make sure the XML element isn't null and that it // actually represents a genotype. if (a_xmlElement == null || ! (a_xmlElement.getTagName().equals(GENOTYPE_TAG))) { throw new ImproperXMLException( "Unable to build Genotype instance from XML Element: " + "given Element is not a 'genotype' element."); } // Fetch all of the nested chromosome elements and convert them // into Chromosome instances. // ------------------------------------------------------------ NodeList chromosomes = a_xmlElement.getElementsByTagName(CHROMOSOME_TAG); int numChromosomes = chromosomes.getLength(); Population population = new Population(a_activeConfiguration, numChromosomes); for (int i = 0; i < numChromosomes; i++) { population.addChromosome(getChromosomeFromElement(a_activeConfiguration, (Element) chromosomes.item(i))); } // Construct a new Genotype with the chromosomes and return it. // ------------------------------------------------------------ return new Genotype(a_activeConfiguration, population); } /** * Unmarshall a Genotype instance from a given XML Document representation. * Its population of Chromosomes will be unmarshalled from the Chromosome * sub-elements. * * @param a_activeConfiguration the current active Configuration object that * is to be used during construction of the Genotype and Chromosome instances * @param a_xmlDocument the XML Document representation of the Genotype * * @return A new Genotype instance, complete with a population of Chromosomes, * setup with the data from the XML Document representation * * @throws ImproperXMLException if the given Document is improperly structured * or missing data * @throws InvalidConfigurationException if the given Configuration is in an * inconsistent state * @throws UnsupportedRepresentationException if the actively configured Gene * implementation does not support the string representation of the alleles * used in the given XML document * @throws GeneCreationException if there is a problem creating or populating * a Gene instance * * @author Neil Rotstan * @since 1.0 */ public static Genotype getGenotypeFromDocument( Configuration a_activeConfiguration, Document a_xmlDocument) throws ImproperXMLException, InvalidConfigurationException, UnsupportedRepresentationException, GeneCreationException { // Extract the root element, which should be a genotype element. // After verifying that the root element is not null and that it // in fact is a genotype element, then convert it into a Genotype // instance. // -------------------------------------------------------------- Element rootElement = a_xmlDocument.getDocumentElement(); if (rootElement == null || ! (rootElement.getTagName().equals(GENOTYPE_TAG))) { throw new ImproperXMLException( "Unable to build Genotype from XML Document: " + "'genotype' element must be at root of document."); } return getGenotypeFromElement(a_activeConfiguration, rootElement); } /** * Unmarshall a Chromosome instance from a given XML Document * representation. Its genes will be unmarshalled from the gene * sub-elements. * * @param a_activeConfiguration the current active Configuration object that * is to be used during construction of the Chromosome instances * @param a_xmlDocument the XML Document representation of the Chromosome * * @return a new Chromosome instance setup with the data from the XML Document * representation * * @throws ImproperXMLException if the given Document is improperly structured * or missing data * @throws InvalidConfigurationException if the given Configuration is in an * inconsistent state * @throws UnsupportedRepresentationException if the actively configured Gene * implementation does not support the string representation of the alleles * used in the given XML document * @throws GeneCreationException if there is a problem creating or populating * a Gene instance * * @author Neil Rotstan * @since 1.0 */ public static Chromosome getChromosomeFromDocument(Configuration a_activeConfiguration, Document a_xmlDocument) throws ImproperXMLException, InvalidConfigurationException, UnsupportedRepresentationException, GeneCreationException { // Extract the root element, which should be a chromosome element. // After verifying that the root element is not null and that it // in fact is a chromosome element, then convert it into a Chromosome // instance. // ------------------------------------------------------------------ Element rootElement = a_xmlDocument.getDocumentElement(); if (rootElement == null || ! (rootElement.getTagName().equals(CHROMOSOME_TAG))) { throw new ImproperXMLException( "Unable to build Chromosome instance from XML Document: " + "'chromosome' element must be at root of Document."); } return getChromosomeFromElement(a_activeConfiguration, rootElement); } /** * Reads in an XML file and returns a Document object. * * @param file the file to be read in * @throws IOException * @throws SAXException * @return Document * * @author Klaus Meffert * @since 2.0 */ public static Document readFile(File file) throws IOException, org.xml.sax.SAXException { return m_documentCreator.parse(file); } /** * Writes an XML file from a Document object. * * @param doc the Document object to be written to file * @param file the file to be written * @throws IOException * * @author Klaus Meffert * @since 2.0 */ public static void writeFile(Document doc, File file) throws IOException { // Use a Transformer for output TransformerFactory tFactory = TransformerFactory.newInstance(); Transformer transformer; try { transformer = tFactory.newTransformer(); } catch (TransformerConfigurationException tex) { throw new IOException(tex.getMessage()); } DOMSource source = new DOMSource(doc); FileOutputStream fos = new FileOutputStream(file); StreamResult result = new StreamResult(fos); try { transformer.transform(source, result); fos.close(); } catch (TransformerException tex) { throw new IOException(tex.getMessage()); } } }