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