Skip to content

Latest commit

 

History

History

jenetics.xml

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

Module: io.jenetics.xml

Javadoc

The io.jenetics.xml module allows to write/read chromosomes and genotypes to/from XML. Since the javax.xml.bind module is marked as deprecated (and for removal), this module will replace the existing JAXB based XML marshalling.

Note
The existing JAXB XML marshalling of the base classes are marked for removal.

The XML marshalling, implemented in this module, is based on the javax.xml.stream.XMLStreamReader/Writer classes and only depends on the java.xml module. This classes are used by the io.jenetics.xml.Reader/Writer classes which can be easily composed to do XML marshalling for arbitrary complex objects.

@FunctionalInterface
public interface Writer<T> {
    public void write(final XMLStreamWriter xml, final T data)
        throws XMLStreamException;
}

The XML Writer is just a functional interface with a single write method. Additional static helper methods allows to build more complex writers.

public abstract class Reader<T> {
    public abstract T read(final XMLStreamReader xml)
        throws XMLStreamException;
}

The XML Reader is more complex, but essentially it just contains the corresponding write method.

Writing XML files

A simple example will show you how to create (compose) a writer class for the IntegerChromosome.

<int-chromosome length="3">
    <min>-2147483648</min>
    <max>2147483647</max>
    <alleles>
        <allele>-1878762439</allele>
        <allele>-957346595</allele>
        <allele>-88668137</allele>
    </alleles>
</int-chromosome>

The snippet above shows the XML structure we want to create with our Writer.

import static io.jenetics.xml.stream.Writer.*;

final Writer<IntegerChromosome> writer =
     elem("int-chromosome",
         attr("length").map(ch -> ch.length()),
         elem("min", Writer.<Integer>text().map(ch -> ch.min())),
         elem("max", Writer.<Integer>text().map(ch -> ch.max())),
         elem("alleles",
             elems("allele",  Writer.<Integer>text())
                 .map(ch -> ch.toSeq().map(g -> g.allele()))
         )
     );

The code above shows the Writer which will create XML files like the given example. As you can see, the needed code mainly reflects the desired XML structure. There is usually no need for implementing the Writer interface yourself. The existing static factory methods elem, attr and elems will return Writer instances, which might itself contain other writers. In this way, it is possible to create arbitrary complex XML writers.

Once you have created a XML Writer for your data object, you can use the XML helper class for creating the needed underlying XMLStreamWriter.

final IntegerChromosome ch = IntegerChromosome.of(
    MIN_VALUE, MAX_VALUE, 3
);
try (OutputStream out = new FileOutputStream("chromosome.xml");
    AutoCloseableXMLStreamWriter xml = XML.writer(out, "\t"))
{
    write(ch, xml);
}

The jenetics.xml module comes with predefined writers for all existing Jenetics data-classes, up to the Genotype class. The Phenotype and the Population class has been left out, because this classes have references to the fitness function, where no general way exists for marshalling this to XML.

As you can see in the following example, the predefined writers are heavily combinable as well.

import io.jenetics.xml.Writers;

final Writer<Genotype<BitGene> bgw =
    Writers.Genotype.writer(Writers.BitChromosome.writer()));

final Writer<Genotype<IntegerGene>> igw =
    Writers.Genotype.writer(Writers.IntegerChromosome.writer()));

final Writer<Genotype<DoubleGene>> dgw =
    Writers.Genotype.writer(Writers.DoubleChromosome.writer()));

A Genotype Writer, can only be created with the writer of the concrete chromosome writer. Otherwise, the genotype writer wouldn’t know how to marshall the chromosomes it contains.

To make it easier to write a list of genotypes, the Writers class contains additional helper methods for XML marshalling of genotypes.

final Path file = Paths.get("population.xml");
final List<Genotype<DoubleGene>> genotypes = ...;
try (OutputStream out = Files.newOutputStream(file)) {
    Writers.write(
        out,
        genotypes,
        Writers.DoubleChromosome.writer()
    );
}

Reading XML files

Reading XML files uses the same concept as writing files. There is an abstract Reader class, which can be easily composed. Additionally, you need to specify a factory function which will create the desired object from the extracted XML data.

A Reader, which will read the XML representation of an IntegerChromosome can be seen in the following code snippet:

import static io.jenetics.xml.stream.Reader.*;

 final Reader<IntegerChromosome> reader =
     elem(
         (Object[] v) -> {
             final int length = (int)v[0];
             final int min = (int)v[1];
             final int max = (int)v[2];
             final List<Integer> alleles = (List<Integer>)v[3];
             assert alleles.size() == length;

             return IntegerChromosome.of(
                 alleles.stream()
                     .map(value -> IntegerGene.of(value, min, max)
                     .toArray(IntegerGene[]::new)
             );
         },
         "int-chromosome",
         attr("length").map(Integer::parseInt),
         elem("min", text().map(Integer::parseInt)),
         elem("max", text().map(Integer::parseInt)),
         elem("alleles",
             elems(elem("allele", text().map(Integer::parseInt)))
         )
     );

To keep the Reader code short and maintainable, you must do some casting in the object creation function. The order of the elements in the Object[] array is the same as in the XML structure-definition part.

As for the writers, the jenetics.xml module contains predefined Readers for all standard data-objects. The XML format of the defined Readers are the same as for the defined Writers. So the readers are able to read the Genotypes written by the Writers.

final Reader<Genotype<BitGene> bgr =
    Readers.Genotype.reader(Readers.BitChromosome.reader()));

final Reader<Genotype<IntegerGene>> igr =
    Writers.Genotype.reader(Readers.IntegerChromosome.reader()));

final Reader<Genotype<DoubleGene>> dgr =
    Readers.Genotype.reader(Readers.DoubleChromosome.reader()));

The following code snippet shows how to read a marshalled population (a list of genotypes) from a file.

final Path file = Paths.get("population.xml");
final List<Genotype<DoubleGene>> genotypes;
try (InputStream in = Files.newInputStream(file)) {
    genotypes = Readers.read(
        in,
        Readers.DoubleChromosome.reader()
    );
}