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