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