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