/*
* Eoulsan development code
*
* This code may be freely distributed and modified under the
* terms of the GNU Lesser General Public License version 2.1 or
* later and CeCILL-C. This should be distributed with the code.
* If you do not have a copy, see:
*
* http://www.gnu.org/licenses/lgpl-2.1.txt
* http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.txt
*
* Copyright for this code is held jointly by the Genomic platform
* of the Institut de Biologie de l'École normale supérieure and
* the individual authors. These should be listed in @author doc
* comments.
*
* For more information on the Eoulsan project and its aims,
* or to join the Eoulsan Google group, visit the home page
* at:
*
* http://outils.genomique.biologie.ens.fr/eoulsan
*
*/
package fr.ens.biologie.genomique.eoulsan.data;
import static fr.ens.biologie.genomique.eoulsan.EoulsanLogger.getLogger;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import fr.ens.biologie.genomique.eoulsan.EoulsanException;
import fr.ens.biologie.genomique.eoulsan.checkers.Checker;
import fr.ens.biologie.genomique.eoulsan.core.Parameter;
import fr.ens.biologie.genomique.eoulsan.core.FileNaming;
import fr.ens.biologie.genomique.eoulsan.core.Module;
import fr.ens.biologie.genomique.eoulsan.splitermergers.Merger;
import fr.ens.biologie.genomique.eoulsan.splitermergers.Splitter;
import fr.ens.biologie.genomique.eoulsan.util.XMLUtils;
/**
* This class define a DataFormat from an XML file.
* @since 1.2
* @author Laurent Jourdren
*/
public final class XMLDataFormat extends AbstractDataFormat
implements Serializable {
private static final long serialVersionUID = -6926659317643003910L;
private static final String DEFAULT_CONTENT_TYPE = "text/plain";
private static final int DEFAULT_MAX_FILES_COUNT = 1;
private String name;
private String description;
private String alias;
private String prefix;
private boolean oneFilePerAnalysis;
private boolean dataFormatFromDesignFile;
private String designMetadataKeyName;
private String sampleMetadataKeyName;
private String contentType = "text/plain";
private final List<String> extensions = new ArrayList<>();
private final List<String> galaxyToolExtensions = new ArrayList<>();
private String generatorClassName;
private final Set<Parameter> generatorParameters = new LinkedHashSet<>();
private String checkerClassName;
private String splitterClassName;
private String mergerClassName;
private int maxFilesCount;
@Override
public String getName() {
return this.name;
}
@Override
public String getAlias() {
return this.alias;
}
@Override
public String getPrefix() {
return this.prefix;
}
@Override
public boolean isOneFilePerAnalysis() {
return this.oneFilePerAnalysis;
}
@Override
public boolean isDataFormatFromDesignFile() {
return this.dataFormatFromDesignFile;
}
@Override
public String getDesignMetadataKeyName() {
return this.designMetadataKeyName;
}
@Override
public String getSampleMetadataKeyName() {
return this.sampleMetadataKeyName;
}
@Override
public String getDefaultExtension() {
return this.extensions.get(0);
}
@Override
public List<String> getExtensions() {
return this.extensions;
}
@Override
public List<String> getGalaxyToolExtensions() {
return this.galaxyToolExtensions;
}
@Override
public boolean isGenerator() {
return this.generatorClassName != null;
}
@Override
public boolean isChecker() {
return this.checkerClassName != null;
}
@Override
public boolean isSplitter() {
return this.splitterClassName != null;
}
@Override
public boolean isMerger() {
return this.mergerClassName != null;
}
@Override
public Module getGenerator() {
final Module generator =
(Module) loadClass(this.generatorClassName, Module.class);
if (generator == null) {
return null;
}
try {
generator.configure(null, this.generatorParameters);
return generator;
} catch (EoulsanException e) {
getLogger().severe("Cannot create generator: " + e.getMessage());
return null;
}
}
@Override
public Checker getChecker() {
return (Checker) loadClass(this.checkerClassName, Checker.class);
}
@Override
public Splitter getSplitter() {
return (Splitter) loadClass(this.splitterClassName, Splitter.class);
}
@Override
public Merger getMerger() {
return (Merger) loadClass(this.mergerClassName, Merger.class);
}
@Override
public String getContentType() {
return this.contentType;
}
@Override
public int getMaxFilesCount() {
return this.maxFilesCount;
}
//
// Other methods
//
private Object loadClass(final String className, final Class<?> interf) {
if (className == null) {
return null;
}
try {
final Class<?> result =
this.getClass().getClassLoader().loadClass(className);
if (result == null) {
return null;
}
final Object o = result.newInstance();
if (interf.isInstance(o)) {
return result.newInstance();
}
return null;
} catch (ClassNotFoundException e) {
return null;
} catch (InstantiationException e) {
return null;
} catch (IllegalAccessException e) {
return null;
}
}
//
// Parsing methods
//
private void parse(final InputStream is) throws ParserConfigurationException,
SAXException, IOException, EoulsanException {
final Document doc;
final DocumentBuilderFactory dbFactory =
DocumentBuilderFactory.newInstance();
final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
doc = dBuilder.parse(is);
doc.getDocumentElement().normalize();
parse(doc);
is.close();
}
private void parse(final Document document) throws EoulsanException {
for (Element e : XMLUtils.getElementsByTagName(document, "dataformat")) {
this.name = XMLUtils.getTagValue(e, "name");
this.description = XMLUtils.getTagValue(e, "description");
this.alias = XMLUtils.getTagValue(e, "alias");
this.prefix = XMLUtils.getTagValue(e, "prefix");
this.oneFilePerAnalysis =
Boolean.parseBoolean(XMLUtils.getTagValue(e, "onefileperanalysis"));
this.designMetadataKeyName = XMLUtils.getTagValue(e, "designmetadatakey");
this.sampleMetadataKeyName = XMLUtils.getTagValue(e, "samplemetadatakey");
this.contentType = XMLUtils.getTagValue(e, "content-type");
this.generatorClassName = XMLUtils.getTagValue(e, "generator");
this.checkerClassName = XMLUtils.getTagValue(e, "checker");
this.splitterClassName = XMLUtils.getTagValue(e, "splitter");
this.mergerClassName = XMLUtils.getTagValue(e, "merger");
if (this.designMetadataKeyName != null
|| this.sampleMetadataKeyName != null) {
this.dataFormatFromDesignFile = true;
}
if (this.designMetadataKeyName != null
&& this.sampleMetadataKeyName != null) {
throw new EoulsanException(
"A DataFormat cannot be provided by a design "
+ "metadata entry and a sample metadata entry.");
}
// Get the parameters of the generator step
for (Element generatorElement : XMLUtils.getElementsByTagName(e,
"generator")) {
final List<String> attributeNames =
XMLUtils.getAttributeNames(generatorElement);
for (String attributeName : attributeNames) {
this.generatorParameters.add(new Parameter(attributeName,
generatorElement.getAttribute(attributeName)));
}
}
// Parse max files count
final String maxFiles = XMLUtils.getTagValue(e, "maxfilescount");
try {
if (maxFiles == null) {
this.maxFilesCount = DEFAULT_MAX_FILES_COUNT;
} else {
this.maxFilesCount = Integer.parseInt(maxFiles);
}
} catch (NumberFormatException exp) {
throw new EoulsanException(
"Invalid maximal files count for data format "
+ this.name + ": " + maxFiles,
exp);
}
// Parse extensions
for (Element e2 : XMLUtils.getElementsByTagName(document, "extensions")) {
for (Element e3 : XMLUtils.getElementsByTagName(e2, "extension")) {
final String defaultAttribute = e3.getAttribute("default");
if (defaultAttribute != null
&& "true".equals(defaultAttribute.trim().toLowerCase())) {
this.extensions.add(0, e3.getTextContent().trim());
} else {
this.extensions.add(e3.getTextContent().trim());
}
}
}
}
// Parse toolshed extensions from Galaxy
for (Element toolshed : XMLUtils.getElementsByTagName(document,
"toolshedgalaxy")) {
for (Element ext : XMLUtils.getElementsByTagName(toolshed, "extension")) {
this.galaxyToolExtensions.add(ext.getTextContent().trim());
}
}
// Check object values
if (this.name == null) {
throw new EoulsanException("The name of the dataformat is null");
}
this.name = this.name.trim().toLowerCase();
if (!FileNaming.isFormatPrefixValid(this.prefix)) {
throw new EoulsanException(
"The prefix of the dataformat is invalid (only ascii letters and digits are allowed): "
+ this.prefix);
}
if (this.description != null) {
this.description = this.description.trim();
}
if (this.alias != null) {
this.alias = this.alias.trim().toLowerCase();
}
if (this.contentType == null || "".equals(this.contentType.trim())) {
this.contentType = DEFAULT_CONTENT_TYPE;
}
if (this.generatorClassName != null
&& "".equals(this.generatorClassName.trim())) {
this.generatorClassName = null;
}
if (this.checkerClassName != null
&& "".equals(this.checkerClassName.trim())) {
this.checkerClassName = null;
}
if (this.splitterClassName != null
&& "".equals(this.splitterClassName.trim())) {
this.splitterClassName = null;
}
if (this.mergerClassName != null
&& "".equals(this.mergerClassName.trim())) {
this.mergerClassName = null;
}
if (this.maxFilesCount < 1 || this.maxFilesCount > 2) {
throw new EoulsanException("Invalid maximal files count for data format "
+ this.name + ": " + this.maxFilesCount);
}
if (this.extensions.size() == 0) {
throw new EoulsanException(
"No extension define for the data format " + this.name);
}
}
//
// Object methods
//
@Override
public boolean equals(final Object o) {
if (o == this) {
return true;
}
if (!(o instanceof DataFormat)) {
return false;
}
if (!(o instanceof XMLDataFormat)) {
return super.equals(o);
}
final XMLDataFormat that = (XMLDataFormat) o;
return Objects.equals(this.name, that.name)
&& Objects.equals(this.description, that.description)
&& Objects.equals(this.alias, that.alias)
&& Objects.equals(this.prefix, that.prefix)
&& Objects.equals(this.oneFilePerAnalysis, that.oneFilePerAnalysis)
&& Objects.equals(this.dataFormatFromDesignFile,
that.dataFormatFromDesignFile)
&& Objects.equals(this.designMetadataKeyName,
that.designMetadataKeyName)
&& Objects.equals(this.sampleMetadataKeyName,
that.sampleMetadataKeyName)
&& Objects.equals(this.contentType, that.contentType)
&& Objects.equals(this.extensions, that.extensions)
&& Objects.equals(this.galaxyToolExtensions, that.galaxyToolExtensions)
&& Objects.equals(this.generatorClassName, that.generatorClassName)
&& Objects.equals(this.checkerClassName, that.checkerClassName)
&& Objects.equals(this.splitterClassName, that.splitterClassName)
&& Objects.equals(this.mergerClassName, that.mergerClassName)
&& Objects.equals(this.maxFilesCount, that.maxFilesCount);
}
@Override
public int hashCode() {
return Objects.hash(this.name, this.description, this.alias, this.prefix,
this.oneFilePerAnalysis, this.dataFormatFromDesignFile,
this.designMetadataKeyName, this.sampleMetadataKeyName,
this.contentType, this.extensions, this.galaxyToolExtensions,
this.generatorClassName, this.checkerClassName, this.splitterClassName,
this.mergerClassName, this.maxFilesCount);
}
@Override
public String toString() {
return com.google.common.base.Objects.toStringHelper(this)
.add("name", this.name).add("description", this.description)
.add("alias", this.alias).add("prefix", this.prefix)
.add("contentType", this.contentType)
.add("defaultExtension", this.extensions.get(0))
.add("extensions", this.extensions)
.add("galaxyToolExtensions", this.galaxyToolExtensions)
.add("generatorClassName", this.generatorClassName)
.add("generatorParameters", this.generatorParameters)
.add("checkerClassName", this.checkerClassName)
.add("splitterClassName", this.splitterClassName)
.add("mergerClassName", this.mergerClassName)
.add("maxFilesCount", this.maxFilesCount).toString();
}
//
// Constructor
//
/**
* Public constructor.
* @param is input stream that contains the value of the data format
* @throws EoulsanException
*/
public XMLDataFormat(final InputStream is) throws EoulsanException {
if (is == null) {
throw new NullPointerException("The input stream is null");
}
try {
parse(is);
} catch (ParserConfigurationException | IOException | SAXException e) {
throw new EoulsanException(e);
}
}
}