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