From b04fef5da23e5563c8f9ae05e1af135b17d31b34 Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Thu, 3 Jan 2019 12:14:35 +0100
Subject: [PATCH] layout also layout modification residues if present and
 without layout

---
 .../layout/ApplySimpleLayoutModelCommand.java | 79 ++++++++++++++++
 .../ApplySimpleLayoutModelCommandTest.java    | 93 +++++++++++++++++++
 .../model/map/species/AntisenseRna.java       | 25 +++--
 .../mapviewer/model/map/species/Gene.java     | 10 +-
 .../mapviewer/model/map/species/Protein.java  |  8 +-
 .../lcsb/mapviewer/model/map/species/Rna.java | 16 +++-
 .../field/AbstractSiteModification.java       | 10 +-
 .../field/SpeciesWithBindingRegion.java       | 20 ++++
 .../field/SpeciesWithCodingRegion.java        | 20 ++++
 .../field/SpeciesWithModificationResidue.java | 15 +++
 .../field/SpeciesWithModificationSite.java    | 13 +++
 .../SpeciesWithProteinBindingDomain.java      | 13 +++
 .../field/SpeciesWithRegulatoryRegion.java    | 13 +++
 .../map/species/field/SpeciesWithResidue.java | 20 ++++
 .../field/SpeciesWithTranscriptionSite.java   | 13 +++
 15 files changed, 354 insertions(+), 14 deletions(-)
 create mode 100644 model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithBindingRegion.java
 create mode 100644 model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithCodingRegion.java
 create mode 100644 model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithModificationResidue.java
 create mode 100644 model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithModificationSite.java
 create mode 100644 model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithProteinBindingDomain.java
 create mode 100644 model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithRegulatoryRegion.java
 create mode 100644 model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithResidue.java
 create mode 100644 model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithTranscriptionSite.java

diff --git a/model-command/src/main/java/lcsb/mapviewer/commands/layout/ApplySimpleLayoutModelCommand.java b/model-command/src/main/java/lcsb/mapviewer/commands/layout/ApplySimpleLayoutModelCommand.java
index c2edd349dc..5881f26413 100644
--- a/model-command/src/main/java/lcsb/mapviewer/commands/layout/ApplySimpleLayoutModelCommand.java
+++ b/model-command/src/main/java/lcsb/mapviewer/commands/layout/ApplySimpleLayoutModelCommand.java
@@ -2,6 +2,7 @@ package lcsb.mapviewer.commands.layout;
 
 import java.awt.geom.Dimension2D;
 import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -14,6 +15,7 @@ import java.util.Set;
 import org.apache.log4j.Logger;
 
 import lcsb.mapviewer.commands.CommandExecutionException;
+import lcsb.mapviewer.common.Configuration;
 import lcsb.mapviewer.common.exception.InvalidArgumentException;
 import lcsb.mapviewer.common.exception.NotImplementedException;
 import lcsb.mapviewer.common.geometry.DoubleDimension;
@@ -40,6 +42,8 @@ import lcsb.mapviewer.model.map.reaction.type.TruncationReaction;
 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 ApplySimpleLayoutModelCommand extends ApplyLayoutModelCommand {
   /**
@@ -341,9 +345,84 @@ public class ApplySimpleLayoutModelCommand extends ApplyLayoutModelCommand {
         element.setX(elementCenterX - SPECIES_WIDTH / 2);
         element.setY(elementCenterY - SPECIES_HEIGHT / 2);
         index++;
+        if (element instanceof SpeciesWithModificationResidue) {
+          modifyElementModificationResiduesLocation((SpeciesWithModificationResidue) element);
+        }
+      }
+    }
+
+  }
+
+  private void modifyElementModificationResiduesLocation(SpeciesWithModificationResidue element) {
+    for (ModificationResidue mr : element.getModificationResidues()) {
+      List<Point2D> usedPoints = new ArrayList<>();
+      for (ModificationResidue inUseModification : element.getModificationResidues()) {
+        if (inUseModification.getPosition() != null) {
+          usedPoints.add(inUseModification.getPosition());
+        }
       }
+      mr.setPosition(findFarthestEmptyPositionOnRectangle(((Species) element).getBorder(), usedPoints));
     }
+  }
 
+  Point2D findFarthestEmptyPositionOnRectangle(Rectangle2D border, List<Point2D> usedPoints) {
+    // list of points reduced to one dimension
+    // every point is taking two position - so we can easier solve wrapping issues
+    List<Double> linearizedPoints = new ArrayList<>();
+    // there are two "guarding" points at the beginning and the end
+    linearizedPoints.add(0.0);
+    linearizedPoints.add(4 * (border.getWidth() + border.getHeight()));
+
+    for (Point2D point : usedPoints) {
+      Double linearizedPoint = null;
+      if (Math.abs(point.getY() - border.getMinY()) < Configuration.EPSILON) {
+        // upper edge
+        linearizedPoint = point.getX() - border.getMinX();
+      } else if (Math.abs(point.getX() - border.getMaxX()) < Configuration.EPSILON) {
+        // right edge
+        linearizedPoint = point.getY() - border.getMinY() + border.getWidth();
+      } else if (Math.abs(point.getY() - border.getMaxY()) < Configuration.EPSILON) {
+        // bottom edge
+        linearizedPoint = border.getMaxX() - point.getX() + border.getWidth() + border.getHeight();
+      } else if (Math.abs(point.getX() - border.getMinX()) < Configuration.EPSILON) {
+        // left edge
+        linearizedPoint = border.getMaxY() - point.getY() + border.getWidth() + border.getHeight() + border.getWidth();
+      } else {
+        logger.warn("Cannot align modification position on the species border");
+      }
+      linearizedPoints.add(linearizedPoint);
+      linearizedPoints.add(linearizedPoint + 2 * (border.getWidth() + border.getHeight()));
+
+    }
+    Collections.sort(linearizedPoints);
+
+    Double longest = linearizedPoints.get(1) - linearizedPoints.get(0);
+    Double position = (linearizedPoints.get(1) + linearizedPoints.get(0)) / 2;
+    for (int i = 1; i < linearizedPoints.size() - 1; i++) {
+      if (longest < linearizedPoints.get(i + 1) - linearizedPoints.get(i)) {
+        longest = linearizedPoints.get(i + 1) - linearizedPoints.get(i);
+        position = (linearizedPoints.get(i + 1) + linearizedPoints.get(i)) / 2;
+      }
+    }
+
+    if (position > 2 * (border.getWidth() + border.getHeight())) {
+      position -= 2 * (border.getWidth() + border.getHeight());
+    }
+
+    if (position < border.getWidth()) {
+      // upper edge
+      return new Point2D.Double(border.getMinX() + position, border.getMinY());
+    } else if (position < border.getWidth() + border.getHeight()) {
+      // right edge
+      return new Point2D.Double(border.getMaxX(), border.getMinY() + position - border.getWidth());
+    } else if (position < border.getWidth() + border.getHeight() + border.getWidth()) {
+      // bottom edge
+      return new Point2D.Double(border.getMinX() + position - border.getHeight() - border.getWidth(), border.getMaxY());
+    } else {
+      // left edge
+      return new Point2D.Double(border.getMinX(),
+          border.getMinY() + position - border.getWidth() - border.getHeight() - border.getWidth());
+    }
   }
 
   protected void modifyModelSize(Model model, Point2D minPoint, Dimension2D dimension) {
diff --git a/model-command/src/test/java/lcsb/mapviewer/commands/layout/ApplySimpleLayoutModelCommandTest.java b/model-command/src/test/java/lcsb/mapviewer/commands/layout/ApplySimpleLayoutModelCommandTest.java
index 4acc6bcc88..8458345aab 100644
--- a/model-command/src/test/java/lcsb/mapviewer/commands/layout/ApplySimpleLayoutModelCommandTest.java
+++ b/model-command/src/test/java/lcsb/mapviewer/commands/layout/ApplySimpleLayoutModelCommandTest.java
@@ -7,7 +7,9 @@ import static org.junit.Assert.assertTrue;
 
 import java.awt.geom.Dimension2D;
 import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -26,6 +28,7 @@ import lcsb.mapviewer.model.map.species.Element;
 import lcsb.mapviewer.model.map.species.GenericProtein;
 import lcsb.mapviewer.model.map.species.Protein;
 import lcsb.mapviewer.model.map.species.Species;
+import lcsb.mapviewer.model.map.species.field.Residue;
 
 public class ApplySimpleLayoutModelCommandTest {
 
@@ -229,6 +232,38 @@ public class ApplySimpleLayoutModelCommandTest {
     assertTrue(p2.contains(p1));
   }
 
+  @Test
+  public void testModifyElementModificationResidueLocation() {
+    ApplySimpleLayoutModelCommand layoutModelCommand = new ApplySimpleLayoutModelCommand(null);
+    GenericProtein p1 = createProtein();
+    p1.addResidue(new Residue());
+    Set<Element> elements = new HashSet<>();
+    elements.add(p1);
+
+    layoutModelCommand.modifyElementLocation(elements, null, new Point2D.Double(100, 100),
+        new DoubleDimension(200, 200));
+
+    assertNotNull(p1.getModificationResidues().get(0).getPosition());
+  }
+
+  @Test
+  public void testModifyElementModificationResidueLocationWhenElementHasLayout() {
+    ApplySimpleLayoutModelCommand layoutModelCommand = new ApplySimpleLayoutModelCommand(null);
+    GenericProtein p1 = createProtein();
+    p1.setX(10);
+    p1.setY(10);
+    p1.setWidth(10);
+    p1.setHeight(10);
+    p1.addResidue(new Residue());
+    Set<Element> elements = new HashSet<>();
+    elements.add(p1);
+
+    layoutModelCommand.modifyElementLocation(elements, null, new Point2D.Double(100, 100),
+        new DoubleDimension(200, 200));
+
+    assertNotNull(p1.getModificationResidues().get(0).getPosition());
+  }
+
   @Test
   public void testModifyElementLocationInsideCompartmentInsideStaticCompartment() {
     ApplySimpleLayoutModelCommand layoutModelCommand = new ApplySimpleLayoutModelCommand(null);
@@ -262,4 +297,62 @@ public class ApplySimpleLayoutModelCommandTest {
     assertTrue(staticCompartment.contains(compartmentToLayout));
   }
 
+  @Test
+  public void testFindFarthestEmptyPositionOnRectangleWithEmptyPoints() {
+    ApplySimpleLayoutModelCommand layoutModelCommand = new ApplySimpleLayoutModelCommand(null);
+    Rectangle2D rect = new Rectangle2D.Double(10, 20, 100, 30);
+
+    assertNotNull(layoutModelCommand.findFarthestEmptyPositionOnRectangle(rect, new ArrayList<>()));
+  }
+
+  @Test
+  public void testFindFarthestEmptyPositionOnRectangleSinglePoint() {
+    ApplySimpleLayoutModelCommand layoutModelCommand = new ApplySimpleLayoutModelCommand(null);
+    Rectangle2D rect = new Rectangle2D.Double(10, 20, 100, 30);
+
+    List<Point2D> points = Arrays.asList(new Point2D[] { new Point2D.Double(55, rect.getMinY()) });
+    Point2D resultPoint = layoutModelCommand.findFarthestEmptyPositionOnRectangle(rect, points);
+    assertNotNull(resultPoint);
+    assertEquals("Expected point on lower edge", rect.getMaxY(), resultPoint.getY(), Configuration.EPSILON);
+
+    points = Arrays.asList(new Point2D[] { new Point2D.Double(55, rect.getMaxY()) });
+    resultPoint = layoutModelCommand.findFarthestEmptyPositionOnRectangle(rect, points);
+    assertNotNull(resultPoint);
+    assertEquals("Expected point on upper edge", rect.getMinY(), resultPoint.getY(), Configuration.EPSILON);
+
+    points = Arrays.asList(new Point2D[] { new Point2D.Double(rect.getMinX(), 35) });
+    resultPoint = layoutModelCommand.findFarthestEmptyPositionOnRectangle(rect, points);
+    assertNotNull(resultPoint);
+    assertEquals(rect.getMaxX(), resultPoint.getX(), Configuration.EPSILON);
+
+    points = Arrays.asList(new Point2D[] { new Point2D.Double(rect.getMaxX(), 35) });
+    resultPoint = layoutModelCommand.findFarthestEmptyPositionOnRectangle(rect, points);
+    assertNotNull(resultPoint);
+    assertEquals("Expected point on left edge", rect.getMinX(), resultPoint.getX(), Configuration.EPSILON);
+  }
+
+  @Test
+  public void testFindFarthestEmptyPositionOnRectangleWithTwoPoints() {
+    ApplySimpleLayoutModelCommand layoutModelCommand = new ApplySimpleLayoutModelCommand(null);
+    Rectangle2D rect = new Rectangle2D.Double(10, 20, 100, 30);
+
+    List<Point2D> points = Arrays
+        .asList(new Point2D[] { new Point2D.Double(55, rect.getMinY()), new Point2D.Double(55, rect.getMaxY()) });
+    Point2D resultPoint = layoutModelCommand.findFarthestEmptyPositionOnRectangle(rect, points);
+    assertNotNull(resultPoint);
+    assertFalse("Expected point shouldn't be on upper edge",
+        Math.abs(rect.getMinY() - resultPoint.getY()) < Configuration.EPSILON);
+    assertFalse("Expected point shouldn't be on lower edge",
+        Math.abs(rect.getMaxY() - resultPoint.getY()) < Configuration.EPSILON);
+
+    points = Arrays
+        .asList(new Point2D[] { new Point2D.Double(rect.getMinX(), 35), new Point2D.Double(rect.getMaxX(), 35) });
+    resultPoint = layoutModelCommand.findFarthestEmptyPositionOnRectangle(rect, points);
+    assertNotNull(resultPoint);
+    assertFalse("Expected point shouldn't be on left edge",
+        Math.abs(rect.getMinX() - resultPoint.getX()) < Configuration.EPSILON);
+    assertFalse("Expected point shouldn't be on right edge",
+        Math.abs(rect.getMaxX() - resultPoint.getX()) < Configuration.EPSILON);
+  }
+
 }
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/species/AntisenseRna.java b/model/src/main/java/lcsb/mapviewer/model/map/species/AntisenseRna.java
index 416802d7db..2542862cca 100644
--- a/model/src/main/java/lcsb/mapviewer/model/map/species/AntisenseRna.java
+++ b/model/src/main/java/lcsb/mapviewer/model/map/species/AntisenseRna.java
@@ -1,6 +1,7 @@
 package lcsb.mapviewer.model.map.species;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
 import javax.persistence.DiscriminatorValue;
@@ -17,6 +18,9 @@ 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.SpeciesWithCodingRegion;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithModificationSite;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithProteinBindingDomain;
 
 /**
  * Entity representing antisense rna element on the map.
@@ -26,7 +30,8 @@ import lcsb.mapviewer.model.map.species.field.ProteinBindingDomain;
  */
 @Entity
 @DiscriminatorValue("ANTISENSE_RNA")
-public class AntisenseRna extends Species {
+public class AntisenseRna extends Species
+    implements SpeciesWithCodingRegion, SpeciesWithModificationSite, SpeciesWithProteinBindingDomain {
 
   /**
    * 
@@ -41,7 +46,7 @@ public class AntisenseRna extends Species {
   @OneToMany(mappedBy = "species")
   @LazyCollection(LazyCollectionOption.FALSE)
   private List<ModificationResidue> regions = new ArrayList<>();
-  
+
   /**
    * Empty constructor required by hibernate.
    */
@@ -81,19 +86,22 @@ public class AntisenseRna extends Species {
     regions.add(antisenseRnaRegion);
     antisenseRnaRegion.setSpecies(this);
   }
-  
+
+  @Override
   public void addCodingRegion(CodingRegion codingRegion) {
     this.addModificationResidue(codingRegion);
   }
-  
+
+  @Override
   public void addProteinBindingDomain(ProteinBindingDomain codingRegion) {
     this.addModificationResidue(codingRegion);
   }
-  
+
+  @Override
   public void addModificationSite(ModificationSite codingRegion) {
     this.addModificationResidue(codingRegion);
   }
-  
+
   @Override
   public AntisenseRna copy() {
     if (this.getClass() == AntisenseRna.class) {
@@ -111,6 +119,11 @@ public class AntisenseRna extends Species {
     return regions;
   }
 
+  @Override
+  public Collection<ModificationResidue> getModificationResidues() {
+    return getRegions();
+  }
+
   /**
    * @param regions
    *          the regions to set
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/species/Gene.java b/model/src/main/java/lcsb/mapviewer/model/map/species/Gene.java
index d5bc6be129..f27bfbc731 100644
--- a/model/src/main/java/lcsb/mapviewer/model/map/species/Gene.java
+++ b/model/src/main/java/lcsb/mapviewer/model/map/species/Gene.java
@@ -18,6 +18,10 @@ 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.RegulatoryRegion;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithCodingRegion;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithModificationSite;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithRegulatoryRegion;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithTranscriptionSite;
 import lcsb.mapviewer.model.map.species.field.TranscriptionSite;
 
 /**
@@ -28,7 +32,7 @@ import lcsb.mapviewer.model.map.species.field.TranscriptionSite;
  */
 @Entity
 @DiscriminatorValue("GENE")
-public class Gene extends Species {
+public class Gene extends Species implements SpeciesWithCodingRegion, SpeciesWithModificationSite, SpeciesWithRegulatoryRegion, SpeciesWithTranscriptionSite {
   
   @SuppressWarnings("unused")
   private static Logger logger = Logger.getLogger(Gene.class);
@@ -87,18 +91,22 @@ public class Gene extends Species {
 
   }
 
+  @Override
   public void addCodingRegion(CodingRegion codingRegion) {
     this.addModificationResidue(codingRegion);
   }
 
+  @Override
   public void addModificationSite(ModificationSite modificationSite) {
     this.addModificationResidue(modificationSite);
   }
 
+  @Override
   public void addRegulatoryRegion(RegulatoryRegion regulatoryRegion) {
     this.addModificationResidue(regulatoryRegion);
   }
 
+  @Override
   public void addTranscriptionSite(TranscriptionSite transcriptionSite) {
     this.addModificationResidue(transcriptionSite);
   }
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/species/Protein.java b/model/src/main/java/lcsb/mapviewer/model/map/species/Protein.java
index 9a52c46031..31d26cd609 100644
--- a/model/src/main/java/lcsb/mapviewer/model/map/species/Protein.java
+++ b/model/src/main/java/lcsb/mapviewer/model/map/species/Protein.java
@@ -15,6 +15,8 @@ import org.hibernate.annotations.LazyCollectionOption;
 import lcsb.mapviewer.model.map.species.field.BindingRegion;
 import lcsb.mapviewer.model.map.species.field.ModificationResidue;
 import lcsb.mapviewer.model.map.species.field.Residue;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithBindingRegion;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithResidue;
 
 /**
  * Entity representing protein element on the map.
@@ -24,7 +26,7 @@ import lcsb.mapviewer.model.map.species.field.Residue;
  */
 @Entity
 @DiscriminatorValue("PROTEIN")
-public abstract class Protein extends Species {
+public abstract class Protein extends Species implements SpeciesWithBindingRegion, SpeciesWithResidue {
 
   /**
    * 
@@ -43,7 +45,7 @@ public abstract class Protein extends Species {
   @OneToMany(mappedBy = "species", orphanRemoval = true)
   @LazyCollection(LazyCollectionOption.FALSE)
   private List<ModificationResidue> modificationResidues = new ArrayList<>();
-  
+
   /**
    * Empty constructor required by hibernate.
    */
@@ -85,10 +87,12 @@ public abstract class Protein extends Species {
     modificationResidue.setSpecies(this);
   }
 
+  @Override
   public void addBindingRegion(BindingRegion bindingRegion) {
     this.addModificationResidue(bindingRegion);
   }
 
+  @Override
   public void addResidue(Residue residue) {
     this.addModificationResidue(residue);
   }
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/species/Rna.java b/model/src/main/java/lcsb/mapviewer/model/map/species/Rna.java
index cbaedd90b4..bed3ed07c3 100644
--- a/model/src/main/java/lcsb/mapviewer/model/map/species/Rna.java
+++ b/model/src/main/java/lcsb/mapviewer/model/map/species/Rna.java
@@ -1,6 +1,7 @@
 package lcsb.mapviewer.model.map.species;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
 import javax.persistence.DiscriminatorValue;
@@ -17,6 +18,9 @@ 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.SpeciesWithCodingRegion;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithModificationSite;
+import lcsb.mapviewer.model.map.species.field.SpeciesWithProteinBindingDomain;
 
 /**
  * Entity representing rna element on the map.
@@ -26,7 +30,7 @@ import lcsb.mapviewer.model.map.species.field.ProteinBindingDomain;
  */
 @Entity
 @DiscriminatorValue("RNA")
-public class Rna extends Species {
+public class Rna extends Species implements SpeciesWithCodingRegion, SpeciesWithProteinBindingDomain, SpeciesWithModificationSite {
 
   /**
    * 
@@ -81,15 +85,18 @@ public class Rna extends Species {
     regions.add(antisenseRnaRegion);
     antisenseRnaRegion.setSpecies(this);
   }
-  
+
+  @Override
   public void addCodingRegion(CodingRegion codingRegion) {
     this.addModificationResidue(codingRegion);
   }
   
+  @Override
   public void addProteinBindingDomain(ProteinBindingDomain codingRegion) {
     this.addModificationResidue(codingRegion);
   }
   
+  @Override
   public void addModificationSite(ModificationSite codingRegion) {
     this.addModificationResidue(codingRegion);
   }
@@ -110,6 +117,11 @@ public class Rna extends Species {
   public List<ModificationResidue> getRegions() {
     return regions;
   }
+  
+  @Override
+  public Collection<ModificationResidue> getModificationResidues() {
+    return getRegions();
+  }
 
   /**
    * @param regions
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/species/field/AbstractSiteModification.java b/model/src/main/java/lcsb/mapviewer/model/map/species/field/AbstractSiteModification.java
index 86ef4c8d19..2f693653e6 100644
--- a/model/src/main/java/lcsb/mapviewer/model/map/species/field/AbstractSiteModification.java
+++ b/model/src/main/java/lcsb/mapviewer/model/map/species/field/AbstractSiteModification.java
@@ -32,7 +32,7 @@ public abstract class AbstractSiteModification extends ModificationResidue {
   }
 
   public AbstractSiteModification(String modificationId) {
-   super(modificationId);
+    super(modificationId);
   }
 
   public ModificationState getState() {
@@ -46,8 +46,12 @@ public abstract class AbstractSiteModification extends ModificationResidue {
   @Override
   public String toString() {
     DecimalFormat format = new DecimalFormat("#.##");
-    String result = getName() + "," + getState() + ",Point2D[" + format.format(getPosition().getX()) + ","
-        + format.format(getPosition().getY()) + "]";
+    String positionString = "Point2D[null]";
+    if (getPosition() != null) {
+      positionString = "Point2D[" + format.format(getPosition().getX()) + "," + format.format(getPosition().getY())
+          + "]";
+    }
+    String result = getName() + "," + getState() + "," + positionString;
     return result;
   }
 
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithBindingRegion.java b/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithBindingRegion.java
new file mode 100644
index 0000000000..0433dc213a
--- /dev/null
+++ b/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithBindingRegion.java
@@ -0,0 +1,20 @@
+package lcsb.mapviewer.model.map.species.field;
+
+/**
+ * Interface implemented by species that support {@link BindingRegion}
+ * modification.
+ * 
+ * @author Piotr Gawron
+ *
+ */
+public interface SpeciesWithBindingRegion extends SpeciesWithModificationResidue {
+
+  /**
+   * Adds a {@link BindingRegion} to the species.
+   * 
+   * @param bindingRegion
+   *          {@link BindingRegion} to add
+   */
+  void addBindingRegion(BindingRegion bindingRegion);
+
+}
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithCodingRegion.java b/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithCodingRegion.java
new file mode 100644
index 0000000000..2941294a8b
--- /dev/null
+++ b/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithCodingRegion.java
@@ -0,0 +1,20 @@
+package lcsb.mapviewer.model.map.species.field;
+
+/**
+ * Interface implemented by species that support {@link CodingRegion}
+ * modification.
+ * 
+ * @author Piotr Gawron
+ *
+ */
+public interface SpeciesWithCodingRegion extends SpeciesWithModificationResidue {
+
+  /**
+   * Adds a {@link CodingRegion} to the species.
+   * 
+   * @param codingRegion
+   *          {@link CodingRegion} to add
+   */
+  void addCodingRegion(CodingRegion codingRegion);
+
+}
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithModificationResidue.java b/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithModificationResidue.java
new file mode 100644
index 0000000000..692fa3e956
--- /dev/null
+++ b/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithModificationResidue.java
@@ -0,0 +1,15 @@
+package lcsb.mapviewer.model.map.species.field;
+
+import java.util.Collection;
+
+/**
+ * Interface implemented by species that support {@link ModificationResidue}.
+ * 
+ * @author Piotr Gawron
+ *
+ */
+public interface SpeciesWithModificationResidue {
+
+  Collection<ModificationResidue> getModificationResidues();
+
+}
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithModificationSite.java b/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithModificationSite.java
new file mode 100644
index 0000000000..2cc929dde2
--- /dev/null
+++ b/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithModificationSite.java
@@ -0,0 +1,13 @@
+package lcsb.mapviewer.model.map.species.field;
+
+public interface SpeciesWithModificationSite extends SpeciesWithModificationResidue {
+
+  /**
+   * Adds a {@link ModificationSite } to the species.
+   * 
+   * @param codingRegion
+   *          {@link ModificationSite } to add
+   */
+  void addModificationSite (ModificationSite  modificationSite );
+
+}
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithProteinBindingDomain.java b/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithProteinBindingDomain.java
new file mode 100644
index 0000000000..ef8c524ea7
--- /dev/null
+++ b/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithProteinBindingDomain.java
@@ -0,0 +1,13 @@
+package lcsb.mapviewer.model.map.species.field;
+
+public interface SpeciesWithProteinBindingDomain extends SpeciesWithModificationResidue {
+
+  /**
+   * Adds a {@link ProteinBindingDomain} to the species.
+   * 
+   * @param proteinBindingDomain
+   *          {@link ProteinBindingDomain} to add
+   */
+  void addProteinBindingDomain(ProteinBindingDomain proteinBindingDomain);
+
+}
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithRegulatoryRegion.java b/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithRegulatoryRegion.java
new file mode 100644
index 0000000000..cbb0526b68
--- /dev/null
+++ b/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithRegulatoryRegion.java
@@ -0,0 +1,13 @@
+package lcsb.mapviewer.model.map.species.field;
+
+public interface SpeciesWithRegulatoryRegion extends SpeciesWithModificationResidue {
+
+  /**
+   * Adds a {@link RegulatoryRegion} to the species.
+   * 
+   * @param regulatoryRegion
+   *          {@link RegulatoryRegion} to add
+   */
+  void addRegulatoryRegion(RegulatoryRegion regulatoryRegion);
+
+}
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithResidue.java b/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithResidue.java
new file mode 100644
index 0000000000..46a1c305ee
--- /dev/null
+++ b/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithResidue.java
@@ -0,0 +1,20 @@
+package lcsb.mapviewer.model.map.species.field;
+
+/**
+ * Interface implemented by species that support {@link Residue}
+ * modification.
+ * 
+ * @author Piotr Gawron
+ *
+ */
+public interface SpeciesWithResidue extends SpeciesWithModificationResidue {
+
+  /**
+   * Adds a {@link Residue} to the species.
+   * 
+   * @param residue
+   *          {@link Residue} to add
+   */
+  void addResidue(Residue residue);
+
+}
diff --git a/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithTranscriptionSite.java b/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithTranscriptionSite.java
new file mode 100644
index 0000000000..c0f7c2ddce
--- /dev/null
+++ b/model/src/main/java/lcsb/mapviewer/model/map/species/field/SpeciesWithTranscriptionSite.java
@@ -0,0 +1,13 @@
+package lcsb.mapviewer.model.map.species.field;
+
+public interface SpeciesWithTranscriptionSite extends SpeciesWithModificationResidue {
+
+  /**
+   * Adds a {@link TranscriptionSite} to the species.
+   * 
+   * @param transcriptionSite
+   *          {@link TranscriptionSite} to add
+   */
+  void addTranscriptionSite(TranscriptionSite transcriptionSite);
+
+}
-- 
GitLab