Merge branch 'bug/JAL-2154projectMappings' into develop
[jalview.git] / src / jalview / gui / Jalview2XML.java
index f12d106..68245b6 100644 (file)
@@ -108,6 +108,7 @@ import java.lang.reflect.InvocationTargetException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -165,7 +166,9 @@ public class Jalview2XML
    */
   Map<String, SequenceI> seqRefIds = null;
 
-  Vector<Object[]> frefedSequence = null;
+  Map<String, SequenceI> incompleteSeqs = null;
+
+  List<SeqFref> frefedSequence = null;
 
   boolean raiseGUI = true; // whether errors are raised in dialog boxes or not
 
@@ -220,6 +223,10 @@ public class Jalview2XML
       {
         seqsToIds.clear();
       }
+      if (incompleteSeqs != null)
+      {
+        incompleteSeqs.clear();
+      }
       // seqRefIds = null;
       // seqsToIds = null;
     }
@@ -242,6 +249,14 @@ public class Jalview2XML
     {
       seqRefIds = new HashMap<String, SequenceI>();
     }
+    if (incompleteSeqs == null)
+    {
+      incompleteSeqs = new HashMap<String, SequenceI>();
+    }
+    if (frefedSequence == null)
+    {
+      frefedSequence = new ArrayList<SeqFref>();
+    }
   }
 
   public Jalview2XML()
@@ -253,78 +268,169 @@ public class Jalview2XML
     this.raiseGUI = raiseGUI;
   }
 
-  public void resolveFrefedSequences()
+  /**
+   * base class for resolving forward references to sequences by their ID
+   * 
+   * @author jprocter
+   *
+   */
+  abstract class SeqFref
   {
-    if (frefedSequence.size() > 0)
+    String sref;
+
+    String type;
+
+    public SeqFref(String _sref, String type)
+    {
+      sref = _sref;
+      this.type = type;
+    }
+
+    public String getSref()
+    {
+      return sref;
+    }
+
+    public SequenceI getSrefSeq()
+    {
+      return seqRefIds.get(sref);
+    }
+
+    public boolean isResolvable()
     {
-      int r = 0, rSize = frefedSequence.size();
-      while (r < rSize)
+      return seqRefIds.get(sref) != null;
+    }
+
+    public SequenceI getSrefDatasetSeq()
+    {
+      SequenceI sq = seqRefIds.get(sref);
+      if (sq != null)
       {
-        Object[] ref = frefedSequence.elementAt(r);
-        if (ref != null)
+        while (sq.getDatasetSequence() != null)
         {
-          String sref = (String) ref[0];
-          if (seqRefIds.containsKey(sref))
-          {
-            if (ref[1] instanceof jalview.datamodel.Mapping)
-            {
-              SequenceI seq = seqRefIds.get(sref);
-              while (seq.getDatasetSequence() != null)
-              {
-                seq = seq.getDatasetSequence();
-              }
-              ((jalview.datamodel.Mapping) ref[1]).setTo(seq);
-            }
-            else
-            {
-              if (ref[1] instanceof jalview.datamodel.AlignedCodonFrame)
-              {
-                SequenceI seq = seqRefIds.get(sref);
-                while (seq.getDatasetSequence() != null)
-                {
-                  seq = seq.getDatasetSequence();
-                }
-                if (ref[2] != null
-                        && ref[2] instanceof jalview.datamodel.Mapping)
-                {
-                  jalview.datamodel.Mapping mp = (jalview.datamodel.Mapping) ref[2];
-                  ((jalview.datamodel.AlignedCodonFrame) ref[1]).addMap(
-                          seq, mp.getTo(), mp.getMap());
-                }
-                else
-                {
-                  System.err
-                          .println("IMPLEMENTATION ERROR: Unimplemented forward sequence references for AlcodonFrames involving "
-                                  + ref[2].getClass() + " type objects.");
-                }
-              }
-              else
-              {
-                System.err
-                        .println("IMPLEMENTATION ERROR: Unimplemented forward sequence references for "
-                                + ref[1].getClass() + " type objects.");
-              }
-            }
-            frefedSequence.remove(r);
-            rSize--;
-          }
-          else
+          sq = sq.getDatasetSequence();
+        }
+      }
+      return sq;
+    }
+    /**
+     * @return true if the forward reference was fully resolved
+     */
+    abstract boolean resolve();
+
+    @Override
+    public String toString()
+    {
+      return type + " reference to " + sref;
+    }
+  }
+
+  /**
+   * create forward reference for a mapping
+   * 
+   * @param sref
+   * @param _jmap
+   * @return
+   */
+  public SeqFref newMappingRef(final String sref,
+          final jalview.datamodel.Mapping _jmap)
+  {
+    SeqFref fref = new SeqFref(sref, "Mapping")
+    {
+      public jalview.datamodel.Mapping jmap = _jmap;
+
+      @Override
+      boolean resolve()
+      {
+        SequenceI seq = getSrefDatasetSeq();
+        if (seq == null)
+        {
+          return false;
+        }
+        jmap.setTo(seq);
+        return true;
+      }
+    };
+    return fref;
+  }
+
+  public SeqFref newAlcodMapRef(final String sref,
+          final AlignedCodonFrame _cf, final jalview.datamodel.Mapping _jmap)
+  {
+
+    SeqFref fref = new SeqFref(sref, "Codon Frame")
+    {
+      AlignedCodonFrame cf = _cf;
+
+      public jalview.datamodel.Mapping mp = _jmap;
+
+      @Override
+      boolean resolve()
+      {
+        SequenceI seq = getSrefDatasetSeq();
+        if (seq == null)
+        {
+          return false;
+        }
+        cf.addMap(seq, mp.getTo(), mp.getMap());
+        return true;
+      }
+    };
+    return fref;
+  }
+
+  public void resolveFrefedSequences()
+  {
+    Iterator<SeqFref> nextFref=frefedSequence.iterator();
+    int toresolve=frefedSequence.size();
+    int unresolved=0,failedtoresolve=0;
+    while (nextFref.hasNext()) {
+      SeqFref ref = nextFref.next();
+      if (ref.isResolvable())
+      {
+        try {
+          if (ref.resolve())
           {
-            System.err
-                    .println("IMPLEMENTATION WARNING: Unresolved forward reference for hash string "
-                            + ref[0]
-                            + " with objecttype "
-                            + ref[1].getClass());
-            r++;
+            nextFref.remove();
+          } else {
+            failedtoresolve++;
           }
-        }
-        else
+        } catch (Exception x) {
+          System.err.println("IMPLEMENTATION ERROR: Failed to resolve forward reference for sequence "+ref.getSref());
+          x.printStackTrace();
+          failedtoresolve++;
+        } 
+      } else {
+        unresolved++;
+      }
+    }
+    if (unresolved>0)
+    {
+      System.err.println("Jalview Project Import: There were " + unresolved
+              + " forward references left unresolved on the stack.");
+    }
+    if (failedtoresolve>0)
+    {
+      System.err.println("SERIOUS! " + failedtoresolve
+              + " resolvable forward references failed to resolve.");
+    }
+    if (incompleteSeqs != null && incompleteSeqs.size() > 0)
+    {
+      System.err.println("Jalview Project Import: There are "
+              + incompleteSeqs.size()
+              + " sequences which may have incomplete metadata.");
+      if (incompleteSeqs.size() < 10)
+      {
+        for (SequenceI s : incompleteSeqs.values())
         {
-          // empty reference
-          frefedSequence.remove(r);
-          rSize--;
+          System.err.println(s.toString());
         }
       }
+      else
+      {
+        System.err
+                .println("Too many to report. Skipping output of incomplete sequences.");
+      }
     }
   }
 
@@ -396,7 +502,20 @@ public class Jalview2XML
     {
       return;
     }
+    saveAllFrames(Arrays.asList(frames), jout);
+  }
 
+  /**
+   * core method for storing state for a set of AlignFrames.
+   * 
+   * @param frames
+   *          - frames involving all data to be exported (including containing
+   *          splitframes)
+   * @param jout
+   *          - project output stream
+   */
+  private void saveAllFrames(List<AlignFrame> frames, JarOutputStream jout)
+  {
     Hashtable<String, AlignFrame> dsses = new Hashtable<String, AlignFrame>();
 
     /*
@@ -416,9 +535,9 @@ public class Jalview2XML
       List<String> viewIds = new ArrayList<String>();
 
       // REVERSE ORDER
-      for (int i = frames.length - 1; i > -1; i--)
+      for (int i = frames.size() - 1; i > -1; i--)
       {
-        AlignFrame af = frames[i];
+        AlignFrame af = frames.get(i);
         // skip ?
         if (skipList != null
                 && skipList
@@ -521,30 +640,20 @@ public class Jalview2XML
   {
     try
     {
-      int ap = 0;
-      int apSize = af.alignPanels.size();
       FileOutputStream fos = new FileOutputStream(jarFile);
       JarOutputStream jout = new JarOutputStream(fos);
-      Hashtable<String, AlignFrame> dsses = new Hashtable<String, AlignFrame>();
-      List<String> viewIds = new ArrayList<String>();
+      List<AlignFrame> frames = new ArrayList<AlignFrame>();
 
-      for (AlignmentPanel apanel : af.alignPanels)
+      // resolve splitframes
+      if (af.getViewport().getCodingComplement() != null)
       {
-        String jfileName = apSize == 1 ? fileName : fileName + ap;
-        ap++;
-        if (!jfileName.endsWith(".xml"))
-        {
-          jfileName = jfileName + ".xml";
-        }
-        saveState(apanel, jfileName, jout, viewIds);
-        String dssid = getDatasetIdRef(af.getViewport().getAlignment()
-                .getDataset());
-        if (!dsses.containsKey(dssid))
-        {
-          dsses.put(dssid, af);
-        }
+        frames = ((SplitFrame) af.getSplitViewContainer()).getAlignFrames();
+      }
+      else
+      {
+        frames.add(af);
       }
-      writeDatasetFor(dsses, fileName, jout);
+      saveAllFrames(frames, jout);
       try
       {
         jout.flush();
@@ -892,7 +1001,8 @@ public class Jalview2XML
       jal = av.getAlignment();
     }
     // SAVE MAPPINGS
-    if (jal.getCodonFrames() != null)
+    // FOR DATASET
+    if (storeDS && jal.getCodonFrames() != null)
     {
       List<AlignedCodonFrame> jac = jal.getCodonFrames();
       for (AlignedCodonFrame acf : jac)
@@ -1940,16 +2050,17 @@ public class Jalview2XML
     if (jds.getDatasetSequence() != null)
     {
       vamsasSeq.setDsseqid(seqHash(jds.getDatasetSequence()));
-      if (jds.getDatasetSequence().getDBRefs() != null)
-      {
-        dbrefs = jds.getDatasetSequence().getDBRefs();
-      }
     }
     else
     {
-      vamsasSeq.setDsseqid(id); // so we can tell which sequences really are
+      // seqId==dsseqid so we can tell which sequences really are
       // dataset sequences only
+      vamsasSeq.setDsseqid(id);
       dbrefs = jds.getDBRefs();
+      if (parentseq == null)
+      {
+        parentseq = jds;
+      }
     }
     if (dbrefs != null)
     {
@@ -2001,38 +2112,32 @@ public class Jalview2XML
       if (jmp.getTo() != null)
       {
         MappingChoice mpc = new MappingChoice();
-        if (recurse
-                && (parentseq != jmp.getTo() || parentseq
-                        .getDatasetSequence() != jmp.getTo()))
+
+        // check/create ID for the sequence referenced by getTo()
+
+        String jmpid = "";
+        SequenceI ps = null;
+        if (parentseq != jmp.getTo()
+                && parentseq.getDatasetSequence() != jmp.getTo())
         {
-          mpc.setSequence(createVamsasSequence(false, seqHash(jmp.getTo()),
-                  jmp.getTo(), jds));
+          // chaining dbref rather than a handshaking one
+          jmpid = seqHash(ps = jmp.getTo());
         }
         else
         {
-          String jmpid = "";
-          SequenceI ps = null;
-          if (parentseq != jmp.getTo()
-                  && parentseq.getDatasetSequence() != jmp.getTo())
-          {
-            // chaining dbref rather than a handshaking one
-            jmpid = seqHash(ps = jmp.getTo());
-          }
-          else
-          {
-            jmpid = seqHash(ps = parentseq);
-          }
-          mpc.setDseqFor(jmpid);
-          if (!seqRefIds.containsKey(mpc.getDseqFor()))
-          {
-            jalview.bin.Cache.log.debug("creatign new DseqFor ID");
-            seqRefIds.put(mpc.getDseqFor(), ps);
-          }
-          else
-          {
-            jalview.bin.Cache.log.debug("reusing DseqFor ID");
-          }
+          jmpid = seqHash(ps = parentseq);
+        }
+        mpc.setDseqFor(jmpid);
+        if (!seqRefIds.containsKey(mpc.getDseqFor()))
+        {
+          jalview.bin.Cache.log.debug("creatign new DseqFor ID");
+          seqRefIds.put(mpc.getDseqFor(), ps);
+        }
+        else
+        {
+          jalview.bin.Cache.log.debug("reusing DseqFor ID");
         }
+
         mp.setMappingChoice(mpc);
       }
     }
@@ -2241,14 +2346,10 @@ public class Jalview2XML
     }
     if (seqRefIds == null)
     {
-      seqRefIds = new HashMap<String, SequenceI>();
-    }
-    if (frefedSequence == null)
-    {
-      frefedSequence = new Vector<Object[]>();
+      initSeqRefs();
     }
-
     AlignFrame af = null, _af = null;
+    IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<AlignmentI, AlignmentI>();
     Map<String, AlignFrame> gatherToThisFrame = new HashMap<String, AlignFrame>();
     final String file = jprovider.getFilename();
     try
@@ -2291,6 +2392,9 @@ public class Jalview2XML
                 af = _af;
                 gatherToThisFrame.put(_af.viewport.getSequenceSetId(), _af);
               }
+              // Save dataset to register mappings once all resolved
+              importedDatasets.put(af.viewport.getAlignment().getDataset(),
+                      af.viewport.getAlignment().getDataset());
             }
           }
           entryCount++;
@@ -2346,11 +2450,6 @@ public class Jalview2XML
       e.printStackTrace();
     }
 
-    if (Desktop.instance != null)
-    {
-      Desktop.instance.stopLoading();
-    }
-
     /*
      * Regather multiple views (with the same sequence set id) to the frame (if
      * any) that is flagged as the one to gather to, i.e. convert them to tabbed
@@ -2364,11 +2463,24 @@ public class Jalview2XML
     }
 
     restoreSplitFrames();
-
+    for (AlignmentI ds : importedDatasets.keySet())
+    {
+      if (ds.getCodonFrames() != null)
+      {
+        StructureSelectionManager.getStructureSelectionManager(
+                Desktop.instance).registerMappings(ds.getCodonFrames());
+      }
+    }
     if (errorMessage != null)
     {
       reportErrors();
     }
+
+    if (Desktop.instance != null)
+    {
+      Desktop.instance.stopLoading();
+    }
+
     return af;
   }
 
@@ -2674,7 +2786,7 @@ public class Jalview2XML
     // LOAD SEQUENCES
 
     List<SequenceI> hiddenSeqs = null;
-    jalview.datamodel.Sequence jseq;
+
 
     List<SequenceI> tmpseqs = new ArrayList<SequenceI>();
 
@@ -2686,21 +2798,36 @@ public class Jalview2XML
     {
       String seqId = jseqs[i].getId();
 
-      if (seqRefIds.get(seqId) != null)
+      SequenceI tmpSeq = seqRefIds.get(seqId);
+      if (tmpSeq != null)
       {
-        tmpseqs.add(seqRefIds.get(seqId));
+        if (!incompleteSeqs.containsKey(seqId))
+        {
+          // may not need this check, but keep it for at least 2.9,1 release
+          if (tmpSeq.getStart()!=jseqs[i].getStart() || tmpSeq.getEnd()!=jseqs[i].getEnd())
+          { 
+            System.err
+                    .println("Warning JAL-2154 regression: updating start/end for sequence "
+                    + tmpSeq.toString());
+          }
+        } else {
+          incompleteSeqs.remove(seqId);
+        }
+        tmpSeq.setStart(jseqs[i].getStart());
+        tmpSeq.setEnd(jseqs[i].getEnd());
+        tmpseqs.add(tmpSeq);
         multipleView = true;
       }
       else
       {
-        jseq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(),
+        tmpSeq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(),
                 vamsasSeq[vi].getSequence());
-        jseq.setDescription(vamsasSeq[vi].getDescription());
-        jseq.setStart(jseqs[i].getStart());
-        jseq.setEnd(jseqs[i].getEnd());
-        jseq.setVamsasId(uniqueSetSuffix + seqId);
-        seqRefIds.put(vamsasSeq[vi].getId(), jseq);
-        tmpseqs.add(jseq);
+        tmpSeq.setDescription(vamsasSeq[vi].getDescription());
+        tmpSeq.setStart(jseqs[i].getStart());
+        tmpSeq.setEnd(jseqs[i].getEnd());
+        tmpSeq.setVamsasId(uniqueSetSuffix + seqId);
+        seqRefIds.put(vamsasSeq[vi].getId(), tmpSeq);
+        tmpseqs.add(tmpSeq);
         vi++;
       }
 
@@ -2716,7 +2843,7 @@ public class Jalview2XML
           hiddenSeqs = new ArrayList<SequenceI>();
         }
 
-        hiddenSeqs.add(seqRefIds.get(seqId));
+        hiddenSeqs.add(tmpSeq);
       }
     }
 
@@ -2726,7 +2853,39 @@ public class Jalview2XML
     SequenceI[] orderedSeqs = tmpseqs
             .toArray(new SequenceI[tmpseqs.size()]);
 
-    AlignmentI al = new Alignment(orderedSeqs);
+    AlignmentI al = null;
+    // so we must create or recover the dataset alignment before going further
+    // ///////////////////////////////
+    if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
+    {
+      // older jalview projects do not have a dataset - so creat alignment and
+      // dataset
+      al = new Alignment(orderedSeqs);
+      al.setDataset(null);
+    }
+    else
+    {
+      boolean isdsal = object.getJalviewModelSequence().getViewportCount() == 0;
+      if (isdsal)
+      {
+        // we are importing a dataset record, so
+        // recover reference to an alignment already materialsed as dataset
+        al = getDatasetFor(vamsasSet.getDatasetId());
+      }
+      if (al == null)
+      {
+        // materialse the alignment
+        al = new Alignment(orderedSeqs);
+      }
+      if (isdsal)
+      {
+        addDatasetRef(vamsasSet.getDatasetId(), al);
+      }
+
+      // finally, verify all data in vamsasSet is actually present in al
+      // passing on flag indicating if it is actually a stored dataset
+      recoverDatasetFor(vamsasSet, al, isdsal);
+    }
 
     if (referenceseqForView != null)
     {
@@ -2739,22 +2898,6 @@ public class Jalview2XML
       al.setProperty(ssp.getKey(), ssp.getValue());
     }
 
-    // /
-    // SequenceFeatures are added to the DatasetSequence,
-    // so we must create or recover the dataset before loading features
-    // ///////////////////////////////
-    if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
-    {
-      // older jalview projects do not have a dataset id.
-      al.setDataset(null);
-    }
-    else
-    {
-      // recover dataset - passing on flag indicating if this a 'viewless'
-      // sequence set (a.k.a. a stored dataset for the project)
-      recoverDatasetFor(vamsasSet, al, object.getJalviewModelSequence()
-              .getViewportCount() == 0);
-    }
     // ///////////////////////////////
 
     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
@@ -2864,8 +3007,8 @@ public class Jalview2XML
             else
             {
               // defer to later
-              frefedSequence.add(new Object[] { maps[m].getDnasq(), cf,
-                  mapping });
+              frefedSequence.add(newAlcodMapRef(maps[m].getDnasq(), cf,
+                      mapping));
             }
           }
           al.addCodonFrame(cf);
@@ -4437,7 +4580,7 @@ public class Jalview2XML
       }
     }
     af.setMenusFromViewport(af.viewport);
-
+    af.setTitle(view.getTitle());
     // TODO: we don't need to do this if the viewport is aready visible.
     /*
      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
@@ -5008,7 +5151,7 @@ public class Jalview2XML
         }
         else
         {
-          frefedSequence.add(new Object[] { dsfor, jmap });
+          frefedSequence.add(newMappingRef(dsfor, jmap));
         }
       }
       else
@@ -5046,6 +5189,7 @@ public class Jalview2XML
           djs.setEnd(jmap.getMap().getToHighest());
           djs.setVamsasId(uniqueSetSuffix + sqid);
           jmap.setTo(djs);
+          incompleteSeqs.put(sqid, djs);
           seqRefIds.put(sqid, djs);
 
         }