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