Merge branch 'releases/Release_2_11_3_Branch'
[jalview.git] / src / jalview / datamodel / SequenceGroup.java
index a1579c5..7e53c46 100755 (executable)
  */
 package jalview.datamodel;
 
+import java.awt.Color;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
 import jalview.analysis.AAFrequency;
 import jalview.analysis.Conservation;
 import jalview.renderer.ResidueShader;
 import jalview.renderer.ResidueShaderI;
 import jalview.schemes.ColourSchemeI;
 
-import java.awt.Color;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
 /**
  * Collects a set contiguous ranges on a set of sequences
  * 
@@ -39,6 +43,26 @@ import java.util.Map;
  */
 public class SequenceGroup implements AnnotatedCollectionI
 {
+  // TODO ideally this event notification functionality should be separated into
+  // a
+  // subclass of ViewportProperties similarly to ViewportRanges. Done here as
+  // quick fix for JAL-2665
+  public static final String SEQ_GROUP_CHANGED = "Sequence group changed";
+
+  protected PropertyChangeSupport changeSupport = new PropertyChangeSupport(
+          this);
+
+  public void addPropertyChangeListener(PropertyChangeListener listener)
+  {
+    changeSupport.addPropertyChangeListener(listener);
+  }
+
+  public void removePropertyChangeListener(PropertyChangeListener listener)
+  {
+    changeSupport.removePropertyChangeListener(listener);
+  }
+  // end of event notification functionality initialisation
+
   String groupName;
 
   String description;
@@ -52,6 +76,12 @@ public class SequenceGroup implements AnnotatedCollectionI
   boolean colourText = false;
 
   /**
+   * True if the group is defined as a group on the alignment, false if it is
+   * just a selection.
+   */
+  boolean isDefined = false;
+
+  /**
    * after Olivier's non-conserved only character display
    */
   boolean showNonconserved = false;
@@ -59,7 +89,7 @@ public class SequenceGroup implements AnnotatedCollectionI
   /**
    * group members
    */
-  private List<SequenceI> sequences = new ArrayList<SequenceI>();
+  private List<SequenceI> sequences;
 
   /**
    * representative sequence for this group (if any)
@@ -73,11 +103,15 @@ public class SequenceGroup implements AnnotatedCollectionI
    */
   public ResidueShaderI cs;
 
-  // start column (base 0)
-  int startRes = 0;
+  /**
+   * start column (base 0)
+   */
+  private int startRes = 0;
 
-  // end column (base 0)
-  int endRes = 0;
+  /**
+   * end column (base 0)
+   */
+  private int endRes = 0;
 
   public Color outlineColour = Color.black;
 
@@ -104,13 +138,23 @@ public class SequenceGroup implements AnnotatedCollectionI
    */
   private boolean normaliseSequenceLogo;
 
-  /**
-   * @return the includeAllConsSymbols
+  /*
+   * visibility of rows or represented rows covered by group
    */
-  public boolean isShowSequenceLogo()
-  {
-    return showSequenceLogo;
-  }
+  private boolean hidereps = false;
+
+  /*
+   * visibility of columns intersecting this group
+   */
+  private boolean hidecols = false;
+
+  AlignmentAnnotation consensus = null;
+
+  AlignmentAnnotation conservation = null;
+
+  private boolean showConsensusHistogram;
+
+  private AnnotatedCollectionI context;
 
   /**
    * Creates a new SequenceGroup object.
@@ -119,6 +163,7 @@ public class SequenceGroup implements AnnotatedCollectionI
   {
     groupName = "JGroup:" + this.hashCode();
     cs = new ResidueShader();
+    sequences = new ArrayList<>();
   }
 
   /**
@@ -161,7 +206,7 @@ public class SequenceGroup implements AnnotatedCollectionI
     this();
     if (seqsel != null)
     {
-      sequences = new ArrayList<SequenceI>();
+      sequences = new ArrayList<>();
       sequences.addAll(seqsel.sequences);
       if (seqsel.groupName != null)
       {
@@ -170,15 +215,20 @@ public class SequenceGroup implements AnnotatedCollectionI
       displayBoxes = seqsel.displayBoxes;
       displayText = seqsel.displayText;
       colourText = seqsel.colourText;
+
       startRes = seqsel.startRes;
       endRes = seqsel.endRes;
-      cs = seqsel.cs;
+      cs = new ResidueShader((ResidueShader) seqsel.cs);
       if (seqsel.description != null)
       {
         description = new String(seqsel.description);
       }
       hidecols = seqsel.hidecols;
       hidereps = seqsel.hidereps;
+      showNonconserved = seqsel.showNonconserved;
+      showSequenceLogo = seqsel.showSequenceLogo;
+      normaliseSequenceLogo = seqsel.normaliseSequenceLogo;
+      showConsensusHistogram = seqsel.showConsensusHistogram;
       idColour = seqsel.idColour;
       outlineColour = seqsel.outlineColour;
       seqrep = seqsel.seqrep;
@@ -195,6 +245,22 @@ public class SequenceGroup implements AnnotatedCollectionI
     }
   }
 
+  /**
+   * Constructor that copies the given list of sequences
+   * 
+   * @param seqs
+   */
+  public SequenceGroup(List<SequenceI> seqs)
+  {
+    this();
+    this.sequences.addAll(seqs);
+  }
+
+  public boolean isShowSequenceLogo()
+  {
+    return showSequenceLogo;
+  }
+
   public SequenceI[] getSelectionAsNewSequences(AlignmentI align)
   {
     int iSize = sequences.size();
@@ -204,18 +270,10 @@ public class SequenceGroup implements AnnotatedCollectionI
     for (int i = 0, ipos = 0; i < inorder.length; i++)
     {
       SequenceI seq = inorder[i];
-
-      seqs[ipos] = seq.getSubSequence(startRes, endRes + 1);
-      if (seqs[ipos] != null)
+      SequenceI seqipos = seqs[ipos] = seq.getSubSequence(startRes,
+              endRes + 1);
+      if (seqipos != null)
       {
-        seqs[ipos].setDescription(seq.getDescription());
-        seqs[ipos].setDBRefs(seq.getDBRefs());
-        seqs[ipos].setSequenceFeatures(seq.getSequenceFeatures());
-        if (seq.getDatasetSequence() != null)
-        {
-          seqs[ipos].setDatasetSequence(seq.getDatasetSequence());
-        }
-
         if (seq.getAnnotation() != null)
         {
           AlignmentAnnotation[] alann = align.getAlignmentAnnotation();
@@ -227,7 +285,7 @@ public class SequenceGroup implements AnnotatedCollectionI
             if (alann != null)
             {
               boolean found = false;
-              for (int pos = 0; pos < alann.length; pos++)
+              for (int pos = 0, np = alann.length; pos < np; pos++)
               {
                 if (alann[pos] == tocopy)
                 {
@@ -245,7 +303,13 @@ public class SequenceGroup implements AnnotatedCollectionI
             newannot.restrict(startRes, endRes);
             newannot.setSequenceRef(seqs[ipos]);
             newannot.adjustForAlignment();
-            seqs[ipos].addAlignmentAnnotation(newannot);
+            ContactMatrixI cm = seq
+                    .getContactMatrixFor(seq.getAnnotation()[a]);
+            if (cm != null)
+            {
+              seqs[ipos].addContactListFor(newannot, cm);
+            }
+            seqipos.addAlignmentAnnotation(newannot);
           }
         }
         ipos++;
@@ -311,7 +375,7 @@ public class SequenceGroup implements AnnotatedCollectionI
     }
     else
     {
-      List<SequenceI> allSequences = new ArrayList<SequenceI>();
+      List<SequenceI> allSequences = new ArrayList<>();
       for (SequenceI seq : sequences)
       {
         allSequences.add(seq);
@@ -471,6 +535,8 @@ public class SequenceGroup implements AnnotatedCollectionI
       if (s != null && !sequences.contains(s))
       {
         sequences.add(s);
+        changeSupport.firePropertyChange(SEQ_GROUP_CHANGED,
+                sequences.size() - 1, sequences.size());
       }
 
       if (recalc)
@@ -579,7 +645,8 @@ public class SequenceGroup implements AnnotatedCollectionI
     } catch (java.lang.OutOfMemoryError err)
     {
       // TODO: catch OOM
-      System.out.println("Out of memory loading groups: " + err);
+      jalview.bin.Console
+              .outPrintln("Out of memory loading groups: " + err);
     }
     return upd;
   }
@@ -595,8 +662,10 @@ public class SequenceGroup implements AnnotatedCollectionI
     conservation.description = "Conservation for group " + getName()
             + " less than " + consPercGaps + "% gaps";
     // preserve width if already set
-    int aWidth = (conservation.annotations != null) ? (endRes < conservation.annotations.length ? conservation.annotations.length
-            : endRes + 1)
+    int aWidth = (conservation.annotations != null)
+            ? (endRes < conservation.annotations.length
+                    ? conservation.annotations.length
+                    : endRes + 1)
             : endRes + 1;
     conservation.annotations = null;
     conservation.annotations = new Annotation[aWidth]; // should be alignment
@@ -616,8 +685,10 @@ public class SequenceGroup implements AnnotatedCollectionI
     consensus.description = "Percent Identity";
     consensusData = cnsns;
     // preserve width if already set
-    int aWidth = (consensus.annotations != null) ? (endRes < consensus.annotations.length ? consensus.annotations.length
-            : endRes + 1)
+    int aWidth = (consensus.annotations != null)
+            ? (endRes < consensus.annotations.length
+                    ? consensus.annotations.length
+                    : endRes + 1)
             : endRes + 1;
     consensus.annotations = null;
     consensus.annotations = new Annotation[aWidth]; // should be alignment width
@@ -664,6 +735,8 @@ public class SequenceGroup implements AnnotatedCollectionI
     synchronized (sequences)
     {
       sequences.remove(s);
+      changeSupport.firePropertyChange(SEQ_GROUP_CHANGED,
+              sequences.size() + 1, sequences.size());
 
       if (recalc)
       {
@@ -696,11 +769,15 @@ public class SequenceGroup implements AnnotatedCollectionI
   /**
    * Set the first column selected by this group. Runs from 0<=i<N_cols
    * 
-   * @param i
+   * @param newStart
    */
-  public void setStartRes(int i)
+  public void setStartRes(int newStart)
   {
-    startRes = i;
+    int before = startRes;
+    startRes = Math.max(0, newStart); // sanity check for negative start column
+                                      // positions
+    changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, startRes);
+
   }
 
   /**
@@ -710,7 +787,9 @@ public class SequenceGroup implements AnnotatedCollectionI
    */
   public void setEndRes(int i)
   {
+    int before = endRes;
     endRes = i;
+    changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, endRes);
   }
 
   /**
@@ -951,11 +1030,6 @@ public class SequenceGroup implements AnnotatedCollectionI
   }
 
   /**
-   * visibility of rows or represented rows covered by group
-   */
-  private boolean hidereps = false;
-
-  /**
    * set visibility of sequences covered by (if no sequence representative is
    * defined) or represented by this group.
    * 
@@ -977,11 +1051,6 @@ public class SequenceGroup implements AnnotatedCollectionI
   }
 
   /**
-   * visibility of columns intersecting this group
-   */
-  private boolean hidecols = false;
-
-  /**
    * set intended visibility of columns covered by this group
    * 
    * @param visibility
@@ -1015,7 +1084,7 @@ public class SequenceGroup implements AnnotatedCollectionI
   {
     SequenceGroup sgroup = new SequenceGroup(this);
     SequenceI[] insect = getSequencesInOrder(alignment);
-    sgroup.sequences = new ArrayList<SequenceI>();
+    sgroup.sequences = new ArrayList<>();
     for (int s = 0; insect != null && s < insect.length; s++)
     {
       if (map == null || map.containsKey(insect[s]))
@@ -1043,13 +1112,6 @@ public class SequenceGroup implements AnnotatedCollectionI
     this.showNonconserved = displayNonconserved;
   }
 
-  AlignmentAnnotation consensus = null, conservation = null;
-
-  /**
-   * flag indicating if consensus histogram should be rendered
-   */
-  private boolean showConsensusHistogram;
-
   /**
    * set this alignmentAnnotation object as the one used to render consensus
    * annotation
@@ -1146,9 +1208,10 @@ public class SequenceGroup implements AnnotatedCollectionI
     {
       if (consensus.annotations[i] != null)
       {
-        if (consensus.annotations[i].description.charAt(0) == '[')
+        String desc = consensus.annotations[i].description;
+        if (desc.length() > 1 && desc.charAt(0) == '[')
         {
-          seqs.append(consensus.annotations[i].description.charAt(1));
+          seqs.append(desc.charAt(1));
         }
         else
         {
@@ -1242,7 +1305,7 @@ public class SequenceGroup implements AnnotatedCollectionI
   {
     // TODO add in other methods like 'getAlignmentAnnotation(String label),
     // etc'
-    ArrayList<AlignmentAnnotation> annot = new ArrayList<AlignmentAnnotation>();
+    ArrayList<AlignmentAnnotation> annot = new ArrayList<>();
     synchronized (sequences)
     {
       for (SequenceI seq : sequences)
@@ -1274,42 +1337,16 @@ public class SequenceGroup implements AnnotatedCollectionI
   @Override
   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
   {
-    List<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
-    if (calcId == null)
-    {
-      return aa;
-    }
-    for (AlignmentAnnotation a : getAlignmentAnnotation())
-    {
-      if (calcId.equals(a.getCalcId()))
-      {
-        aa.add(a);
-      }
-    }
-    return aa;
+    return AlignmentAnnotation.findAnnotation(
+            Arrays.asList(getAlignmentAnnotation()), calcId);
   }
 
-  /**
-   * Returns a list of annotations that match the specified sequenceRef, calcId
-   * and label, ignoring null values.
-   * 
-   * @return list of AlignmentAnnotation objects
-   */
   @Override
   public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
           String calcId, String label)
   {
-    ArrayList<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
-    for (AlignmentAnnotation ann : getAlignmentAnnotation())
-    {
-      if (ann.getCalcId() != null && ann.getCalcId().equals(calcId)
-              && ann.sequenceRef != null && ann.sequenceRef == seq
-              && ann.label != null && ann.label.equals(label))
-      {
-        aa.add(ann);
-      }
-    }
-    return aa;
+    return AlignmentAnnotation.findAnnotations(
+            Arrays.asList(getAlignmentAnnotation()), seq, calcId, label);
   }
 
   /**
@@ -1320,17 +1357,8 @@ public class SequenceGroup implements AnnotatedCollectionI
    */
   public boolean hasAnnotation(String calcId)
   {
-    if (calcId != null && !"".equals(calcId))
-    {
-      for (AlignmentAnnotation a : getAlignmentAnnotation())
-      {
-        if (a.getCalcId() == calcId)
-        {
-          return true;
-        }
-      }
-    }
-    return false;
+    return AlignmentAnnotation
+            .hasAnnotation(Arrays.asList(getAlignmentAnnotation()), calcId);
   }
 
   /**
@@ -1340,20 +1368,52 @@ public class SequenceGroup implements AnnotatedCollectionI
   {
     synchronized (sequences)
     {
+      int before = sequences.size();
       sequences.clear();
+      changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before,
+              sequences.size());
     }
   }
 
-  private AnnotatedCollectionI context;
+  /**
+   * Sets the alignment or group context for this group, and whether it is
+   * defined as a group
+   * 
+   * @param ctx
+   *          the context for the group
+   * @param defined
+   *          whether the group is defined on the alignment or is just a
+   *          selection
+   * @throws IllegalArgumentException
+   *           if setting the context would result in a circular reference chain
+   */
+  public void setContext(AnnotatedCollectionI ctx, boolean defined)
+  {
+    setContext(ctx);
+    this.isDefined = defined;
+  }
 
   /**
-   * set the alignment or group context for this group
+   * Sets the alignment or group context for this group
    * 
-   * @param context
+   * @param ctx
+   *          the context for the group
+   * @throws IllegalArgumentException
+   *           if setting the context would result in a circular reference chain
    */
-  public void setContext(AnnotatedCollectionI context)
+  public void setContext(AnnotatedCollectionI ctx)
   {
-    this.context = context;
+    AnnotatedCollectionI ref = ctx;
+    while (ref != null)
+    {
+      if (ref == this || ref.getContext() == ctx)
+      {
+        throw new IllegalArgumentException(
+                "Circular reference in SequenceGroup.context");
+      }
+      ref = ref.getContext();
+    }
+    this.context = ctx;
   }
 
   /*
@@ -1367,6 +1427,11 @@ public class SequenceGroup implements AnnotatedCollectionI
     return context;
   }
 
+  public boolean isDefined()
+  {
+    return isDefined;
+  }
+
   public void setColourScheme(ColourSchemeI scheme)
   {
     if (cs == null)
@@ -1394,9 +1459,77 @@ public class SequenceGroup implements AnnotatedCollectionI
   @Override
   public boolean isNucleotide()
   {
-    if (context != null) {
+    if (context != null)
+    {
       return context.isNucleotide();
     }
     return false;
   }
+
+  /**
+   * @param seq
+   * @return true if seq is a member of the group
+   */
+
+  public boolean contains(SequenceI seq1)
+  {
+    return sequences.contains(seq1);
+  }
+
+  /**
+   * @param seq
+   * @param apos
+   * @return true if startRes<=apos and endRes>=apos and seq is in the group
+   */
+  public boolean contains(SequenceI seq, int apos)
+  {
+    return (startRes <= apos && endRes >= apos) && sequences.contains(seq);
+  }
+
+  ////
+  //// Contact Matrix Holder Boilerplate
+  ////
+  ContactMapHolder cmholder = new ContactMapHolder();
+
+  @Override
+  public Collection<ContactMatrixI> getContactMaps()
+  {
+    return cmholder.getContactMaps();
+  }
+
+  @Override
+  public ContactMatrixI getContactMatrixFor(AlignmentAnnotation ann)
+  {
+    return cmholder.getContactMatrixFor(ann);
+  }
+
+  @Override
+  public ContactListI getContactListFor(AlignmentAnnotation _aa, int column)
+  {
+    return cmholder.getContactListFor(_aa, column);
+  }
+
+  @Override
+  public AlignmentAnnotation addContactList(ContactMatrixI cm)
+  {
+    AlignmentAnnotation aa = cmholder.addContactList(cm);
+
+    Annotation _aa[] = new Annotation[getWidth()];
+    Annotation dummy = new Annotation(0.0f);
+    for (int i = 0; i < _aa.length; _aa[i++] = dummy)
+    {
+      ;
+    }
+    aa.annotations = _aa;
+    // TODO passing annotations back to context to be added
+    return aa;
+  }
+
+  @Override
+  public void addContactListFor(AlignmentAnnotation annotation,
+          ContactMatrixI cm)
+  {
+    cmholder.addContactListFor(annotation, cm);
+  }
+
 }