JAL-2089 patch broken merge to master for Release 2.10.0b1
[jalview.git] / src / jalview / datamodel / Alignment.java
index c9ec77b..2289ac6 100755 (executable)
 package jalview.datamodel;
 
 import jalview.analysis.AlignmentUtils;
+import jalview.datamodel.AlignedCodonFrame.SequenceToSequenceMapping;
 import jalview.io.FastaFile;
 import jalview.util.Comparison;
+import jalview.util.LinkedIdentityHashSet;
 import jalview.util.MessageManager;
 
 import java.util.ArrayList;
@@ -30,7 +32,6 @@ import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Hashtable;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -226,18 +227,21 @@ public class Alignment implements AlignmentI
   {
     if (dataset != null)
     {
+
       // maintain dataset integrity
-      if (snew.getDatasetSequence() != null)
-      {
-        getDataset().addSequence(snew.getDatasetSequence());
-      }
-      else
+      SequenceI dsseq = snew.getDatasetSequence();
+      if (dsseq == null)
       {
         // derive new sequence
         SequenceI adding = snew.deriveSequence();
-        getDataset().addSequence(adding.getDatasetSequence());
         snew = adding;
+        dsseq = snew.getDatasetSequence();
+      }
+      if (getDataset().findIndex(dsseq) == -1)
+      {
+        getDataset().addSequence(dsseq);
       }
+
     }
     if (sequences == null)
     {
@@ -256,18 +260,22 @@ public class Alignment implements AlignmentI
     }
   }
 
-  /**
-   * Adds a sequence to the alignment. Recalculates maxLength and size.
-   * 
-   * @param snew
-   */
   @Override
-  public void setSequenceAt(int i, SequenceI snew)
+  public SequenceI replaceSequenceAt(int i, SequenceI snew)
   {
     synchronized (sequences)
     {
-      deleteSequence(i);
-      sequences.set(i, snew);
+      if (sequences.size() > i)
+      {
+        return sequences.set(i, snew);
+
+      }
+      else
+      {
+        sequences.add(snew);
+        hiddenSequences.adjustHeightSequenceAdded();
+      }
+      return null;
     }
   }
 
@@ -283,13 +291,23 @@ public class Alignment implements AlignmentI
   }
 
   @Override
-  public void finalize()
+  public void finalize() throws Throwable
   {
     if (getDataset() != null)
     {
       getDataset().removeAlignmentRef();
     }
 
+    nullReferences();
+    super.finalize();
+  }
+
+  /**
+   * Defensively nulls out references in case this object is not garbage
+   * collected
+   */
+  void nullReferences()
+  {
     dataset = null;
     sequences = null;
     groups = null;
@@ -298,14 +316,16 @@ public class Alignment implements AlignmentI
   }
 
   /**
-   * decrement the alignmentRefs counter by one and call finalize if it goes to
-   * zero.
+   * decrement the alignmentRefs counter by one and null references if it goes
+   * to zero.
+   * 
+   * @throws Throwable
    */
-  private void removeAlignmentRef()
+  private void removeAlignmentRef() throws Throwable
   {
     if (--alignmentRefs == 0)
     {
-      finalize();
+      nullReferences();
     }
   }
 
@@ -1030,6 +1050,70 @@ public class Alignment implements AlignmentI
   }
 
   /**
+   * add dataset sequences to seq for currentSeq and any sequences it references
+   */
+  private void resolveAndAddDatasetSeq(SequenceI currentSeq,
+          Set<SequenceI> seqs, boolean createDatasetSequence)
+  {
+    SequenceI alignedSeq = currentSeq;
+    if (currentSeq.getDatasetSequence() != null)
+    {
+      currentSeq = currentSeq.getDatasetSequence();
+    }
+    else
+    {
+      if (createDatasetSequence)
+      {
+        currentSeq = currentSeq.createDatasetSequence();
+      }
+    }
+    if (seqs.contains(currentSeq))
+    {
+      return;
+    }
+    List<SequenceI> toProcess = new ArrayList<SequenceI>();
+    toProcess.add(currentSeq);
+    while (toProcess.size() > 0)
+    {
+      // use a queue ?
+      SequenceI curDs = toProcess.remove(0);
+      if (seqs.contains(curDs))
+      {
+        continue;
+      }
+      seqs.add(curDs);
+      // iterate over database references, making sure we add forward referenced
+      // sequences
+      if (curDs.getDBRefs() != null)
+      {
+        for (DBRefEntry dbr : curDs.getDBRefs())
+        {
+          if (dbr.getMap() != null && dbr.getMap().getTo() != null)
+          {
+            if (dbr.getMap().getTo() == alignedSeq)
+            {
+              /*
+               * update mapping to be to the newly created dataset sequence
+               */
+              dbr.getMap().setTo(currentSeq);
+            }
+            if (dbr.getMap().getTo().getDatasetSequence() != null)
+            {
+              throw new Error(
+                      "Implementation error: Map.getTo() for dbref " + dbr
+                              + " from " + curDs.getName()
+                              + " is not a dataset sequence.");
+            }
+            // we recurse to add all forward references to dataset sequences via
+            // DBRefs/etc
+            toProcess.add(dbr.getMap().getTo());
+          }
+        }
+      }
+    }
+  }
+
+  /**
    * Creates a new dataset for this alignment. Can only be done once - if
    * dataset is not null this will not be performed.
    */
@@ -1039,22 +1123,32 @@ public class Alignment implements AlignmentI
     {
       return;
     }
-    SequenceI[] seqs = new SequenceI[getHeight()];
-    SequenceI currentSeq;
+    // try to avoid using SequenceI.equals at this stage, it will be expensive
+    Set<SequenceI> seqs = new LinkedIdentityHashSet<SequenceI>();
+
     for (int i = 0; i < getHeight(); i++)
     {
-      currentSeq = getSequenceAt(i);
-      if (currentSeq.getDatasetSequence() != null)
-      {
-        seqs[i] = currentSeq.getDatasetSequence();
-      }
-      else
+      SequenceI currentSeq = getSequenceAt(i);
+      resolveAndAddDatasetSeq(currentSeq, seqs, true);
+    }
+
+    // verify all mappings are in dataset
+    for (AlignedCodonFrame cf : codonFrameList)
+    {
+      for (SequenceToSequenceMapping ssm : cf.getMappings())
       {
-        seqs[i] = currentSeq.createDatasetSequence();
+        if (!seqs.contains(ssm.getFromSeq()))
+        {
+          resolveAndAddDatasetSeq(ssm.getFromSeq(), seqs, false);
+        }
+        if (!seqs.contains(ssm.getMapping().getTo()))
+        {
+          resolveAndAddDatasetSeq(ssm.getMapping().getTo(), seqs, false);
+        }
       }
     }
-
-    dataset = new Alignment(seqs);
+    // finally construct dataset
+    dataset = new Alignment(seqs.toArray(new SequenceI[seqs.size()]));
     // move mappings to the dataset alignment
     dataset.codonFrameList = this.codonFrameList;
     this.codonFrameList = null;
@@ -1296,22 +1390,6 @@ public class Alignment implements AlignmentI
     }
   }
 
-  /**
-   * adds a set of mappings (while ignoring any duplicates)
-   */
-  @Override
-  public void addCodonFrames(Iterable<AlignedCodonFrame> codons)
-  {
-    if (codons != null)
-    {
-      Iterator<AlignedCodonFrame> it = codons.iterator();
-      while (it.hasNext())
-      {
-        addCodonFrame(it.next());
-      }
-    }
-  }
-
   /*
    * (non-Javadoc)
    * 
@@ -1390,11 +1468,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();
@@ -1405,6 +1479,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)
@@ -1420,9 +1496,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++)
@@ -1721,9 +1801,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
@@ -1734,12 +1816,17 @@ public class Alignment implements AlignmentI
           boolean preserveUnmappedGaps)
   {
     // TODO should this method signature be the one in the interface?
+    // JBPComment - yes - neither flag is used, so should be deleted.
     boolean thisIsNucleotide = this.isNucleotide();
     boolean thatIsProtein = !al.isNucleotide();
     if (!thatIsProtein && !thisIsNucleotide)
     {
       return AlignmentUtils.alignProteinAsDna(this, al);
     }
+    else if (thatIsProtein && thisIsNucleotide)
+    {
+      return AlignmentUtils.alignCdsAsProtein(this, al);
+    }
     return AlignmentUtils.alignAs(this, al);
   }