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