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