From eb97d7f5b8e48008693c7a033823028cc3febb33 Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Wed, 14 Nov 2018 11:06:26 +0100
Subject: [PATCH] export of reaction contains information about arrow types and
 line types

---
 .../converter/model/sbml/SbmlParser.java      |   7 +-
 .../sbml/reaction/SBOTermReactionType.java    |  20 +-
 .../sbml/reaction/SbmlReactionExporter.java   |  22 ++-
 .../sbml/reaction/SbmlReactionParser.java     | 119 ++++++++++--
 .../sbml/GenericSbmlToXmlParserTest.java      |   3 +-
 .../reaction/AllSbmlReactionParserTests.java  |   3 +-
 .../reaction/SbmlReactionExportArrowType.java | 183 ++++++++++++++++++
 7 files changed, 336 insertions(+), 21 deletions(-)
 create mode 100644 converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/reaction/SbmlReactionExportArrowType.java

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 84188fea07..2b97fc6adf 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
@@ -40,7 +40,9 @@ import lcsb.mapviewer.model.map.model.ModelFullIndexed;
 import lcsb.mapviewer.model.map.modifier.Modulation;
 import lcsb.mapviewer.model.map.reaction.AbstractNode;
 import lcsb.mapviewer.model.map.reaction.Modifier;
+import lcsb.mapviewer.model.map.reaction.NodeOperator;
 import lcsb.mapviewer.model.map.reaction.Reaction;
+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;
@@ -98,6 +100,7 @@ public class SbmlParser implements IConverter {
         speciesParser.mergeLayout(model.getSpeciesList(), layout, sbmlModel);
         reactionParser.mergeLayout(model.getReactions(), layout, sbmlModel);
       }
+
       reactionParser.validateReactions(model.getReactions());
 
       if (sbmlModel.getConstraintCount() > 0) {
@@ -228,7 +231,9 @@ public class SbmlParser implements IConverter {
       if (node.getLine() == null) {
         return false;
       } else if (node.getLine().length() == 0) {
-        return false;
+        if (!(node instanceof NodeOperator) || !(reaction instanceof BooleanLogicGateReaction)) {
+          return false;
+        }
       }
     }
     return true;
diff --git a/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/reaction/SBOTermReactionType.java b/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/reaction/SBOTermReactionType.java
index 1461e078d0..dcc34214e6 100644
--- a/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/reaction/SBOTermReactionType.java
+++ b/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/reaction/SBOTermReactionType.java
@@ -6,10 +6,15 @@ import java.util.List;
 import org.apache.log4j.Logger;
 
 import lcsb.mapviewer.model.map.reaction.Reaction;
+import lcsb.mapviewer.model.map.reaction.type.BooleanLogicGateReaction;
+import lcsb.mapviewer.model.map.reaction.type.CatalysisReaction;
 import lcsb.mapviewer.model.map.reaction.type.DissociationReaction;
 import lcsb.mapviewer.model.map.reaction.type.HeterodimerAssociationReaction;
+import lcsb.mapviewer.model.map.reaction.type.InhibitionReaction;
 import lcsb.mapviewer.model.map.reaction.type.KnownTransitionOmittedReaction;
+import lcsb.mapviewer.model.map.reaction.type.ModulationReaction;
 import lcsb.mapviewer.model.map.reaction.type.NegativeInfluenceReaction;
+import lcsb.mapviewer.model.map.reaction.type.PhysicalStimulationReaction;
 import lcsb.mapviewer.model.map.reaction.type.PositiveInfluenceReaction;
 import lcsb.mapviewer.model.map.reaction.type.ReducedModulationReaction;
 import lcsb.mapviewer.model.map.reaction.type.ReducedPhysicalStimulationReaction;
@@ -18,7 +23,10 @@ import lcsb.mapviewer.model.map.reaction.type.StateTransitionReaction;
 import lcsb.mapviewer.model.map.reaction.type.TranscriptionReaction;
 import lcsb.mapviewer.model.map.reaction.type.TranslationReaction;
 import lcsb.mapviewer.model.map.reaction.type.TransportReaction;
+import lcsb.mapviewer.model.map.reaction.type.TriggerReaction;
 import lcsb.mapviewer.model.map.reaction.type.TruncationReaction;
+import lcsb.mapviewer.model.map.reaction.type.UnknownCatalysisReaction;
+import lcsb.mapviewer.model.map.reaction.type.UnknownInhibitionReaction;
 import lcsb.mapviewer.model.map.reaction.type.UnknownNegativeInfluenceReaction;
 import lcsb.mapviewer.model.map.reaction.type.UnknownPositiveInfluenceReaction;
 import lcsb.mapviewer.model.map.reaction.type.UnknownReducedModulationReaction;
@@ -27,24 +35,32 @@ import lcsb.mapviewer.model.map.reaction.type.UnknownReducedTriggerReaction;
 import lcsb.mapviewer.model.map.reaction.type.UnknownTransitionReaction;
 
 public enum SBOTermReactionType {
+  BOOLEAN_LOGIC_GATE(BooleanLogicGateReaction.class, new String[] { "SBO:0000547" }),
+  CATALYSIS(CatalysisReaction.class, new String[] { "SBO:0000013" }),
   DISSOCIATION(DissociationReaction.class, new String[] { "SBO:0000180" }),
   HETERODIMER_ASSOCIATION(HeterodimerAssociationReaction.class, new String[] { "SBO:0000177" }),
+  INHIBITION(InhibitionReaction.class, new String[] { "SBO:0000537" }),
   KNOWN_TRANSITION_OMITTED(KnownTransitionOmittedReaction.class, new String[] { "SBO:0000205" }),
+  MODULATION(ModulationReaction.class, new String[] { "SBO:0000594" }),
   NEGATIVE_INFLUENCE(NegativeInfluenceReaction.class, new String[] { "SBO:0000407" }),
+  PHYSICAL_STIMULATION(PhysicalStimulationReaction.class, new String[] { "SBO:0000459" }),
   POSITIVE_INFLUENCE(PositiveInfluenceReaction.class, new String[] { "SBO:0000171" }),
   REDUCED_MODULATION(ReducedModulationReaction.class, new String[] { "SBO:0000632" }),
   REDUCED_PHYSICAL_STIMULATION(ReducedPhysicalStimulationReaction.class, new String[] { "SBO:0000411" }),
-  REDUCED_TRIGGER(ReducedTriggerReaction.class, new String[] { "SBO:0000461" }),
+  REDUCED_TRIGGER(ReducedTriggerReaction.class, new String[] { "SBO:0000533" }),
   STATE_TRANSITION(StateTransitionReaction.class, new String[] { "SBO:0000176" }),
   TRANSCRIPTION(TranscriptionReaction.class, new String[] { "SBO:0000183" }),
   TRANSLATION(TranslationReaction.class, new String[] { "SBO:0000184" }),
   TRANSPORT(TransportReaction.class, new String[] { "SBO:0000185" }),
+  TRIGGER(TriggerReaction.class, new String[] { "SBO:0000461" }),
   TRUNCATION(TruncationReaction.class, new String[] { "SBO:0000178" }),
+  UNKNOWN_CATALYSIS(UnknownCatalysisReaction.class, new String[] { "SBO:0000462" }),
+  UNKNOWN_INHIBITION(UnknownInhibitionReaction.class, new String[] { "SBO:0000536" }),
   UNKNOWN_NEGATIVE_INFLUENCE(UnknownNegativeInfluenceReaction.class, new String[] { "SBO:0000169" }),
   UNKNOWN_POSITIVE_INFLUENCE(UnknownPositiveInfluenceReaction.class, new String[] { "SBO:0000172" }),
   UNKNOWN_REDUCED_MODULATION(UnknownReducedModulationReaction.class, new String[] { "SBO:0000631" }),
   UNKNOWN_REDUCED_PHYSICAL_STIMULATION(UnknownReducedPhysicalStimulationReaction.class, new String[] { "SBO:0000170" }),
-  UNKNOWN_REDUCED_TRIGGER(UnknownReducedTriggerReaction.class, new String[] { "SBO:0000533" }),
+  UNKNOWN_REDUCED_TRIGGER(UnknownReducedTriggerReaction.class, new String[] { "SBO:0000534" }),
   UNKNOWN_TRANSITION(UnknownTransitionReaction.class, new String[] { "SBO:0000396" }),
   ;
 
diff --git a/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/reaction/SbmlReactionExporter.java b/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/reaction/SbmlReactionExporter.java
index 8143019170..5594f48fab 100644
--- a/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/reaction/SbmlReactionExporter.java
+++ b/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/reaction/SbmlReactionExporter.java
@@ -24,7 +24,9 @@ import org.sbml.jsbml.ext.layout.ReactionGlyph;
 import org.sbml.jsbml.ext.layout.SpeciesReferenceGlyph;
 import org.sbml.jsbml.ext.layout.SpeciesReferenceRole;
 import org.sbml.jsbml.ext.render.ColorDefinition;
+import org.sbml.jsbml.ext.render.LocalRenderInformation;
 import org.sbml.jsbml.ext.render.LocalStyle;
+import org.sbml.jsbml.ext.render.RenderGroup;
 import org.w3c.dom.Node;
 
 import lcsb.mapviewer.common.Configuration;
@@ -243,15 +245,31 @@ public class SbmlReactionExporter extends SbmlBioEntityExporter<Reaction, org.sb
       } else {
         modifierGlyph.setRole(SpeciesReferenceRole.MODIFIER);
       }
+      LocalStyle style = createStyleForModifier(modifier);
+      assignStyleToGlyph(modifierGlyph, style);
     }
-    
+
     LocalStyle style = createStyle(reaction);
     ColorDefinition color = getColorDefinition(reaction.getReactants().get(0).getLine().getColor());
     style.getGroup().setStrokeWidth(reaction.getReactants().get(0).getLine().getWidth());
     style.getGroup().setFill(color.getId());
+    style.getGroup().setStroke(reaction.getReactants().get(0).getLine().getType().name());
+    style.getGroup().setEndHead(reaction.getProducts().get(0).getLine().getEndAtd().getArrowType().name());
 
     assignStyleToGlyph(reactionGlyph, style);
-    
+
+  }
+
+  private LocalStyle createStyleForModifier(Modifier modifier) {
+    LocalRenderInformation renderInformation = new LocalRenderInformation();
+    LocalStyle style = new LocalStyle();
+    style.getRoleList()
+        .add("style_" + modifier.getReaction().getElementId() + "_modifier_" + modifier.getElement().getElementId());
+    style.setGroup(new RenderGroup());
+    renderInformation.addLocalStyle(style);
+    getRenderPlugin().addLocalRenderInformation(renderInformation);
+    style.getGroup().setStroke(modifier.getLine().getType().name());
+    return style;
   }
 
   private void addOperatorLineToGlyph(ReactionGlyph reactantGlyph, NodeOperator operator, boolean reverse) {
diff --git a/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/reaction/SbmlReactionParser.java b/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/reaction/SbmlReactionParser.java
index 78f4e55e4d..0b001e485d 100644
--- a/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/reaction/SbmlReactionParser.java
+++ b/converter-sbml/src/main/java/lcsb/mapviewer/converter/model/sbml/reaction/SbmlReactionParser.java
@@ -26,6 +26,7 @@ import org.sbml.jsbml.ext.layout.SpeciesReferenceGlyph;
 import org.sbml.jsbml.ext.render.LocalStyle;
 import org.sbml.jsbml.ext.render.RenderConstants;
 import org.sbml.jsbml.ext.render.RenderGraphicalObjectPlugin;
+import org.sbml.jsbml.ext.render.RenderGroup;
 import org.w3c.dom.Node;
 
 import lcsb.mapviewer.common.exception.InvalidArgumentException;
@@ -39,6 +40,7 @@ import lcsb.mapviewer.converter.model.sbml.SbmlParameterParser;
 import lcsb.mapviewer.converter.model.sbml.species.SbmlSpeciesParser;
 import lcsb.mapviewer.model.graphics.ArrowType;
 import lcsb.mapviewer.model.graphics.ArrowTypeData;
+import lcsb.mapviewer.model.graphics.LineType;
 import lcsb.mapviewer.model.graphics.PolylineData;
 import lcsb.mapviewer.model.map.kinetics.SbmlArgument;
 import lcsb.mapviewer.model.map.kinetics.SbmlKinetics;
@@ -48,6 +50,7 @@ import lcsb.mapviewer.model.map.modifier.Trigger;
 import lcsb.mapviewer.model.map.reaction.AbstractNode;
 import lcsb.mapviewer.model.map.reaction.AndOperator;
 import lcsb.mapviewer.model.map.reaction.AssociationOperator;
+import lcsb.mapviewer.model.map.reaction.DissociationOperator;
 import lcsb.mapviewer.model.map.reaction.Modifier;
 import lcsb.mapviewer.model.map.reaction.NodeOperator;
 import lcsb.mapviewer.model.map.reaction.Product;
@@ -55,7 +58,10 @@ import lcsb.mapviewer.model.map.reaction.Reactant;
 import lcsb.mapviewer.model.map.reaction.Reaction;
 import lcsb.mapviewer.model.map.reaction.ReactionNode;
 import lcsb.mapviewer.model.map.reaction.SplitOperator;
+import lcsb.mapviewer.model.map.reaction.TruncationOperator;
+import lcsb.mapviewer.model.map.reaction.type.DissociationReaction;
 import lcsb.mapviewer.model.map.reaction.type.HeterodimerAssociationReaction;
+import lcsb.mapviewer.model.map.reaction.type.TruncationReaction;
 import lcsb.mapviewer.model.map.species.Element;
 import lcsb.mapviewer.model.map.species.Species;
 import lcsb.mapviewer.modelutils.map.ElementUtils;
@@ -107,6 +113,7 @@ public class SbmlReactionParser extends SbmlBioEntityParser {
       }
       used.add(source);
       try {
+        Map<ReactionNode, SpeciesReferenceGlyph> glyphByNode = new HashMap<>();
         Reaction reactionWithLayout = source.copy();
         // getId doesn't have to be unique, therefore we concatenate with reaction
         reactionWithLayout.setIdReaction(glyph.getReaction() + "__" + glyph.getId());
@@ -135,6 +142,7 @@ public class SbmlReactionParser extends SbmlBioEntityParser {
             throw new InvalidInputDataExecption(
                 "Cannot find reaction node for layouted reaction: " + speciesGlyph.getSpecies() + ", " + glyph.getId());
           }
+          glyphByNode.put(minervaNode, speciesRefernceGlyph);
           Element minervaElement = minervaModel.getElementByElementId(speciesGlyph.getId());
           if (minervaElement == null) {
             throw new InvalidInputDataExecption("Cannot find layouted reaction node for layouted reaction: "
@@ -190,19 +198,18 @@ public class SbmlReactionParser extends SbmlBioEntityParser {
           reactionWithLayout.addNode(operator);
         }
         if (reactionWithLayout.getReactants().size() > 0 && reactionWithLayout.getProducts().size() > 1) {
-          PolylineData line = new PolylineData();
-          Point2D p1 = reactionWithLayout.getReactants().get(0).getLine().getEndPoint();
-          Point2D p2 = reactionWithLayout.getProducts().get(0).getLine().getBeginPoint();
-          Point2D center = new Point2D.Double((p1.getX() + p2.getX()) / 2, (p1.getY() + p2.getY()) / 2);
-          line.addPoint(p2);
-          line.addPoint(center);
-          NodeOperator operator = new SplitOperator();
-          operator.addOutputs(reactionWithLayout.getProducts());
-          operator.setLine(line);
+          NodeOperator operator = createOutputOperator(reactionWithLayout);
           reactionWithLayout.addNode(operator);
         }
         assignRenderDataToReaction(glyph, reactionWithLayout);
 
+        for (Modifier modifier : reactionWithLayout.getModifiers()) {
+          SpeciesReferenceGlyph speciesGlyph = glyphByNode.get(modifier);
+          if (speciesGlyph != null) {
+            assignRenderDataToModifier(speciesGlyph, modifier);
+          }
+        }
+
         minervaModel.addReaction(reactionWithLayout);
       } catch (InvalidArgumentException e) {
         throw new InvalidInputDataExecption(e);
@@ -225,6 +232,67 @@ public class SbmlReactionParser extends SbmlBioEntityParser {
     }
   }
 
+  private void assignRenderDataToModifier(SpeciesReferenceGlyph glyph, ReactionNode modifier)
+      throws InvalidInputDataExecption {
+    RenderGraphicalObjectPlugin rgop = (RenderGraphicalObjectPlugin) glyph.getExtension(RenderConstants.shortLabel);
+    if (rgop != null) {
+      LocalStyle style = getStyleForRole(rgop.getObjectRole());
+      if (style == null) {
+        throw new InvalidInputDataExecption("Style " + rgop.getObjectRole() + " is not defined");
+      }
+      applyStyleToReactionNode(modifier, style);
+    }
+  }
+
+  private void applyStyleToReactionNode(ReactionNode modifier, LocalStyle style) {
+    RenderGroup group = style.getGroup();
+    if (group.isSetFill()) {
+      Color color = getColorByColorDefinition(group.getFill());
+      modifier.getLine().setColor(color);
+    }
+    if (group.isSetStrokeWidth()) {
+      modifier.getLine().setWidth(group.getStrokeWidth());
+    }
+    if (group.isSetStroke()) {
+      try {
+        LineType type = LineType.valueOf(group.getStroke());
+        modifier.getLine().setType(type);
+      } catch (Exception e) {
+        logger.warn(new ElementUtils().getElementTag(modifier.getReaction()) + "Problematic line type: "
+            + group.getStroke(), e);
+      }
+    }
+    if (group.isSetEndHead()) {
+      try {
+        ArrowType type = ArrowType.valueOf(group.getEndHead());
+        modifier.getLine().getEndAtd().setArrowType(type);
+      } catch (Exception e) {
+        logger.warn(new ElementUtils().getElementTag(modifier.getReaction()) + "Problematic arrow type: "
+            + group.getStroke(), e);
+      }
+    }
+  }
+
+  private NodeOperator createOutputOperator(Reaction reactionWithLayout) {
+    PolylineData line = new PolylineData();
+    Point2D p1 = reactionWithLayout.getReactants().get(0).getLine().getEndPoint();
+    Point2D p2 = reactionWithLayout.getProducts().get(0).getLine().getBeginPoint();
+    Point2D center = new Point2D.Double((p1.getX() + p2.getX()) / 2, (p1.getY() + p2.getY()) / 2);
+    line.addPoint(p2);
+    line.addPoint(center);
+    NodeOperator operator;
+    if (reactionWithLayout instanceof TruncationReaction) {
+      operator = new TruncationOperator();
+    } else if (reactionWithLayout instanceof DissociationReaction) {
+      operator = new DissociationOperator();
+    } else {
+      operator = new SplitOperator();
+    }
+    operator.addOutputs(reactionWithLayout.getProducts());
+    operator.setLine(line);
+    return operator;
+  }
+
   private NodeOperator createInputOperator(Reaction reactionWithLayout) {
     PolylineData line = new PolylineData();
     Point2D p1 = reactionWithLayout.getReactants().get(0).getLine().getEndPoint();
@@ -232,7 +300,7 @@ public class SbmlReactionParser extends SbmlBioEntityParser {
     Point2D center = new Point2D.Double((p1.getX() + p2.getX()) / 2, (p1.getY() + p2.getY()) / 2);
     line.addPoint(p1);
     line.addPoint(center);
-    NodeOperator operator ;
+    NodeOperator operator;
     if (reactionWithLayout instanceof HeterodimerAssociationReaction) {
       operator = new AssociationOperator();
     } else {
@@ -287,15 +355,38 @@ public class SbmlReactionParser extends SbmlBioEntityParser {
   }
 
   public void applyStyleToReaction(Reaction reactionWithLayout, LocalStyle style) {
-    if (style.getGroup().getFill() != null) {
-      Color color = getColorByColorDefinition(style.getGroup().getFill());
+    RenderGroup group = style.getGroup();
+    if (group.getFill() != null) {
+      Color color = getColorByColorDefinition(group.getFill());
       for (AbstractNode node : reactionWithLayout.getNodes()) {
         node.getLine().setColor(color);
       }
     }
-    if (style.getGroup().getStrokeWidth() != null) {
+    if (group.isSetStrokeWidth()) {
       for (AbstractNode node : reactionWithLayout.getNodes()) {
-        node.getLine().setWidth(style.getGroup().getStrokeWidth());
+        node.getLine().setWidth(group.getStrokeWidth());
+      }
+    }
+    if (group.isSetStroke()) {
+      try {
+        LineType type = LineType.valueOf(group.getStroke());
+        for (AbstractNode node : reactionWithLayout.getNodes()) {
+          node.getLine().setType(type);
+        }
+      } catch (Exception e) {
+        logger.warn(new ElementUtils().getElementTag(reactionWithLayout) + "Problematic line type: "
+            + group.getStroke(), e);
+      }
+    }
+    if (group.isSetEndHead()) {
+      try {
+        ArrowType type = ArrowType.valueOf(group.getEndHead());
+        for (Product node : reactionWithLayout.getProducts()) {
+          node.getLine().getEndAtd().setArrowType(type);
+        }
+      } catch (Exception e) {
+        logger.warn(new ElementUtils().getElementTag(reactionWithLayout) + "Problematic arrow type: "
+            + group.getStroke(), e);
       }
     }
   }
diff --git a/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/GenericSbmlToXmlParserTest.java b/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/GenericSbmlToXmlParserTest.java
index 10843990e9..f9e8649a5a 100644
--- a/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/GenericSbmlToXmlParserTest.java
+++ b/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/GenericSbmlToXmlParserTest.java
@@ -22,6 +22,7 @@ import lcsb.mapviewer.converter.ConverterParams;
 import lcsb.mapviewer.converter.IConverter;
 import lcsb.mapviewer.model.map.model.Model;
 import lcsb.mapviewer.model.map.model.ModelComparator;
+import lcsb.mapviewer.model.map.reaction.Reaction;
 
 @RunWith(Parameterized.class)
 public class GenericSbmlToXmlParserTest {
@@ -65,7 +66,7 @@ public class GenericSbmlToXmlParserTest {
           + filePath.getFileName().toString().substring(0, filePath.getFileName().toString().indexOf(".xml"));
       String xmlFilePath = pathWithouExtension.concat(".xml");
       converter.exportModelToFile(model, xmlFilePath);
-
+      
       Model model2 = converter.createModel(new ConverterParams().filename(xmlFilePath).sizeAutoAdjust(false));
       model2.setName(null);
 
diff --git a/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/reaction/AllSbmlReactionParserTests.java b/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/reaction/AllSbmlReactionParserTests.java
index d6e54657fa..b6b6039b1f 100644
--- a/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/reaction/AllSbmlReactionParserTests.java
+++ b/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/reaction/AllSbmlReactionParserTests.java
@@ -5,7 +5,8 @@ import org.junit.runners.Suite;
 import org.junit.runners.Suite.SuiteClasses;
 
 @RunWith(Suite.class)
-@SuiteClasses({ SbmlReactionExporterTest.class,
+@SuiteClasses({ SbmlReactionExportArrowType.class,
+    SbmlReactionExporterTest.class,
     SbmlReactionParserTest.class
 })
 public class AllSbmlReactionParserTests {
diff --git a/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/reaction/SbmlReactionExportArrowType.java b/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/reaction/SbmlReactionExportArrowType.java
new file mode 100644
index 0000000000..99be077087
--- /dev/null
+++ b/converter-sbml/src/test/java/lcsb/mapviewer/converter/model/sbml/reaction/SbmlReactionExportArrowType.java
@@ -0,0 +1,183 @@
+package lcsb.mapviewer.converter.model.sbml.reaction;
+
+import static org.junit.Assert.assertEquals;
+
+import java.awt.geom.Point2D;
+import java.io.ByteArrayInputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.log4j.Logger;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import lcsb.mapviewer.common.exception.NotImplementedException;
+import lcsb.mapviewer.converter.ConverterParams;
+import lcsb.mapviewer.converter.InvalidInputDataExecption;
+import lcsb.mapviewer.converter.model.celldesigner.CellDesignerXmlParser;
+import lcsb.mapviewer.converter.model.celldesigner.reaction.ReactionLineData;
+import lcsb.mapviewer.converter.model.sbml.SbmlExporter;
+import lcsb.mapviewer.converter.model.sbml.SbmlParser;
+import lcsb.mapviewer.model.graphics.PolylineData;
+import lcsb.mapviewer.model.map.InconsistentModelException;
+import lcsb.mapviewer.model.map.model.Model;
+import lcsb.mapviewer.model.map.model.ModelFullIndexed;
+import lcsb.mapviewer.model.map.reaction.AndOperator;
+import lcsb.mapviewer.model.map.reaction.AssociationOperator;
+import lcsb.mapviewer.model.map.reaction.NodeOperator;
+import lcsb.mapviewer.model.map.reaction.Product;
+import lcsb.mapviewer.model.map.reaction.Reactant;
+import lcsb.mapviewer.model.map.reaction.Reaction;
+import lcsb.mapviewer.model.map.reaction.ReactionComparator;
+import lcsb.mapviewer.model.map.reaction.SplitOperator;
+import lcsb.mapviewer.model.map.reaction.TruncationOperator;
+import lcsb.mapviewer.model.map.reaction.type.HeterodimerAssociationReaction;
+import lcsb.mapviewer.model.map.reaction.type.SimpleReactionInterface;
+import lcsb.mapviewer.model.map.reaction.type.TruncationReaction;
+import lcsb.mapviewer.model.map.reaction.type.TwoProductReactionInterface;
+import lcsb.mapviewer.model.map.reaction.type.TwoReactantReactionInterface;
+import lcsb.mapviewer.model.map.species.Ion;
+import lcsb.mapviewer.modelutils.map.ElementUtils;
+
+@RunWith(Parameterized.class)
+public class SbmlReactionExportArrowType {
+
+  @SuppressWarnings("unused")
+  private static Logger logger = Logger.getLogger(SbmlReactionExportArrowType.class);
+
+  SbmlParser parser = new SbmlParser();
+  SbmlExporter exporter = new SbmlExporter();
+
+  @Parameters(name = "{index} : {0}")
+  public static Collection<Object[]> data() throws Exception {
+    Collection<Object[]> data = new ArrayList<>();
+    ElementUtils eu = new ElementUtils();
+    for (Class<?> clazz : eu.getAvailableReactionSubclasses()) {
+      Model modelAfterSerialization = createModelForReactionType(clazz);
+
+      data.add(new Object[] { clazz.getSimpleName(), modelAfterSerialization });
+    }
+    return data;
+  }
+
+  private static Model createModelForReactionType(Class<?> clazz) throws InstantiationException, IllegalAccessException,
+      InvocationTargetException, NoSuchMethodException, InconsistentModelException, InvalidInputDataExecption {
+    Model model = new ModelFullIndexed(null);
+    model.setWidth(1000);
+    model.setHeight(1000);
+    Ion ion = new Ion("x1");
+    ion.setName("ion 1");
+    ion.setWidth(100);
+    ion.setHeight(100);
+    ion.setX(200);
+    ion.setY(50);
+    model.addElement(ion);
+
+    Ion ion2 = new Ion("x2");
+    ion2.setName("ion 1");
+    ion2.setWidth(100);
+    ion2.setHeight(100);
+    ion2.setX(200);
+    ion2.setY(250);
+    model.addElement(ion2);
+
+    Ion ion3 = new Ion("x3");
+    ion3.setName("ion 1");
+    ion3.setWidth(100);
+    ion3.setHeight(100);
+    ion3.setX(400);
+    ion3.setY(450);
+    model.addElement(ion3);
+
+    Reaction reaction = (Reaction) clazz.getConstructor().newInstance();
+    reaction.addReactant(createReactant(ion));
+    Product product = createProduct(ion2);
+    reaction.addProduct(product);
+    if (SimpleReactionInterface.class.isAssignableFrom(clazz)) {
+    } else if (TwoReactantReactionInterface.class.isAssignableFrom(clazz)) {
+      reaction.addReactant(createReactant(ion3));
+      NodeOperator operator;
+      if (HeterodimerAssociationReaction.class.isAssignableFrom(clazz)) {
+        operator = new AssociationOperator();
+      } else {
+        operator = new AndOperator();
+        ReactionLineData lineData = ReactionLineData.TRIGGER;
+        product.getLine().getEndAtd().setArrowLineType(lineData.getLineType());
+        product.getLine().getEndAtd().setArrowType(lineData.getProductArrowType());
+        product.getLine().setType(lineData.getLineType());
+      }
+      operator.addInputs(reaction.getReactants());
+      operator.setLine(new PolylineData(new Point2D.Double(100, 100), new Point2D.Double(100, 200)));
+      reaction.addNode(operator);
+    } else if (TwoProductReactionInterface.class.isAssignableFrom(clazz)) {
+      reaction.addProduct(createProduct(ion3));
+      NodeOperator operator = new SplitOperator();
+      if (TruncationReaction.class.isAssignableFrom(clazz)) {
+        operator = new TruncationOperator();
+      }
+      operator.addOutputs(reaction.getProducts());
+      operator.setLine(new PolylineData(new Point2D.Double(10, 10), new Point2D.Double(10, 20)));
+      reaction.addNode(operator);
+    } else {
+      throw new NotImplementedException();
+    }
+    model.addReaction(reaction);
+
+    CellDesignerXmlParser cellDesignerXmlParser = new CellDesignerXmlParser();
+    String xmlString = cellDesignerXmlParser.toXml(model);
+    Model modelAfterSerialization = cellDesignerXmlParser
+        .createModel(new ConverterParams().inputStream(new ByteArrayInputStream(xmlString.getBytes())));
+    return modelAfterSerialization;
+  }
+
+  private static Reactant createReactant(Ion ion) {
+    Reactant result = new Reactant(ion);
+    Point2D point = ion.getCenter();
+    point.setLocation(point.getX() + 300, point.getY());
+    result.setLine(new PolylineData(ion.getCenter(), point));
+    return result;
+  }
+
+  private static Product createProduct(Ion ion) {
+    Product result = new Product(ion);
+    Point2D point = ion.getCenter();
+    point.setLocation(point.getX() + 300, point.getY());
+    result.setLine(new PolylineData(ion.getCenter(), point));
+    return result;
+  }
+
+  Model modelToBeTested;
+
+  public SbmlReactionExportArrowType(String name, Model model) {
+    modelToBeTested = model;
+  }
+
+  @Test
+  public void test() throws Exception {
+    try {
+      Model modelAfterSerialization = getModelAfterSerializing(modelToBeTested);
+
+      ReactionComparator reactionComparator = new ReactionComparator();
+
+      Reaction originalReaction = modelToBeTested.getReactions().iterator().next();
+      Reaction afterSerializationReaction = modelAfterSerialization.getReactions().iterator().next();
+      
+      afterSerializationReaction.setIdReaction(originalReaction.getIdReaction());
+      assertEquals(0, reactionComparator.compare(originalReaction, afterSerializationReaction));
+    } catch (Exception e) {
+      e.printStackTrace();
+      throw e;
+    }
+  }
+
+  private Model getModelAfterSerializing(Model originalModel) throws Exception {
+    String xml = exporter.toXml(originalModel);
+    ByteArrayInputStream stream = new ByteArrayInputStream(xml.getBytes("UTF-8"));
+    Model result = parser.createModel(new ConverterParams().inputStream(stream));
+    return result;
+  }
+
+}
-- 
GitLab