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