/*
* Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
* Copyright (C) $$Year-Rel$$ The Jalview Authors
*
* This file is part of Jalview.
*
* Jalview is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* Jalview is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Jalview. If not, see .
* The Jalview Authors are detailed in the 'AUTHORS' file.
*/
package jalview.io.vamsas;
import jalview.analysis.TreeBuilder;
import jalview.analysis.TreeModel;
import jalview.bin.Console;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.AlignmentView;
import jalview.datamodel.BinaryNode;
import jalview.datamodel.SeqCigar;
import jalview.datamodel.Sequence;
import jalview.datamodel.SequenceI;
import jalview.datamodel.SequenceNode;
import jalview.gui.TreePanel;
import jalview.io.NewickFile;
import jalview.io.VamsasAppDatastore;
import jalview.viewmodel.AlignmentViewport;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;
import uk.ac.vamsas.client.Vobject;
import uk.ac.vamsas.objects.core.AlignmentSequence;
import uk.ac.vamsas.objects.core.Entry;
import uk.ac.vamsas.objects.core.Input;
import uk.ac.vamsas.objects.core.Newick;
import uk.ac.vamsas.objects.core.Param;
import uk.ac.vamsas.objects.core.Provenance;
import uk.ac.vamsas.objects.core.Seg;
import uk.ac.vamsas.objects.core.Treenode;
import uk.ac.vamsas.objects.core.Vref;
public class Tree extends DatastoreItem
{
AlignmentI jal;
TreePanel tp;
uk.ac.vamsas.objects.core.Tree tree;
uk.ac.vamsas.objects.core.Alignment alignment; // may be null => dataset or
// other kind of tree
private NewickFile ntree;
private String title;
private AlignmentView inputData = null;
public static void updateFrom(VamsasAppDatastore datastore,
jalview.gui.AlignFrame alignFrame,
uk.ac.vamsas.objects.core.Tree vtree)
{
Tree toTree = new Tree(datastore, alignFrame, vtree);
}
public Tree(VamsasAppDatastore datastore,
jalview.gui.AlignFrame alignFrame,
uk.ac.vamsas.objects.core.Tree vtree)
{
super(datastore, vtree, TreePanel.class);
doJvUpdate();
}
private NewickFile getNtree() throws IOException
{
return new jalview.io.NewickFile(tree.getNewick(0).getContent());
}
public Tree(VamsasAppDatastore datastore, TreePanel tp2, AlignmentI jal2,
uk.ac.vamsas.objects.core.Alignment alignment2)
{
super(datastore, tp2, uk.ac.vamsas.objects.core.Tree.class);
jal = jal2;
tp = (TreePanel) jvobj;
alignment = alignment2;
tree = (uk.ac.vamsas.objects.core.Tree) vobj;
doSync();
}
/*
* (non-Javadoc)
*
* @see jalview.io.vamsas.DatastoreItem#addFromDocument()
*/
@Override
public void addFromDocument()
{
tree = (uk.ac.vamsas.objects.core.Tree) vobj; // vtree;
TreePanel tp = (TreePanel) jvobj; // getvObj2jv(tree);
// make a new tree
Object[] idata = recoverInputData(tree.getProvenance());
try
{
if (idata != null && idata[0] != null)
{
inputData = (AlignmentView) idata[0];
}
ntree = getNtree();
title = tree.getNewick(0).getTitle();
if (title == null || title.length() == 0)
{
title = tree.getTitle(); // hack!!!!
}
} catch (Exception e)
{
Console.warn("Problems parsing treefile '"
+ tree.getNewick(0).getContent() + "'", e);
}
}
/*
* (non-Javadoc)
*
* @see jalview.io.vamsas.DatastoreItem#conflict()
*/
@Override
public void conflict()
{
Console.info(
"Update (with conflict) from vamsas document to alignment associated tree not implemented yet.");
}
/*
* (non-Javadoc)
*
* @see jalview.io.vamsas.DatastoreItem#update()
*/
@Override
public void updateToDoc()
{
if (isModifiable(tree.getModifiable()))
{
// synchronize(); // update();
// verify any changes.
log.info("TODO: Update tree in document from jalview.");
}
else
{
// handle conflict
log.info(
"TODO: Add the locally modified tree in Jalview as a new tree in document, leaving locked tree unchanged.");
}
}
/*
* (non-Javadoc)
*
* @see jalview.io.vamsas.DatastoreItem#updateFromDoc()
*/
@Override
public void updateFromDoc()
{
// should probably just open a new tree panel in the same place as the old
// one
// TODO: Tree.updateFromDoc
/*
* TreePanel tp = (TreePanel) jvobj; // getvObj2jv(tree);
*
* // make a new tree Object[] idata =
* recoverInputData(tree.getProvenance()); try { if (idata != null &&
* idata[0] != null) { inputData = (AlignmentView) idata[0]; } ntree =
* getNtree(); title = tree.getNewick(0).getTitle(); if (title == null ||
* title.length() == 0) { title = tree.getTitle(); // hack!!!! } } catch
* (Exception e) { Cache.warn("Problems parsing treefile '" +
* tree.getNewick(0).getContent() + "'", e); }
*/
log.debug("Update the local tree in jalview from the document.");
if (isModifiable(tree.getModifiable()))
{
// synchronize(); // update();
// verify any changes.
log.debug("Update tree in document from jalview.");
}
else
{
// handle conflict
log.debug("Add modified jalview tree as new tree in document.");
}
}
/**
* correctly creates provenance for trees calculated on an alignment by
* jalview.
*
* @param jal
* @param tp
* @return
*/
private Provenance makeTreeProvenance(AlignmentI jal, TreePanel tp)
{
Console.debug("Making Tree provenance for " + tp.getTitle());
Provenance prov = new Provenance();
prov.addEntry(new Entry());
prov.getEntry(0).setAction("imported " + tp.getTitle());
prov.getEntry(0).setUser(provEntry.getUser());
prov.getEntry(0).setApp(provEntry.getApp());
prov.getEntry(0).setDate(provEntry.getDate());
AlignmentView originalData = tp.getTree().getOriginalData();
if (originalData != null)
{
Input vInput = new Input();
// LATER: check to see if tree input data is contained in this alignment -
// or just correctly resolve the tree's seqData to the correct alignment
// in
// the document.
Vector alsqrefs = getjv2vObjs(findAlignmentSequences(jal,
tp.getTree().getOriginalData().getSequences()));
Object[] alsqs = new Object[alsqrefs.size()];
alsqrefs.copyInto(alsqs);
vInput.setObjRef(alsqs);
// now create main provenance data
prov.getEntry(0).setAction("created " + tp.getTitle());
prov.getEntry(0).addInput(vInput);
// jalview's special input parameter for distance matrix calculations
vInput.setName("jalview:seqdist"); // TODO: settle on appropriate name.
prov.getEntry(0).addParam(new Param());
prov.getEntry(0).getParam(0).setName("treeType");
prov.getEntry(0).getParam(0).setType("utf8");
prov.getEntry(0).getParam(0)
.setContent(TreeBuilder.NEIGHBOUR_JOINING);
// TODO: type of tree is a general parameter
int ranges[] = originalData.getVisibleContigs();
// VisibleContigs are with respect to alignment coordinates. Still need
// offsets
int start = tp.getTree().getOriginalData().getAlignmentOrigin();
for (int r = 0; r < ranges.length; r += 2)
{
Seg visSeg = new Seg();
visSeg.setStart(1 + start + ranges[r]);
visSeg.setEnd(start + ranges[r + 1]);
visSeg.setInclusive(true);
vInput.addSeg(visSeg);
}
}
Console.debug("Finished Tree provenance for " + tp.getTitle());
return prov;
}
/**
* look up SeqCigars in an existing alignment.
*
* @param jal
* @param sequences
* @return vector of alignment sequences in order of SeqCigar array (but
* missing unfound seqcigars)
*/
private Vector findAlignmentSequences(AlignmentI jal,
SeqCigar[] sequences)
{
SeqCigar[] tseqs = new SeqCigar[sequences.length];
System.arraycopy(sequences, 0, tseqs, 0, sequences.length);
Vector alsq = new Vector<>();
List jalsqs = jal.getSequences();
synchronized (jalsqs)
{
for (SequenceI asq : jalsqs)
{
for (int t = 0; t < sequences.length; t++)
{
if (tseqs[t] != null && (tseqs[t].getRefSeq() == asq
|| tseqs[t].getRefSeq() == asq.getDatasetSequence()))
// && tseqs[t].getStart()>=asq.getStart() &&
// tseqs[t].getEnd()<=asq.getEnd())
{
tseqs[t] = null;
alsq.add(asq);
}
}
}
}
if (alsq.size() < sequences.length)
{
Console.warn(
"Not recovered all alignment sequences for given set of input sequence CIGARS");
}
return alsq;
}
/**
*
* Update jalview newick representation with TreeNode map
*
* @param tp
* the treepanel that this tree is bound to.
*/
public void UpdateSequenceTreeMap(TreePanel tp)
{
if (tp == null || tree == null)
{
return;
}
if (tp.getTree() == null)
{
Console.warn("Not updating SequenceTreeMap for " + tree.getVorbaId());
return;
}
Vector leaves = tp.getTree()
.findLeaves(tp.getTree().getTopNode());
Treenode[] tn = tree.getTreenode(); // todo: select nodes for this
// particular tree
int sz = tn.length;
int i = 0;
while (i < sz)
{
Treenode node = tn[i++];
BinaryNode mappednode = findNodeSpec(node.getNodespec(), leaves);
if (mappednode != null && mappednode instanceof SequenceNode)
{
SequenceNode leaf = (SequenceNode) mappednode;
// check if we can make the specified association
Object jvseq = null;
int vrf = 0, refv = 0;
while (jvseq == null && vrf < node.getVrefCount())
{
if (refv < node.getVref(vrf).getRefsCount())
{
Object noderef = node.getVref(vrf).getRefs(refv++);
if (noderef instanceof AlignmentSequence)
{
// we only make these kind of associations
jvseq = getvObj2jv((Vobject) noderef);
}
}
else
{
refv = 0;
vrf++;
}
}
if (jvseq instanceof SequenceI)
{
leaf.setElement((SequenceI) jvseq);
leaf.setPlaceholder(false);
}
else
{
leaf.setPlaceholder(true);
leaf.setElement(
new Sequence(leaf.getName(), "THISISAPLACEHLDER"));
}
}
}
}
// / TODO: refactor to vamsas :start
/**
* construct treenode mappings for mapped sequences
*
* @param treeModel
* @param newick
* @return
*/
public Treenode[] makeTreeNodes(TreeModel treeModel, Newick newick)
{
Vector leaves = treeModel
.findLeaves(treeModel.getTopNode());
Vector tnv = new Vector();
Enumeration l = leaves.elements();
Hashtable nodespecs = new Hashtable();
while (l.hasMoreElements())
{
jalview.datamodel.BinaryNode tnode = (jalview.datamodel.BinaryNode) l
.nextElement();
if (tnode instanceof jalview.datamodel.SequenceNode)
{
if (!((jalview.datamodel.SequenceNode) tnode).isPlaceholder())
{
Object assocseq = ((BinaryNode) tnode).element();
if (assocseq instanceof SequenceI)
{
Vobject vobj = this.getjv2vObj(assocseq);
if (vobj != null)
{
Treenode node = new Treenode();
if (newick.isRegisterable())
{
this.cdoc.registerObject(newick);
node.addTreeId(newick);
}
node.setNodespec(makeNodeSpec(nodespecs, tnode));
node.setName(tnode.getName());
Vref vr = new Vref();
vr.addRefs(vobj);
node.addVref(vr);
tnv.addElement(node);
}
else
{
jalview.bin.Console.errPrintln("WARNING: Unassociated treeNode "
+ tnode.element().toString() + " "
+ ((tnode.getName() != null)
? " label " + tnode.getName()
: ""));
}
}
}
}
}
if (tnv.size() > 0)
{
Treenode[] tn = new Treenode[tnv.size()];
tnv.copyInto(tn);
return tn;
}
return new Treenode[] {};
}
private String makeNodeSpec(Hashtable nodespecs,
jalview.datamodel.BinaryNode tnode)
{
String nname = new String(tnode.getName());
Integer nindx = (Integer) nodespecs.get(nname);
if (nindx == null)
{
nindx = Integer.valueOf(1);
}
nname = nindx.toString() + " " + nname;
return nname;
}
/**
* call to match up Treenode specs to NJTree parsed from document object.
*
* @param nodespec
* @param leaves
* as returned from NJTree.findLeaves( .., ..) ..
* @return
*/
private jalview.datamodel.BinaryNode findNodeSpec(String nodespec,
Vector leaves)
{
int occurence = -1;
String nspec = nodespec.substring(nodespec.indexOf(' ') + 1);
String oval = nodespec.substring(0, nodespec.indexOf(' '));
try
{
occurence = Integer.valueOf(oval).intValue();
} catch (Exception e)
{
jalview.bin.Console.errPrintln("Invalid nodespec '" + nodespec + "'");
return null;
}
jalview.datamodel.BinaryNode bn = null;
int nocc = 0;
Enumeration en = leaves.elements();
while (en.hasMoreElements() && nocc < occurence)
{
bn = (jalview.datamodel.BinaryNode) en.nextElement();
if (bn instanceof jalview.datamodel.SequenceNode
&& bn.getName().equals(nspec))
{
--occurence;
}
else
{
bn = null;
}
}
return bn;
}
// todo: end refactor to vamsas library
/**
* add jalview object to vamsas document
*
*/
@Override
public void addToDocument()
{
tree = new uk.ac.vamsas.objects.core.Tree();
bindjvvobj(tp, tree);
tree.setTitle(tp.getTitle());
Newick newick = new Newick();
newick.setContent(tp.getTree().print());
newick.setTitle(tp.getTitle());
tree.addNewick(newick);
tree.setProvenance(makeTreeProvenance(jal, tp));
tree.setTreenode(makeTreeNodes(tp.getTree(), newick));
alignment.addTree(tree);
}
/**
* note: this function assumes that all sequence and alignment objects
* referenced in input data has already been associated with jalview objects.
*
* @param tp
* @param alignFrame
* @return Object[] { AlignmentView, AlignmentI - reference alignment for
* input }
*/
public Object[] recoverInputData(Provenance tp)
{
AlignmentViewport javport = null;
jalview.datamodel.AlignmentI jal = null;
jalview.datamodel.CigarArray view = null;
for (int pe = 0; pe < tp.getEntryCount(); pe++)
{
if (tp.getEntry(pe).getInputCount() > 0)
{
if (tp.getEntry(pe).getInputCount() > 1)
{
Console.warn("Ignoring additional input spec in provenance entry "
+ tp.getEntry(pe).toString());
}
// LATER: deal sensibly with multiple inputs
Input vInput = tp.getEntry(pe).getInput(0);
// is this the whole alignment or a specific set of sequences ?
if (vInput.getObjRefCount() == 0)
{
if (tree.getV_parent() != null && tree
.getV_parent() instanceof uk.ac.vamsas.objects.core.Alignment)
{
javport = getViewport(tree.getV_parent());
jal = javport.getAlignment();
view = javport.getAlignment().getCompactAlignment();
}
}
else
{
// Explicit reference - to alignment, sequences or what.
if (vInput.getObjRefCount() == 1 && vInput.getObjRef(
0) instanceof uk.ac.vamsas.objects.core.Alignment)
{
// recover an AlignmentView for the input data
javport = getViewport((Vobject) vInput.getObjRef(0));
jal = javport.getAlignment();
view = javport.getAlignment().getCompactAlignment();
}
else if (vInput.getObjRef(
0) instanceof uk.ac.vamsas.objects.core.AlignmentSequence)
{
// recover an AlignmentView for the input data
javport = getViewport(
((Vobject) vInput.getObjRef(0)).getV_parent());
jal = javport.getAlignment();
jalview.datamodel.SequenceI[] seqs = new jalview.datamodel.SequenceI[vInput
.getObjRefCount()];
for (int i = 0, iSize = vInput.getObjRefCount(); i < iSize; i++)
{
SequenceI seq = (SequenceI) getvObj2jv(
(Vobject) vInput.getObjRef(i));
seqs[i] = seq;
}
view = new jalview.datamodel.Alignment(seqs)
.getCompactAlignment();
}
}
int from = 1, to = jal.getWidth();
int offset = 0; // deleteRange modifies its frame of reference
for (int r = 0, s = vInput.getSegCount(); r < s; r++)
{
Seg visSeg = vInput.getSeg(r);
int se[] = getSegRange(visSeg, true); // jalview doesn't do
// bidirection alignments yet.
if (to < se[1])
{
Console.warn("Ignoring invalid segment in InputData spec.");
}
else
{
if (se[0] > from)
{
view.deleteRange(offset + from - 1, offset + se[0] - 2);
offset -= se[0] - from;
}
from = se[1] + 1;
}
}
if (from < to)
{
view.deleteRange(offset + from - 1, offset + to - 1); // final
// deletion -
// TODO: check
// off by
// one for to
}
return new Object[] { new AlignmentView(view), jal };
}
}
Console.debug(
"Returning null for input data recovery from provenance.");
return null;
}
private AlignmentViewport getViewport(Vobject v_parent)
{
if (v_parent instanceof uk.ac.vamsas.objects.core.Alignment)
{
return datastore
.findViewport((uk.ac.vamsas.objects.core.Alignment) v_parent);
}
return null;
}
public NewickFile getNewickTree()
{
return ntree;
}
public String getTitle()
{
return title;
}
public AlignmentView getInputData()
{
return inputData;
}
public boolean isValidTree()
{
try
{
if (ntree == null)
{
return false;
}
ntree.parse();
if (ntree.getTree() != null)
{
ntree = getNtree();
}
return true;
} catch (Exception e)
{
Console.debug("Failed to parse newick tree string", e);
}
return false;
}
}