Merge branch 'develop' into patch/JAL-4281_idwidthandannotHeight_in_project
[jalview.git] / src / jalview / project / Jalview2XML.java
index 0313c8f..d5b3808 100644 (file)
@@ -91,12 +91,15 @@ import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ContactListI;
 import jalview.datamodel.ContactMatrix;
 import jalview.datamodel.ContactMatrixI;
 import jalview.datamodel.DBRefEntry;
+import jalview.datamodel.FloatContactMatrix;
 import jalview.datamodel.GeneLocus;
 import jalview.datamodel.GraphLine;
 import jalview.datamodel.GroupSet;
+import jalview.datamodel.GroupSetI;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.Point;
 import jalview.datamodel.RnaViewerModel;
@@ -199,6 +202,7 @@ import jalview.xml.binding.jalview.JalviewUserColours.Colour;
 import jalview.xml.binding.jalview.MapListType;
 import jalview.xml.binding.jalview.MapListType.MapListFrom;
 import jalview.xml.binding.jalview.MapListType.MapListTo;
+import jalview.xml.binding.jalview.MapOnAMatrixType;
 import jalview.xml.binding.jalview.Mapping;
 import jalview.xml.binding.jalview.MatrixType;
 import jalview.xml.binding.jalview.NoValueColour;
@@ -268,7 +272,7 @@ public class Jalview2XML
 
   Map<String, SequenceI> incompleteSeqs = null;
 
-  List<SeqFref> frefedSequence = null;
+  List<forwardRef> frefedSequence = null;
 
   boolean raiseGUI = true; // whether errors are raised in dialog boxes or not
 
@@ -283,6 +287,13 @@ public class Jalview2XML
    * entry names
    */
   private Map<RnaModel, String> rnaSessions = new HashMap<>();
+  
+  /**
+   * map from contact matrices to their XML ids
+   */
+  private Map<ContactMatrixI,String> contactMatrices = new HashMap<>();
+  private Map<String, ContactMatrixI> contactMatrixRefs = new HashMap<>();
+  private List<jalview.xml.binding.jalview.MatrixType> xmlMatrices= new ArrayList<>();
 
   /**
    * A helper method for safely using the value of an optional attribute that
@@ -380,18 +391,17 @@ public class Jalview2XML
   }
 
   /**
-   * base class for resolving forward references to sequences by their ID
+   * base class for resolving forward references to an as-yet unmarshalled object referenced by already unmarshalled objects
    * 
    * @author jprocter
    *
    */
-  abstract class SeqFref
-  {
+  abstract class forwardRef {
     String sref;
 
     String type;
 
-    public SeqFref(String _sref, String type)
+    public forwardRef(String _sref, String type)
     {
       sref = _sref;
       this.type = type;
@@ -402,6 +412,28 @@ public class Jalview2XML
       return sref;
     }
 
+    public abstract boolean isResolvable();
+    /**
+     * @return true if the forward reference was fully resolved
+     */
+    abstract boolean resolve();
+
+    @Override
+    public String toString()
+    {
+      return type + " reference to " + sref;
+    }
+  }
+  /**
+   * resolve forward references to sequences by their ID
+   * @author jprocter
+   */
+  abstract class SeqFref extends forwardRef
+  {
+    public SeqFref(String _sref, String type)
+    {
+      super(_sref, type);
+    }
     public SequenceI getSrefSeq()
     {
       return seqRefIds.get(sref);
@@ -424,17 +456,6 @@ public class Jalview2XML
       }
       return sq;
     }
-
-    /**
-     * @return true if the forward reference was fully resolved
-     */
-    abstract boolean resolve();
-
-    @Override
-    public String toString()
-    {
-      return type + " reference to " + sref;
-    }
   }
 
   /**
@@ -497,15 +518,42 @@ public class Jalview2XML
     };
     return fref;
   }
+  
+  public forwardRef newMatrixFref(final String matRef,
+          final jalview.util.MapList mapping, final AlignmentAnnotation jaa)
+  {
+    forwardRef fref = new forwardRef(matRef,
+            "Matrix Reference for sequence and annotation")
+    {
+
+      @Override
+      boolean resolve()
+      {
+        ContactMatrixI cm = contactMatrixRefs.get(matRef);
+        PAEContactMatrix newpae = new PAEContactMatrix(jaa.sequenceRef,
+                mapping, cm);
+
+        jaa.sequenceRef.addContactListFor(jaa, newpae);
+        return true;
+      }
+
+      @Override
+      public boolean isResolvable()
+      {
+        return (contactMatrixRefs.get(matRef) != null);
+      }
+    };
+    return fref;
+  }
 
   public void resolveFrefedSequences()
   {
-    Iterator<SeqFref> nextFref = frefedSequence.iterator();
+    Iterator<forwardRef> nextFref = frefedSequence.iterator();
     int toresolve = frefedSequence.size();
     int unresolved = 0, failedtoresolve = 0;
     while (nextFref.hasNext())
     {
-      SeqFref ref = nextFref.next();
+      forwardRef ref = nextFref.next();
       if (ref.isResolvable())
       {
         try
@@ -631,7 +679,7 @@ public class Jalview2XML
    */
   public void saveState(JarOutputStream jout)
   {
-    AlignFrame[] frames = Desktop.getAlignFrames();
+    AlignFrame[] frames = Desktop.getDesktopAlignFrames();
 
     setStateSavedUpToDate(true);
 
@@ -1772,6 +1820,20 @@ public class Jalview2XML
       // jms.addViewport(view);
       object.getViewport().add(view);
     }
+    
+    
+    if (storeDS)
+    {
+      // store matrices referenced by any views or annotation in this dataset
+      if (xmlMatrices!=null && xmlMatrices.size()>0)
+      {
+        Console.debug("Adding "+xmlMatrices.size()+" matrices to dataset.");
+        vamsasSet.getMatrix().addAll(xmlMatrices);
+        xmlMatrices.clear();
+      }
+    }
+
+    
     // object.setJalviewModelSequence(jms);
     // object.getVamsasModel().addSequenceSet(vamsasSet);
     object.getVamsasModel().getSequenceSet().add(vamsasSet);
@@ -2336,64 +2398,7 @@ public class Jalview2XML
                     .getContactMatrixFor(annotation);
             if (cm != null)
             {
-              MatrixType xmlmat = new MatrixType();
-              xmlmat.setType(cm.getType());
-              xmlmat.setRows(BigInteger.valueOf(cm.getWidth()));
-              xmlmat.setCols(BigInteger.valueOf(cm.getHeight()));
-              // consider using an opaque to/from -> allow instance to control
-              // its representation ?
-              xmlmat.setElements(ContactMatrix.contactToFloatString(cm));
-              if (cm.hasGroups())
-              {
-                for (BitSet gp : cm.getGroups())
-                {
-                  xmlmat.getGroups().add(stringifyBitset(gp));
-                }
-              }
-              if (cm.hasTree())
-              {
-                // provenance object for tree ?
-                xmlmat.getNewick().add(cm.getNewick());
-                xmlmat.setTreeMethod(cm.getTreeMethod());
-              }
-              if (cm.hasCutHeight())
-              {
-                xmlmat.setCutHeight(cm.getCutHeight());
-              }
-              // set/get properties
-              if (cm instanceof MappableContactMatrixI)
-              {
-                jalview.util.MapList mlst = ((MappableContactMatrixI) cm)
-                        .getMapFor(annotation.sequenceRef);
-                if (mlst != null)
-                {
-                  MapListType mp = new MapListType();
-                  List<int[]> r = mlst.getFromRanges();
-                  for (int[] range : r)
-                  {
-                    MapListFrom mfrom = new MapListFrom();
-                    mfrom.setStart(range[0]);
-                    mfrom.setEnd(range[1]);
-                    // mp.addMapListFrom(mfrom);
-                    mp.getMapListFrom().add(mfrom);
-                  }
-                  r = mlst.getToRanges();
-                  for (int[] range : r)
-                  {
-                    MapListTo mto = new MapListTo();
-                    mto.setStart(range[0]);
-                    mto.setEnd(range[1]);
-                    // mp.addMapListTo(mto);
-                    mp.getMapListTo().add(mto);
-                  }
-                  mp.setMapFromUnit(
-                          BigInteger.valueOf(mlst.getFromRatio()));
-                  mp.setMapToUnit(BigInteger.valueOf(mlst.getToRatio()));
-                  xmlmat.setMapping(mp);
-                }
-              }
-              // and add to model
-              an.getContactmatrix().add(xmlmat);
+              storeMatrixFor(vamsasSet, an,annotation, cm);
             }
           }
         }
@@ -2500,6 +2505,88 @@ public class Jalview2XML
 
   }
 
+  private void storeMatrixFor(SequenceSet root, Annotation an, AlignmentAnnotation annotation, ContactMatrixI cm)
+  {
+    String cmId = contactMatrices.get(cm);
+    MatrixType xmlmat=null;
+    
+    // first create an xml ref for the matrix data, if none exist
+    if (cmId == null)
+    {
+      xmlmat = new MatrixType();
+      xmlmat.setType(cm.getType());
+      xmlmat.setRows(BigInteger.valueOf(cm.getWidth()));
+      xmlmat.setCols(BigInteger.valueOf(cm.getHeight()));
+      // consider using an opaque to/from -> allow instance to control
+      // its representation ?
+      xmlmat.setElements(ContactMatrix.contactToFloatString(cm));
+      if (cm.hasGroups())
+      {
+        for (BitSet gp : cm.getGroups())
+        {
+          xmlmat.getGroups().add(stringifyBitset(gp));
+        }
+      }
+      if (cm.hasTree())
+      {
+        // provenance object for tree ?
+        xmlmat.getNewick().add(cm.getNewick());
+        xmlmat.setTreeMethod(cm.getTreeMethod());
+      }
+      if (cm.hasCutHeight())
+      {
+        xmlmat.setCutHeight(cm.getCutHeight());
+      }
+      xmlmat.setId(cmId = "m"+contactMatrices.size()+System.currentTimeMillis());
+      Console.trace("Matrix data stored :"+cmId);
+      contactMatrices.put(cm, cmId);
+      contactMatrixRefs.put(cmId, cm);
+      xmlMatrices.add(xmlmat);
+    } else {
+      Console.trace("Existing Matrix stored :"+cmId);
+    }
+
+    // now store mapping
+
+    MapOnAMatrixType xmlmatmapping = new MapOnAMatrixType();
+    xmlmatmapping.setMatrix(cmId);
+    
+    // Pretty much all matrices currently managed in this way are
+    // mappableContactMatrixI implementations - but check anyway
+    if (cm instanceof MappableContactMatrixI)
+    {
+      jalview.util.MapList mlst = ((MappableContactMatrixI) cm)
+              .getMapFor(annotation.sequenceRef);
+      if (mlst != null)
+      {
+        MapListType mp = new MapListType();
+        List<int[]> r = mlst.getFromRanges();
+        for (int[] range : r)
+        {
+          MapListFrom mfrom = new MapListFrom();
+          mfrom.setStart(range[0]);
+          mfrom.setEnd(range[1]);
+          // mp.addMapListFrom(mfrom);
+          mp.getMapListFrom().add(mfrom);
+        }
+        r = mlst.getToRanges();
+        for (int[] range : r)
+        {
+          MapListTo mto = new MapListTo();
+          mto.setStart(range[0]);
+          mto.setEnd(range[1]);
+          // mp.addMapListTo(mto);
+          mp.getMapListTo().add(mto);
+        }
+        mp.setMapFromUnit(BigInteger.valueOf(mlst.getFromRatio()));
+        mp.setMapToUnit(BigInteger.valueOf(mlst.getToRatio()));
+        xmlmatmapping.setMapping(mp);
+      }
+    }
+    // and add to model
+    an.getContactmatrix().add(xmlmatmapping);
+  }
+
   private String stringifyBitset(BitSet gp)
   {
     StringBuilder sb = new StringBuilder();
@@ -3076,6 +3163,7 @@ public class Jalview2XML
       initSeqRefs();
     }
     AlignFrame af = null, _af = null;
+    List<AlignFrame> toRepaint=new ArrayList<AlignFrame>();
     IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<>();
     Map<String, AlignFrame> gatherToThisFrame = new HashMap<>();
     final String file = jprovider.getFilename();
@@ -3110,6 +3198,7 @@ public class Jalview2XML
             if (_af != null && object.getViewport().size() > 0)
             // getJalviewModelSequence().getViewportCount() > 0)
             {
+              toRepaint.add(_af);
               if (af == null)
               {
                 // store a reference to the first view
@@ -3139,6 +3228,10 @@ public class Jalview2XML
       } while (jarentry != null);
       jin.close();
       resolveFrefedSequences();
+      for (AlignFrame alignFrame:toRepaint)
+      {
+        alignFrame.repaint();
+      }
     } catch (IOException ex)
     {
       ex.printStackTrace();
@@ -3529,6 +3622,14 @@ public class Jalview2XML
     }
 
     // ////////////////////////////////
+    // LOAD MATRICES (IF ANY)
+    
+    if (vamsasSet.getMatrix()!=null && vamsasSet.getMatrix().size()>0)
+    {
+      importMatrixData(vamsasSet.getMatrix());
+    }
+    
+    // ////////////////////////////////
     // LOAD SEQUENCES
 
     List<SequenceI> hiddenSeqs = null;
@@ -4060,80 +4161,10 @@ public class Jalview2XML
           if (annotation.getContactmatrix() != null
                   && annotation.getContactmatrix().size() > 0)
           {
-            for (MatrixType xmlmat : annotation.getContactmatrix())
+            for (MapOnAMatrixType xmlmat : annotation.getContactmatrix())
             {
-              if (PAEContactMatrix.PAEMATRIX.equals(xmlmat.getType()))
-              {
-                if (!xmlmat.getRows().equals(xmlmat.getCols()))
-                {
-                  Console.error("Can't handle non square PAE Matrices");
-                }
-                else
-                {
-                  float[][] elements = ContactMatrix
-                          .fromFloatStringToContacts(xmlmat.getElements(),
-                                  xmlmat.getCols().intValue(),
-                                  xmlmat.getRows().intValue());
-                  jalview.util.MapList mapping = null;
-                  if (xmlmat.getMapping() != null)
-                  {
-                    MapListType m = xmlmat.getMapping();
-                    // Mapping m = dr.getMapping();
-                    int fr[] = new int[m.getMapListFrom().size() * 2];
-                    Iterator<MapListFrom> from = m.getMapListFrom()
-                            .iterator();// enumerateMapListFrom();
-                    for (int _i = 0; from.hasNext(); _i += 2)
-                    {
-                      MapListFrom mf = from.next();
-                      fr[_i] = mf.getStart();
-                      fr[_i + 1] = mf.getEnd();
-                    }
-                    int fto[] = new int[m.getMapListTo().size() * 2];
-                    Iterator<MapListTo> to = m.getMapListTo().iterator();// enumerateMapListTo();
-                    for (int _i = 0; to.hasNext(); _i += 2)
-                    {
-                      MapListTo mf = to.next();
-                      fto[_i] = mf.getStart();
-                      fto[_i + 1] = mf.getEnd();
-                    }
-
-                    mapping = new jalview.util.MapList(fr, fto,
-                            m.getMapFromUnit().intValue(),
-                            m.getMapToUnit().intValue());
-                  }
-                  List<BitSet> newgroups = new ArrayList<BitSet>();
-                  if (xmlmat.getGroups().size() > 0)
-                  {
-                    for (String sgroup : xmlmat.getGroups())
-                    {
-                      newgroups.add(deStringifyBitset(sgroup));
-                    }
-                  }
-                  String nwk = xmlmat.getNewick().size() > 0
-                          ? xmlmat.getNewick().get(0)
-                          : null;
-                  if (xmlmat.getNewick().size() > 1)
-                  {
-                    Console.log.info(
-                            "Ignoring additional clusterings for contact matrix");
-                  }
-                  String treeMethod = xmlmat.getTreeMethod();
-                  double thresh = xmlmat.getCutHeight() != null
-                          ? xmlmat.getCutHeight()
-                          : 0;
-                  GroupSet grpset = new GroupSet();
-                  grpset.restoreGroups(newgroups, treeMethod, nwk, thresh);
-                  PAEContactMatrix newpae = new PAEContactMatrix(
-                          jaa.sequenceRef, mapping, elements, grpset);
-                  jaa.sequenceRef.addContactListFor(jaa, newpae);
-                }
-              }
-              else
-              {
-                Console.error("Ignoring CONTACT_MAP annotation with type "
-                        + xmlmat.getType());
-              }
-            }
+              restoreMatrixFor(jaa.sequenceRef, jaa, xmlmat);
+            } 
           }
         }
 
@@ -4363,6 +4394,105 @@ public class Jalview2XML
     return af;
   }
 
+  private void importMatrixData(List<MatrixType> xmlmatrices)
+  {
+    for (MatrixType xmlmat:xmlmatrices)
+    {
+      if (!PAEContactMatrix.PAEMATRIX.equals(xmlmat.getType()))
+      {
+        Console.error("Ignoring matrix '"+xmlmat.getId()+"' of type '"+xmlmat.getType());
+        continue;
+      }
+
+      if (!xmlmat.getRows().equals(xmlmat.getCols()))
+      {
+        Console.error("Can't handle non square matrices");
+        continue;
+      }
+
+      float[][] elements = ContactMatrix
+              .fromFloatStringToContacts(xmlmat.getElements(),
+                      xmlmat.getCols().intValue(),
+                      xmlmat.getRows().intValue());
+      
+      List<BitSet> newgroups = new ArrayList<BitSet>();
+      if (xmlmat.getGroups().size() > 0)
+      {
+        for (String sgroup : xmlmat.getGroups())
+        {
+          newgroups.add(deStringifyBitset(sgroup));
+        }
+      }
+      String nwk = xmlmat.getNewick().size() > 0
+              ? xmlmat.getNewick().get(0)
+              : null;
+      if (xmlmat.getNewick().size() > 1)
+      {
+        Console.log.info(
+                "Ignoring additional clusterings for contact matrix");
+      }
+      String treeMethod = xmlmat.getTreeMethod();
+      double thresh = xmlmat.getCutHeight() != null
+              ? xmlmat.getCutHeight()
+              : 0;
+      GroupSet grpset = new GroupSet();
+      grpset.restoreGroups(newgroups, treeMethod, nwk, thresh);
+      
+      FloatContactMatrix newcm = new FloatContactMatrix(elements, grpset);
+      contactMatrixRefs.put(xmlmat.getId(), newcm);
+      Console.trace("Restored base contact matrix "+xmlmat.getId());
+    }
+  }
+
+  private void restoreMatrixFor(SequenceI sequenceRef,
+          AlignmentAnnotation jaa, MapOnAMatrixType xmlmatmapping)
+  {    
+    // restore mapping data to matrix data
+    jalview.util.MapList mapping = null;
+    if (xmlmatmapping.getMapping() != null)
+    {
+      MapListType m = xmlmatmapping.getMapping();
+      // Mapping m = dr.getMapping();
+      int fr[] = new int[m.getMapListFrom().size() * 2];
+      Iterator<MapListFrom> from = m.getMapListFrom().iterator();// enumerateMapListFrom();
+      for (int _i = 0; from.hasNext(); _i += 2)
+      {
+        MapListFrom mf = from.next();
+        fr[_i] = mf.getStart();
+        fr[_i + 1] = mf.getEnd();
+      }
+      int fto[] = new int[m.getMapListTo().size() * 2];
+      Iterator<MapListTo> to = m.getMapListTo().iterator();// enumerateMapListTo();
+      for (int _i = 0; to.hasNext(); _i += 2)
+      {
+        MapListTo mf = to.next();
+        fto[_i] = mf.getStart();
+        fto[_i + 1] = mf.getEnd();
+      }
+
+      mapping = new jalview.util.MapList(fr, fto,
+              m.getMapFromUnit().intValue(), m.getMapToUnit().intValue());
+    }
+    
+    // locate matrix data in project XML and import
+    ContactMatrixI cm = contactMatrixRefs.get(xmlmatmapping.getMatrix());
+    if (cm == null)
+    {
+      frefedSequence
+              .add(newMatrixFref(xmlmatmapping.getMatrix(), mapping, jaa));
+    }
+    else
+    {
+      // create the PAEMatrix now
+      PAEContactMatrix newpae = new PAEContactMatrix(jaa.sequenceRef,
+              mapping, cm);
+
+      jaa.sequenceRef.addContactListFor(jaa, newpae);
+    }
+
+    return;
+  }
+
   /**
    * Load Overview window, restoring colours, 'show hidden regions' flag, title
    * and geometry as saved
@@ -6937,7 +7067,7 @@ public class Jalview2XML
     if (stateSavedUpToDate()) // nothing happened since last project save
       return true;
 
-    AlignFrame[] frames = Desktop.getAlignFrames();
+    AlignFrame[] frames = Desktop.getDesktopAlignFrames();
     if (frames != null)
     {
       for (int i = 0; i < frames.length; i++)