From a123fb5807c9479699f71bbc2c0b73bb165c1eeb Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Thu, 3 Jan 2019 15:30:14 +0100
Subject: [PATCH] basic export of modificationrediues implemented

---
 .../model/sbml/MultiPackageNamingUtils.java   |  38 +++
 .../converter/model/sbml/SbmlParser.java      |   8 +
 .../sbml/species/SbmlSpeciesExporter.java     | 244 ++++++++++++++++--
 .../model/sbml/species/SbmlSpeciesParser.java | 132 +++++++++-
 .../model/sbml/SbmlExporterTest.java          |   3 +
 .../modelutils/map/ElementUtils.java          |   6 +
 6 files changed, 414 insertions(+), 17 deletions(-)

diff --git a/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/MultiPackageNamingUtils.java b/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/MultiPackageNamingUtils.java
index c2905e08ce..a4f69ebc8e 100644
--- a/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/MultiPackageNamingUtils.java
+++ b/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/MultiPackageNamingUtils.java
@@ -1,9 +1,12 @@
 package lcsb.mapviewer.converter.model.sbml;
 
+import org.apache.log4j.Logger;
 import org.sbml.jsbml.ext.multi.MultiSpeciesType;
 
 import lcsb.mapviewer.model.map.species.Element;
 import lcsb.mapviewer.model.map.species.Species;
+import lcsb.mapviewer.model.map.species.field.ModificationResidue;
+import lcsb.mapviewer.model.map.species.field.ModificationState;
 
 /**
  * Class responsible for providing identifiers of structures inside multi
@@ -13,7 +16,9 @@ import lcsb.mapviewer.model.map.species.Species;
  *
  */
 public final class MultiPackageNamingUtils {
+  private static Logger logger = Logger.getLogger(MultiPackageNamingUtils.class);
 
+  private static final String MINERVA_MODIFICATION_TYPE_PREFIX = "minerva_modification_type_";
   private static final String MINERVA_POSITION_TO_COMPARTMENT_PREFIX = "minerva_position_to_compartment_";
   private static final String MINERVA_STRUCTURAL_STATE_PREFIX = "minerva_structural_state_";
 
@@ -65,4 +70,37 @@ public final class MultiPackageNamingUtils {
     return featureTypeId.startsWith(MINERVA_POSITION_TO_COMPARTMENT_PREFIX);
   }
 
+  public static String getModificationFeatureId(Species species, Class<? extends ModificationResidue> class1) {
+    return MINERVA_MODIFICATION_TYPE_PREFIX + species.getClass().getSimpleName() + "_" + class1.getSimpleName();
+  }
+
+  public static String getModificationFeatureId(Species species, Class<? extends ModificationResidue> class1,
+      ModificationState state) {
+    String stateName = "null";
+    if (state != null) {
+      stateName = state.name();
+    }
+    
+    return MINERVA_MODIFICATION_TYPE_PREFIX + species.getClass().getSimpleName() + "_" + class1.getSimpleName() + "_"
+        + stateName;
+  }
+
+  public static boolean isModificationFeatureId(String featureTypeString) {
+    return featureTypeString.startsWith(MINERVA_MODIFICATION_TYPE_PREFIX);
+  }
+
+  public static boolean isModificationFeatureId(String speciesFeatureType,
+      Class<? extends ModificationResidue> class1) {
+    return isModificationFeatureId(speciesFeatureType) && speciesFeatureType.contains("_" + class1.getSimpleName());
+  }
+
+  public static ModificationState getModificationStateFromFeatureTypeName(String featureTypeString) {
+    for (ModificationState state : ModificationState.values()) {
+      if (featureTypeString.endsWith(state.name())) {
+        return state;
+      }
+    }
+    return null;
+  }
+
 }
diff --git a/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/SbmlParser.java b/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/SbmlParser.java
index bacbc97877..48f4e8d3ac 100644
--- a/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/SbmlParser.java
+++ b/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/SbmlParser.java
@@ -46,6 +46,8 @@ import lcsb.mapviewer.model.map.reaction.type.BooleanLogicGateReaction;
 import lcsb.mapviewer.model.map.species.Complex;
 import lcsb.mapviewer.model.map.species.Element;
 import lcsb.mapviewer.model.map.species.Species;
+import lcsb.mapviewer.model.map.species.field.ModificationResidue;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithModificationResidue;
 
 public class SbmlParser implements IConverter {
 
@@ -184,6 +186,12 @@ public class SbmlParser implements IConverter {
     for (Element element : model.getElements()) {
       if (element.getWidth() == 0 || element.getHeight() == 0) {
         bioEntitesRequiringLayout.add(element);
+      } else if (element instanceof SpeciesWithModificationResidue) {
+        for (ModificationResidue mr : ((SpeciesWithModificationResidue) element).getModificationResidues()) {
+          if (mr.getPosition() == null) {
+            bioEntitesRequiringLayout.add(element);
+          }
+        }
       }
     }
     for (Reaction reaction : model.getReactions()) {
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 eb1b3053c6..670c655dbc 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
@@ -1,11 +1,19 @@
 package lcsb.mapviewer.converter.model.sbml.species;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 import org.sbml.jsbml.Model;
 import org.sbml.jsbml.ext.layout.AbstractReferenceGlyph;
+import org.sbml.jsbml.ext.layout.BoundingBox;
+import org.sbml.jsbml.ext.layout.Dimensions;
+import org.sbml.jsbml.ext.layout.Point;
 import org.sbml.jsbml.ext.multi.MultiModelPlugin;
 import org.sbml.jsbml.ext.multi.MultiSpeciesPlugin;
 import org.sbml.jsbml.ext.multi.MultiSpeciesType;
@@ -20,10 +28,24 @@ import lcsb.mapviewer.converter.model.sbml.SbmlElementExporter;
 import lcsb.mapviewer.converter.model.sbml.SbmlExtension;
 import lcsb.mapviewer.converter.model.sbml.compartment.SbmlCompartmentExporter;
 import lcsb.mapviewer.model.map.InconsistentModelException;
+import lcsb.mapviewer.model.map.species.AntisenseRna;
 import lcsb.mapviewer.model.map.species.Complex;
 import lcsb.mapviewer.model.map.species.Element;
+import lcsb.mapviewer.model.map.species.Gene;
 import lcsb.mapviewer.model.map.species.Protein;
+import lcsb.mapviewer.model.map.species.Rna;
 import lcsb.mapviewer.model.map.species.Species;
+import lcsb.mapviewer.model.map.species.field.AbstractRegionModification;
+import lcsb.mapviewer.model.map.species.field.AbstractSiteModification;
+import lcsb.mapviewer.model.map.species.field.BindingRegion;
+import lcsb.mapviewer.model.map.species.field.CodingRegion;
+import lcsb.mapviewer.model.map.species.field.ModificationResidue;
+import lcsb.mapviewer.model.map.species.field.ModificationSite;
+import lcsb.mapviewer.model.map.species.field.ProteinBindingDomain;
+import lcsb.mapviewer.model.map.species.field.RegulatoryRegion;
+import lcsb.mapviewer.model.map.species.field.Residue;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithModificationResidue;
+import lcsb.mapviewer.model.map.species.field.TranscriptionSite;
 
 public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.jsbml.Species> {
 
@@ -32,7 +54,6 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
   /**
    * Default class logger.
    */
-  @SuppressWarnings("unused")
   private static Logger logger = Logger.getLogger(SbmlSpeciesExporter.class);
 
   private SbmlCompartmentExporter compartmentExporter;
@@ -75,6 +96,113 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
     result.addExtension("multi", multiExtension);
     assignStructuralStateToMulti(element, multiExtension, speciesType);
     assignPostionToCompartmentToMulti(element, multiExtension, speciesType);
+    assignElementModificationResiduesToMulti(element, multiExtension, speciesType);
+  }
+
+  private void assignElementModificationResiduesToMulti(Species element, MultiSpeciesPlugin multiExtension,
+      MultiSpeciesType speciesType) {
+    if (element instanceof Protein) {
+      assignModificationResiduesToMulti(((Protein) element).getModificationResidues(), multiExtension, speciesType);
+    } else if (element instanceof Gene) {
+      assignModificationResiduesToMulti(((Gene) element).getModificationResidues(), multiExtension, speciesType);
+    } else if (element instanceof Rna) {
+      assignModificationResiduesToMulti(((Rna) element).getRegions(), multiExtension, speciesType);
+    } else if (element instanceof AntisenseRna) {
+      assignModificationResiduesToMulti(((AntisenseRna) element).getRegions(), multiExtension, speciesType);
+    }
+
+  }
+
+  private void assignModificationResiduesToMulti(Collection<ModificationResidue> modificationResidues,
+      MultiSpeciesPlugin multiExtension, MultiSpeciesType speciesType) {
+    for (ModificationResidue mr : modificationResidues) {
+      SpeciesFeatureType feature = null;
+      PossibleSpeciesFeatureValue value;
+
+      if (mr instanceof BindingRegion) {
+        feature = getBindingRegionFeature(mr.getSpecies(), speciesType);
+      } else if (mr instanceof CodingRegion) {
+        feature = getCodingRegionFeature(mr.getSpecies(), speciesType);
+      } else if (mr instanceof ProteinBindingDomain) {
+        feature = getProteinBindingDomainFeature(mr.getSpecies(), speciesType);
+      } else if (mr instanceof RegulatoryRegion) {
+        feature = getRegulatoryRegionFeature(mr.getSpecies(), speciesType);
+      } else if (mr instanceof TranscriptionSite) {
+        feature = getTranscriptionSiteFeature(mr.getSpecies(), speciesType);
+      } else if (mr instanceof ModificationSite) {
+        feature = getModificationSiteFeature((ModificationSite) mr, speciesType);
+      } else if (mr instanceof Residue) {
+        feature = getResidueFeature((Residue) mr, speciesType);
+      } else {
+        logger.warn("Don't know how to export modification: " + mr.getClass());
+      }
+      value = getPosibleFeatureIdByName(mr.getName(), feature);
+      SpeciesFeatureValue featureValue = addSpeciesFeatureValue(multiExtension, feature, value);
+      featureValue.setId(getModificationResidueUniqueId(mr));
+    }
+  }
+
+  private SpeciesFeatureType getCodingRegionFeature(Species species, MultiSpeciesType speciesType) {
+    String featureId = MultiPackageNamingUtils.getModificationFeatureId(species, CodingRegion.class);
+    String featureName = "Coding region";
+    return getOrCreateFeatureById(featureId, featureName, speciesType);
+  }
+
+  private SpeciesFeatureType getBindingRegionFeature(Species species, MultiSpeciesType speciesType) {
+    String featureId = MultiPackageNamingUtils.getModificationFeatureId(species, BindingRegion.class);
+    String featureName = "Binding region";
+    return getOrCreateFeatureById(featureId, featureName, speciesType);
+  }
+
+  private SpeciesFeatureType getProteinBindingDomainFeature(Species species, MultiSpeciesType speciesType) {
+    String featureId = MultiPackageNamingUtils.getModificationFeatureId(species, ProteinBindingDomain.class);
+    String featureName = "Protein binding domain";
+    return getOrCreateFeatureById(featureId, featureName, speciesType);
+  }
+
+  private SpeciesFeatureType getRegulatoryRegionFeature(Species species, MultiSpeciesType speciesType) {
+    String featureId = MultiPackageNamingUtils.getModificationFeatureId(species, RegulatoryRegion.class);
+    String featureName = "Regulatory region";
+    return getOrCreateFeatureById(featureId, featureName, speciesType);
+  }
+
+  private SpeciesFeatureType getTranscriptionSiteFeature(Species species, MultiSpeciesType speciesType) {
+    String featureId = MultiPackageNamingUtils.getModificationFeatureId(species, TranscriptionSite.class);
+    String featureName = "Transcription site";
+    return getOrCreateFeatureById(featureId, featureName, speciesType);
+  }
+
+  private SpeciesFeatureType getModificationSiteFeature(ModificationSite modificationSite,
+      MultiSpeciesType speciesType) {
+    String featureId = MultiPackageNamingUtils.getModificationFeatureId(modificationSite.getSpecies(),
+        ModificationSite.class, modificationSite.getState());
+    String featureName = "";
+    if (modificationSite.getState() != null) {
+      featureName = modificationSite.getState().getFullName();
+    }
+    return getOrCreateFeatureById(featureId, featureName, speciesType);
+  }
+
+  private SpeciesFeatureType getResidueFeature(Residue residue, MultiSpeciesType speciesType) {
+    String featureId = MultiPackageNamingUtils.getModificationFeatureId(residue.getSpecies(), Residue.class,
+        residue.getState());
+    String featureName = "";
+    if (residue.getState() != null) {
+      featureName = residue.getState().getFullName();
+    }
+    return getOrCreateFeatureById(featureId, featureName, speciesType);
+  }
+
+  private SpeciesFeatureType getOrCreateFeatureById(String featureId, String featureName,
+      MultiSpeciesType speciesType) {
+    SpeciesFeatureType feature = speciesType.getSpeciesFeatureType(featureId);
+    if (feature == null) {
+      feature = new SpeciesFeatureType();
+      feature.setName(featureName);
+      feature.setId(featureId);
+      speciesType.getListOfSpeciesFeatureTypes().add(feature);
+    }
+    return feature;
   }
 
   private void assignStructuralStateToMulti(Species element, MultiSpeciesPlugin multiExtension,
@@ -90,14 +218,28 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
       PossibleSpeciesFeatureValue structuralStateFeatureValue = getPosibleFeatureIdByName(structuralState,
           structuralStateFeature);
 
-      SpeciesFeature feature = multiExtension.createSpeciesFeature();
-      feature.setSpeciesFeatureType(structuralStateFeature.getId());
-      SpeciesFeatureValue value = new SpeciesFeatureValue();
-      value.setValue(structuralStateFeatureValue.getId());
-      feature.addSpeciesFeatureValue(value);
+      addSpeciesFeatureValue(multiExtension, structuralStateFeature, structuralStateFeatureValue);
     }
   }
 
+  private SpeciesFeatureValue addSpeciesFeatureValue(MultiSpeciesPlugin multiExtension, SpeciesFeatureType fetureType,
+      PossibleSpeciesFeatureValue possibleValue) {
+    SpeciesFeature feature = null;
+    for (SpeciesFeature existingFeature : multiExtension.getListOfSpeciesFeatures()) {
+      if (existingFeature.getSpeciesFeatureType().equals(fetureType.getId())) {
+        feature = existingFeature;
+      }
+    }
+    if (feature == null) {
+      feature = multiExtension.createSpeciesFeature();
+      feature.setSpeciesFeatureType(fetureType.getId());
+    }
+    SpeciesFeatureValue value = new SpeciesFeatureValue();
+    value.setValue(possibleValue.getId());
+    feature.addSpeciesFeatureValue(value);
+    return value;
+  }
+
   private PossibleSpeciesFeatureValue getPosibleFeatureIdByName(String featureValueName,
       SpeciesFeatureType speciesFeature) {
     PossibleSpeciesFeatureValue structuralStateFeatureValue = null;
@@ -122,11 +264,7 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
     PossibleSpeciesFeatureValue structuralStateFeatureValue = getPosibleFeatureIdByName(positionToCompartmentName,
         structuralStateFeature);
 
-    SpeciesFeature feature = multiExtension.createSpeciesFeature();
-    feature.setSpeciesFeatureType(structuralStateFeature.getId());
-    SpeciesFeatureValue value = new SpeciesFeatureValue();
-    value.setValue(structuralStateFeatureValue.getId());
-    feature.addSpeciesFeatureValue(value);
+    addSpeciesFeatureValue(multiExtension, structuralStateFeature, structuralStateFeatureValue);
   }
 
   private MultiSpeciesType getMultiSpeciesType(Species element) {
@@ -168,6 +306,19 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
         structuralState = ((Complex) element).getStructuralState();
       }
       multiDistinguisher = structuralState;
+
+      if (element instanceof SpeciesWithModificationResidue) {
+        List<String> modifications = new ArrayList<>();
+        for (ModificationResidue mr : ((SpeciesWithModificationResidue) element).getModificationResidues()) {
+          String modificationId = mr.getName() + mr.getClass().getSimpleName();
+          if (mr instanceof AbstractSiteModification) {
+            modificationId += ((AbstractSiteModification) mr).getState();
+          }
+          modifications.add(modificationId);
+        }
+        Collections.sort(modifications);
+        multiDistinguisher += "\n" + StringUtils.join(modifications, ",");
+      }
     }
     String result = element.getClass().getSimpleName() + "\n" + element.getName() + "\n" + compartmentName + "\n"
         + complexName
@@ -234,12 +385,73 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
     return result;
   }
 
-  private void addPositionToCompartmentToPossibleValues(MultiSpeciesType speciesType, Species species) {
-    if (species.getPositionToCompartment() != null) {
-      SpeciesFeatureType feature = speciesType.getListOfSpeciesFeatureTypes()
-          .get(MultiPackageNamingUtils.getPositionToCompartmentFeatureId(species));
-      addPosibleValueToFeature(feature, species.getPositionToCompartment().name());
+  @Override
+  protected void assignLayoutToGlyph(Species element, AbstractReferenceGlyph speciesGlyph) {
+    super.assignLayoutToGlyph(element, speciesGlyph);
+    if (isExtensionEnabled(SbmlExtension.MULTI)) {
+      if (element instanceof SpeciesWithModificationResidue) {
+        for (ModificationResidue mr : ((SpeciesWithModificationResidue) element).getModificationResidues()) {
+          org.sbml.jsbml.Species sbmlSpecies = getSbmlModel().getSpecies(speciesGlyph.getReference());
+          createModificationGlyph(mr, sbmlSpecies);
+        }
+      }
     }
   }
 
+  private void createModificationGlyph(ModificationResidue mr, org.sbml.jsbml.Species sbmlSpecies) {
+    MultiSpeciesPlugin speciesExtension = (MultiSpeciesPlugin) sbmlSpecies.getExtension("multi");
+    SpeciesFeature feature = null;
+    String featureTypeId = MultiPackageNamingUtils.getModificationFeatureId(mr.getSpecies(), mr.getClass());
+    if (mr instanceof AbstractSiteModification) {
+      featureTypeId = MultiPackageNamingUtils.getModificationFeatureId(mr.getSpecies(), mr.getClass(),
+          ((AbstractSiteModification) mr).getState());
+    }
+    for (SpeciesFeature existingFeature : speciesExtension.getListOfSpeciesFeatures()) {
+      if (existingFeature.getSpeciesFeatureType().equals(featureTypeId)) {
+        feature = existingFeature;
+      }
+    }
+
+    SpeciesFeatureValue modificationFeatureValue = null;
+    for (SpeciesFeatureValue featureValue : feature.getListOfSpeciesFeatureValues()) {
+      if (featureValue.getId().equals(getModificationResidueUniqueId(mr))) {
+        modificationFeatureValue = featureValue;
+      }
+    }
+
+    AbstractReferenceGlyph modificationGlyph = getLayout().createGeneralGlyph("modification_" + (idCounter++),
+        modificationFeatureValue.getId());
+    double width, height;
+    if (mr instanceof AbstractRegionModification) {
+      width = ((AbstractRegionModification) mr).getWidth();
+      height = ((AbstractRegionModification) mr).getHeight();
+    } else {
+      width = 12;
+      height = 12;
+    }
+    double x = mr.getPosition().getX() - width / 2;
+    double y = mr.getPosition().getY() - height / 2;
+    logger.debug(mr.getPosition().getX() + ";" + mr.getPosition().getY() + ";" + modificationFeatureValue.getId());
+    logger.debug(x + ";" + y + ";" + modificationFeatureValue.getId());
+    BoundingBox boundingBox = new BoundingBox();
+    boundingBox.setPosition(new Point(x, y));
+    Dimensions dimensions = new Dimensions();
+    dimensions.setWidth(width);
+    dimensions.setHeight(height);
+    boundingBox.setDimensions(dimensions);
+    modificationGlyph.setBoundingBox(boundingBox);
+  }
+
+  private int modificationResidueCounter = 0;
+  private Map<String, String> modificationResidueIds = new HashMap<>();
+
+  private String getModificationResidueUniqueId(ModificationResidue mr) {
+    String result = modificationResidueIds.get(mr.getIdModificationResidue() + "_" + getSbmlIdKey(mr.getSpecies()));
+    if (result == null) {
+      result = "modification_residue_" + (modificationResidueCounter++);
+      modificationResidueIds.put(mr.getIdModificationResidue() + "_" + getSbmlIdKey(mr.getSpecies()), 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 1c184c1671..4fb0cf872e 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
@@ -1,5 +1,6 @@
 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;
@@ -9,6 +10,8 @@ import org.sbml.jsbml.ListOf;
 import org.sbml.jsbml.Model;
 import org.sbml.jsbml.ext.layout.AbstractReferenceGlyph;
 import org.sbml.jsbml.ext.layout.CompartmentGlyph;
+import org.sbml.jsbml.ext.layout.GeneralGlyph;
+import org.sbml.jsbml.ext.layout.GraphicalObject;
 import org.sbml.jsbml.ext.layout.SpeciesGlyph;
 import org.sbml.jsbml.ext.multi.MultiModelPlugin;
 import org.sbml.jsbml.ext.multi.MultiSpeciesPlugin;
@@ -33,7 +36,24 @@ import lcsb.mapviewer.model.map.species.Element;
 import lcsb.mapviewer.model.map.species.Protein;
 import lcsb.mapviewer.model.map.species.Species;
 import lcsb.mapviewer.model.map.species.Unknown;
+import lcsb.mapviewer.model.map.species.field.AbstractRegionModification;
+import lcsb.mapviewer.model.map.species.field.BindingRegion;
+import lcsb.mapviewer.model.map.species.field.CodingRegion;
+import lcsb.mapviewer.model.map.species.field.ModificationResidue;
+import lcsb.mapviewer.model.map.species.field.ModificationSite;
 import lcsb.mapviewer.model.map.species.field.PositionToCompartment;
+import lcsb.mapviewer.model.map.species.field.ProteinBindingDomain;
+import lcsb.mapviewer.model.map.species.field.RegulatoryRegion;
+import lcsb.mapviewer.model.map.species.field.Residue;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithBindingRegion;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithCodingRegion;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithModificationResidue;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithModificationSite;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithProteinBindingDomain;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithRegulatoryRegion;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithResidue;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithTranscriptionSite;
+import lcsb.mapviewer.model.map.species.field.TranscriptionSite;
 import lcsb.mapviewer.modelutils.map.ElementUtils;
 
 public class SbmlSpeciesParser extends SbmlElementParser<org.sbml.jsbml.Species> {
@@ -142,13 +162,91 @@ public class SbmlSpeciesParser extends SbmlElementParser<org.sbml.jsbml.Species>
           minervaElement.setPositionToCompartment(PositionToCompartment.getByString(positionToCompartments.get(0)));
         }
       } else {
-        logger.warn(warnPrefix + "Invalid structural state for class " + featureTypeString);
+        logger.warn(warnPrefix + "Position to compartment for class " + featureTypeString);
       }
+    } else if (MultiPackageNamingUtils.isModificationFeatureId(featureTypeString)) {
+      createModificationResidues(minervaElement, speciesType, feature);
     } else {
       logger.warn(warnPrefix + "Feature not supported: " + featureTypeString);
     }
   }
 
+  private void createModificationResidues(Species minervaElement, MultiSpeciesType speciesType,
+      SpeciesFeature feature) {
+    String warnPrefix = new ElementUtils().getElementTag(minervaElement);
+    ModificationResidue mr = null;
+
+    String featureTypeString = feature.getSpeciesFeatureType();
+    SpeciesFeatureType featureType = speciesType.getListOfSpeciesFeatureTypes().get(featureTypeString);
+    for (SpeciesFeatureValue featureValue : feature.getListOfSpeciesFeatureValues()) {
+      if (MultiPackageNamingUtils.isModificationFeatureId(featureTypeString, BindingRegion.class)) {
+        mr = new BindingRegion();
+        if (minervaElement instanceof SpeciesWithBindingRegion) {
+          ((SpeciesWithBindingRegion) minervaElement).addBindingRegion((BindingRegion) mr);
+        } else {
+          logger.warn(warnPrefix + "Object class doesn't support " + mr.getClass());
+        }
+      } else if (MultiPackageNamingUtils.isModificationFeatureId(featureTypeString, CodingRegion.class)) {
+        mr = new CodingRegion();
+        if (minervaElement instanceof SpeciesWithCodingRegion) {
+          ((SpeciesWithCodingRegion) minervaElement).addCodingRegion((CodingRegion) mr);
+        } else {
+          logger.warn(warnPrefix + "Object class doesn't support " + mr.getClass());
+        }
+      } else if (MultiPackageNamingUtils.isModificationFeatureId(featureTypeString, ProteinBindingDomain.class)) {
+        mr = new ProteinBindingDomain();
+        if (minervaElement instanceof SpeciesWithProteinBindingDomain) {
+          ((SpeciesWithProteinBindingDomain) minervaElement).addProteinBindingDomain((ProteinBindingDomain) mr);
+        } else {
+          logger.warn(warnPrefix + "Object class doesn't support " + mr.getClass());
+        }
+      } else if (MultiPackageNamingUtils.isModificationFeatureId(featureTypeString, RegulatoryRegion.class)) {
+        mr = new RegulatoryRegion();
+        if (minervaElement instanceof SpeciesWithRegulatoryRegion) {
+          ((SpeciesWithRegulatoryRegion) minervaElement).addRegulatoryRegion((RegulatoryRegion) mr);
+        } else {
+          logger.warn(warnPrefix + "Object class doesn't support " + mr.getClass());
+        }
+      } else if (MultiPackageNamingUtils.isModificationFeatureId(featureTypeString, TranscriptionSite.class)) {
+        mr = new TranscriptionSite();
+        if (minervaElement instanceof SpeciesWithTranscriptionSite) {
+          ((SpeciesWithTranscriptionSite) minervaElement).addTranscriptionSite((TranscriptionSite) mr);
+        } else {
+          logger.warn(warnPrefix + "Object class doesn't support " + mr.getClass());
+        }
+      } else if (MultiPackageNamingUtils.isModificationFeatureId(featureTypeString, ModificationSite.class)) {
+        mr = new ModificationSite();
+        ((ModificationSite) mr)
+            .setState(MultiPackageNamingUtils.getModificationStateFromFeatureTypeName(featureTypeString));
+        if (minervaElement instanceof SpeciesWithModificationSite) {
+          ((SpeciesWithModificationSite) minervaElement).addModificationSite((ModificationSite) mr);
+        } else {
+          logger.warn(warnPrefix + "Object class doesn't support " + mr.getClass());
+        }
+      } else if (MultiPackageNamingUtils.isModificationFeatureId(featureTypeString, Residue.class)) {
+        mr = new Residue();
+        ((Residue) mr)
+            .setState(MultiPackageNamingUtils.getModificationStateFromFeatureTypeName(featureTypeString));
+        if (minervaElement instanceof SpeciesWithResidue) {
+          ((SpeciesWithResidue) minervaElement).addResidue((Residue) mr);
+        } else {
+          logger.warn(warnPrefix + "Object class doesn't support " + mr.getClass());
+        }
+      } else {
+        logger.warn(warnPrefix + "Unsupported modification type: " + featureTypeString);
+      }
+      PossibleSpeciesFeatureValue possibleSpeciesFeatureValue = featureType.getListOfPossibleSpeciesFeatureValues()
+          .get(featureValue.getValue());
+      if (possibleSpeciesFeatureValue != null) {
+        mr.setName(possibleSpeciesFeatureValue.getName());
+        mr.setIdModificationResidue(featureValue.getId());
+      } else {
+        logger.warn(warnPrefix + "Invalid feature value: " + featureValue.getValue());
+      }
+    }
+
+  }
+
   private String extractSBOTermFromSpecies(org.sbml.jsbml.Species species) {
     String sboTerm = species.getSBOTermID();
     MultiSpeciesPlugin multiExtension = (MultiSpeciesPlugin) species.getExtension("multi");
@@ -184,10 +282,42 @@ public class SbmlSpeciesParser extends SbmlElementParser<org.sbml.jsbml.Species>
         }
       }
       assignCompartment(element, compartmentId);
+      assignModificationResiduesLayout(element);
     }
     return result;
   }
 
+  private void assignModificationResiduesLayout(Element element) {
+    if (element instanceof SpeciesWithModificationResidue) {
+      for (ModificationResidue mr : ((SpeciesWithModificationResidue) element).getModificationResidues()) {
+        GeneralGlyph residueGlyph = null;
+        for (GraphicalObject graphicalObject : getLayout().getListOfAdditionalGraphicalObjects()) {
+          if (graphicalObject instanceof GeneralGlyph) {
+            if (((GeneralGlyph) graphicalObject).getReference().equals(mr.getIdModificationResidue())) {
+              residueGlyph = (GeneralGlyph) graphicalObject;
+            }
+          }
+        }
+        if (residueGlyph != null) {
+          if (residueGlyph.getBoundingBox() == null || residueGlyph.getBoundingBox().getDimensions() == null) {
+            logger.warn(new ElementUtils().getElementTag(mr) + "Layout doesn't contain coordinates");
+          } 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();
+            mr.setPosition(new Point2D.Double(x + width / 2, y + height / 2));
+            if (mr instanceof AbstractRegionModification) {
+              ((AbstractRegionModification) mr).setWidth(width);
+              ((AbstractRegionModification) mr).setHeight(height);
+            }
+          }
+        }
+      }
+    }
+
+  }
+
   private void assignCompartment(Element element, String compartmentId) {
     Compartment compartment = getMinervaModel().getElementByElementId(compartmentId);
     if (compartment == null && getLayout() != null) {
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 5181a0ff31..1a6733fff5 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
@@ -595,14 +595,17 @@ public class SbmlExporterTest {
     Residue mr = new Residue("x1");
     mr.setName("217U");
     mr.setState(ModificationState.PHOSPHORYLATED);
+    mr.setPosition(new Point2D.Double(10,11));
     element.addResidue(mr);
     mr = new Residue("Y");
     mr.setName("218");
     mr.setState(ModificationState.PHOSPHORYLATED);
+    mr.setPosition(new Point2D.Double(10,12));
     element.addResidue(mr);
     mr = new Residue("Z");
     mr.setName("219");
     mr.setState(ModificationState.UBIQUITINATED);
+    mr.setPosition(new Point2D.Double(10,13));
     element.addResidue(mr);
     model.addElement(element);
     Model deserializedModel = getModelAfterSerializing(model);
diff --git a/model/src/main/java/lcsb/mapviewer/modelutils/map/ElementUtils.java b/model/src/main/java/lcsb/mapviewer/modelutils/map/ElementUtils.java
index 94777c846e..5313c5b44f 100644
--- a/model/src/main/java/lcsb/mapviewer/modelutils/map/ElementUtils.java
+++ b/model/src/main/java/lcsb/mapviewer/modelutils/map/ElementUtils.java
@@ -16,6 +16,7 @@ import lcsb.mapviewer.model.map.BioEntity;
 import lcsb.mapviewer.model.map.reaction.Reaction;
 import lcsb.mapviewer.model.map.reaction.ReactionNode;
 import lcsb.mapviewer.model.map.species.Element;
+import lcsb.mapviewer.model.map.species.field.ModificationResidue;
 
 /**
  * Class with some util method for {@link BioEntity} objects.
@@ -262,4 +263,9 @@ public final class ElementUtils {
     return "[" + node.getClass().getSimpleName() + "]" + getElementTag(node.getElement());
   }
 
+  public String getElementTag(ModificationResidue mr) {
+    return "[" + mr.getClass().getSimpleName() + "," + mr.getIdModificationResidue() + "]"
+        + getElementTag(mr.getSpecies());
+  }
+
 }
-- 
GitLab