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