JAL-1115 change implementation to avoid possibility of deadlock
[jalview.git] / src / jalview / datamodel / Alignment.java
index 3f9f03f..d737bd5 100755 (executable)
@@ -22,13 +22,14 @@ package jalview.datamodel;
 
 import jalview.analysis.AlignmentUtils;
 import jalview.io.FastaFile;
+import jalview.util.Comparison;
 import jalview.util.MessageManager;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Hashtable;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -43,12 +44,11 @@ import java.util.Vector;
  */
 public class Alignment implements AlignmentI
 {
-  protected Alignment dataset;
+  private Alignment dataset;
 
   protected List<SequenceI> sequences;
 
-  protected List<SequenceGroup> groups = java.util.Collections
-          .synchronizedList(new ArrayList<SequenceGroup>());
+  protected List<SequenceGroup> groups;
 
   protected char gapCharacter = '-';
 
@@ -60,20 +60,21 @@ public class Alignment implements AlignmentI
 
   public boolean hasRNAStructure = false;
 
-  /** DOCUMENT ME!! */
   public AlignmentAnnotation[] annotations;
 
-  HiddenSequences hiddenSequences = new HiddenSequences(this);
+  HiddenSequences hiddenSequences;
 
   public Hashtable alignmentProperties;
 
-  private Set<AlignedCodonFrame> codonFrameList = new LinkedHashSet<AlignedCodonFrame>();
+  private List<AlignedCodonFrame> codonFrameList;
 
   private void initAlignment(SequenceI[] seqs)
   {
-    int i = 0;
+    groups = Collections.synchronizedList(new ArrayList<SequenceGroup>());
+    hiddenSequences = new HiddenSequences(this);
+    codonFrameList = new ArrayList<AlignedCodonFrame>();
 
-    if (jalview.util.Comparison.isNucleotide(seqs))
+    if (Comparison.isNucleotide(seqs))
     {
       type = NUCLEOTIDE;
     }
@@ -82,10 +83,9 @@ public class Alignment implements AlignmentI
       type = PROTEIN;
     }
 
-    sequences = java.util.Collections
-            .synchronizedList(new ArrayList<SequenceI>());
+    sequences = Collections.synchronizedList(new ArrayList<SequenceI>());
 
-    for (i = 0; i < seqs.length; i++)
+    for (int i = 0; i < seqs.length; i++)
     {
       sequences.add(seqs[i]);
     }
@@ -104,13 +104,15 @@ public class Alignment implements AlignmentI
       seqs[i] = new Sequence(seqs[i]);
     }
 
+    initAlignment(seqs);
+
     /*
-     * Share the same dataset sequence mappings (if any). TODO: find a better
-     * place for these to live (alignment dataset?).
+     * Share the same dataset sequence mappings (if any). 
      */
-    this.setCodonFrames(al.getCodonFrames());
-
-    initAlignment(seqs);
+    if (dataset == null && al.getDataset() == null)
+    {
+      this.setCodonFrames(al.getCodonFrames());
+    }
   }
 
   /**
@@ -987,7 +989,7 @@ public class Alignment implements AlignmentI
   }
 
   @Override
-  public void setDataset(Alignment data)
+  public void setDataset(AlignmentI data)
   {
     if (dataset == null && data == null)
     {
@@ -995,7 +997,12 @@ public class Alignment implements AlignmentI
     }
     else if (dataset == null && data != null)
     {
-      dataset = data;
+      if (!(data instanceof Alignment))
+      {
+        throw new Error(
+                "Implementation Error: jalview.datamodel.Alignment does not yet support other implementations of AlignmentI as its dataset reference");
+      }
+      dataset = (Alignment) data;
       for (int i = 0; i < getHeight(); i++)
       {
         SequenceI currentSeq = getSequenceAt(i);
@@ -1281,8 +1288,8 @@ public class Alignment implements AlignmentI
   @Override
   public void addCodonFrame(AlignedCodonFrame codons)
   {
-    Set<AlignedCodonFrame> acfs = getCodonFrames();
-    if (codons != null && acfs != null)
+    List<AlignedCodonFrame> acfs = getCodonFrames();
+    if (codons != null && acfs != null && !acfs.contains(codons))
     {
       acfs.add(codons);
     }
@@ -1319,7 +1326,7 @@ public class Alignment implements AlignmentI
    * @see jalview.datamodel.AlignmentI#setCodonFrames()
    */
   @Override
-  public void setCodonFrames(Set<AlignedCodonFrame> acfs)
+  public void setCodonFrames(List<AlignedCodonFrame> acfs)
   {
     if (dataset != null)
     {
@@ -1339,8 +1346,12 @@ public class Alignment implements AlignmentI
    * @see jalview.datamodel.AlignmentI#getCodonFrames()
    */
   @Override
-  public Set<AlignedCodonFrame> getCodonFrames()
+  public List<AlignedCodonFrame> getCodonFrames()
   {
+    // TODO: Fix this method to fix failing AlignedCodonFrame tests
+    // this behaviour is currently incorrect. method should return codon frames
+    // for just the alignment,
+    // selected from dataset
     return dataset != null ? dataset.getCodonFrames() : codonFrameList;
   }
 
@@ -1351,7 +1362,7 @@ public class Alignment implements AlignmentI
   @Override
   public boolean removeCodonFrame(AlignedCodonFrame codons)
   {
-    Set<AlignedCodonFrame> acfs = getCodonFrames();
+    List<AlignedCodonFrame> acfs = getCodonFrames();
     if (codons == null || acfs == null)
     {
       return false;
@@ -1362,11 +1373,7 @@ public class Alignment implements AlignmentI
   @Override
   public void append(AlignmentI toappend)
   {
-    if (toappend == this)
-    {
-      System.err.println("Self append may cause a deadlock.");
-    }
-    // TODO test this method for a future 2.5 release
+    // TODO JAL-1270 needs test coverage
     // currently tested for use in jalview.gui.SequenceFetcher
     boolean samegap = toappend.getGapCharacter() == getGapCharacter();
     char oldc = toappend.getGapCharacter();
@@ -1377,6 +1384,8 @@ public class Alignment implements AlignmentI
             .getFullAlignment().getSequences() : toappend.getSequences();
     if (sqs != null)
     {
+      // avoid self append deadlock by
+      List<SequenceI> toappendsq = new ArrayList<SequenceI>();
       synchronized (sqs)
       {
         for (SequenceI addedsq : sqs)
@@ -1392,9 +1401,13 @@ public class Alignment implements AlignmentI
               }
             }
           }
-          addSequence(addedsq);
+          toappendsq.add(addedsq);
         }
       }
+      for (SequenceI addedsq : toappendsq)
+      {
+        addSequence(addedsq);
+      }
     }
     AlignmentAnnotation[] alan = toappend.getAlignmentAnnotation();
     for (int a = 0; alan != null && a < alan.length; a++)
@@ -1402,6 +1415,7 @@ public class Alignment implements AlignmentI
       addAnnotation(alan[a]);
     }
 
+    // use add method
     getCodonFrames().addAll(toappend.getCodonFrames());
 
     List<SequenceGroup> sg = toappend.getGroups();
@@ -1692,9 +1706,11 @@ public class Alignment implements AlignmentI
    * Parameters control whether gaps in exon (mapped) and intron (unmapped)
    * regions are preserved. Gaps that connect introns to exons are treated
    * conservatively, i.e. only preserved if both intron and exon gaps are
-   * preserved.
+   * preserved. TODO: check caveats below where the implementation fails
    * 
    * @param al
+   *          - must have same dataset, and sequences in al must have equivalent
+   *          dataset sequence and start/end bounds under given mapping
    * @param preserveMappedGaps
    *          if true, gaps within and between mapped codons are preserved
    * @param preserveUnmappedGaps
@@ -1705,31 +1721,17 @@ public class Alignment implements AlignmentI
           boolean preserveUnmappedGaps)
   {
     // TODO should this method signature be the one in the interface?
-    int count = 0;
     boolean thisIsNucleotide = this.isNucleotide();
     boolean thatIsProtein = !al.isNucleotide();
     if (!thatIsProtein && !thisIsNucleotide)
     {
       return AlignmentUtils.alignProteinAsDna(this, al);
     }
-
-    char thisGapChar = this.getGapCharacter();
-    String gap = thisIsNucleotide && thatIsProtein ? String
-            .valueOf(new char[] { thisGapChar, thisGapChar, thisGapChar })
-            : String.valueOf(thisGapChar);
-
-    // TODO handle intron regions? Needs a 'holistic' alignment of dna,
-    // not just sequence by sequence. But how to 'gap' intron regions?
-
-    /*
-     * Get mappings from 'that' alignment's sequences to this.
-     */
-    for (SequenceI alignTo : getSequences())
+    else if (thatIsProtein && thisIsNucleotide)
     {
-      count += AlignmentUtils.alignSequenceAs(alignTo, al, gap,
-              preserveMappedGaps, preserveUnmappedGaps) ? 1 : 0;
+      return AlignmentUtils.alignCdsAsProtein(this, al);
     }
-    return count;
+    return AlignmentUtils.alignAs(this, al);
   }
 
   /**
@@ -1770,4 +1772,82 @@ public class Alignment implements AlignmentI
     }
     return hasValidSeq;
   }
+
+  /**
+   * Update any mappings to 'virtual' sequences to compatible real ones, if
+   * present in the added sequences. Returns a count of mappings updated.
+   * 
+   * @param seqs
+   * @return
+   */
+  @Override
+  public int realiseMappings(List<SequenceI> seqs)
+  {
+    int count = 0;
+    for (SequenceI seq : seqs)
+    {
+      for (AlignedCodonFrame mapping : getCodonFrames())
+      {
+        count += mapping.realiseWith(seq);
+      }
+    }
+    return count;
+  }
+
+  /**
+   * Returns the first AlignedCodonFrame that has a mapping between the given
+   * dataset sequences
+   * 
+   * @param mapFrom
+   * @param mapTo
+   * @return
+   */
+  @Override
+  public AlignedCodonFrame getMapping(SequenceI mapFrom, SequenceI mapTo)
+  {
+    for (AlignedCodonFrame acf : getCodonFrames())
+    {
+      if (acf.getAaForDnaSeq(mapFrom) == mapTo)
+      {
+        return acf;
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public int[] getVisibleStartAndEndIndex(List<int[]> hiddenCols)
+  {
+    int[] alignmentStartEnd = new int[] { 0, getWidth() - 1 };
+    int startPos = alignmentStartEnd[0];
+    int endPos = alignmentStartEnd[1];
+
+    int[] lowestRange = new int[] { -1, -1 };
+    int[] higestRange = new int[] { -1, -1 };
+
+    for (int[] hiddenCol : hiddenCols)
+    {
+      lowestRange = (hiddenCol[0] <= startPos) ? hiddenCol : lowestRange;
+      higestRange = (hiddenCol[1] >= endPos) ? hiddenCol : higestRange;
+    }
+
+    if (lowestRange[0] == -1 && lowestRange[1] == -1)
+    {
+      startPos = alignmentStartEnd[0];
+    }
+    else
+    {
+      startPos = lowestRange[1] + 1;
+    }
+
+    if (higestRange[0] == -1 && higestRange[1] == -1)
+    {
+      endPos = alignmentStartEnd[1];
+    }
+    else
+    {
+      endPos = higestRange[0] - 1;
+    }
+    return new int[] { startPos, endPos };
+  }
 }