SiLeBAT/FSK-Lab

View on GitHub
de.bund.bfr.knime.fsklab.deprecatednodes/src-1_0_2/de/bund/bfr/knime/fsklab/nodes/MetadataDocument.java

Summary

Maintainability
D
2 days
Test Coverage
/*
 ***************************************************************************************************
 * Copyright (c) 2017 Federal Institute for Risk Assessment (BfR), Germany
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program. If
 * not, see <http://www.gnu.org/licenses/>.
 *
 * Contributors: Department Biological Safety - BfR
 *************************************************************************************************
 */
package de.bund.bfr.knime.fsklab.nodes;

import java.text.ParseException;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.xml.stream.XMLStreamException;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.sbml.jsbml.ASTNode;
import org.sbml.jsbml.Annotation;
import org.sbml.jsbml.AssignmentRule;
import org.sbml.jsbml.InitialAssignment;
import org.sbml.jsbml.Model;
import org.sbml.jsbml.Parameter;
import org.sbml.jsbml.SBMLDocument;
import org.sbml.jsbml.UnitDefinition;
import org.sbml.jsbml.ext.arrays.ArraysConstants;
import org.sbml.jsbml.ext.arrays.ArraysSBasePlugin;
import org.sbml.jsbml.ext.arrays.Dimension;
import org.sbml.jsbml.ext.arrays.Index;
import org.sbml.jsbml.xml.XMLNode;
import org.sbml.jsbml.xml.XMLTriple;
import de.bund.bfr.knime.fsklab.nodes.Variable.DataType;
import de.bund.bfr.pmfml.ModelClass;
import de.bund.bfr.pmfml.ModelType;
import de.bund.bfr.pmfml.PMFUtil;
import de.bund.bfr.pmfml.sbml.Limits;
import de.bund.bfr.pmfml.sbml.LimitsConstraint;
import de.bund.bfr.pmfml.sbml.PMFCompartment;
import de.bund.bfr.pmfml.sbml.PMFSpecies;
import de.bund.bfr.pmfml.sbml.SBMLFactory;

public class MetadataDocument {

  public SBMLDocument doc;

  public MetadataDocument(FskMetaData template) {
    // Creates SBMLDocument for the primary model
    doc = new SBMLDocument(3, 1);

    // Adds namespaces to the sbmlDocument
    doc.addDeclaredNamespace("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
    doc.addDeclaredNamespace("xmlns:pmml", "http://www.dmg.org/PMML-4_2");
    doc.addDeclaredNamespace("xmlns:pmf",
        "http://sourceforge.net/projects/microbialmodelingexchange/files/PMF-ML");
    doc.addDeclaredNamespace("xmlns:dc", "http://purl.org/dc/elements/1.1");
    doc.addDeclaredNamespace("xmlns:dcterms", "http://purl.org/dc/terms/");
    doc.addDeclaredNamespace("xmlns:pmmlab",
        "http://sourceforge.net/projects/microbialmodelingexchange/files/PMF-ML");
    doc.addDeclaredNamespace("xmlns:numl", "http://www.numl.org/numl/level1/version1");
    doc.addDeclaredNamespace("xmlns:xlink", "http//www.w3.org/1999/xlink");

    // Adds document annotation
    doc.setAnnotation(new MetadataAnnotation(template).annotation);

    // Creates model and names it
    Model model;
    if (StringUtils.isEmpty(template.modelId)) {
      model = doc.createModel();
    } else {
      model = doc.createModel(PMFUtil.createId(template.modelId));
    }

    if (StringUtils.isNotEmpty(template.modelName)) {
      model.setName(template.modelName);
    }

    // Sets model notes
    if (StringUtils.isNotEmpty(template.notes)) {
      try {
        model.setNotes(template.notes);
      } catch (XMLStreamException e) {
        e.printStackTrace();
      }
    }

    // Creates and adds compartment to the model
    if (StringUtils.isNotEmpty(template.matrix)) {
      PMFCompartment compartment =
          SBMLFactory.createPMFCompartment(PMFUtil.createId(template.matrix), template.matrix);
      if (StringUtils.isNotEmpty(template.matrixDetails)) {
        compartment.setDetail(template.matrixDetails);
      }
      model.addCompartment(compartment.getCompartment());
    }

    // TODO: Creates and adds species to the model

    // Creates and adds species to the model if:
    // - template.organism is specified
    // - template.dependentVariable.unit is specified
    // - model has a compartment
    // if (!Strings.isNullOrEmpty(template.organism) &&
    // template.dependentVariable != null
    // && !Strings.isNullOrEmpty(template.dependentVariable.unit) &&
    // model.getNumCompartments() > 0) {
    // String speciesId = PMFUtil.createId(template.organism);
    // String speciesName = template.organism;
    // String speciesUnit =
    // PMFUtil.createId(template.dependentVariable.unit);
    // PMFSpecies species =
    // SBMLFactory.createPMFSpecies(model.getCompartment(0).getId(),
    // speciesId, speciesName,
    // speciesUnit);
    // model.addSpecies(species.getSpecies());
    // }

    // Add unit definitions here (before parameters)
    Set<String> unitsSet = new LinkedHashSet<>();
    template.dependentVariables.forEach(v -> unitsSet.add(v.unit.trim()));
    template.independentVariables.forEach(v -> unitsSet.add(v.unit.trim()));
    unitsSet.remove("dimensionless");  // `dimensionless` cannot be added in SBML
    
    for (String unit : unitsSet) {
      UnitDefinition ud = model.createUnitDefinition(PMFUtil.createId(unit));
      ud.setName(unit);
    }

    // Adds dep parameter
    for (Variable v : template.dependentVariables) {
      if (StringUtils.isEmpty(v.name))
        continue;

      Parameter param = model.createParameter(PMFUtil.createId(v.name));
      param.setName(v.name);

      // Write unit if v.unit is not null
      if (StringUtils.isNotEmpty(v.unit)) {
        try {
          param.setUnits(PMFUtil.createId(v.unit));
        } catch (IllegalArgumentException e) {
          e.printStackTrace();
        }
      }

      // Write min and max values if not null
      if (StringUtils.isNoneEmpty(v.min, v.max)) {
        try {
          double min = Double.parseDouble(v.min);
          double max = Double.parseDouble(v.max);
          LimitsConstraint lc = new LimitsConstraint(v.name.replaceAll("\\.", "\\_"), min, max);
          if (lc.getConstraint() != null) {
            model.addConstraint(lc.getConstraint());
          }
        } catch (NumberFormatException e) {
          e.printStackTrace();
        }
      }
    }

    // Adds independent parameters
    for (Variable v : template.independentVariables) {
      if (StringUtils.isEmpty(v.name))
        continue;

      Parameter param = model.createParameter(PMFUtil.createId(v.name));
      param.setName(v.name);

      // Write value if v.type and v.value are not null
      if (v.type != null && StringUtils.isNotEmpty(v.value)) {
        switch (v.type) {
          case integer:
            param.setValue(Double.valueOf(v.value).intValue());
            break;
          case numeric:
            param.setValue(Double.parseDouble(v.value));
            break;
          case array:
            // TODO: Add array
            try {
              param.setValue(0);
              addArrayToParameter(param, v.value, v.name);
            } catch (ParseException e) {
              e.printStackTrace();
            }
            break;
          case character:
            // TODO: Add character
            break;
        }
      }

      // Write unit if v.unit is not null
      if (StringUtils.isNotEmpty(v.unit)) {
        try {
          param.setUnits(PMFUtil.createId(v.unit));
        } catch (IllegalArgumentException e) {
          e.printStackTrace();
        }
      }

      // Write min and max values if not null
      if (StringUtils.isNoneEmpty(v.min, v.max)) {
        try {
          double min = Double.parseDouble(v.min);
          double max = Double.parseDouble(v.max);
          LimitsConstraint lc = new LimitsConstraint(param.getId(), min, max);
          if (lc.getConstraint() != null) {
            model.addConstraint(lc.getConstraint());
          }
        } catch (NumberFormatException e) {
          e.printStackTrace();
        }
      }
    }

    // Add rule
    if (model.getNumParameters() > 0 && StringUtils.isNotEmpty(model.getParameter(0).getId())) {
      AssignmentRule rule = new AssignmentRule(3, 1);
      // Assigns the id of the dependent parameter which happens to be the
      // first parameter of the model
      rule.setVariable(model.getParameter(0).getId());

      ModelClass modelClass = template.subject == null ? ModelClass.UNKNOWN : template.subject;
      rule.setAnnotation(new RuleAnnotation(modelClass).annotation);
      model.addRule(rule);
    }
  }

  private static void addArrayToParameter(Parameter parameter, String value, String var)
      throws ParseException {
    String cleanArray = value.substring(2, value.length() - 1);
    String[] tokens = cleanArray.split(",");
    List<Double> array =
        Arrays.stream(tokens).map(Double::parseDouble).collect(Collectors.toList());
    int size = tokens.length;

    // Create dimension within parameter
    ArraysSBasePlugin arrayPlugin =
        (ArraysSBasePlugin) parameter.getPlugin(ArraysConstants.shortLabel);
    Dimension dim = arrayPlugin.createDimension("d0");
    dim.setSize(Integer.toString(size));
    dim.setArrayDimension(0);

    // Create initial assignment
    InitialAssignment ia = parameter.getModel().createInitialAssignment();
    ia.setVariable(var);

    // Create math of initial assignment with a selector function
    ia.setMath(new SelectorNode(array, ia).node);

    ArraysSBasePlugin iaPlugin = (ArraysSBasePlugin) ia.getPlugin(ArraysConstants.shortLabel);

    // Add dimension to initial assignment
    iaPlugin.addDimension(dim.clone());

    // Add index to initial assignment
    Index index = iaPlugin.createIndex();
    index.setReferencedAttribute("symbol");
    index.setArrayDimension(0);
    index.setMath(new ASTNode("d0"));
  }

  public MetadataDocument(final SBMLDocument doc) {
    this.doc = doc;
  }

  public FskMetaData getMetaData() {
    FskMetaData template = new FskMetaData();

    Model model = doc.getModel();

    // caches limits
    List<Limits> limits = model.getListOfConstraints().stream().map(LimitsConstraint::new)
        .map(LimitsConstraint::getLimits).collect(Collectors.toList());

    template.modelId = model.getId();
    template.modelName = model.getName();

    // organism data
    if (model.getNumSpecies() > 0) {
      PMFSpecies species = SBMLFactory.createPMFSpecies(model.getSpecies(0));
      template.organism = species.getName();
      if (species.isSetDetail()) {
        template.organismDetails = species.getDetail();
      }
    }

    // matrix data
    if (model.getNumCompartments() > 0) {
      PMFCompartment compartment = SBMLFactory.createPMFCompartment(model.getCompartment(0));
      template.matrix = compartment.getName();
      if (compartment.isSetDetail()) {
        template.matrixDetails = compartment.getDetail();
      }
    }

    // creator
    MetadataAnnotation annot = new MetadataAnnotation(doc.getAnnotation());
    template.creator = annot.givenName;
    template.familyName = annot.familyName;
    template.contact = annot.contact;
    template.createdDate = annot.createdDate;
    template.modifiedDate = annot.modifiedDate;
    template.type = annot.type;
    template.rights = annot.rights;
    template.referenceDescription = annot.referenceDescription;
    template.referenceDescriptionLink = annot.referenceDescriptionLink;

    if (model.getNumRules() > 0) {
      AssignmentRule rule = (AssignmentRule) model.getRule(0);
      template.subject = new RuleAnnotation(rule.getAnnotation()).modelClass;
    }

    // model notes
    if (model.isSetNotes()) {
      try {
        template.notes = Jsoup.parse(model.getNotesString()).text();
      } catch (XMLStreamException e) {
        System.err.println("Error accesing the notes of " + model);
        e.printStackTrace();
      }
    }

    for (Parameter param : model.getListOfParameters()) {
      Variable var = new Variable();
      var.name = param.getName();

      var.unit = "";
      if (param.isSetUnits() && !param.getUnits().equals("dimensionless")) {
        UnitDefinition unitDef = model.getUnitDefinition(param.getUnits());
        if (unitDef != null) {
          var.unit = unitDef.getName();
        }
      }

      Limits paramLimits =
          limits.stream().filter(lim -> lim.getVar().equals(param.getId())).findFirst().get();
      var.min = paramLimits.getMin().toString();
      var.max = paramLimits.getMax().toString();

      // If param has value then var is an independent variable, otherwise
      // dependent
      if (param.isSetValue()) {
        if (param.getNumPlugins() > 0) {
          var.type = DataType.array;
          InitialAssignment ia = model.getInitialAssignment(var.name);
          List<Double> array = new SelectorNode(ia.getMath()).getArray();
          var.value = "c("
              + array.stream().map(d -> Double.toString(d)).collect(Collectors.joining(",")) + ")";
        } else {
          var.type = param.getValue() % 1 == 0 ? DataType.integer : DataType.numeric;
          var.value = Double.toString(param.getValue());
        }

        template.independentVariables.add(var);
      } else {
        var.type = null;
        var.value = "";

        template.dependentVariables.add(var);
      }
    }

    template.hasData = false;

    return template;
  }

  public static class MetadataAnnotation {

    private static final String METADATA_TAG = "metadata"; // Metadata tag
    private static final String METADATA_NS = "pmf"; // Metadata namespace

    // Namespace and tag for the creator: <dc:creator>
    private final static String CREATOR_NS = "dc";
    private final static String CREATOR_TAG = "creator";

    // Namespace and tag for the created date: <dcterms:created>
    private final static String CREATED_NS = "dcterms";
    private final static String CREATED_TAG = "created";

    // Namespace and tag for the modified date: <dcterms:modified>
    private final static String MODIFIED_NS = "dcterms";
    private final static String MODIFIED_TAG = "modified";

    // Namespace and tag for the type: <dc:type>
    private final static String TYPE_NS = "dc";
    private final static String TYPE_TAG = "type";

    // Namespace and tag for the rights: <dc:rights>
    private final static String RIGHTS_NS = "dc";
    private final static String RIGHTS_TAG = "rights";

    // Namespace and tag for the reference description: <dc:description>
    private final static String REFDESC_NS = "dc";
    private final static String REFDESC_TAG = "description";

    // Namespace and tag for the reference description link: <dc:source>
    private final static String REFDESCLINK_NS = "dc";
    private final static String REFDESCLINK_TAG = "source";

    public Annotation annotation;

    // Fields with metadata
    public String givenName;
    public String familyName;
    public String contact;
    public Date createdDate;
    public Date modifiedDate;
    public ModelType type;
    public String rights;
    public String referenceDescription;
    public String referenceDescriptionLink;

    public MetadataAnnotation(final FskMetaData metadata) {

      XMLTriple pmfTriple = new XMLTriple(METADATA_TAG, "", METADATA_NS);
      XMLNode pmfNode = new XMLNode(pmfTriple);

      // Builds creator node
      if (StringUtils.isNotEmpty(metadata.creator) || StringUtils.isNotEmpty(metadata.familyName)
          || StringUtils.isNotEmpty(metadata.contact)) {
        givenName = StringUtils.defaultString(metadata.creator);
        familyName = StringUtils.defaultString(metadata.familyName);
        contact = StringUtils.defaultString(metadata.contact);

        String creator = givenName + "." + familyName + "." + contact;
        XMLNode creatorNode = new XMLNode(new XMLTriple(CREATOR_TAG, null, CREATOR_NS));
        creatorNode.addChild(new XMLNode(creator));
        pmfNode.addChild(creatorNode);
      }

      // Builds created date node
      if (metadata.createdDate != null) {
        XMLNode createdNode = new XMLNode(new XMLTriple(CREATED_TAG, "", CREATED_NS));
        createdNode.addChild(new XMLNode(FskMetaData.dateFormat.format(metadata.createdDate)));
        pmfNode.addChild(createdNode);
      }

      // Builds modified date node
      if (metadata.modifiedDate != null) {
        XMLNode modifiedNode = new XMLNode(new XMLTriple(MODIFIED_TAG, "", MODIFIED_NS));
        modifiedNode.addChild(new XMLNode(FskMetaData.dateFormat.format(metadata.modifiedDate)));
        pmfNode.addChild(modifiedNode);
      }

      // Builds type node
      if (metadata.type != null) {
        XMLNode typeNode = new XMLNode(new XMLTriple(TYPE_TAG, "", TYPE_NS));
        typeNode.addChild(new XMLNode(metadata.type.name()));
        pmfNode.addChild(typeNode);
      }

      // Builds rights node
      if (StringUtils.isNotEmpty(metadata.rights)) {
        XMLNode rightsNode = new XMLNode(new XMLTriple(RIGHTS_TAG, "", RIGHTS_NS));
        rightsNode.addChild(new XMLNode(metadata.rights));
        pmfNode.addChild(rightsNode);
      }

      // Builds reference description node
      if (StringUtils.isNotEmpty(metadata.referenceDescription)) {
        XMLNode refdescNode = new XMLNode(new XMLTriple(REFDESC_TAG, "", REFDESC_NS));
        refdescNode.addChild(new XMLNode(metadata.referenceDescription));
        pmfNode.addChild(refdescNode);
      }

      // Builds reference description link node
      if (StringUtils.isNotEmpty(metadata.referenceDescriptionLink)) {
        XMLNode refdescLinkNode = new XMLNode(new XMLTriple(REFDESCLINK_TAG, "", REFDESCLINK_NS));
        refdescLinkNode.addChild(new XMLNode(metadata.referenceDescriptionLink));
        pmfNode.addChild(refdescLinkNode);
      }

      // Create annotation
      annotation = new Annotation();
      annotation.setNonRDFAnnotation(pmfNode);
    }

    public MetadataAnnotation(final Annotation annotation) {
      XMLNode pmfNode = annotation.getNonRDFannotation().getChildElement(METADATA_TAG, "");

      // Reads creatorNode
      XMLNode creatorNode = pmfNode.getChildElement(CREATOR_TAG, "");
      if (creatorNode != null) {
        String[] tempStrings = creatorNode.getChild(0).getCharacters().split("\\.", 3);
        givenName = StringUtils.defaultString(tempStrings[0]);
        familyName = StringUtils.defaultString(tempStrings[1]);
        contact = StringUtils.defaultString(tempStrings[2]);
      }

      // Reads created date
      XMLNode createdNode = pmfNode.getChildElement(CREATED_TAG, "");
      if (createdNode != null) {
        try {
          createdDate = FskMetaData.dateFormat.parse(createdNode.getChild(0).getCharacters());
        } catch (ParseException e) {
          e.printStackTrace();
        }
      }

      // Reads modified date
      XMLNode modifiedNode = pmfNode.getChildElement(MODIFIED_TAG, "");
      if (modifiedNode != null) {
        try {
          modifiedDate = FskMetaData.dateFormat.parse(modifiedNode.getChild(0).getCharacters());
        } catch (ParseException e) {
          e.printStackTrace();
        }
      }

      // Reads model type
      XMLNode typeNode = pmfNode.getChildElement(TYPE_TAG, "");
      if (typeNode != null) {
        type = ModelType.valueOf(typeNode.getChild(0).getCharacters());
      }

      // Reads rights
      XMLNode rightsNode = pmfNode.getChildElement(RIGHTS_TAG, "");
      if (rightsNode != null) {
        rights = rightsNode.getChild(0).getCharacters();
      }

      // Reads reference description
      XMLNode refdescNode = pmfNode.getChildElement(REFDESC_TAG, "");
      if (refdescNode != null) {
        referenceDescription = refdescNode.getChild(0).getCharacters();
      }

      // Reads reference description link
      XMLNode refdescLinkNode = pmfNode.getChildElement(REFDESCLINK_TAG, "");
      if (refdescLinkNode != null) {
        referenceDescriptionLink = refdescLinkNode.getChild(0).getCharacters();
      }

      // Copies annotation
      this.annotation = annotation;
    }
  }

  /**
   * Reduced version of PMF-ML ModelRuleAnnotation with only the model class.
   */
  public static class RuleAnnotation {

    public static final String SUBJECT_NAME = "subject";
    public static final String SUBJECT_URI = "pmmlab";

    public ModelClass modelClass;
    public Annotation annotation;

    public RuleAnnotation(final ModelClass modelClass) {
      // Builds metadata node
      XMLTriple pmfTriple = new XMLTriple("metadata", null, "pmf");
      XMLNode pmfNode = new XMLNode(pmfTriple);

      // Builds model class node
      XMLTriple modelClassTriple = new XMLTriple(SUBJECT_NAME, null, SUBJECT_URI);
      XMLNode modelClassNode = new XMLNode(modelClassTriple);
      modelClassNode.addChild(new XMLNode(modelClass.fullName()));
      pmfNode.addChild(modelClassNode);

      // Create annotation
      annotation = new Annotation();
      annotation.setNonRDFAnnotation(pmfNode);
    }

    public RuleAnnotation(final Annotation annotation) {

      XMLNode pmfNode = annotation.getNonRDFannotation().getChildElement("metadata", "");

      // Reads model class node
      XMLNode modelClassNode = pmfNode.getChildElement(SUBJECT_NAME, "");
      if (modelClassNode != null) {
        modelClass = ModelClass.fromName(modelClassNode.getChild(0).getCharacters());
      }

      // Copies annotation
      this.annotation = annotation;
    }
  }
}