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