Merge branch 'bug/JAL-2154projectMappings' into develop
authorJim Procter <jprocter@issues.jalview.org>
Mon, 1 Aug 2016 14:40:24 +0000 (15:40 +0100)
committerJim Procter <jprocter@issues.jalview.org>
Mon, 1 Aug 2016 14:40:24 +0000 (15:40 +0100)
1  2 
src/jalview/gui/Jalview2XML.java

@@@ -108,6 -108,7 +108,7 @@@ import java.lang.reflect.InvocationTarg
  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 +166,9 @@@ public class Jalview2XM
     */
    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
  
        {
          seqsToIds.clear();
        }
+       if (incompleteSeqs != null)
+       {
+         incompleteSeqs.clear();
+       }
        // seqRefIds = null;
        // seqsToIds = null;
      }
      {
        seqRefIds = new HashMap<String, SequenceI>();
      }
+     if (incompleteSeqs == null)
+     {
+       incompleteSeqs = new HashMap<String, SequenceI>();
+     }
+     if (frefedSequence == null)
+     {
+       frefedSequence = new ArrayList<SeqFref>();
+     }
    }
  
    public 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.");
+       }
      }
    }
  
      {
        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>();
  
      /*
        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
    {
      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();
        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)
      if (jds.getDatasetSequence() != null)
      {
        vamsasSeq.setDsseqid(seqHash(jds.getDatasetSequence()));
      }
      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)
      {
        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);
        }
      }
      }
      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
            if (true) // !skipViewport(object))
            {
              _af = loadFromObject(object, file, true, jprovider);
 -            if (object.getJalviewModelSequence().getViewportCount() > 0)
 +            if (_af != null
 +                    && object.getJalviewModelSequence().getViewportCount() > 0)
              {
 -              af = _af;
 -              if (af.viewport.isGatherViewsHere())
 +              if (af == null)
                {
 -                gatherToThisFrame.put(af.viewport.getSequenceSetId(), af);
 +                // store a reference to the first view
 +                af = _af;
 +              }
 +              if (_af.viewport.isGatherViewsHere())
 +              {
 +                // if this is a gathered view, keep its reference since
 +                // after gathering views, only this frame will remain
 +                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++;
        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
      }
  
      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;
    }
  
      // LOAD SEQUENCES
  
      List<SequenceI> hiddenSeqs = null;
-     jalview.datamodel.Sequence jseq;
  
      List<SequenceI> tmpseqs = new ArrayList<SequenceI>();
  
      {
        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++;
        }
  
            hiddenSeqs = new ArrayList<SequenceI>();
          }
  
-         hiddenSeqs.add(seqRefIds.get(seqId));
+         hiddenSeqs.add(tmpSeq);
        }
      }
  
      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)
      {
        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??
              else
              {
                // defer to later
-               frefedSequence.add(new Object[] { maps[m].getDnasq(), cf,
-                   mapping });
+               frefedSequence.add(newAlcodMapRef(maps[m].getDnasq(), cf,
+                       mapping));
              }
            }
            al.addCodonFrame(cf);
        }
      }
      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
          }
          else
          {
-           frefedSequence.add(new Object[] { dsfor, jmap });
+           frefedSequence.add(newMappingRef(dsfor, jmap));
          }
        }
        else
            djs.setEnd(jmap.getMap().getToHighest());
            djs.setVamsasId(uniqueSetSuffix + sqid);
            jmap.setTo(djs);
+           incompleteSeqs.put(sqid, djs);
            seqRefIds.put(sqid, djs);
  
          }