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