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