/*
* 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());
}
}
}