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