JAL-2089 patch broken merge to master for Release 2.10.0b1
[jalview.git] / src / jalview / io / vamsas / Tree.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.io.vamsas;
22
23 import jalview.analysis.NJTree;
24 import jalview.bin.Cache;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.AlignmentView;
27 import jalview.datamodel.BinaryNode;
28 import jalview.datamodel.SeqCigar;
29 import jalview.datamodel.Sequence;
30 import jalview.datamodel.SequenceI;
31 import jalview.datamodel.SequenceNode;
32 import jalview.gui.TreePanel;
33 import jalview.io.NewickFile;
34 import jalview.io.VamsasAppDatastore;
35 import jalview.viewmodel.AlignmentViewport;
36
37 import java.io.IOException;
38 import java.util.Enumeration;
39 import java.util.Hashtable;
40 import java.util.List;
41 import java.util.Vector;
42
43 import uk.ac.vamsas.client.Vobject;
44 import uk.ac.vamsas.objects.core.AlignmentSequence;
45 import uk.ac.vamsas.objects.core.Entry;
46 import uk.ac.vamsas.objects.core.Input;
47 import uk.ac.vamsas.objects.core.Newick;
48 import uk.ac.vamsas.objects.core.Param;
49 import uk.ac.vamsas.objects.core.Provenance;
50 import uk.ac.vamsas.objects.core.Seg;
51 import uk.ac.vamsas.objects.core.Treenode;
52 import uk.ac.vamsas.objects.core.Vref;
53
54 public class Tree extends DatastoreItem
55 {
56   AlignmentI jal;
57
58   TreePanel tp;
59
60   uk.ac.vamsas.objects.core.Tree tree;
61
62   uk.ac.vamsas.objects.core.Alignment alignment; // may be null => dataset or
63
64   // other kind of tree
65   private NewickFile ntree;
66
67   private String title;
68
69   private AlignmentView inputData = null;
70
71   public static void updateFrom(VamsasAppDatastore datastore,
72           jalview.gui.AlignFrame alignFrame,
73           uk.ac.vamsas.objects.core.Tree vtree)
74   {
75     Tree toTree = new Tree(datastore, alignFrame, vtree);
76   }
77
78   public Tree(VamsasAppDatastore datastore,
79           jalview.gui.AlignFrame alignFrame,
80           uk.ac.vamsas.objects.core.Tree vtree)
81   {
82     super(datastore, vtree, TreePanel.class);
83     doJvUpdate();
84   }
85
86   private NewickFile getNtree() throws IOException
87   {
88     return new jalview.io.NewickFile(tree.getNewick(0).getContent());
89   }
90
91   public Tree(VamsasAppDatastore datastore, TreePanel tp2, AlignmentI jal2,
92           uk.ac.vamsas.objects.core.Alignment alignment2)
93   {
94     super(datastore, tp2, uk.ac.vamsas.objects.core.Tree.class);
95
96     jal = jal2;
97     tp = (TreePanel) jvobj;
98     alignment = alignment2;
99
100     tree = (uk.ac.vamsas.objects.core.Tree) vobj;
101     doSync();
102   }
103
104   /*
105    * (non-Javadoc)
106    * 
107    * @see jalview.io.vamsas.DatastoreItem#addFromDocument()
108    */
109   @Override
110   public void addFromDocument()
111   {
112     tree = (uk.ac.vamsas.objects.core.Tree) vobj; // vtree;
113     TreePanel tp = (TreePanel) jvobj; // getvObj2jv(tree);
114     // make a new tree
115     Object[] idata = recoverInputData(tree.getProvenance());
116     try
117     {
118       if (idata != null && idata[0] != null)
119       {
120         inputData = (AlignmentView) idata[0];
121       }
122       ntree = getNtree();
123       title = tree.getNewick(0).getTitle();
124       if (title == null || title.length() == 0)
125       {
126         title = tree.getTitle(); // hack!!!!
127       }
128     } catch (Exception e)
129     {
130       Cache.log.warn("Problems parsing treefile '"
131               + tree.getNewick(0).getContent() + "'", e);
132     }
133   }
134
135   /*
136    * (non-Javadoc)
137    * 
138    * @see jalview.io.vamsas.DatastoreItem#conflict()
139    */
140   @Override
141   public void conflict()
142   {
143     Cache.log
144             .info("Update (with conflict) from vamsas document to alignment associated tree not implemented yet.");
145   }
146
147   /*
148    * (non-Javadoc)
149    * 
150    * @see jalview.io.vamsas.DatastoreItem#update()
151    */
152   @Override
153   public void updateToDoc()
154   {
155     if (isModifiable(tree.getModifiable()))
156     {
157       // synchronize(); // update();
158       // verify any changes.
159       log.info("TODO: Update tree in document from jalview.");
160     }
161     else
162     {
163       // handle conflict
164       log.info("TODO: Add the locally modified tree in Jalview as a new tree in document, leaving locked tree unchanged.");
165     }
166   }
167
168   /*
169    * (non-Javadoc)
170    * 
171    * @see jalview.io.vamsas.DatastoreItem#updateFromDoc()
172    */
173   @Override
174   public void updateFromDoc()
175   {
176     // should probably just open a new tree panel in the same place as the old
177     // one
178     // TODO: Tree.updateFromDoc
179     /*
180      * TreePanel tp = (TreePanel) jvobj; // getvObj2jv(tree);
181      * 
182      * // make a new tree Object[] idata =
183      * recoverInputData(tree.getProvenance()); try { if (idata != null &&
184      * idata[0] != null) { inputData = (AlignmentView) idata[0]; } ntree =
185      * getNtree(); title = tree.getNewick(0).getTitle(); if (title == null ||
186      * title.length() == 0) { title = tree.getTitle(); // hack!!!! } } catch
187      * (Exception e) { Cache.log.warn("Problems parsing treefile '" +
188      * tree.getNewick(0).getContent() + "'", e); }
189      */
190     log.debug("Update the local tree in jalview from the document.");
191
192     if (isModifiable(tree.getModifiable()))
193     {
194       // synchronize(); // update();
195       // verify any changes.
196       log.debug("Update tree in document from jalview.");
197     }
198     else
199     {
200       // handle conflict
201       log.debug("Add modified jalview tree as new tree in document.");
202     }
203   }
204
205   /**
206    * correctly creates provenance for trees calculated on an alignment by
207    * jalview.
208    * 
209    * @param jal
210    * @param tp
211    * @return
212    */
213   private Provenance makeTreeProvenance(AlignmentI jal, TreePanel tp)
214   {
215     Cache.log.debug("Making Tree provenance for " + tp.getTitle());
216     Provenance prov = new Provenance();
217     prov.addEntry(new Entry());
218     prov.getEntry(0).setAction("imported " + tp.getTitle());
219     prov.getEntry(0).setUser(provEntry.getUser());
220     prov.getEntry(0).setApp(provEntry.getApp());
221     prov.getEntry(0).setDate(provEntry.getDate());
222     if (tp.getTree().hasOriginalSequenceData())
223     {
224       Input vInput = new Input();
225       // LATER: check to see if tree input data is contained in this alignment -
226       // or just correctly resolve the tree's seqData to the correct alignment
227       // in
228       // the document.
229       Vector alsqrefs = getjv2vObjs(findAlignmentSequences(jal,
230               tp.getTree().seqData.getSequences()));
231       Object[] alsqs = new Object[alsqrefs.size()];
232       alsqrefs.copyInto(alsqs);
233       vInput.setObjRef(alsqs);
234       // now create main provenance data
235       prov.getEntry(0).setAction("created " + tp.getTitle());
236       prov.getEntry(0).addInput(vInput);
237       // jalview's special input parameter for distance matrix calculations
238       vInput.setName("jalview:seqdist"); // TODO: settle on appropriate name.
239       prov.getEntry(0).addParam(new Param());
240       prov.getEntry(0).getParam(0).setName("treeType");
241       prov.getEntry(0).getParam(0).setType("utf8");
242       prov.getEntry(0).getParam(0).setContent("NJ"); // TODO: type of tree is a
243       // general parameter
244       int ranges[] = tp.getTree().seqData.getVisibleContigs();
245       // VisibleContigs are with respect to alignment coordinates. Still need
246       // offsets
247       int start = tp.getTree().seqData.getAlignmentOrigin();
248       for (int r = 0; r < ranges.length; r += 2)
249       {
250         Seg visSeg = new Seg();
251         visSeg.setStart(1 + start + ranges[r]);
252         visSeg.setEnd(start + ranges[r + 1]);
253         visSeg.setInclusive(true);
254         vInput.addSeg(visSeg);
255       }
256     }
257     Cache.log.debug("Finished Tree provenance for " + tp.getTitle());
258     return prov;
259   }
260
261   /**
262    * look up SeqCigars in an existing alignment.
263    * 
264    * @param jal
265    * @param sequences
266    * @return vector of alignment sequences in order of SeqCigar array (but
267    *         missing unfound seqcigars)
268    */
269   private Vector findAlignmentSequences(AlignmentI jal, SeqCigar[] sequences)
270   {
271     SeqCigar[] tseqs = new SeqCigar[sequences.length];
272     System.arraycopy(sequences, 0, tseqs, 0, sequences.length);
273     Vector alsq = new Vector();
274     List<SequenceI> jalsqs;
275     synchronized (jalsqs = jal.getSequences())
276     {
277       for (SequenceI asq : jalsqs)
278       {
279         for (int t = 0; t < sequences.length; t++)
280         {
281           if (tseqs[t] != null
282                   && (tseqs[t].getRefSeq() == asq || tseqs[t].getRefSeq() == asq
283                           .getDatasetSequence()))
284           // && tseqs[t].getStart()>=asq.getStart() &&
285           // tseqs[t].getEnd()<=asq.getEnd())
286           {
287             tseqs[t] = null;
288             alsq.add(asq);
289           }
290         }
291       }
292     }
293     if (alsq.size() < sequences.length)
294     {
295       Cache.log
296               .warn("Not recovered all alignment sequences for given set of input sequence CIGARS");
297     }
298     return alsq;
299   }
300
301   /**
302    * 
303    * Update jalview newick representation with TreeNode map
304    * 
305    * @param tp
306    *          the treepanel that this tree is bound to.
307    */
308   public void UpdateSequenceTreeMap(TreePanel tp)
309   {
310     if (tp == null || tree == null)
311     {
312       return;
313     }
314
315     if (tp.getTree() == null)
316     {
317       Cache.log.warn("Not updating SequenceTreeMap for "
318               + tree.getVorbaId());
319       return;
320     }
321     Vector<SequenceNode> leaves = tp.getTree().findLeaves(
322             tp.getTree().getTopNode());
323     Treenode[] tn = tree.getTreenode(); // todo: select nodes for this
324     // particular tree
325     int sz = tn.length;
326     int i = 0;
327
328     while (i < sz)
329     {
330       Treenode node = tn[i++];
331       BinaryNode mappednode = findNodeSpec(node.getNodespec(), leaves);
332       if (mappednode != null && mappednode instanceof SequenceNode)
333       {
334         SequenceNode leaf = (SequenceNode) mappednode;
335         // check if we can make the specified association
336         Object jvseq = null;
337         int vrf = 0, refv = 0;
338         while (jvseq == null && vrf < node.getVrefCount())
339         {
340           if (refv < node.getVref(vrf).getRefsCount())
341           {
342             Object noderef = node.getVref(vrf).getRefs(refv++);
343             if (noderef instanceof AlignmentSequence)
344             {
345               // we only make these kind of associations
346               jvseq = getvObj2jv((Vobject) noderef);
347             }
348           }
349           else
350           {
351             refv = 0;
352             vrf++;
353           }
354         }
355         if (jvseq instanceof SequenceI)
356         {
357           leaf.setElement(jvseq);
358           leaf.setPlaceholder(false);
359         }
360         else
361         {
362           leaf.setPlaceholder(true);
363           leaf.setElement(new Sequence(leaf.getName(), "THISISAPLACEHLDER"));
364         }
365       }
366     }
367   }
368
369   // / TODO: refactor to vamsas :start
370   /**
371    * construct treenode mappings for mapped sequences
372    * 
373    * @param ntree
374    * @param newick
375    * @return
376    */
377   public Treenode[] makeTreeNodes(NJTree ntree, Newick newick)
378   {
379     Vector<SequenceNode> leaves = ntree.findLeaves(ntree.getTopNode());
380     Vector tnv = new Vector();
381     Enumeration l = leaves.elements();
382     Hashtable nodespecs = new Hashtable();
383     while (l.hasMoreElements())
384     {
385       jalview.datamodel.BinaryNode tnode = (jalview.datamodel.BinaryNode) l
386               .nextElement();
387       if (tnode instanceof jalview.datamodel.SequenceNode)
388       {
389         if (!((jalview.datamodel.SequenceNode) tnode).isPlaceholder())
390         {
391           Object assocseq = ((jalview.datamodel.SequenceNode) tnode)
392                   .element();
393           if (assocseq instanceof SequenceI)
394           {
395             Vobject vobj = this.getjv2vObj(assocseq);
396             if (vobj != null)
397             {
398               Treenode node = new Treenode();
399               if (newick.isRegisterable())
400               {
401                 this.cdoc.registerObject(newick);
402                 node.addTreeId(newick);
403               }
404               node.setNodespec(makeNodeSpec(nodespecs, tnode));
405               node.setName(tnode.getName());
406               Vref vr = new Vref();
407               vr.addRefs(vobj);
408               node.addVref(vr);
409               tnv.addElement(node);
410             }
411             else
412             {
413               System.err.println("WARNING: Unassociated treeNode "
414                       + tnode.element().toString()
415                       + " "
416                       + ((tnode.getName() != null) ? " label "
417                               + tnode.getName() : ""));
418             }
419           }
420         }
421       }
422     }
423     if (tnv.size() > 0)
424     {
425       Treenode[] tn = new Treenode[tnv.size()];
426       tnv.copyInto(tn);
427       return tn;
428     }
429     return new Treenode[] {};
430   }
431
432   private String makeNodeSpec(Hashtable nodespecs,
433           jalview.datamodel.BinaryNode tnode)
434   {
435     String nname = new String(tnode.getName());
436     Integer nindx = (Integer) nodespecs.get(nname);
437     if (nindx == null)
438     {
439       nindx = new Integer(1);
440     }
441     nname = nindx.toString() + " " + nname;
442     return nname;
443   }
444
445   /**
446    * call to match up Treenode specs to NJTree parsed from document object.
447    * 
448    * @param nodespec
449    * @param leaves
450    *          as returned from NJTree.findLeaves( .., ..) ..
451    * @return
452    */
453   private jalview.datamodel.BinaryNode findNodeSpec(String nodespec,
454           Vector leaves)
455   {
456     int occurence = -1;
457     String nspec = nodespec.substring(nodespec.indexOf(' ') + 1);
458     String oval = nodespec.substring(0, nodespec.indexOf(' '));
459     try
460     {
461       occurence = new Integer(oval).intValue();
462     } catch (Exception e)
463     {
464       System.err.println("Invalid nodespec '" + nodespec + "'");
465       return null;
466     }
467     jalview.datamodel.BinaryNode bn = null;
468
469     int nocc = 0;
470     Enumeration en = leaves.elements();
471     while (en.hasMoreElements() && nocc < occurence)
472     {
473       bn = (jalview.datamodel.BinaryNode) en.nextElement();
474       if (bn instanceof jalview.datamodel.SequenceNode
475               && bn.getName().equals(nspec))
476       {
477         --occurence;
478       }
479       else
480       {
481         bn = null;
482       }
483     }
484     return bn;
485   }
486
487   // todo: end refactor to vamsas library
488   /**
489    * add jalview object to vamsas document
490    * 
491    */
492   @Override
493   public void addToDocument()
494   {
495     tree = new uk.ac.vamsas.objects.core.Tree();
496     bindjvvobj(tp, tree);
497     tree.setTitle(tp.getTitle());
498     Newick newick = new Newick();
499     newick.setContent(tp.getTree().toString());
500     newick.setTitle(tp.getTitle());
501     tree.addNewick(newick);
502     tree.setProvenance(makeTreeProvenance(jal, tp));
503     tree.setTreenode(makeTreeNodes(tp.getTree(), newick));
504
505     alignment.addTree(tree);
506   }
507
508   /**
509    * note: this function assumes that all sequence and alignment objects
510    * referenced in input data has already been associated with jalview objects.
511    * 
512    * @param tp
513    * @param alignFrame
514    * @return Object[] { AlignmentView, AlignmentI - reference alignment for
515    *         input }
516    */
517   public Object[] recoverInputData(Provenance tp)
518   {
519     AlignmentViewport javport = null;
520     jalview.datamodel.AlignmentI jal = null;
521     jalview.datamodel.CigarArray view = null;
522     for (int pe = 0; pe < tp.getEntryCount(); pe++)
523     {
524       if (tp.getEntry(pe).getInputCount() > 0)
525       {
526         if (tp.getEntry(pe).getInputCount() > 1)
527         {
528           Cache.log
529                   .warn("Ignoring additional input spec in provenance entry "
530                           + tp.getEntry(pe).toString());
531         }
532         // LATER: deal sensibly with multiple inputs
533         Input vInput = tp.getEntry(pe).getInput(0);
534         // is this the whole alignment or a specific set of sequences ?
535         if (vInput.getObjRefCount() == 0)
536         {
537           if (tree.getV_parent() != null
538                   && tree.getV_parent() instanceof uk.ac.vamsas.objects.core.Alignment)
539           {
540             javport = getViewport(tree.getV_parent());
541             jal = javport.getAlignment();
542             view = javport.getAlignment().getCompactAlignment();
543           }
544         }
545         else
546         {
547           // Explicit reference - to alignment, sequences or what.
548           if (vInput.getObjRefCount() == 1
549                   && vInput.getObjRef(0) instanceof uk.ac.vamsas.objects.core.Alignment)
550           {
551             // recover an AlignmentView for the input data
552             javport = getViewport((Vobject) vInput.getObjRef(0));
553             jal = javport.getAlignment();
554             view = javport.getAlignment().getCompactAlignment();
555           }
556           else if (vInput.getObjRef(0) instanceof uk.ac.vamsas.objects.core.AlignmentSequence)
557           {
558             // recover an AlignmentView for the input data
559             javport = getViewport(((Vobject) vInput.getObjRef(0))
560                     .getV_parent());
561             jal = javport.getAlignment();
562             jalview.datamodel.SequenceI[] seqs = new jalview.datamodel.SequenceI[vInput
563                     .getObjRefCount()];
564             for (int i = 0, iSize = vInput.getObjRefCount(); i < iSize; i++)
565             {
566               SequenceI seq = (SequenceI) getvObj2jv((Vobject) vInput
567                       .getObjRef(i));
568               seqs[i] = seq;
569             }
570             view = new jalview.datamodel.Alignment(seqs)
571                     .getCompactAlignment();
572
573           }
574         }
575         int from = 1, to = jal.getWidth();
576         int offset = 0; // deleteRange modifies its frame of reference
577         for (int r = 0, s = vInput.getSegCount(); r < s; r++)
578         {
579           Seg visSeg = vInput.getSeg(r);
580           int se[] = getSegRange(visSeg, true); // jalview doesn't do
581           // bidirection alignments yet.
582           if (to < se[1])
583           {
584             Cache.log.warn("Ignoring invalid segment in InputData spec.");
585           }
586           else
587           {
588             if (se[0] > from)
589             {
590               view.deleteRange(offset + from - 1, offset + se[0] - 2);
591               offset -= se[0] - from;
592             }
593             from = se[1] + 1;
594           }
595         }
596         if (from < to)
597         {
598           view.deleteRange(offset + from - 1, offset + to - 1); // final
599           // deletion -
600           // TODO: check
601           // off by
602           // one for to
603         }
604         return new Object[] { new AlignmentView(view), jal };
605       }
606     }
607     Cache.log
608             .debug("Returning null for input data recovery from provenance.");
609     return null;
610   }
611
612   private AlignmentViewport getViewport(Vobject v_parent)
613   {
614     if (v_parent instanceof uk.ac.vamsas.objects.core.Alignment)
615     {
616       return datastore
617               .findViewport((uk.ac.vamsas.objects.core.Alignment) v_parent);
618     }
619     return null;
620   }
621
622   public NewickFile getNewickTree()
623   {
624     return ntree;
625   }
626
627   public String getTitle()
628   {
629     return title;
630   }
631
632   public AlignmentView getInputData()
633   {
634     return inputData;
635   }
636
637   public boolean isValidTree()
638   {
639     try
640     {
641       if (ntree == null)
642       {
643         return false;
644       }
645       ntree.parse();
646       if (ntree.getTree() != null)
647       {
648         ntree = getNtree();
649       }
650       return true;
651     } catch (Exception e)
652     {
653       Cache.log.debug("Failed to parse newick tree string", e);
654     }
655     return false;
656   }
657 }