From 47679cc55a6341c79c83691aecd421ef5240a6b4 Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Wed, 23 Oct 2019 13:28:26 +0200
Subject: [PATCH] import/export of new structural state implemented

---
 .../sbml/species/SbmlSpeciesExporter.java     | 79 ++++++++++++++++++-
 .../model/sbml/species/SbmlSpeciesParser.java | 79 +++++++++++++++++--
 .../model/sbml/SbmlExporterTest.java          | 25 +++---
 .../sbml/species/SbmlSpeciesExporterTest.java |  5 +-
 4 files changed, 169 insertions(+), 19 deletions(-)

diff --git a/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/species/SbmlSpeciesExporter.java b/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/species/SbmlSpeciesExporter.java
index a7cf1fdfd1..3688c5e1e8 100644
--- a/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/species/SbmlSpeciesExporter.java
+++ b/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/species/SbmlSpeciesExporter.java
@@ -32,6 +32,7 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
 
   private SbmlCompartmentExporter compartmentExporter;
   private int modificationResidueCounter = 0;
+  private int structuralStateCounter = 0;
   private Map<String, String> modificationResidueIds = new HashMap<>();
 
   public SbmlSpeciesExporter(Model sbmlModel,
@@ -172,14 +173,15 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
 
   private void assignStructuralStateToMulti(Species element, MultiSpeciesPlugin multiExtension,
       MultiSpeciesType speciesType) {
-    String structuralState = null;
+    StructuralState structuralState = null;
     if (element instanceof Protein) {
       structuralState = ((Protein) element).getStructuralState();
     } else if (element instanceof Complex) {
       structuralState = ((Complex) element).getStructuralState();
     }
     if (structuralState != null) {
-      assignValueToFeature(element, multiExtension, speciesType, structuralState, BioEntityFeature.STRUCTURAL_STATE);
+      assignValueToFeature(element, multiExtension, speciesType, structuralState.getValue(),
+          BioEntityFeature.STRUCTURAL_STATE);
     }
   }
 
@@ -374,13 +376,16 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
 
     String multiDistinguisher = null;
     if (isExtensionEnabled(SbmlExtension.MULTI)) {
-      String structuralState = null;
+      StructuralState structuralState = null;
       if (element instanceof Protein) {
+
         structuralState = ((Protein) element).getStructuralState();
       } else if (element instanceof Complex) {
         structuralState = ((Complex) element).getStructuralState();
       }
-      multiDistinguisher = structuralState;
+      if (structuralState != null) {
+        multiDistinguisher = structuralState.getValue();
+      }
 
       if (element instanceof SpeciesWithModificationResidue) {
         List<String> modifications = new ArrayList<>();
@@ -465,6 +470,9 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
           createModificationGlyph(mr, speciesGlyph);
         }
       }
+      if (element.getStructuralState() != null) {
+        createStructuralStateGlyph(element, speciesGlyph);
+      }
     }
     TextGlyph textGlyph = getLayout().createTextGlyph("text_" + element.getElementId(), element.getName());
     textGlyph
@@ -484,6 +492,44 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
 
   }
 
+  private void createStructuralStateGlyph(Species element, AbstractReferenceGlyph speciesGlyph) {
+    org.sbml.jsbml.Species sbmlSpecies = getSbmlModel().getSpecies(speciesGlyph.getReference());
+
+    MultiSpeciesPlugin speciesExtension = (MultiSpeciesPlugin) sbmlSpecies.getExtension("multi");
+
+    SpeciesFeatureType structuralStateFeature = getFeature(element.getClass(), getMultiSpeciesType(element),
+        BioEntityFeature.STRUCTURAL_STATE);
+
+    PossibleSpeciesFeatureValue possibleSpeciesFeatureValue = getPosibleFeatureIdByName(
+        element.getStructuralState().getValue(), structuralStateFeature);
+
+    SpeciesFeature feature = null;
+    for (SpeciesFeature existingFeature : speciesExtension.getListOfSpeciesFeatures()) {
+      if (existingFeature.getSpeciesFeatureType().equals(structuralStateFeature.getId())) {
+        feature = existingFeature;
+      }
+    }
+
+    SpeciesFeatureValue modificationFeatureValue = null;
+    for (SpeciesFeatureValue featureValue : feature.getListOfSpeciesFeatureValues()) {
+      if (possibleSpeciesFeatureValue.getId().equals(featureValue.getValue())) {
+        modificationFeatureValue = featureValue;
+      }
+    }
+
+    modificationFeatureValue.setId(getStructuralStateUniqueId(element.getStructuralState()));
+
+    GeneralGlyph modificationGlyph = getLayout().createGeneralGlyph("structural_state_" + (idCounter++),
+        modificationFeatureValue.getId());
+    ReferenceGlyph referenceGlyph = new ReferenceGlyph("structural_state_reference_" + (idCounter++));
+    referenceGlyph.setGlyph(speciesGlyph.getId());
+    referenceGlyph.setBoundingBox(createBoundingBoxForStructuralState(element.getStructuralState()));
+
+    modificationGlyph.addReferenceGlyph(referenceGlyph);
+    modificationGlyph.setBoundingBox(createBoundingBoxForStructuralState(element.getStructuralState()));
+
+  }
+
   @SuppressWarnings("unchecked")
   private MultiSpeciesType createSpeciesTypeForClass(MultiModelPlugin multiPlugin, Class<? extends Element> clazz) {
     MultiSpeciesType speciesType = new MultiSpeciesType();
@@ -560,6 +606,20 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
     modificationGlyph.setBoundingBox(createBoundingBoxForModification(mr));
   }
 
+  private BoundingBox createBoundingBoxForStructuralState(StructuralState structuralState) {
+    double width = structuralState.getWidth();
+    double height = structuralState.getHeight();
+    double x = structuralState.getPosition().getX();
+    double y = structuralState.getPosition().getY();
+    BoundingBox boundingBox = new BoundingBox();
+    boundingBox.setPosition(new Point(x, y, structuralState.getSpecies().getZ() + 1));
+    Dimensions dimensions = new Dimensions();
+    dimensions.setWidth(width);
+    dimensions.setHeight(height);
+    boundingBox.setDimensions(dimensions);
+    return boundingBox;
+  }
+
   private BoundingBox createBoundingBoxForModification(ModificationResidue mr) {
     double width, height;
     if (mr instanceof AbstractRegionModification) {
@@ -592,4 +652,15 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
     return result;
   }
 
+  private String getStructuralStateUniqueId(StructuralState mr) {
+    String modificationId = "structural_state_" + getSbmlIdKey(mr.getSpecies());
+    String result = modificationResidueIds.get(modificationId);
+    if (result == null) {
+      result = "structural_state_" + (structuralStateCounter++);
+      modificationResidueIds.put(modificationId, result);
+    }
+    return result;
+    
+  }
+
 }
diff --git a/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/species/SbmlSpeciesParser.java b/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/species/SbmlSpeciesParser.java
index d6c62603e5..7a98e43eea 100644
--- a/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/species/SbmlSpeciesParser.java
+++ b/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/species/SbmlSpeciesParser.java
@@ -2,8 +2,7 @@ package lcsb.mapviewer.converter.model.sbml.species;
 
 import java.awt.geom.Point2D;
 import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -31,6 +30,8 @@ public class SbmlSpeciesParser extends SbmlElementParser<org.sbml.jsbml.Species>
   private static String ARTIFITIAL_SOURCE_ID = "sbml_artifitial_source";
   Logger logger = LogManager.getLogger(SbmlSpeciesParser.class);
 
+  Map<String, String> structuralStateIdMap = new HashMap<>();
+
   public SbmlSpeciesParser(Model model, lcsb.mapviewer.model.map.model.Model minervaModel) {
     super(model, minervaModel);
   }
@@ -65,13 +66,20 @@ public class SbmlSpeciesParser extends SbmlElementParser<org.sbml.jsbml.Species>
     String featureTypeString = feature.getSpeciesFeatureType();
     List<String> featureValues = getFeatureValues(speciesType, feature);
     if (MultiPackageNamingUtils.isFeatureId(featureTypeString, BioEntityFeature.STRUCTURAL_STATE)) {
+      String structuralStateId = null;
+      for (SpeciesFeatureValue featureValue : feature.getListOfSpeciesFeatureValues()) {
+        if (featureValue.getId() != null) {
+          structuralStateId = featureValue.getId();
+        }
+      }
       if (minervaElement instanceof Protein) {
-        ((Protein) minervaElement).setStructuralState(String.join("; ", featureValues));
+        minervaElement.setStructuralState(createStructuralState(featureValues));
       } else if (minervaElement instanceof Complex) {
-        ((Complex) minervaElement).setStructuralState(String.join("; ", featureValues));
+        minervaElement.setStructuralState(createStructuralState(featureValues));
       } else {
         logger.warn(warnPrefix + "Structural state not supported");
       }
+      structuralStateIdMap.put(minervaElement.getElementId(), structuralStateId);
     } else if (MultiPackageNamingUtils.isFeatureId(featureTypeString, BioEntityFeature.POSITION_TO_COMPARTMENT)) {
       if (featureValues.size() != 1) {
         logger.warn(warnPrefix + "Position to compartment must have exactly one value");
@@ -161,6 +169,12 @@ public class SbmlSpeciesParser extends SbmlElementParser<org.sbml.jsbml.Species>
     }
   }
 
+  private StructuralState createStructuralState(List<String> featureValues) {
+    StructuralState structuralState = new StructuralState();
+    structuralState.setValue(String.join("; ", featureValues));
+    return structuralState;
+  }
+
   private List<String> getFeatureValues(MultiSpeciesType speciesType, SpeciesFeature feature) {
     SpeciesFeatureType featureType = speciesType.getListOfSpeciesFeatureTypes().get(feature.getSpeciesFeatureType());
 
@@ -320,7 +334,29 @@ public class SbmlSpeciesParser extends SbmlElementParser<org.sbml.jsbml.Species>
             // find a reference to the alias in layout, so we know it's the proper value
             ReferenceGlyph referenceGlyph = ((GeneralGlyph) graphicalObject).getListOfReferenceGlyphs().get(0);
             if (Objects.equal(referenceGlyph.getGlyph(), mr.getSpecies().getElementId())) {
-//            if (referenceGlyph.getGlyph().equals(mr.getSpecies().getElementId())) {
+              // if (referenceGlyph.getGlyph().equals(mr.getSpecies().getElementId())) {
+              residueGlyph = (GeneralGlyph) graphicalObject;
+            }
+          } else {
+            residueGlyph = (GeneralGlyph) graphicalObject;
+          }
+        }
+      }
+    }
+    return residueGlyph;
+  }
+
+  private GeneralGlyph getResidueGlyphForStructuralState(StructuralState mr) {
+    GeneralGlyph residueGlyph = null;
+    String structurlaStateId = structuralStateIdMap.get(mr.getSpecies().getElementId());
+    for (GraphicalObject graphicalObject : getLayout().getListOfAdditionalGraphicalObjects()) {
+      if (graphicalObject instanceof GeneralGlyph) {
+        if (Objects.equal(((GeneralGlyph) graphicalObject).getReference(), structurlaStateId)) {
+          if (((GeneralGlyph) graphicalObject).getListOfReferenceGlyphs().size() > 0) {
+            // find a reference to the alias in layout, so we know it's the proper value
+            ReferenceGlyph referenceGlyph = ((GeneralGlyph) graphicalObject).getListOfReferenceGlyphs().get(0);
+            if (Objects.equal(referenceGlyph.getGlyph(), mr.getSpecies().getElementId())) {
+              // if (referenceGlyph.getGlyph().equals(mr.getSpecies().getElementId())) {
               residueGlyph = (GeneralGlyph) graphicalObject;
             }
           } else {
@@ -366,21 +402,54 @@ public class SbmlSpeciesParser extends SbmlElementParser<org.sbml.jsbml.Species>
 
     for (Element element : result) {
       String compartmentId = null;
+      String speciesId = element.getElementId();
       if (getLayout().getSpeciesGlyph(element.getElementId()) != null) {
         compartmentId = ((org.sbml.jsbml.Species) getLayout().getSpeciesGlyph(element.getElementId())
             .getSpeciesInstance()).getCompartment();
+        speciesId = ((org.sbml.jsbml.Species) getLayout().getSpeciesGlyph(element.getElementId()).getSpeciesInstance())
+            .getId();
       } else {
         if (!element.getElementId().equals(ARTIFITIAL_SINK_ID)
             && !element.getElementId().equals(ARTIFITIAL_SOURCE_ID)) {
           compartmentId = getSbmlModel().getSpecies(element.getElementId()).getCompartment();
         }
       }
+      structuralStateIdMap.put(element.getElementId(), structuralStateIdMap.get(speciesId));
       assignCompartment(element, compartmentId);
       assignModificationResiduesLayout(element);
+      assignStructuralStateLayout(element);
     }
     return result;
   }
 
+  private void assignStructuralStateLayout(Element element) {
+    if (element instanceof Protein || element instanceof Complex) {
+      StructuralState state = ((Species) element).getStructuralState();
+      if (state != null) {
+        state.setFontSize(10);
+        state.setHeight(20);
+        state.setWidth(element.getWidth());
+        state.setPosition(new Point2D.Double(element.getX(), element.getY() - 10));
+        GeneralGlyph residueGlyph = getResidueGlyphForStructuralState(state);
+        if (residueGlyph != null) {
+          if (residueGlyph.getBoundingBox() == null || residueGlyph.getBoundingBox().getDimensions() == null) {
+            logger.warn(
+                new ElementUtils().getElementTag(element) + " Layout doesn't contain coordinates for structural state");
+          } else {
+            double width = residueGlyph.getBoundingBox().getDimensions().getWidth();
+            double height = residueGlyph.getBoundingBox().getDimensions().getHeight();
+            double x = residueGlyph.getBoundingBox().getPosition().getX();
+            double y = residueGlyph.getBoundingBox().getPosition().getY();
+            state.setPosition(new Point2D.Double(x, y));
+            state.setWidth(width);
+            state.setHeight(height);
+          }
+        }
+
+      }
+    }
+  }
+
   @Override
   protected void applyStyleToElement(Element elementWithLayout, LocalStyle style) {
     super.applyStyleToElement(elementWithLayout, style);
diff --git a/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/SbmlExporterTest.java b/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/SbmlExporterTest.java
index 145e36e21b..6a2c07c771 100644
--- a/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/SbmlExporterTest.java
+++ b/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/SbmlExporterTest.java
@@ -3,7 +3,6 @@ package lcsb.mapviewer.converter.model.sbml;
 import static org.junit.Assert.*;
 
 import java.awt.Color;
-import java.awt.geom.Line2D;
 import java.awt.geom.Point2D;
 import java.io.File;
 import java.lang.reflect.Modifier;
@@ -17,7 +16,6 @@ import org.sbml.jsbml.SBMLDocument;
 import org.sbml.jsbml.ext.multi.*;
 
 import lcsb.mapviewer.common.Configuration;
-import lcsb.mapviewer.common.comparator.LineComparator;
 import lcsb.mapviewer.common.comparator.ListComparator;
 import lcsb.mapviewer.converter.ConverterParams;
 import lcsb.mapviewer.converter.model.celldesigner.CellDesignerXmlParser;
@@ -397,7 +395,7 @@ public class SbmlExporterTest extends SbmlTestFunctions {
   public void testExportProteinState() throws Exception {
     Model model = createEmptyModel();
     GenericProtein element = createProtein();
-    element.setStructuralState("xxx");
+    element.setStructuralState(createStructuralState(element));
     model.addElement(element);
     Model deserializedModel = getModelAfterSerializing(model);
 
@@ -430,9 +428,9 @@ public class SbmlExporterTest extends SbmlTestFunctions {
 
   @Test
   public void testMultiExtensionProteinStateInTypes() throws Exception {
-    String structuralState = "xxx";
     Model model = createEmptyModel();
     GenericProtein element = createProtein();
+    StructuralState structuralState = createStructuralState(element);
     element.setStructuralState(structuralState);
     model.addElement(element);
     org.sbml.jsbml.Model sbmlModel = exporter.toSbmlDocument(model).getModel();
@@ -442,7 +440,7 @@ public class SbmlExporterTest extends SbmlTestFunctions {
     for (MultiSpeciesType speciesType : multiPlugin.getListOfSpeciesTypes()) {
       for (SpeciesFeatureType featureType : speciesType.getListOfSpeciesFeatureTypes()) {
         for (PossibleSpeciesFeatureValue featureValue : featureType.getListOfPossibleSpeciesFeatureValues()) {
-          if (featureValue.getName().equals(structuralState)) {
+          if (featureValue.getName().equals(structuralState.getValue())) {
             structuralStateValueFound = true;
           }
         }
@@ -454,13 +452,12 @@ public class SbmlExporterTest extends SbmlTestFunctions {
 
   @Test
   public void testMultiExtensionProteinStateSuplicateInTypes() throws Exception {
-    String structuralState = "xxx";
     Model model = createEmptyModel();
     GenericProtein element = createProtein();
-    element.setStructuralState(structuralState);
+    element.setStructuralState(createStructuralState(element));
     model.addElement(element);
     element = createProtein();
-    element.setStructuralState(structuralState);
+    element.setStructuralState(createStructuralState(element));
     model.addElement(element);
     org.sbml.jsbml.Model sbmlModel = exporter.toSbmlDocument(model).getModel();
     MultiModelPlugin multiPlugin = (MultiModelPlugin) sbmlModel.getExtension("multi");
@@ -498,9 +495,9 @@ public class SbmlExporterTest extends SbmlTestFunctions {
 
   @Test
   public void testMultiExtensionStructuralStateTypeDefinition() throws Exception {
-    String structuralState = "xxx";
     Model model = createEmptyModel();
     GenericProtein element = createProtein();
+    StructuralState structuralState = createStructuralState(element);
     element.setStructuralState(structuralState);
     model.addElement(element);
     org.sbml.jsbml.Model sbmlModel = exporter.toSbmlDocument(model).getModel();
@@ -512,6 +509,16 @@ public class SbmlExporterTest extends SbmlTestFunctions {
         speciesExtension.getListOfSpeciesFeatures().size() > 0);
   }
 
+  private StructuralState createStructuralState(Element element) {
+    StructuralState structuralState = new StructuralState();
+    structuralState.setValue("xxx");
+    structuralState.setPosition(new Point2D.Double(element.getX(), element.getY() - 10));
+    structuralState.setWidth(element.getWidth());
+    structuralState.setHeight(20.0);
+    structuralState.setFontSize(10.0);
+    return structuralState;
+  }
+
   @Test
   public void testExportResidue() throws Exception {
     Model model = createEmptyModel();
diff --git a/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/species/SbmlSpeciesExporterTest.java b/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/species/SbmlSpeciesExporterTest.java
index 190972f434..caa0240f2b 100644
--- a/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/species/SbmlSpeciesExporterTest.java
+++ b/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/species/SbmlSpeciesExporterTest.java
@@ -18,6 +18,7 @@ import lcsb.mapviewer.model.map.compartment.Compartment;
 import lcsb.mapviewer.model.map.model.Model;
 import lcsb.mapviewer.model.map.model.ModelFullIndexed;
 import lcsb.mapviewer.model.map.species.*;
+import lcsb.mapviewer.model.map.species.field.StructuralState;
 
 public class SbmlSpeciesExporterTest extends SbmlTestFunctions {
 
@@ -84,7 +85,9 @@ public class SbmlSpeciesExporterTest extends SbmlTestFunctions {
   public void testIdsOfSpeciesWithStructuralStates() throws InconsistentModelException {
     Species protein1 = createProtein();
     Protein protein2 = createProtein();
-    protein2.setStructuralState("X");
+    StructuralState state = new StructuralState();
+    state.setValue("X");
+    protein2.setStructuralState(state);
     Model model = new ModelFullIndexed(null);
     model.addElement(protein1);
     model.addElement(protein2);
-- 
GitLab