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