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