Merge branch 'develop' into bug/JAL-2154projectMappings
authorJim Procter <jprocter@issues.jalview.org>
Thu, 25 Aug 2016 11:10:25 +0000 (12:10 +0100)
committerJim Procter <jprocter@issues.jalview.org>
Thu, 25 Aug 2016 11:10:25 +0000 (12:10 +0100)
 Conflicts:
src/jalview/gui/AlignFrame.java - propagated MessageManager tweak to jalview/gui/CrossRefAction.java

15 files changed:
src/jalview/analysis/CrossRef.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AlignmentI.java
src/jalview/datamodel/Sequence.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/CrossRefAction.java [new file with mode: 0644]
src/jalview/gui/Jalview2XML.java
src/jalview/gui/SequenceFetcher.java
src/jalview/util/LinkedIdentityHashSet.java [new file with mode: 0644]
src/jalview/util/MapList.java
test/jalview/datamodel/AlignmentTest.java
test/jalview/io/CrossRef2xmlTests.java [new file with mode: 0644]
test/jalview/io/Jalview2xmlBase.java [new file with mode: 0644]
test/jalview/io/Jalview2xmlTests.java
test/jalview/io/testProps_nodas.jvprops [new file with mode: 0644]

index 288d60e..ddfd7ff 100644 (file)
@@ -271,20 +271,45 @@ public class CrossRef
              * but findInDataset() matches ENSP when looking for Uniprot...
              */
             SequenceI matchInDataset = findInDataset(xref);
+            if (matchInDataset != null && xref.getMap().getTo() != null
+                    && matchInDataset != xref.getMap().getTo())
+            {
+              System.err
+                      .println("Implementation problem (reopen JAL-2154): CrossRef.findInDataset seems to have recovered a different sequence than the one explicitly mapped for xref."
+                              + "Found:"
+                              + matchInDataset
+                              + "\nExpected:"
+                              + xref.getMap().getTo()
+                              + "\nFor xref:"
+                              + xref);
+            }
             /*matcher.findIdMatch(mappedTo);*/
             if (matchInDataset != null)
             {
               if (!rseqs.contains(matchInDataset))
               {
                 rseqs.add(matchInDataset);
+                // need to try harder to only add unique mappings
+                if (xref.getMap().getMap().isTripletMap()
+                        && dataset.getMapping(seq, matchInDataset) == null
+                        && cf.getMappingBetween(seq, matchInDataset) == null)
+                {
+                  // materialise a mapping for highlighting between these sequences
+                  if (fromDna)
+                  {
+                    cf.addMap(dss, matchInDataset, xref.getMap().getMap(), xref.getMap().getMappedFromId());
+                  } else {
+                    cf.addMap(matchInDataset, dss, xref.getMap().getMap().getInverse(), xref.getMap().getMappedFromId());
+                  }
+                }
               }
               refIterator.remove();
               continue;
             }
+            // TODO: need to determine if this should be a deriveSequence
             SequenceI rsq = new Sequence(mappedTo);
             rseqs.add(rsq);
-            if (xref.getMap().getMap().getFromRatio() != xref.getMap()
-                    .getMap().getToRatio())
+            if (xref.getMap().getMap().isTripletMap())
             {
               // get sense of map correct for adding to product alignment.
               if (fromDna)
@@ -413,7 +438,11 @@ public class CrossRef
                 }
                 else
                 {
-                  matcher.add(map.getTo());
+                  if (dataset.findIndex(map.getTo()) == -1)
+                  {
+                    dataset.addSequence(map.getTo());
+                    matcher.add(map.getTo());
+                  }
                 }
                 try
                 {
@@ -483,8 +512,11 @@ public class CrossRef
         }
         retrievedSequence.updatePDBIds();
         rseqs.add(retrievedDss);
-        dataset.addSequence(retrievedDss);
-        matcher.add(retrievedDss);
+        if (dataset.findIndex(retrievedDss) == -1)
+        {
+          dataset.addSequence(retrievedDss);
+          matcher.add(retrievedDss);
+        }
       }
     }
   }
@@ -662,24 +694,28 @@ public class CrossRef
           DBRefEntry xref, AlignedCodonFrame mappings, boolean fromDna)
   {
     MapList mapping = null;
-
+    SequenceI dsmapFrom = mapFrom.getDatasetSequence() == null ? mapFrom
+            : mapFrom.getDatasetSequence();
+    SequenceI dsmapTo = mapFrom.getDatasetSequence() == null ? mapTo
+            : mapTo.getDatasetSequence();
     /*
-     * look for a reverse mapping, if found make its inverse
+     * look for a reverse mapping, if found make its inverse. 
+     * Note - we do this on dataset sequences only.
      */
-    if (mapTo.getDBRefs() != null)
+    if (dsmapTo.getDBRefs() != null)
     {
-      for (DBRefEntry dbref : mapTo.getDBRefs())
+      for (DBRefEntry dbref : dsmapTo.getDBRefs())
       {
         String name = dbref.getSource() + "|" + dbref.getAccessionId();
-        if (dbref.hasMap() && mapFrom.getName().startsWith(name))
+        if (dbref.hasMap() && dsmapFrom.getName().startsWith(name))
         {
           /*
            * looks like we've found a map from 'mapTo' to 'mapFrom'
            * - invert it to make the mapping the other way 
            */
           MapList reverse = dbref.getMap().getMap().getInverse();
-          xref.setMap(new Mapping(mapTo, reverse));
-          mappings.addMap(mapFrom, mapTo, reverse);
+          xref.setMap(new Mapping(dsmapTo, reverse));
+          mappings.addMap(mapFrom, dsmapTo, reverse);
           return true;
         }
       }
index 32bb761..2f64759 100755 (executable)
@@ -21,6 +21,7 @@
 package jalview.datamodel;
 
 import jalview.analysis.AlignmentUtils;
+import jalview.datamodel.AlignedCodonFrame.SequenceToSequenceMapping;
 import jalview.io.FastaFile;
 import jalview.util.Comparison;
 import jalview.util.MessageManager;
@@ -225,18 +226,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)
     {
@@ -255,18 +259,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;
     }
   }
 
@@ -1029,6 +1037,62 @@ 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)
+  {
+    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().getDatasetSequence() != null)
+            {
+              throw new Error("Implementation error: Map.getTo() for dbref"
+                      + dbr + " is not a dataset sequence.");
+              // TODO: if this happens, could also rewrite the reference to
+              // point to new 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.
    */
@@ -1038,22 +1102,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 jalview.util.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;
index f1db4c0..1d37fa6 100755 (executable)
@@ -108,11 +108,14 @@ public interface AlignmentI extends AnnotatedCollectionI
    * Used to set a particular index of the alignment with the given sequence.
    * 
    * @param i
-   *          Index of sequence to be updated.
+   *          Index of sequence to be updated. if i>length, sequence will be
+   *          added to end, with no intervening positions.
    * @param seq
-   *          New sequence to be inserted.
+   *          New sequence to be inserted. The existing sequence at position i
+   *          will be replaced.
+   * @return existing sequence (or null if i>current length)
    */
-  void setSequenceAt(int i, SequenceI seq);
+  SequenceI replaceSequenceAt(int i, SequenceI seq);
 
   /**
    * Deletes a sequence from the alignment
index a857712..88f4308 100755 (executable)
@@ -307,8 +307,9 @@ public class Sequence extends ASequence implements SequenceI
               && datasetSequence.getSequenceFeatures() != null
               && datasetSequence.getSequenceFeatures().length > 0)
       {
-        System.err
-              .println("Warning: JAL-2046 side effect ? Possible implementation error: overwriting dataset sequence features by setting sequence features on alignment");
+        new Exception(
+                "Warning: JAL-2046 side effect ? Possible implementation error: overwriting dataset sequence features by setting sequence features on alignment")
+                .printStackTrace();
       }
       datasetSequence.setSequenceFeatures(features);
     }
index f6268c0..dd8fb7a 100644 (file)
@@ -32,7 +32,6 @@ import jalview.api.AlignViewControllerI;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureSettingsControllerI;
-import jalview.api.FeatureSettingsModelI;
 import jalview.api.SplitContainerI;
 import jalview.api.ViewStyleI;
 import jalview.api.analysis.ScoreModelI;
@@ -54,7 +53,6 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentOrder;
 import jalview.datamodel.AlignmentView;
 import jalview.datamodel.ColumnSelection;
-import jalview.datamodel.DBRefSource;
 import jalview.datamodel.HiddenSequences;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SeqCigar;
@@ -74,7 +72,6 @@ import jalview.io.JalviewFileView;
 import jalview.io.JnetAnnotationMaker;
 import jalview.io.NewickFile;
 import jalview.io.TCoffeeScoreFile;
-import jalview.io.gff.SequenceOntologyI;
 import jalview.jbgui.GAlignFrame;
 import jalview.schemes.Blosum62ColourScheme;
 import jalview.schemes.BuriedColourScheme;
@@ -94,12 +91,10 @@ import jalview.schemes.TaylorColourScheme;
 import jalview.schemes.TurnColourScheme;
 import jalview.schemes.UserColourScheme;
 import jalview.schemes.ZappoColourScheme;
-import jalview.structure.StructureSelectionManager;
 import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
 import jalview.ws.DBRefFetcher;
 import jalview.ws.DBRefFetcher.FetchFinishedListenerI;
-import jalview.ws.SequenceFetcher;
 import jalview.ws.jws1.Discoverer;
 import jalview.ws.jws2.Jws2Discoverer;
 import jalview.ws.jws2.jabaws2.Jws2Instance;
@@ -4676,236 +4671,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   protected void showProductsFor(final SequenceI[] sel,
           final boolean _odna, final String source)
   {
-    Runnable foo = new Runnable()
-    {
-
-      @Override
-      public void run()
-      {
-        final long sttime = System.currentTimeMillis();
-        AlignFrame.this.setProgressBar(MessageManager.formatMessage(
-                "status.searching_for_sequences_from",
-                new Object[] { source }), sttime);
-        try
-        {
-          AlignmentI alignment = AlignFrame.this.getViewport()
-                  .getAlignment();
-          AlignmentI dataset = alignment.getDataset() == null ? alignment
-                  : alignment.getDataset();
-          boolean dna = alignment.isNucleotide();
-          if (_odna != dna)
-          {
-            System.err
-                    .println("Conflict: showProducts for alignment originally "
-                            + "thought to be "
-                            + (_odna ? "DNA" : "Protein")
-                            + " now searching for "
-                            + (dna ? "DNA" : "Protein") + " Context.");
-          }
-          AlignmentI xrefs = new CrossRef(sel, dataset).findXrefSequences(
-                  source, dna);
-          if (xrefs == null)
-          {
-            return;
-          }
-          /*
-           * get display scheme (if any) to apply to features
-           */
-          FeatureSettingsModelI featureColourScheme = new SequenceFetcher()
-                  .getFeatureColourScheme(source);
-
-          AlignmentI xrefsAlignment = makeCrossReferencesAlignment(dataset,
-                  xrefs);
-          if (!dna)
-          {
-            xrefsAlignment = AlignmentUtils.makeCdsAlignment(
-                    xrefsAlignment.getSequencesArray(), dataset, sel);
-            xrefsAlignment.alignAs(alignment);
-          }
-
-          /*
-           * If we are opening a splitframe, make a copy of this alignment (sharing the same dataset
-           * sequences). If we are DNA, drop introns and update mappings
-           */
-          AlignmentI copyAlignment = null;
-
-          if (Cache.getDefault(Preferences.ENABLE_SPLIT_FRAME, true))
-          {
-            boolean copyAlignmentIsAligned = false;
-            if (dna)
-            {
-              copyAlignment = AlignmentUtils.makeCdsAlignment(sel, dataset,
-                      xrefsAlignment.getSequencesArray());
-              if (copyAlignment.getHeight() == 0)
-              {
-                JOptionPane.showMessageDialog(AlignFrame.this,
-                        MessageManager.getString("label.cant_map_cds"),
-                        MessageManager.getString("label.operation_failed"),
-                        JOptionPane.OK_OPTION);
-                System.err.println("Failed to make CDS alignment");
-              }
-
-              /*
-               * pending getting Embl transcripts to 'align', 
-               * we are only doing this for Ensembl
-               */
-              // TODO proper criteria for 'can align as cdna'
-              if (DBRefSource.ENSEMBL.equalsIgnoreCase(source)
-                      || AlignmentUtils.looksLikeEnsembl(alignment))
-              {
-                copyAlignment.alignAs(alignment);
-                copyAlignmentIsAligned = true;
-              }
-            }
-            else
-            {
-              copyAlignment = AlignmentUtils.makeCopyAlignment(sel,
-                      xrefs.getSequencesArray(), dataset);
-            }
-            copyAlignment.setGapCharacter(AlignFrame.this.viewport
-                    .getGapCharacter());
-
-            StructureSelectionManager ssm = StructureSelectionManager
-                    .getStructureSelectionManager(Desktop.instance);
-
-            /*
-             * register any new mappings for sequence mouseover etc
-             * (will not duplicate any previously registered mappings)
-             */
-            ssm.registerMappings(dataset.getCodonFrames());
-
-            if (copyAlignment.getHeight() <= 0)
-            {
-              System.err.println("No Sequences generated for xRef type "
-                      + source);
-              return;
-            }
-            /*
-             * align protein to dna
-             */
-            if (dna && copyAlignmentIsAligned)
-            {
-              xrefsAlignment.alignAs(copyAlignment);
-            }
-            else
-            {
-              /*
-               * align cdna to protein - currently only if 
-               * fetching and aligning Ensembl transcripts!
-               */
-              // TODO: generalise for other sources of locus/transcript/cds data
-              if (dna && DBRefSource.ENSEMBL.equalsIgnoreCase(source))
-              {
-                copyAlignment.alignAs(xrefsAlignment);
-              }
-            }
-          }
-          /*
-           * build AlignFrame(s) according to available alignment data
-           */
-          AlignFrame newFrame = new AlignFrame(xrefsAlignment,
-                  DEFAULT_WIDTH, DEFAULT_HEIGHT);
-          if (Cache.getDefault("HIDE_INTRONS", true))
-          {
-            newFrame.hideFeatureColumns(SequenceOntologyI.EXON, false);
-          }
-          String newtitle = String.format("%s %s %s",
-                  dna ? MessageManager.getString("label.proteins")
-                          : MessageManager.getString("label.nucleotides"),
-                  MessageManager.getString("label.for"), getTitle());
-          newFrame.setTitle(newtitle);
-
-          if (copyAlignment == null)
-          {
-            /*
-             * split frame display is turned off in preferences file
-             */
-            Desktop.addInternalFrame(newFrame, newtitle, DEFAULT_WIDTH,
-                    DEFAULT_HEIGHT);
-            return; // via finally clause
-          }
-          AlignFrame copyThis = new AlignFrame(copyAlignment,
-                  AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
-          copyThis.setTitle(AlignFrame.this.getTitle());
-
-          boolean showSequenceFeatures = viewport.isShowSequenceFeatures();
-          newFrame.setShowSeqFeatures(showSequenceFeatures);
-          copyThis.setShowSeqFeatures(showSequenceFeatures);
-          FeatureRenderer myFeatureStyling = alignPanel.getSeqPanel().seqCanvas
-                  .getFeatureRenderer();
-
-          /*
-           * copy feature rendering settings to split frame
-           */
-          newFrame.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
-                  .transferSettings(myFeatureStyling);
-          copyThis.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
-                  .transferSettings(myFeatureStyling);
-
-          /*
-           * apply 'database source' feature configuration
-           * if any was found
-           */
-          // TODO is this the feature colouring for the original
-          // alignment or the fetched xrefs? either could be Ensembl
-          newFrame.getViewport().applyFeaturesStyle(featureColourScheme);
-          copyThis.getViewport().applyFeaturesStyle(featureColourScheme);
-
-          SplitFrame sf = new SplitFrame(dna ? copyThis : newFrame,
-                  dna ? newFrame : copyThis);
-          newFrame.setVisible(true);
-          copyThis.setVisible(true);
-          String linkedTitle = MessageManager
-                  .getString("label.linked_view_title");
-          Desktop.addInternalFrame(sf, linkedTitle, -1, -1);
-          sf.adjustDivider();
-        } catch (OutOfMemoryError e)
-        {
-          new OOMWarning("whilst fetching crossreferences", e);
-        } catch (Throwable e)
-        {
-          Cache.log.error("Error when finding crossreferences", e);
-        } finally
-        {
-          AlignFrame.this.setProgressBar(MessageManager.formatMessage(
-                  "status.finished_searching_for_sequences_from",
-                  new Object[] { source }), sttime);
-        }
-      }
-
-      /**
-       * Makes an alignment containing the given sequences, and adds them to the
-       * given dataset, which is also set as the dataset for the new alignment
-       * 
-       * TODO: refactor to DatasetI method
-       * 
-       * @param dataset
-       * @param seqs
-       * @return
-       */
-      protected AlignmentI makeCrossReferencesAlignment(AlignmentI dataset,
-              AlignmentI seqs)
-      {
-        SequenceI[] sprods = new SequenceI[seqs.getHeight()];
-        for (int s = 0; s < sprods.length; s++)
-        {
-          sprods[s] = (seqs.getSequenceAt(s)).deriveSequence();
-          if (dataset.getSequences() == null
-                  || !dataset.getSequences().contains(
-                          sprods[s].getDatasetSequence()))
-          {
-            dataset.addSequence(sprods[s].getDatasetSequence());
-          }
-          sprods[s].updatePDBIds();
-        }
-        Alignment al = new Alignment(sprods);
-        al.setDataset(dataset);
-        return al;
-      }
-
-    };
-    Thread frunner = new Thread(foo);
-    frunner.start();
+    new Thread(CrossRefAction.showProductsFor(sel, _odna, source, this))
+            .start();
   }
 
   /**
diff --git a/src/jalview/gui/CrossRefAction.java b/src/jalview/gui/CrossRefAction.java
new file mode 100644 (file)
index 0000000..32af226
--- /dev/null
@@ -0,0 +1,312 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.gui;
+
+import jalview.analysis.AlignmentUtils;
+import jalview.analysis.CrossRef;
+import jalview.api.AlignmentViewPanel;
+import jalview.api.FeatureSettingsModelI;
+import jalview.bin.Cache;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.DBRefSource;
+import jalview.datamodel.SequenceI;
+import jalview.io.gff.SequenceOntologyI;
+import jalview.structure.StructureSelectionManager;
+import jalview.util.MessageManager;
+import jalview.ws.SequenceFetcher;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JOptionPane;
+
+/**
+ * Factory constructor and runnable for discovering and displaying
+ * cross-references for a set of aligned sequences
+ * 
+ * @author jprocter
+ *
+ */
+public class CrossRefAction implements Runnable
+{
+  private AlignFrame alignFrame;
+
+  private SequenceI[] sel;
+
+  private boolean _odna;
+
+  private String source;
+
+  List<AlignmentViewPanel> xrefViews = new ArrayList<AlignmentViewPanel>();
+
+  public List<jalview.api.AlignmentViewPanel> getXrefViews()
+  {
+    return xrefViews;
+  }
+
+  @Override
+  public void run()
+  {
+    final long sttime = System.currentTimeMillis();
+    alignFrame.setProgressBar(
+            MessageManager.formatMessage(
+                    "status.searching_for_sequences_from",
+                    new Object[] { source }), sttime);
+    try
+    {
+      AlignmentI alignment = alignFrame.getViewport().getAlignment();
+      AlignmentI dataset = alignment.getDataset() == null ? alignment
+              : alignment.getDataset();
+      boolean dna = alignment.isNucleotide();
+      if (_odna != dna)
+      {
+        System.err
+                .println("Conflict: showProducts for alignment originally "
+                        + "thought to be " + (_odna ? "DNA" : "Protein")
+                        + " now searching for " + (dna ? "DNA" : "Protein")
+                        + " Context.");
+      }
+      AlignmentI xrefs = new CrossRef(sel, dataset).findXrefSequences(
+              source, dna);
+      if (xrefs == null)
+      {
+        return;
+      }
+      /*
+       * get display scheme (if any) to apply to features
+       */
+      FeatureSettingsModelI featureColourScheme = new SequenceFetcher()
+              .getFeatureColourScheme(source);
+
+      AlignmentI xrefsAlignment = makeCrossReferencesAlignment(dataset,
+              xrefs);
+      if (!dna)
+      {
+        xrefsAlignment = AlignmentUtils.makeCdsAlignment(
+                xrefsAlignment.getSequencesArray(), dataset, sel);
+        xrefsAlignment.alignAs(alignment);
+      }
+
+      /*
+       * If we are opening a splitframe, make a copy of this alignment (sharing the same dataset
+       * sequences). If we are DNA, drop introns and update mappings
+       */
+      AlignmentI copyAlignment = null;
+
+      if (Cache.getDefault(Preferences.ENABLE_SPLIT_FRAME, true))
+      {
+        boolean copyAlignmentIsAligned = false;
+        if (dna)
+        {
+          copyAlignment = AlignmentUtils.makeCdsAlignment(sel, dataset,
+                  xrefsAlignment.getSequencesArray());
+          if (copyAlignment.getHeight() == 0)
+          {
+            JOptionPane.showMessageDialog(alignFrame,
+                    MessageManager.getString("label.cant_map_cds"),
+                    MessageManager.getString("label.operation_failed"),
+                    JOptionPane.OK_OPTION);
+            System.err.println("Failed to make CDS alignment");
+          }
+
+          /*
+           * pending getting Embl transcripts to 'align', 
+           * we are only doing this for Ensembl
+           */
+          // TODO proper criteria for 'can align as cdna'
+          if (DBRefSource.ENSEMBL.equalsIgnoreCase(source)
+                  || AlignmentUtils.looksLikeEnsembl(alignment))
+          {
+            copyAlignment.alignAs(alignment);
+            copyAlignmentIsAligned = true;
+          }
+        }
+        else
+        {
+          copyAlignment = AlignmentUtils.makeCopyAlignment(sel,
+                  xrefs.getSequencesArray(), dataset);
+        }
+        copyAlignment
+                .setGapCharacter(alignFrame.viewport.getGapCharacter());
+
+        StructureSelectionManager ssm = StructureSelectionManager
+                .getStructureSelectionManager(Desktop.instance);
+
+        /*
+         * register any new mappings for sequence mouseover etc
+         * (will not duplicate any previously registered mappings)
+         */
+        ssm.registerMappings(dataset.getCodonFrames());
+
+        if (copyAlignment.getHeight() <= 0)
+        {
+          System.err.println("No Sequences generated for xRef type "
+                  + source);
+          return;
+        }
+        /*
+         * align protein to dna
+         */
+        if (dna && copyAlignmentIsAligned)
+        {
+          xrefsAlignment.alignAs(copyAlignment);
+        }
+        else
+        {
+          /*
+           * align cdna to protein - currently only if 
+           * fetching and aligning Ensembl transcripts!
+           */
+          // TODO: generalise for other sources of locus/transcript/cds data
+          if (dna && DBRefSource.ENSEMBL.equalsIgnoreCase(source))
+          {
+            copyAlignment.alignAs(xrefsAlignment);
+          }
+        }
+      }
+      /*
+       * build AlignFrame(s) according to available alignment data
+       */
+      AlignFrame newFrame = new AlignFrame(xrefsAlignment,
+              AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
+      if (Cache.getDefault("HIDE_INTRONS", true))
+      {
+        newFrame.hideFeatureColumns(SequenceOntologyI.EXON, false);
+      }
+      String newtitle = String.format("%s %s %s",
+              dna ? MessageManager.getString("label.proteins")
+                      : MessageManager.getString("label.nucleotides"),
+              MessageManager.getString("label.for"), alignFrame.getTitle());
+      newFrame.setTitle(newtitle);
+
+      if (copyAlignment == null)
+      {
+        /*
+         * split frame display is turned off in preferences file
+         */
+        Desktop.addInternalFrame(newFrame, newtitle,
+                AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
+        xrefViews.add(newFrame.alignPanel);
+        return; // via finally clause
+      }
+      AlignFrame copyThis = new AlignFrame(copyAlignment,
+              AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
+      copyThis.setTitle(alignFrame.getTitle());
+
+      boolean showSequenceFeatures = alignFrame.getViewport()
+              .isShowSequenceFeatures();
+      newFrame.setShowSeqFeatures(showSequenceFeatures);
+      copyThis.setShowSeqFeatures(showSequenceFeatures);
+      FeatureRenderer myFeatureStyling = alignFrame.alignPanel
+              .getSeqPanel().seqCanvas.getFeatureRenderer();
+
+      /*
+       * copy feature rendering settings to split frame
+       */
+      newFrame.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
+              .transferSettings(myFeatureStyling);
+      copyThis.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
+              .transferSettings(myFeatureStyling);
+
+      /*
+       * apply 'database source' feature configuration
+       * if any was found
+       */
+      // TODO is this the feature colouring for the original
+      // alignment or the fetched xrefs? either could be Ensembl
+      newFrame.getViewport().applyFeaturesStyle(featureColourScheme);
+      copyThis.getViewport().applyFeaturesStyle(featureColourScheme);
+
+      SplitFrame sf = new SplitFrame(dna ? copyThis : newFrame,
+              dna ? newFrame : copyThis);
+      newFrame.setVisible(true);
+      copyThis.setVisible(true);
+      String linkedTitle = MessageManager
+              .getString("label.linked_view_title");
+      Desktop.addInternalFrame(sf, linkedTitle, -1, -1);
+      sf.adjustDivider();
+
+      // finally add the top, then bottom frame to the view list
+      xrefViews.add(dna ? copyThis.alignPanel : newFrame.alignPanel);
+      xrefViews.add(!dna ? copyThis.alignPanel : newFrame.alignPanel);
+
+    } catch (OutOfMemoryError e)
+    {
+      new OOMWarning("whilst fetching crossreferences", e);
+    } catch (Throwable e)
+    {
+      Cache.log.error("Error when finding crossreferences", e);
+    } finally
+    {
+      alignFrame.setProgressBar(MessageManager.formatMessage(
+              "status.finished_searching_for_sequences_from",
+              new Object[] { source }), sttime);
+    }
+  }
+
+  /**
+   * Makes an alignment containing the given sequences, and adds them to the
+   * given dataset, which is also set as the dataset for the new alignment
+   * 
+   * TODO: refactor to DatasetI method
+   * 
+   * @param dataset
+   * @param seqs
+   * @return
+   */
+  protected AlignmentI makeCrossReferencesAlignment(AlignmentI dataset,
+          AlignmentI seqs)
+  {
+    SequenceI[] sprods = new SequenceI[seqs.getHeight()];
+    for (int s = 0; s < sprods.length; s++)
+    {
+      sprods[s] = (seqs.getSequenceAt(s)).deriveSequence();
+      if (dataset.getSequences() == null
+              || !dataset.getSequences().contains(
+                      sprods[s].getDatasetSequence()))
+      {
+        dataset.addSequence(sprods[s].getDatasetSequence());
+      }
+      sprods[s].updatePDBIds();
+    }
+    Alignment al = new Alignment(sprods);
+    al.setDataset(dataset);
+    return al;
+  }
+
+  public CrossRefAction(AlignFrame alignFrame, SequenceI[] sel,
+          boolean _odna, String source)
+  {
+    this.alignFrame = alignFrame;
+    this.sel = sel;
+    this._odna = _odna;
+    this.source = source;
+  }
+
+  public static CrossRefAction showProductsFor(final SequenceI[] sel,
+          final boolean _odna, final String source,
+          final AlignFrame alignFrame)
+  {
+    return new CrossRefAction(alignFrame, sel, _odna, source);
+  }
+
+}
index 68245b6..c80f3de 100644 (file)
@@ -365,6 +365,12 @@ public class Jalview2XML
       public jalview.datamodel.Mapping mp = _jmap;
 
       @Override
+      public boolean isResolvable()
+      {
+        return super.isResolvable() && mp.getTo() != null;
+      };
+
+      @Override
       boolean resolve()
       {
         SequenceI seq = getSrefDatasetSeq();
@@ -787,37 +793,42 @@ public class Jalview2XML
 
     JSeq jseq;
     Set<String> calcIdSet = new HashSet<String>();
-
+    // record the set of vamsas sequence XML POJO we create.
+    HashMap<String,Sequence> vamsasSetIds = new HashMap<String,Sequence>(); 
     // SAVE SEQUENCES
     for (final SequenceI jds : rjal.getSequences())
     {
       final SequenceI jdatasq = jds.getDatasetSequence() == null ? jds
               : jds.getDatasetSequence();
       String id = seqHash(jds);
-
-      if (seqRefIds.get(id) != null)
-      {
-        // This happens for two reasons: 1. multiple views are being serialised.
-        // 2. the hashCode has collided with another sequence's code. This DOES
-        // HAPPEN! (PF00072.15.stk does this)
-        // JBPNote: Uncomment to debug writing out of files that do not read
-        // back in due to ArrayOutOfBoundExceptions.
-        // System.err.println("vamsasSeq backref: "+id+"");
-        // System.err.println(jds.getName()+"
-        // "+jds.getStart()+"-"+jds.getEnd()+" "+jds.getSequenceAsString());
-        // System.err.println("Hashcode: "+seqHash(jds));
-        // SequenceI rsq = (SequenceI) seqRefIds.get(id + "");
-        // System.err.println(rsq.getName()+"
-        // "+rsq.getStart()+"-"+rsq.getEnd()+" "+rsq.getSequenceAsString());
-        // System.err.println("Hashcode: "+seqHash(rsq));
-      }
-      else
-      {
-        vamsasSeq = createVamsasSequence(id, jds);
-        vamsasSet.addSequence(vamsasSeq);
-        seqRefIds.put(id, jds);
+      if (vamsasSetIds.get(id) == null)
+      {
+        if (seqRefIds.get(id) != null && !storeDS)
+        {
+          // This happens for two reasons: 1. multiple views are being
+          // serialised.
+          // 2. the hashCode has collided with another sequence's code. This
+          // DOES
+          // HAPPEN! (PF00072.15.stk does this)
+          // JBPNote: Uncomment to debug writing out of files that do not read
+          // back in due to ArrayOutOfBoundExceptions.
+          // System.err.println("vamsasSeq backref: "+id+"");
+          // System.err.println(jds.getName()+"
+          // "+jds.getStart()+"-"+jds.getEnd()+" "+jds.getSequenceAsString());
+          // System.err.println("Hashcode: "+seqHash(jds));
+          // SequenceI rsq = (SequenceI) seqRefIds.get(id + "");
+          // System.err.println(rsq.getName()+"
+          // "+rsq.getStart()+"-"+rsq.getEnd()+" "+rsq.getSequenceAsString());
+          // System.err.println("Hashcode: "+seqHash(rsq));
+        }
+        else
+        {
+          vamsasSeq = createVamsasSequence(id, jds);
+          vamsasSet.addSequence(vamsasSeq);
+          vamsasSetIds.put(id, vamsasSeq);
+          seqRefIds.put(id, jds);
+        }
       }
-
       jseq = new JSeq();
       jseq.setStart(jds.getStart());
       jseq.setEnd(jds.getEnd());
@@ -2808,15 +2819,28 @@ public class Jalview2XML
           { 
             System.err
                     .println("Warning JAL-2154 regression: updating start/end for sequence "
-                    + tmpSeq.toString());
+                            + tmpSeq.toString() + " to " + jseqs[i]);
           }
         } else {
           incompleteSeqs.remove(seqId);
         }
+        if (vamsasSeq.length > vi && vamsasSeq[vi].getId().equals(seqId))
+        {
+          // most likely we are reading a dataset XML document so
+          // update from vamsasSeq section of XML for this sequence
+          tmpSeq.setName(vamsasSeq[vi].getName());
+          tmpSeq.setDescription(vamsasSeq[vi].getDescription());
+          tmpSeq.setSequence(vamsasSeq[vi].getSequence());
+          vi++;
+        }
+        else
+        {
+          // reading multiple views, so vamsasSeq set is a subset of JSeq
+          multipleView = true;
+        }
         tmpSeq.setStart(jseqs[i].getStart());
         tmpSeq.setEnd(jseqs[i].getEnd());
         tmpseqs.add(tmpSeq);
-        multipleView = true;
       }
       else
       {
@@ -2905,6 +2929,12 @@ public class Jalview2XML
     {
       // load sequence features, database references and any associated PDB
       // structures for the alignment
+      //
+      // prior to 2.10, this part would only be executed the first time a
+      // sequence was encountered, but not afterwards.
+      // now, for 2.10 projects, this is also done if the xml doc includes
+      // dataset sequences not actually present in any particular view.
+      //
       for (int i = 0; i < vamsasSeq.length; i++)
       {
         if (jseqs[i].getFeaturesCount() > 0)
@@ -2931,13 +2961,17 @@ public class Jalview2XML
               }
 
             }
-
-            al.getSequenceAt(i).getDatasetSequence().addSequenceFeature(sf);
+            // adds feature to datasequence's feature set (since Jalview 2.10)
+            al.getSequenceAt(i).addSequenceFeature(sf);
           }
         }
         if (vamsasSeq[i].getDBRefCount() > 0)
         {
-          addDBRefs(al.getSequenceAt(i).getDatasetSequence(), vamsasSeq[i]);
+          // adds dbrefs to datasequence's set (since Jalview 2.10)
+          addDBRefs(
+                  al.getSequenceAt(i).getDatasetSequence() == null ? al.getSequenceAt(i)
+                          : al.getSequenceAt(i).getDatasetSequence(),
+                  vamsasSeq[i]);
         }
         if (jseqs[i].getPdbidsCount() > 0)
         {
@@ -2970,7 +3004,15 @@ public class Jalview2XML
             }
             StructureSelectionManager.getStructureSelectionManager(
                     Desktop.instance).registerPDBEntry(entry);
-            al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
+            // adds PDBEntry to datasequence's set (since Jalview 2.10)
+            if (al.getSequenceAt(i).getDatasetSequence() != null)
+            {
+              al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
+            }
+            else
+            {
+              al.getSequenceAt(i).addPDBId(entry);
+            }
           }
         }
       }
@@ -2999,16 +3041,16 @@ public class Jalview2XML
             if (maps[m].getMapping() != null)
             {
               mapping = addMapping(maps[m].getMapping());
-            }
-            if (dnaseq != null && mapping.getTo() != null)
-            {
-              cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
-            }
-            else
-            {
-              // defer to later
-              frefedSequence.add(newAlcodMapRef(maps[m].getDnasq(), cf,
-                      mapping));
+              if (dnaseq != null && mapping.getTo() != null)
+              {
+                cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
+              }
+              else
+              {
+                // defer to later
+                frefedSequence.add(newAlcodMapRef(maps[m].getDnasq(), cf,
+                        mapping));
+              }
             }
           }
           al.addCodonFrame(cf);
@@ -4898,7 +4940,7 @@ public class Jalview2XML
     for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++)
     {
       Sequence vamsasSeq = vamsasSet.getSequence(i);
-      ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed);
+      ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i);
     }
     // create a new dataset
     if (ds == null)
@@ -4925,18 +4967,29 @@ public class Jalview2XML
    *          dataset alignment
    * @param dseqs
    *          vector to add new dataset sequence to
+   * @param ignoreUnrefed
+   *          - when true, don't create new sequences from vamsasSeq if it's id
+   *          doesn't already have an asssociated Jalview sequence.
+   * @param vseqpos
+   *          - used to reorder the sequence in the alignment according to the
+   *          vamsasSeq array ordering, to preserve ordering of dataset
    */
   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
-          AlignmentI ds, Vector dseqs, boolean ignoreUnrefed)
+          AlignmentI ds, Vector dseqs, boolean ignoreUnrefed, int vseqpos)
   {
     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
     // xRef Codon Maps
     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
+    boolean reorder = false;
     SequenceI dsq = null;
     if (sq != null && sq.getDatasetSequence() != null)
     {
       dsq = sq.getDatasetSequence();
     }
+    else
+    {
+      reorder = true;
+    }
     if (sq == null && ignoreUnrefed)
     {
       return;
@@ -5032,6 +5085,35 @@ public class Jalview2XML
         // + (post ? "appended" : ""));
       }
     }
+    else
+    {
+      // sequence refs are identical. We may need to update the existing dataset
+      // alignment with this one, though.
+      if (ds != null && dseqs == null)
+      {
+        int opos = ds.findIndex(dsq);
+        SequenceI tseq = null;
+        if (opos != -1 && vseqpos != opos)
+        {
+          // remove from old position
+          ds.deleteSequence(dsq);
+        }
+        if (vseqpos < ds.getHeight())
+        {
+          if (vseqpos != opos)
+          {
+            // save sequence at destination position
+            tseq = ds.getSequenceAt(vseqpos);
+            ds.replaceSequenceAt(vseqpos, dsq);
+            ds.addSequence(tseq);
+          }
+        }
+        else
+        {
+          ds.addSequence(dsq);
+        }
+      }
+    }
   }
 
   /*
index 828a2aa..a875053 100755 (executable)
@@ -43,6 +43,7 @@ import java.awt.event.KeyAdapter;
 import java.awt.event.KeyEvent;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 
@@ -202,8 +203,19 @@ public class SequenceFetcher extends JPanel implements Runnable
 
   private IProgressIndicator progressIndicator;
 
+  private volatile boolean _isConstructing = false;
+
+  private List<AlignFrame> newAlframes = null;
+
   public SequenceFetcher(IProgressIndicator guiIndic)
   {
+    this(guiIndic, null, null);
+  }
+
+  public SequenceFetcher(IProgressIndicator guiIndic,
+          final String selectedDb, final String queryString)
+  {
+    this._isConstructing=true;
     this.progressIndicator = guiIndic;
     final SequenceFetcher us = this;
     // launch initialiser thread
@@ -215,7 +227,8 @@ public class SequenceFetcher extends JPanel implements Runnable
       {
         if (getSequenceFetcherSingleton(progressIndicator) != null)
         {
-          us.initGui(progressIndicator);
+          us.initGui(progressIndicator, selectedDb, queryString);
+          us._isConstructing=false;
         }
         else
         {
@@ -241,6 +254,26 @@ public class SequenceFetcher extends JPanel implements Runnable
     });
     sf.start();
   }
+  /**
+   * blocking call which creates a new sequence fetcher panel, configures it and presses the OK button with the given database and query.
+   * @param database
+   * @param query
+   */
+  public static List<AlignFrame> fetchAndShow(String database, String query)
+  {
+    final SequenceFetcher sf = new SequenceFetcher(Desktop.instance, database, query);
+    while (sf._isConstructing)
+    {
+      try { Thread.sleep(50);
+      } catch (Exception q)
+      {
+        return Collections.emptyList();
+      }
+    }
+    sf.newAlframes = new ArrayList<AlignFrame>();
+    sf.run();
+    return sf.newAlframes;
+  }
 
   private class DatabaseAuthority extends DefaultMutableTreeNode
   {
@@ -251,13 +284,59 @@ public class SequenceFetcher extends JPanel implements Runnable
   {
 
   };
+  
+  /**
+   * initialise the database and query for this fetcher panel
+   * 
+   * @param selectedDb
+   *          - string that should correspond to a sequence fetcher
+   * @param queryString
+   *          - string that will be entered in the query dialog
+   * @return true if UI was configured with valid database and query string
+   */
+  protected boolean setInitialQuery(String selectedDb, String queryString)
+  {
+    if (selectedDb == null || selectedDb.trim().length() == 0)
+    {
+      return false;
+    }
+    try
+    {
+      List<DbSourceProxy> sp = sfetch.getSourceProxy(selectedDb);
+      for (DbSourceProxy sourcep : sp)
+      {
+        if (sourcep.getTier() == 0)
+        {
+          database.selection = Arrays
+                  .asList(new DbSourceProxy[] { sourcep });
+          break;
+        }
+      }
+      if (database.selection == null || database.selection.size() == 0)
+      {
+        System.err.println("Ignoring fetch parameter db='" + selectedDb
+                + "'");
+        return false;
+      }
+      textArea.setText(queryString);
+    } catch (Exception q)
+    {
+      System.err.println("Ignoring fetch parameter db='" + selectedDb
+              + "' and query='" + queryString + "'");
+      return false;
+    }
+    return true;
+  }
 
   /**
    * called by thread spawned by constructor
    * 
    * @param guiWindow
+   * @param queryString
+   * @param selectedDb
    */
-  private void initGui(IProgressIndicator guiWindow)
+  private void initGui(IProgressIndicator guiWindow, String selectedDb,
+          String queryString)
   {
     this.guiWindow = guiWindow;
     if (guiWindow instanceof AlignFrame)
@@ -268,6 +347,16 @@ public class SequenceFetcher extends JPanel implements Runnable
     try
     {
       jbInit();
+      /*
+       * configure the UI with any query parameters we were called with
+       */
+      if (!setInitialQuery(selectedDb, queryString))
+      {
+        /*
+         * none provided, so show the database chooser
+         */
+        database.waitForInput();
+      }
     } catch (Exception ex)
     {
       ex.printStackTrace();
@@ -425,11 +514,6 @@ public class SequenceFetcher extends JPanel implements Runnable
     this.add(jPanel3, java.awt.BorderLayout.CENTER);
     this.add(jPanel2, java.awt.BorderLayout.NORTH);
     jScrollPane1.getViewport().add(textArea);
-
-    /*
-     * open the database tree
-     */
-    database.waitForInput();
   }
 
   private void pdbSourceAction()
@@ -942,7 +1026,10 @@ public class SequenceFetcher extends JPanel implements Runnable
         {
           af.hideFeatureColumns(SequenceOntologyI.EXON, false);
         }
-
+        if (newAlframes != null)
+        {
+          newAlframes.add(af);
+        }
         Desktop.addInternalFrame(af, title, AlignFrame.DEFAULT_WIDTH,
                 AlignFrame.DEFAULT_HEIGHT);
 
diff --git a/src/jalview/util/LinkedIdentityHashSet.java b/src/jalview/util/LinkedIdentityHashSet.java
new file mode 100644 (file)
index 0000000..5cdbeb1
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.util;
+
+import java.util.AbstractSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+
+/**
+ * Order preserving Set based on System.identityHashCode() for an object, which
+ * also supports Object->index lookup.
+ * 
+ * @author Jim Procter (2016) based on Evgeniy Dorofeev's response: via
+ *         https://stackoverflow.com/questions/17276658/linkedidentityhashset
+ * 
+ */
+public class LinkedIdentityHashSet<E> extends AbstractSet<E>
+{
+  LinkedHashMap<IdentityWrapper, IdentityWrapper> set = new LinkedHashMap<IdentityWrapper, IdentityWrapper>();
+
+  static class IdentityWrapper
+  {
+    Object obj;
+
+    public int p;
+
+    IdentityWrapper(Object obj, int p)
+    {
+      this.obj = obj;
+      this.p = p;
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+      return this.obj == obj;
+    }
+
+    @Override
+    public int hashCode()
+    {
+      return System.identityHashCode(obj);
+    }
+  }
+
+  @Override
+  public boolean add(E e)
+  {
+    IdentityWrapper el = (new IdentityWrapper(e, set.size()));
+    return set.putIfAbsent(el, el) == null;
+  }
+
+  @Override
+  public Iterator<E> iterator()
+  {
+    return new Iterator<E>()
+    {
+      final Iterator<IdentityWrapper> se = set.keySet().iterator();
+
+      @Override
+      public boolean hasNext()
+      {
+        return se.hasNext();
+      }
+
+      @SuppressWarnings("unchecked")
+      @Override
+      public E next()
+      {
+        return (E) se.next().obj;
+      }
+    };
+  }
+
+  @Override
+  public int size()
+  {
+    return set.size();
+  }
+
+  /**
+   * Lookup the index for e in the set
+   * 
+   * @param e
+   * @return position of e in the set when it was added.
+   */
+  public int indexOf(E e)
+  {
+    return set.get(e).p;
+  }
+}
index cae968e..dc5bee8 100644 (file)
@@ -1103,4 +1103,14 @@ public class MapList
     return forwardStrand;
   }
 
+  /**
+   * 
+   * @return true if from, or to is a three to 1 mapping
+   */
+  public boolean isTripletMap()
+  {
+    return (toRatio == 3 && fromRatio == 1)
+            || (fromRatio == 3 && toRatio == 1);
+  }
+
 }
index b75ef50..2863340 100644 (file)
@@ -27,6 +27,7 @@ import static org.testng.AssertJUnit.assertNull;
 import static org.testng.AssertJUnit.assertSame;
 import static org.testng.AssertJUnit.assertTrue;
 
+import jalview.datamodel.AlignedCodonFrame.SequenceToSequenceMapping;
 import jalview.io.AppletFormatAdapter;
 import jalview.io.FormatAdapter;
 import jalview.util.MapList;
@@ -37,6 +38,7 @@ import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 
+import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -101,6 +103,324 @@ public class AlignmentTest
     return a;
   }
 
+  /**
+   * assert wrapper: tests all references in the given alignment are consistent
+   * 
+   * @param alignment
+   */
+  public static void assertAlignmentDatasetRefs(AlignmentI alignment)
+  {
+    verifyAlignmentDatasetRefs(alignment, true, null);
+  }
+
+  /**
+   * assert wrapper: tests all references in the given alignment are consistent
+   * 
+   * @param alignment
+   * @param message
+   *          - prefixed to any assert failed messages
+   */
+  public static void assertAlignmentDatasetRefs(AlignmentI alignment,
+          String message)
+  {
+    verifyAlignmentDatasetRefs(alignment, true, message);
+  }
+
+  /**
+   * verify sequence and dataset references are properly contained within
+   * dataset
+   * 
+   * @param alignment
+   *          - the alignmentI object to verify (either alignment or dataset)
+   * @param raiseAssert
+   *          - when set, testng assertions are raised.
+   *          @param message
+   *          - null or a string message to prepend to the assert failed messages.
+   * @return true if alignment references were in order, otherwise false.
+   */
+  public static boolean verifyAlignmentDatasetRefs(AlignmentI alignment,
+          boolean raiseAssert, String message)
+  {
+    if (message==null) { message = ""; }
+    if (alignment == null)
+    {
+      if (raiseAssert)
+      {
+        Assert.fail(message+"Alignment for verification was null.");
+      }
+      return false;
+    }
+    if (alignment.getDataset() != null)
+    {
+      AlignmentI dataset = alignment.getDataset();
+      // check all alignment sequences have their dataset within the dataset
+      for (SequenceI seq : alignment.getSequences())
+      {
+        SequenceI seqds = seq.getDatasetSequence();
+        if (seqds.getDatasetSequence() != null)
+        {
+          if (raiseAssert)
+          {
+            Assert.fail(message+" Alignment contained a sequence who's dataset sequence has a second dataset reference.");
+          }
+          return false;
+        }
+        if (dataset.findIndex(seqds) == -1)
+        {
+          if (raiseAssert)
+          {
+            Assert.fail(message+" Alignment contained a sequence who's dataset sequence was not in the dataset.");
+          }
+          return false;
+        }
+      }
+      return verifyAlignmentDatasetRefs(alignment.getDataset(), raiseAssert, message);
+    }
+    else
+    {
+      int dsp = -1;
+      // verify all dataset sequences
+      for (SequenceI seqds : alignment.getSequences())
+      {
+        dsp++;
+        if (seqds.getDatasetSequence() != null)
+        {
+          if (raiseAssert)
+          {
+            Assert.fail(message+" Dataset contained a sequence with non-null dataset reference (ie not a dataset sequence!)");
+          }
+          return false;
+        }
+        int foundp = alignment.findIndex(seqds);
+        if (foundp != dsp)
+        {
+          if (raiseAssert)
+          {
+            Assert.fail(message
+                    + " Dataset sequence array contains a reference at "
+                    + dsp + " to a sequence first seen at " + foundp + " ("
+                    + seqds.toString() + ")");
+          }
+          return false;
+        }
+        if (seqds.getDBRefs() != null)
+        {
+          for (DBRefEntry dbr : seqds.getDBRefs())
+          {
+            if (dbr.getMap() != null)
+            {
+              SequenceI seqdbrmapto = dbr.getMap().getTo();
+              if (seqdbrmapto != null)
+              {
+                if (seqdbrmapto.getDatasetSequence() != null)
+                {
+                  if (raiseAssert)
+                  {
+                    Assert.fail(message+" DBRefEntry for sequence in alignment had map to sequence which was not a dataset sequence");
+                  }
+                  return false;
+
+                }
+                if (alignment.findIndex(dbr.getMap().getTo()) == -1)
+                {
+                  if (raiseAssert)
+                  {
+                    Assert.fail(message+" DBRefEntry for sequence in alignment had map to sequence not in dataset");
+                  }
+                  return false;
+                }
+              }
+            }
+          }
+        }
+      }
+      // finally, verify codonmappings involve only dataset sequences.
+      if (alignment.getCodonFrames() != null)
+      {
+        for (AlignedCodonFrame alc : alignment.getCodonFrames())
+        {
+          for (SequenceToSequenceMapping ssm : alc.getMappings())
+          {
+            if (ssm.getFromSeq().getDatasetSequence() != null)
+            {
+              if (raiseAssert)
+              {
+                Assert.fail(message+" CodonFrame-SSM-FromSeq is not a dataset sequence");
+              }
+              return false;
+            }
+            if (alignment.findIndex(ssm.getFromSeq()) == -1)
+            {
+
+              if (raiseAssert)
+              {
+                Assert.fail(message+" CodonFrame-SSM-FromSeq is not contained in dataset");
+              }
+              return false;
+            }
+            if (ssm.getMapping().getTo().getDatasetSequence() != null)
+            {
+              if (raiseAssert)
+              {
+                Assert.fail(message+" CodonFrame-SSM-Mapping-ToSeq is not a dataset sequence");
+              }
+              return false;
+            }
+            if (alignment.findIndex(ssm.getMapping().getTo()) == -1)
+            {
+
+              if (raiseAssert)
+              {
+                Assert.fail(message+" CodonFrame-SSM-Mapping-ToSeq is not contained in dataset");
+              }
+              return false;
+            }
+          }
+        }
+      }
+    }
+    return true; // all relationships verified!
+  }
+
+  /**
+   * call verifyAlignmentDatasetRefs with and without assertion raising enabled,
+   * to check expected pass/fail actually occurs in both conditions
+   * 
+   * @param al
+   * @param expected
+   * @param msg
+   */
+  private void assertVerifyAlignment(AlignmentI al, boolean expected,
+          String msg)
+  {
+    if (expected)
+    {
+      try
+      {
+
+        Assert.assertTrue(verifyAlignmentDatasetRefs(al, true, null),
+                "Valid test alignment failed when raiseAsserts enabled:"
+                        + msg);
+      } catch (AssertionError ae)
+      {
+        ae.printStackTrace();
+        Assert.fail(
+                "Valid test alignment raised assertion errors when raiseAsserts enabled: "
+                        + msg, ae);
+      }
+      // also check validation passes with asserts disabled
+      Assert.assertTrue(verifyAlignmentDatasetRefs(al, false, null),
+              "Valid test alignment tested false when raiseAsserts disabled:"
+                      + msg);
+    }
+    else
+    {
+      boolean assertRaised = false;
+      try
+      {
+        verifyAlignmentDatasetRefs(al, true, null);
+      } catch (AssertionError ae)
+      {
+        // expected behaviour
+        assertRaised = true;
+      }
+      if (!assertRaised)
+      {
+        Assert.fail("Invalid test alignment passed when raiseAsserts enabled:"
+                + msg);
+      }
+      // also check validation passes with asserts disabled
+      Assert.assertFalse(verifyAlignmentDatasetRefs(al, false, null),
+              "Invalid test alignment tested true when raiseAsserts disabled:"
+                      + msg);
+    }
+  }
+  @Test(groups = { "Functional" })
+  public void testVerifyAlignmentDatasetRefs()
+  {
+    SequenceI sq1 = new Sequence("sq1", "ASFDD"), sq2 = new Sequence("sq2",
+            "TTTTTT");
+
+    // construct simple valid alignment dataset
+    Alignment al = new Alignment(new SequenceI[] {
+        sq1, sq2 });
+    // expect this to pass
+    assertVerifyAlignment(al, true, "Simple valid alignment didn't verify");
+
+    // check test for sequence->datasetSequence validity
+    sq1.setDatasetSequence(sq2);
+    assertVerifyAlignment(
+            al,
+            false,
+            "didn't detect dataset sequence with a dataset sequence reference.");
+
+    sq1.setDatasetSequence(null);
+    assertVerifyAlignment(
+            al,
+            true,
+            "didn't reinstate validity after nulling dataset sequence dataset reference");
+
+    // now create dataset and check again
+    al.createDatasetAlignment();
+    assertNotNull(al.getDataset());
+
+    assertVerifyAlignment(al, true,
+            "verify failed after createDatasetAlignment");
+
+    // create a dbref on sq1 with a sequence ref to sq2
+    DBRefEntry dbrs1tos2 = new DBRefEntry("UNIPROT", "1", "Q111111");
+    dbrs1tos2.setMap(new Mapping(sq2.getDatasetSequence(),
+            new int[] { 1, 5 }, new int[] { 2, 6 }, 1, 1));
+    sq1.getDatasetSequence().addDBRef(dbrs1tos2);
+    assertVerifyAlignment(al, true,
+            "verify failed after addition of valid DBRefEntry/map");
+    // now create a dbref on a new sequence which maps to another sequence
+    // outside of the dataset
+    SequenceI sqout = new Sequence("sqout", "ututututucagcagcag"), sqnew = new Sequence(
+            "sqnew", "EEERRR");
+    DBRefEntry sqnewsqout = new DBRefEntry("ENAFOO", "1", "R000001");
+    sqnewsqout.setMap(new Mapping(sqout, new int[] { 1, 6 }, new int[] { 1,
+        18 }, 1, 3));
+    al.getDataset().addSequence(sqnew);
+
+    assertVerifyAlignment(al, true,
+            "verify failed after addition of new sequence to dataset");
+    // now start checking exception conditions
+    sqnew.addDBRef(sqnewsqout);
+    assertVerifyAlignment(
+            al,
+            false,
+            "verify passed when a dbref with map to sequence outside of dataset was added");
+    // make the verify pass by adding the outsider back in
+    al.getDataset().addSequence(sqout);
+    assertVerifyAlignment(al, true,
+            "verify should have passed after adding dbref->to sequence in to dataset");
+    // and now the same for a codon mapping...
+    SequenceI sqanotherout = new Sequence("sqanotherout",
+            "aggtutaggcagcagcag");
+
+    AlignedCodonFrame alc = new AlignedCodonFrame();
+    alc.addMap(sqanotherout, sqnew, new MapList(new int[] { 1, 6 },
+            new int[] { 1, 18 }, 3, 1));
+
+    al.addCodonFrame(alc);
+    Assert.assertEquals(al.getDataset().getCodonFrames().size(), 1);
+
+    assertVerifyAlignment(
+            al,
+            false,
+            "verify passed when alCodonFrame mapping to sequence outside of dataset was added");
+    // make the verify pass by adding the outsider back in
+    al.getDataset().addSequence(sqanotherout);
+    assertVerifyAlignment(
+            al,
+            true,
+            "verify should have passed once all sequences involved in alCodonFrame were added to dataset");
+    al.getDataset().addSequence(sqanotherout);
+    assertVerifyAlignment(al, false,
+            "verify should have failed when a sequence was added twice to the dataset");
+
+  }
   /*
    * Read in Stockholm format test data including secondary structure
    * annotations.
@@ -460,6 +780,60 @@ public class AlignmentTest
     assertTrue(ds.getCodonFrames().contains(acf));
   }
 
+  /**
+   * tests the addition of *all* sequences referred to by a sequence being added
+   * to the dataset
+   */
+  @Test(groups = "Functional")
+  public void testCreateDatasetAlignmentWithMappedToSeqs()
+  {
+    // Alignment with two sequences, gapped.
+    SequenceI sq1 = new Sequence("sq1", "A--SDF");
+    SequenceI sq2 = new Sequence("sq2", "G--TRQ");
+
+    // cross-references to two more sequences.
+    DBRefEntry dbr = new DBRefEntry("SQ1", "", "sq3");
+    SequenceI sq3 = new Sequence("sq3", "VWANG");
+    dbr.setMap(new Mapping(sq3, new MapList(new int[] { 1, 4 }, new int[] {
+        2, 5 }, 1, 1)));
+    sq1.addDBRef(dbr);
+
+    SequenceI sq4 = new Sequence("sq4", "ERKWI");
+    DBRefEntry dbr2 = new DBRefEntry("SQ2", "", "sq4");
+    dbr2.setMap(new Mapping(sq4, new MapList(new int[] { 1, 4 }, new int[] {
+        2, 5 }, 1, 1)));
+    sq2.addDBRef(dbr2);
+    // and a 1:1 codonframe mapping between them.
+    AlignedCodonFrame alc = new AlignedCodonFrame();
+    alc.addMap(sq1, sq2, new MapList(new int[] { 1, 4 },
+            new int[] { 1, 4 }, 1, 1));
+
+    AlignmentI protein = new Alignment(new SequenceI[] { sq1, sq2 });
+
+    /*
+     * create the alignment dataset
+     * note this creates sequence datasets where missing
+     * as a side-effect (in this case, on seq2
+     */
+
+    // TODO promote this method to AlignmentI
+    ((Alignment) protein).createDatasetAlignment();
+
+    AlignmentI ds = protein.getDataset();
+
+    // should be 4 sequences in dataset - two materialised, and two propagated
+    // from dbref
+    assertEquals(4, ds.getHeight());
+    assertTrue(ds.getSequences().contains(sq1.getDatasetSequence()));
+    assertTrue(ds.getSequences().contains(sq2.getDatasetSequence()));
+    assertTrue(ds.getSequences().contains(sq3));
+    assertTrue(ds.getSequences().contains(sq4));
+    // Should have one codon frame mapping between sq1 and sq2 via dataset
+    // sequences
+    assertEquals(ds.getCodonFrame(sq1.getDatasetSequence()),
+            ds.getCodonFrame(sq2.getDatasetSequence()));
+  }
+
   @Test(groups = "Functional")
   public void testAddCodonFrame()
   {
@@ -483,6 +857,27 @@ public class AlignmentTest
   }
 
   @Test(groups = "Functional")
+  public void testAddSequencePreserveDatasetIntegrity()
+  {
+    Sequence seq = new Sequence("testSeq", "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+    Alignment align = new Alignment(new SequenceI[] { seq });
+    align.createDatasetAlignment();
+    AlignmentI ds = align.getDataset();
+    SequenceI copy = new Sequence(seq);
+    copy.insertCharAt(3, 5, '-');
+    align.addSequence(copy);
+    Assert.assertEquals(align.getDataset().getHeight(), 1,
+            "Dataset shouldn't have more than one sequence.");
+
+    Sequence seq2 = new Sequence("newtestSeq", "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+    align.addSequence(seq2);
+    Assert.assertEquals(align.getDataset().getHeight(), 2,
+            "Dataset should now have two sequences.");
+
+    assertAlignmentDatasetRefs(align,
+            "addSequence broke dataset reference integrity");
+  }
+  @Test(groups = "Functional")
   public void getVisibleStartAndEndIndexTest()
   {
     Sequence seq = new Sequence("testSeq", "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
diff --git a/test/jalview/io/CrossRef2xmlTests.java b/test/jalview/io/CrossRef2xmlTests.java
new file mode 100644 (file)
index 0000000..20a95d0
--- /dev/null
@@ -0,0 +1,423 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.io;
+
+import jalview.analysis.CrossRef;
+import jalview.api.AlignmentViewPanel;
+import jalview.datamodel.AlignedCodonFrame;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentTest;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.gui.CrossRefAction;
+import jalview.gui.Desktop;
+import jalview.gui.Jalview2XML;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+@Test(singleThreaded = true)
+public class CrossRef2xmlTests extends Jalview2xmlBase
+{
+
+  /**
+   * test store and recovery of expanded views
+   * 
+   * @throws Exception
+   */
+  @Test(groups = { "Operational" }, enabled = true)
+  public void testRetrieveAndShowCrossref() throws Exception
+  {
+    // for every set of db queries
+    // retrieve db query
+    // verify presence of expected xrefs
+    // show xrefs - verify expected type of frame is shown for each xref
+    // show xrefs again
+    // - verify original -> xref -> xref(original) recovers frame containing at
+    // least the first retrieved sequence
+    // store
+    // 1. whole project
+    // 2. individual frames
+    // 3. load each one back and verify
+    // . aligned sequences (.toString() )
+    // . xrefs (.toString() )
+    // . codonframes
+    //
+    //
+    HashMap<String, String> dbtoviewBit = new HashMap<String, String>();
+    List<String> keyseq = new ArrayList<String>();
+    HashMap<String, File> savedProjects = new HashMap<String, File>();
+
+    for (String[] did : new String[][] { { "UNIPROT", "P01731" } })
+    {
+      // pass counters - 0 - first pass, 1 means retrieve project rather than
+      // perform action
+      int pass1 = 0, pass2 = 0, pass3 = 0;
+      // each do loop performs two iterations in the first outer loop pass, but
+      // only performs one iteration on the second outer loop
+      // ie. pass 1 = 0 {pass 2= 0 { pass 3 = 0,1 }, pass 2=1 { pass 3 = 0 }}, 1
+      // { pass 2 = 0 { pass 3 = 0 } }
+      do
+      {
+        String first = did[0] + " " + did[1];
+        AlignFrame af = null;
+        boolean dna;
+        AlignmentI retral;
+        AlignmentI dataset;
+        SequenceI[] seqs;
+        List<String> ptypes = null;
+        if (pass1 == 0)
+        {
+          // retrieve dbref
+
+          keyseq.add(first);
+
+          af = jalview.gui.SequenceFetcher.fetchAndShow(did[0], did[1])
+                  .get(0);
+          Assert.assertTrue(af != null, "Didn't retrieve " + first);
+
+          // verify references for retrieved data
+          AlignmentTest.assertAlignmentDatasetRefs(af.getViewport()
+                  .getAlignment(), "Pass (" + pass1 + "," + pass2 + ","
+                  + pass3 + "): Fetch " + first + ":");
+          dna = af.getViewport().getAlignment().isNucleotide();
+          retral = af.getViewport().getAlignment();
+          dataset = retral.getDataset();
+          seqs = retral.getSequencesArray();
+
+        }
+        else
+        {
+          Desktop.instance.closeAll_actionPerformed(null);
+          // recover stored project
+          af = new FileLoader(false).LoadFileWaitTillLoaded(savedProjects
+                  .get(first).toString(), FormatAdapter.FILE);
+          System.out.println("Recovered view for '" + first + "' from '"
+                  + savedProjects.get(first).toString() + "'");
+          dna = af.getViewport().getAlignment().isNucleotide();
+          retral = af.getViewport().getAlignment();
+          dataset = retral.getDataset();
+          seqs = retral.getSequencesArray();
+
+          // verify references for recovered data
+          AlignmentTest.assertAlignmentDatasetRefs(af.getViewport()
+                  .getAlignment(), "Pass (" + pass1 + "," + pass2 + ","
+                  + pass3 + "): Recover " + first + ":");
+
+        }
+
+        // store project on first pass, compare next pass
+        stringify(dbtoviewBit, savedProjects, first, af.alignPanel);
+
+        ptypes = (seqs == null || seqs.length == 0) ? null : new CrossRef(
+                seqs, dataset).findXrefSourcesForSequences(dna);
+
+        // start of pass2: retrieve each cross-ref for fetched or restored
+        // project.
+        do // first cross ref and recover crossref loop
+        {
+
+          for (String db : ptypes)
+          {
+            // counter for splitframe views retrieved via crossref
+            int firstcr_ap = 0;
+            // build next key so we an retrieve all views
+            String nextxref = first + " -> " + db + "{" + firstcr_ap + "}";
+            // perform crossref action, or retrieve stored project
+            List<AlignmentViewPanel> cra_views = new ArrayList<AlignmentViewPanel>();
+            CrossRefAction cra = null;
+            
+            if (pass2 == 0)
+            { // retrieve and show cross-refs in this thread
+              cra = new CrossRefAction(af, seqs, dna, db);
+              cra.run();
+              Assert.assertTrue(cra.getXrefViews().size() > 0,
+                      "No crossrefs retrieved for " + db);
+              cra_views = cra.getXrefViews();
+            }
+            else
+            {
+              Desktop.instance.closeAll_actionPerformed(null);
+              pass3 = 0;
+              // recover stored project
+              AlignFrame af2 = new FileLoader(false)
+                      .LoadFileWaitTillLoaded(savedProjects.get(nextxref)
+                              .toString(), FormatAdapter.FILE);
+              System.out.println("Recovered view for '" + nextxref
+                      + "' from '" + savedProjects.get(nextxref).toString()
+                      + "'");
+              // gymnastics to recover the alignPanel/Complementary alignPanel
+              if (af2.getViewport().isNucleotide())
+              {
+                // top view, then bottom
+                cra_views.add(af2.getViewport().getAlignPanel());
+                cra_views.add(((jalview.gui.AlignViewport) af2
+                        .getViewport().getCodingComplement())
+                        .getAlignPanel());
+
+              }
+              else
+              {
+                // bottom view, then top
+                cra_views.add(((jalview.gui.AlignViewport) af2
+                        .getViewport().getCodingComplement())
+                        .getAlignPanel());
+                cra_views.add(af2.getViewport().getAlignPanel());
+
+              }
+            }
+            HashMap<String, List<String>> xrptypes = new HashMap<String, List<String>>();
+            // first save/verify views.
+            for (AlignmentViewPanel avp : cra_views)
+            {
+              nextxref = first + " -> " + db + "{" + firstcr_ap++ + "}";
+              // verify references for this panel
+              AlignmentTest.assertAlignmentDatasetRefs(avp.getAlignment(),
+                      "Pass (" + pass1 + "," + pass2 + "," + pass3
+                              + "): before start of pass3: " + nextxref
+                              + ":");
+
+              SequenceI[] xrseqs = avp.getAlignment().getSequencesArray();
+
+              List<String> _xrptypes = (seqs == null || seqs.length == 0) ? null
+                      : new CrossRef(xrseqs, dataset)
+                              .findXrefSourcesForSequences(avp
+                                      .getAlignViewport().isNucleotide());
+              
+              stringify(dbtoviewBit, savedProjects, nextxref, avp);
+              xrptypes.put(nextxref, _xrptypes);
+
+            }
+
+            // now do the second xref pass starting from either saved or just
+            // recovered split pane, in sequence
+            do // retrieve second set of cross refs or recover and verify
+            {
+              firstcr_ap = 0;
+              for (AlignmentViewPanel avp : cra_views)
+              {
+                nextxref = first + " -> " + db + "{" + firstcr_ap++ + "}";
+                for (String xrefdb : xrptypes.get(nextxref))
+                {
+                  List<AlignmentViewPanel> cra_views2 = new ArrayList<AlignmentViewPanel>();
+                  int q = 0;
+                  String nextnextxref = nextxref
+                          + " -> " + xrefdb + "{" + q + "}";
+
+                  if (pass3 == 0)
+                  {
+
+                    SequenceI[] xrseqs = avp.getAlignment()
+                            .getSequencesArray();
+                    AlignFrame nextaf = Desktop.getAlignFrameFor(avp
+                            .getAlignViewport());
+
+                    cra = new CrossRefAction(nextaf, xrseqs, avp
+                            .getAlignViewport().isNucleotide(), xrefdb);
+                    cra.run();
+                    Assert.assertTrue(
+                            cra.getXrefViews().size() > 0,
+                            "No crossrefs found for '" + nextnextxref
+                                    + "' to " + xrefdb + " via '"
+                                    + nextaf.getTitle() + "'");
+                    cra_views2 = cra.getXrefViews();
+                  }
+                  else
+                  {
+                    Desktop.instance.closeAll_actionPerformed(null);
+                    // recover stored project
+                    AlignFrame af2 = new FileLoader(false)
+                            .LoadFileWaitTillLoaded(
+                                    savedProjects.get(nextnextxref)
+                                            .toString(), FormatAdapter.FILE);
+                    System.out.println("Recovered view for '"
+                            + nextnextxref + "' from '"
+                            + savedProjects.get(nextnextxref).toString()
+                            + "'");
+                    // gymnastics to recover the alignPanel/Complementary
+                    // alignPanel
+                    if (af2.getViewport().isNucleotide())
+                    {
+                      // top view, then bottom
+                      cra_views2.add(af2.getViewport().getAlignPanel());
+                      cra_views2.add(((jalview.gui.AlignViewport) af2
+                              .getViewport().getCodingComplement())
+                              .getAlignPanel());
+
+                    }
+                    else
+                    {
+                      // bottom view, then top
+                      cra_views2.add(((jalview.gui.AlignViewport) af2
+                              .getViewport().getCodingComplement())
+                              .getAlignPanel());
+                      cra_views2.add(af2.getViewport().getAlignPanel());
+                    }
+                    Assert.assertEquals(cra_views2.size(), 2);
+                    Assert.assertNotNull(cra_views2.get(0));
+                    Assert.assertNotNull(cra_views2.get(1));
+                  }
+
+                  for (AlignmentViewPanel nextavp : cra_views2)
+                  {
+                    nextnextxref = nextxref
+                            + " -> " + xrefdb + "{" + q++ + "}";
+
+                    // verify references for this panel
+                    AlignmentTest.assertAlignmentDatasetRefs(
+                            nextavp.getAlignment(), "" + "Pass (" + pass1
+                                    + "," + pass2 + "): For "
+                                    + nextnextxref + ":");
+
+                    stringify(dbtoviewBit, savedProjects, nextnextxref,
+                            nextavp);
+                    keyseq.add(nextnextxref);
+                  }
+                } // end of loop around showing all xrefdb for crossrf2
+
+              } // end of loop around all viewpanels from crossrf1
+            } while (pass2 == 2 && pass3++ < 2);
+            // fetchdb->crossref1->crossref-2->verify for xrefs we
+            // either loop twice when pass2=0, or just once when pass2=1
+            // (recovered project from previous crossref)
+
+          } // end of loop over db-xrefs for crossref-2
+
+          // fetchdb-->crossref1
+          // for each xref we try to retrieve xref, store and verify when
+          // pass1=0, or just retrieve and verify when pass1=1
+        } while (pass1 == 1 && pass2++ < 2);
+        // fetchdb
+        // for each ref we
+        // loop twice: first, do the retrieve, second recover from saved project
+
+        // increment pass counters, so we repeat traversal starting from the
+        // oldest saved project first.
+        if (pass1 == 0)
+        {
+          // verify stored projects for first set of cross references
+          pass1 = 1;
+          // and verify cross-references retrieved from stored projects
+          pass2 = 0;
+          pass3 = 0;
+        }
+        else
+        {
+          // verify stored projects for second set of cross references
+          pass2 = 1;
+          // and verify cross-references retrievable from those stored projects.
+          pass3 = 0;
+        }
+      } while (pass3 < 2);
+    }
+  }
+
+  /**
+   * first time called, record strings derived from alignment and
+   * alignedcodonframes, and save view to a project file. Second time called,
+   * compare strings to existing ones. org.testng.Assert.assertTrue on
+   * stringmatch
+   * 
+   * @param dbtoviewBit
+   *          map between xrefpath and view string
+   * @param savedProjects
+   *          - map from xrefpath to saved project filename (createTempFile)
+   * @param xrefpath
+   *          - xrefpath - unique ID for this context (composed of sequence of
+   *          db-fetch/cross-ref actions preceeding state)
+   * @param avp
+   *          - viewpanel to store (for viewpanels in splitframe, the same
+   *          project should be written for both panels, only one needs
+   *          recovering for comparison on the next stringify call, but each
+   *          viewpanel needs to be called with a distinct xrefpath to ensure
+   *          each one's strings are compared)
+   */
+  private void stringify(HashMap<String, String> dbtoviewBit,
+          HashMap<String, File> savedProjects, String xrefpath,
+          AlignmentViewPanel avp)
+  {
+    if (savedProjects != null)
+    {
+      if (savedProjects.get(xrefpath) == null)
+      {
+        // write a project file for this view. On the second pass, this will be
+        // recovered and cross-references verified
+        try
+        {
+          File prfile = File.createTempFile("crossRefTest", ".jvp");
+          AlignFrame af = Desktop.getAlignFrameFor(avp.getAlignViewport());
+          new Jalview2XML(false).saveAlignment(af, prfile.toString(),
+                  af.getTitle());
+          System.out.println("Written view from '" + xrefpath + "' as '"
+                  + prfile.getAbsolutePath() + "'");
+          savedProjects.put(xrefpath, prfile);
+        } catch (IOException q)
+        {
+          Assert.fail("Unexpected IO Exception", q);
+        }
+      }
+      else
+      {
+        System.out.println("Stringify check on view from '" + xrefpath
+                + "' [ possibly retrieved from '"
+                + savedProjects.get(xrefpath).getAbsolutePath() + "' ]");
+
+      }
+    }
+
+    StringBuilder sbr = new StringBuilder();
+    sbr.append(avp.getAlignment().toString());
+    sbr.append("\n");
+    sbr.append("<End of alignment>");
+    sbr.append("\n");
+    sbr.append(avp.getAlignment().getDataset());
+    sbr.append("\n");
+    sbr.append("<End of dataset>");
+    sbr.append("\n");
+    int p = 0;
+    if (avp.getAlignment().getCodonFrames() != null)
+    {
+      for (AlignedCodonFrame ac : avp.getAlignment().getCodonFrames())
+      {
+        sbr.append("<AlignedCodonFrame " + p++ + ">");
+        sbr.append("\n");
+        sbr.append(ac.toString());
+        sbr.append("\n");
+      }
+    }
+    String dbt = dbtoviewBit.get(xrefpath);
+    if (dbt == null)
+    {
+      dbtoviewBit.put(xrefpath, sbr.toString());
+    }
+    else
+    {
+      Assert.assertEquals(sbr.toString(), dbt, "stringify mismatch for "
+              + xrefpath);
+    }
+  }
+}
diff --git a/test/jalview/io/Jalview2xmlBase.java b/test/jalview/io/Jalview2xmlBase.java
new file mode 100644 (file)
index 0000000..379fd68
--- /dev/null
@@ -0,0 +1,76 @@
+package jalview.io;
+
+import jalview.bin.Cache;
+import jalview.bin.Jalview;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.SequenceI;
+import jalview.gui.Desktop;
+
+import java.util.Date;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeTest;
+
+public class Jalview2xmlBase
+{
+
+  /**
+   * @throws java.lang.Exception
+   */
+  @BeforeClass(alwaysRun = true)
+  public static void setUpBeforeClass() throws Exception
+  {
+    /*
+     * use read-only test properties file
+     */
+    Cache.loadProperties("test/jalview/io/testProps.jvprops");
+
+    /*
+     * set news feed last read to a future time to ensure no
+     * 'unread' news item is displayed
+     */
+    Date oneHourFromNow = new Date(System.currentTimeMillis() + 3600 * 1000);
+    Cache.setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED", oneHourFromNow);
+
+    Jalview.main(new String[] {});
+  }
+
+  /**
+   * @throws java.lang.Exception
+   */
+  @AfterClass(alwaysRun = true)
+  public static void tearDownAfterClass() throws Exception
+  {
+    jalview.gui.Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+  @BeforeTest(alwaysRun = true)
+  public static void clearDesktop()
+  {
+    if (Desktop.instance != null && Desktop.getAlignFrames() != null)
+    {
+      Desktop.instance.closeAll_actionPerformed(null);
+    }
+  }
+
+  public int countDsAnn(jalview.viewmodel.AlignmentViewport avp)
+  {
+    int numdsann = 0;
+    for (SequenceI sq : avp.getAlignment().getDataset().getSequences())
+    {
+      if (sq.getAnnotation() != null)
+      {
+        for (AlignmentAnnotation dssa : sq.getAnnotation())
+        {
+          if (dssa.isValidStruc())
+          {
+            numdsann++;
+          }
+        }
+      }
+    }
+    return numdsann;
+  }
+
+}
index 784f3dd..f7853ff 100644 (file)
@@ -29,8 +29,6 @@ import static org.testng.AssertJUnit.assertTrue;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.api.ViewStyleI;
-import jalview.bin.Cache;
-import jalview.bin.Jalview;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.HiddenSequences;
@@ -49,70 +47,18 @@ import jalview.viewmodel.AlignmentViewport;
 
 import java.io.File;
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import org.testng.Assert;
 import org.testng.AssertJUnit;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 @Test(singleThreaded = true)
-public class Jalview2xmlTests
+public class Jalview2xmlTests extends Jalview2xmlBase
 {
 
-  /**
-   * @throws java.lang.Exception
-   */
-  @BeforeClass(alwaysRun = true)
-  public static void setUpBeforeClass() throws Exception
-  {
-    /*
-     * use read-only test properties file
-     */
-    Cache.loadProperties("test/jalview/io/testProps.jvprops");
-
-    /*
-     * set news feed last read to a future time to ensure no
-     * 'unread' news item is displayed
-     */
-    Date oneHourFromNow = new Date(System.currentTimeMillis() + 3600 * 1000);
-    Cache.setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED", oneHourFromNow);
-
-    Jalview.main(new String[] {});
-  }
-
-  /**
-   * @throws java.lang.Exception
-   */
-  @AfterClass(alwaysRun = true)
-  public static void tearDownAfterClass() throws Exception
-  {
-    Desktop.instance.closeAll_actionPerformed(null);
-  }
-
-  int countDsAnn(jalview.viewmodel.AlignmentViewport avp)
-  {
-    int numdsann = 0;
-    for (SequenceI sq : avp.getAlignment().getDataset().getSequences())
-    {
-      if (sq.getAnnotation() != null)
-      {
-        for (AlignmentAnnotation dssa : sq.getAnnotation())
-        {
-          if (dssa.isValidStruc())
-          {
-            numdsann++;
-          }
-        }
-      }
-    }
-    return numdsann;
-  }
-
   @Test(groups = { "Functional" })
   public void testRNAStructureRecovery() throws Exception
   {
diff --git a/test/jalview/io/testProps_nodas.jvprops b/test/jalview/io/testProps_nodas.jvprops
new file mode 100644 (file)
index 0000000..da95549
--- /dev/null
@@ -0,0 +1,83 @@
+#---JalviewX Properties File---
+#Fri Apr 25 09:54:25 BST 2014
+SCREEN_Y=768
+SCREEN_X=936
+SHOW_WSDISCOVERY_ERRORS=true
+LATEST_VERSION=2.8.0b1
+SHOW_CONSERVATION=true
+JALVIEW_RSS_WINDOW_SCREEN_WIDTH=550
+JAVA_CONSOLE_SCREEN_WIDTH=450
+LAST_DIRECTORY=/Volumes/Data/Users/jimp/Documents/testing/Jalview/examples
+ID_ITALICS=true
+SORT_ALIGNMENT=No sort
+SHOW_IDENTITY=true
+WSMENU_BYHOST=false
+SEQUENCE_LINKS=EMBL-EBI Search|http\://www.ebi.ac.uk/ebisearch/search.ebi?db\=allebi&query\=$SEQUENCE_ID$
+SHOW_FULLSCREEN=false
+RECENT_URL=http\://www.jalview.org/examples/exampleFile_2_7.jar
+FONT_NAME=SansSerif
+BLC_JVSUFFIX=true
+VERSION_CHECK=false
+YEAR=2011
+SHOW_DBREFS_TOOLTIP=true
+MSF_JVSUFFIX=true
+SCREENGEOMETRY_HEIGHT=1600
+JAVA_CONSOLE_SCREEN_Y=475
+JAVA_CONSOLE_SCREEN_X=830
+PFAM_JVSUFFIX=true
+PIR_JVSUFFIX=true
+STARTUP_FILE=http\://www.jalview.org/examples/exampleFile_2_3.jar
+JAVA_CONSOLE_SCREEN_HEIGHT=162
+PIR_MODELLER=false
+GAP_SYMBOL=-
+SHOW_QUALITY=true
+SHOW_GROUP_CONSERVATION=false
+SHOW_JWS2_SERVICES=true
+SHOW_NPFEATS_TOOLTIP=true
+FONT_STYLE=plain
+ANTI_ALIAS=false
+SORT_BY_TREE=false
+RSBS_SERVICES=|Multi-Harmony|Analysis|Sequence Harmony and Multi-Relief (Brandt et al. 2010)|hseparable,gapCharacter\='-',returns\='ANNOTATION'|?tool\=jalview|http\://zeus.few.vu.nl/programs/shmrwww/index.php?tool\=jalview&groups\=$PARTITION\:min\='2',minsize\='2',sep\=' '$&ali_file\=$ALIGNMENT\:format\='FASTA',writeasfile$
+AUTHORFNAMES=Jim Procter, Andrew Waterhouse, Jan Engelhardt, Lauren Lui, Michele Clamp, James Cuff, Steve Searle, David Martin & Geoff Barton
+JALVIEW_RSS_WINDOW_SCREEN_HEIGHT=328
+SHOW_GROUP_CONSENSUS=false
+SHOW_CONSENSUS_HISTOGRAM=true
+SHOW_OVERVIEW=false
+AUTHORS=J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle
+FIGURE_AUTOIDWIDTH=false
+SCREEN_WIDTH=900
+ANNOTATIONCOLOUR_MIN=ffc800
+SHOW_STARTUP_FILE=false
+RECENT_FILE=examples/uniref50.fa\t/Volumes/Data/Users/jimp/Documents/testing/Jalview/examples/RF00031_folded.stk\t/Volumes/Data/Users/jimp/bs_ig_mult.out
+DEFAULT_FILE_FORMAT=FASTA
+SHOW_JAVA_CONSOLE=false
+VERSION=2.8b1
+FIGURE_USERIDWIDTH=
+WSMENU_BYTYPE=false
+DEFAULT_COLOUR=None
+NOQUESTIONNAIRES=true
+JALVIEW_NEWS_RSS_LASTMODIFIED=Apr 23, 2014 2\:53\:26 PM
+BUILD_DATE=01 November 2013
+PILEUP_JVSUFFIX=true
+SHOW_CONSENSUS_LOGO=false
+SCREENGEOMETRY_WIDTH=2560
+SHOW_ANNOTATIONS=true
+JALVIEW_RSS_WINDOW_SCREEN_Y=0
+USAGESTATS=false
+JALVIEW_RSS_WINDOW_SCREEN_X=0
+SHOW_UNCONSERVED=false
+SHOW_JVSUFFIX=true
+SCREEN_HEIGHT=650
+ANNOTATIONCOLOUR_MAX=ff0000
+AUTO_CALC_CONSENSUS=true
+FASTA_JVSUFFIX=true
+DAS_ACTIVE_SOURCE=
+JWS2HOSTURLS=http\://www.compbio.dundee.ac.uk/jabaws
+PAD_GAPS=false
+CLUSTAL_JVSUFFIX=true
+SHOW_ENFIN_SERVICES=true
+FONT_SIZE=10
+RIGHT_ALIGN_IDS=false
+USE_PROXY=false
+WRAP_ALIGNMENT=false
+DAS_REGISTRY_URL=http\://www.nowhere/