4 * Jalview - A Sequence Alignment Editor and Viewer
\r
5 * Copyright (C) 2007 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
\r
7 * This program is free software; you can redistribute it and/or
\r
8 * modify it under the terms of the GNU General Public License
\r
9 * as published by the Free Software Foundation; either version 2
\r
10 * of the License, or (at your option) any later version.
\r
12 * This program is distributed in the hope that it will be useful,
\r
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
15 * GNU General Public License for more details.
\r
17 * You should have received a copy of the GNU General Public License
\r
18 * along with this program; if not, write to the Free Software
\r
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
\r
24 // http://evolution.genetics.washington.edu/phylip/newick_doc.html
\r
25 // TODO: Implement Basic NHX tag parsing and preservation
\r
26 // TODO: http://evolution.genetics.wustl.edu/eddy/forester/NHX.html
\r
27 // TODO: Extended SequenceNodeI to hold parsed NHX strings
\r
28 package uk.ac.vamsas.objects.utils.trees;
\r
31 import java.util.Enumeration;
\r
32 import java.util.Hashtable;
\r
33 import java.util.Vector;
\r
34 import java.util.regex.Matcher;
\r
35 import java.util.regex.Pattern;
\r
37 import uk.ac.vamsas.client.Vobject;
\r
38 import uk.ac.vamsas.objects.core.Treenode;
\r
39 import uk.ac.vamsas.objects.core.Vref;
\r
45 * @version $Revision: 1.12 $
\r
47 public class NewickFile {
\r
50 private boolean HasBootstrap = false;
\r
52 private boolean HasDistances = false;
\r
54 private boolean RootHasDistance = false;
\r
57 boolean ReplaceUnderscores = true;
\r
59 boolean printRootInfo = true; // in case root has anything to preserve
\r
61 private Pattern[] NodeSafeName = new Pattern[] {
\r
62 Pattern.compile("[\\[,:'()]"), // test for requiring quotes
\r
63 Pattern.compile("'"), // escaping quote characters
\r
64 Pattern.compile("\\s") // unqoted whitespace transformation
\r
67 char QuoteChar = '\'';
\r
69 String newickFile = null;
\r
72 * Creates a new NewickFile object
\r
75 * Newick style tree string
\r
77 * @throws IOException
\r
78 * if string is not a valid newick file
\r
80 public NewickFile(String inStr) throws IOException {
\r
85 public NewickFile(File inFile) throws IOException {
\r
86 errormessage = "Problem's reading file " + inFile;
\r
87 dataIn = new java.io.BufferedReader(new InputStreamReader(
\r
88 new java.io.FileInputStream(inFile)));
\r
93 * Creates a new NewickFile object.
\r
98 public NewickFile(SequenceNode newtree) {
\r
103 * Creates a new NewickFile object.
\r
110 public NewickFile(SequenceNode newtree, boolean bootstrap) {
\r
111 HasBootstrap = bootstrap;
\r
116 * Creates a new NewickFile object.
\r
125 public NewickFile(SequenceNode newtree, boolean bootstrap, boolean distances) {
\r
127 HasBootstrap = bootstrap;
\r
128 HasDistances = distances;
\r
132 * Creates a new NewickFile object.
\r
140 * @param rootdistance
\r
143 public NewickFile(SequenceNode newtree, boolean bootstrap, boolean distances,
\r
144 boolean rootdistance) {
\r
146 HasBootstrap = bootstrap;
\r
147 HasDistances = distances;
\r
148 RootHasDistance = rootdistance;
\r
157 * Message qualifier (ie the incorrect data)
\r
159 * range of characters either side to dump
\r
161 * character position
\r
163 * newick string being parsed
\r
165 * @return composed error message
\r
167 private String ErrorStringrange(String Error, String Er, int r, int p,
\r
169 return ((Error == null) ? "" : Error)
\r
174 + s.substring(((p - r) < 0) ? 0 : (p - r), ((p + r) > s.length()) ? s
\r
175 .length() : (p + r)) + " )\n";
\r
180 * @return true if tree has bootstrap values
\r
182 public boolean HasBootstrap() {
\r
183 return HasBootstrap;
\r
188 * @return true if tree has distances on branches
\r
190 public boolean HasDistances() {
\r
191 return HasDistances;
\r
196 * @return true if root has a stem distance
\r
198 public boolean HasRootDistance() {
\r
199 return RootHasDistance;
\r
203 * hacked out of jalview code
\r
207 String errormessage;
\r
209 java.io.BufferedReader dataIn = null;
\r
211 public String nextLine() throws IOException {
\r
212 if (dataIn == null && newickFile == null)
\r
213 throw new IOException(
\r
214 "IMPLEMENTATION ERROR: NewickFile has not been initialised for reading a newick string.");
\r
215 if (dataIn == null) {
\r
216 dataIn = new BufferedReader(new StringReader(newickFile));
\r
220 return dataIn.readLine();
\r
221 throw new IOException("Invalid Source Stream:" + errormessage);
\r
225 * call this to convert the newick string into a binary node linked tree
\r
226 * Note: this is automatically called by the constructors, so you normally
\r
227 * wouldn't need to use this.
\r
229 * @throws IOException
\r
230 * if the newick string cannot be parsed.
\r
232 public void parse() throws IOException {
\r
234 if (newickFile == null) {
\r
235 // fill nf with complete tree file
\r
237 StringBuffer file = new StringBuffer();
\r
239 while ((nf = nextLine()) != null) {
\r
243 nf = file.toString();
\r
248 root = new SequenceNode();
\r
250 SequenceNode realroot = null;
\r
251 SequenceNode c = root;
\r
255 // int flen = nf.length();
\r
257 String Error = null;
\r
258 String nodename = null;
\r
260 float DefDistance = (float) 0.001; // @param Default distance for a node -
\r
262 int DefBootstrap = -1; // @param Default bootstrap for a node
\r
264 float distance = DefDistance;
\r
265 int bootstrap = DefBootstrap;
\r
267 boolean ascending = false; // flag indicating that we are leaving the
\r
270 Pattern majorsyms = Pattern.compile("[(\\['),;]");
\r
272 Matcher mjsyms = majorsyms.matcher(nf);
\r
276 while (mjsyms.find(cp) && (Error == null)) {
\r
277 int fcp = mjsyms.start();
\r
279 switch (schar = nf.charAt(fcp)) {
\r
282 // ascending should not be set
\r
283 // New Internal node
\r
285 Error = ErrorStringrange(Error, "Unexpected '('", 7, fcp, nf);
\r
293 if (c.right() == null) {
\r
294 c.setRight(new SequenceNode(null, c, null, DefDistance, DefBootstrap,
\r
296 c = (SequenceNode) c.right();
\r
298 if (c.left() != null) {
\r
299 // Dummy node for polytomy - keeps c.left free for new node
\r
300 SequenceNode tmpn = new SequenceNode(null, c, null, 0, 0, true);
\r
301 tmpn.SetChildren(c.left(), c.right());
\r
305 c.setLeft(new SequenceNode(null, c, null, DefDistance, DefBootstrap,
\r
307 c = (SequenceNode) c.left();
\r
310 if (realroot == null) {
\r
315 distance = DefDistance;
\r
316 bootstrap = DefBootstrap;
\r
321 // Deal with quoted fields
\r
324 Matcher qnodename = Pattern.compile("([^']|'')+'").matcher(nf);
\r
325 if (qnodename.find(fcp)) {
\r
326 nodename = new String(qnodename.group(1));
\r
327 cp = qnodename.end(0);
\r
329 Error = ErrorStringrange(Error, "Unterminated quotes for nodename",
\r
336 // Reached termininating root node label.
\r
337 if (schar == ';' && d != -1) {
\r
338 Error = ErrorStringrange(Error,
\r
339 "Wayward semicolon (depth=" + d + ")", 7, fcp, nf);
\r
342 // Skip Comment or structured/extended NH format info
\r
343 if (schar == '[') {
\r
344 if ((nextcp=nf.indexOf(']', fcp)) > -1) {
\r
345 // verified that comment is properly terminated.
\r
346 // now skip the comment field
\r
348 break; // go and search for the next node separator, leaving ncp at beginning of node info
\r
350 Error = ErrorStringrange(Error, "Unterminated comment", 3, fcp, nf);
\r
356 // Parse simpler field strings from substring between ncp and node separator
\r
357 String fstring = nf.substring(ncp, fcp);
\r
358 // extract any comments from the nodeinfo.
\r
359 while (fstring.indexOf(']')>-1)
\r
361 int cstart=fstring.indexOf('[');
\r
362 int cend=fstring.indexOf(']');
\r
363 String comment = fstring.substring(cstart+1,cend); // TODO: put this somewhere ?
\r
364 fstring = fstring.substring(0, cstart)+fstring.substring(cend+1);
\r
366 Matcher uqnodename = Pattern.compile("^([^' :;\\](),]+).*").matcher(
\r
368 if (uqnodename.matches()
\r
369 && ((uqnodename.start(1) == 0) || (fstring.charAt(uqnodename
\r
370 .start(1) - 1) != ':'))) // JBPNote HACK!
\r
372 if (nodename == null) {
\r
373 if (ReplaceUnderscores) {
\r
374 nodename = uqnodename.group(1).replace('_', ' ');
\r
376 nodename = uqnodename.group(1);
\r
379 Error = ErrorStringrange(Error,
\r
380 "File has broken algorithm - overwritten nodename", 10, fcp, nf);
\r
384 Matcher nbootstrap = Pattern.compile("\\s*([+0-9]+)\\s*:.*").matcher(
\r
387 if (nbootstrap.matches())
\r
389 if (nodename!=null && nbootstrap.group(1).equals(nodename))
\r
391 nodename=null; // empty nodename - only bootstrap value
\r
393 if ((nodename==null || nodename.length()==0) || nbootstrap.start(1)>=uqnodename.end(1))
\r
396 bootstrap = (new Integer(nbootstrap.group(1))).intValue();
\r
397 HasBootstrap = true;
\r
398 } catch (Exception e) {
\r
399 Error = ErrorStringrange(Error, "Can't parse bootstrap value", 4,
\r
400 ncp + nbootstrap.start(0), nf);
\r
405 Matcher ndist = Pattern.compile(".*:([-0-9Ee.+]+)").matcher(fstring);
\r
406 boolean nodehasdistance = false;
\r
408 if (ndist.matches()) {
\r
410 distance = (new Float(ndist.group(1))).floatValue();
\r
411 HasDistances = true;
\r
412 nodehasdistance = true;
\r
413 } catch (Exception e) {
\r
414 Error = ErrorStringrange(Error, "Can't parse node distance value",
\r
415 7, ncp + ndist.start(0), nf);
\r
420 // Write node info here
\r
421 c.setName(nodename);
\r
422 // Trees without distances still need a render distance
\r
423 c.dist = (HasDistances) ? distance : DefDistance;
\r
424 // be consistent for internal bootstrap defaults too
\r
425 c.setBootstrap((HasBootstrap) ? bootstrap : DefBootstrap);
\r
426 if (c == realroot) {
\r
427 RootHasDistance = nodehasdistance; // JBPNote This is really
\r
428 // UGLY!!! Ensure root node gets
\r
429 // its given distance
\r
432 // Find a place to put the leaf
\r
433 SequenceNode newnode = new SequenceNode(null, c, nodename,
\r
434 (HasDistances) ? distance : DefDistance,
\r
435 (HasBootstrap) ? bootstrap : DefBootstrap, false);
\r
437 if (c.right() == null) {
\r
438 c.setRight(newnode);
\r
440 if (c.left() == null) {
\r
441 c.setLeft(newnode);
\r
443 // Insert a dummy node for polytomy
\r
444 // dummy nodes have distances
\r
445 SequenceNode newdummy = new SequenceNode(null, c, null,
\r
446 (HasDistances ? 0 : DefDistance), 0, true);
\r
447 newdummy.SetChildren(c.left(), newnode);
\r
448 c.setLeft(newdummy);
\r
454 // move back up the tree from preceding closure
\r
455 c = c.AscendTree();
\r
457 if ((d > -1) && (c == null)) {
\r
458 Error = ErrorStringrange(
\r
460 "File broke algorithm: Lost place in tree (is there an extra ')' ?)",
\r
465 if (nf.charAt(fcp) == ')') {
\r
469 if (nf.charAt(fcp) == ',') {
\r
473 // Just advance focus, if we need to
\r
474 if ((c.left() != null) && (!c.left().isLeaf())) {
\r
475 c = (SequenceNode) c.left();
\r
481 // Reset new node properties to obvious fakes
\r
483 distance = DefDistance;
\r
484 bootstrap = DefBootstrap;
\r
486 // Advance character pointers if necessary
\r
488 ncp = cp = fcp + 1;
\r
495 if (Error != null) {
\r
496 throw (new IOException("NewickFile: " + Error + "\n"));
\r
499 root = (SequenceNode) root.right().detach(); // remove the imaginary root.
\r
501 if (!RootHasDistance) {
\r
502 root.dist = (HasDistances) ? 0 : DefDistance;
\r
509 * @return DOCUMENT ME!
\r
511 public SequenceNode getTree() {
\r
515 public uk.ac.vamsas.objects.core.Treenode[] matchTreeNodeNames(
\r
516 String[] names, Vobject[] boundObjects) {
\r
518 // also - need to reconstruct a names object id mapping (or BInaryNode) mapping for the parsed tree file
\r
523 * Generate a newick format tree according to internal flags for bootstraps,
\r
524 * distances and root distances.
\r
526 * @return new hampshire tree in a single line
\r
528 public String print() {
\r
529 synchronized (this) {
\r
530 StringBuffer tf = new StringBuffer();
\r
533 return (tf.append(";").toString());
\r
540 * Generate a newick format tree according to internal flags for distances and
\r
541 * root distances and user specificied writing of bootstraps.
\r
543 * @param withbootstraps
\r
544 * controls if bootstrap values are explicitly written.
\r
546 * @return new hampshire tree in a single line
\r
548 public String print(boolean withbootstraps) {
\r
549 synchronized (this) {
\r
550 boolean boots = this.HasBootstrap;
\r
551 this.HasBootstrap = withbootstraps;
\r
553 String rv = print();
\r
554 this.HasBootstrap = boots;
\r
562 * Generate newick format tree according to internal flags for writing root
\r
565 * @param withbootstraps
\r
566 * explicitly write bootstrap values
\r
568 * explicitly write distances
\r
570 * @return new hampshire tree in a single line
\r
572 public String print(boolean withbootstraps, boolean withdists) {
\r
573 synchronized (this) {
\r
574 boolean dists = this.HasDistances;
\r
575 this.HasDistances = withdists;
\r
577 String rv = print(withbootstraps);
\r
578 this.HasDistances = dists;
\r
585 * Generate newick format tree according to user specified flags
\r
587 * @param withbootstraps
\r
588 * explicitly write bootstrap values
\r
590 * explicitly write distances
\r
591 * @param printRootInfo
\r
592 * explicitly write root distance
\r
594 * @return new hampshire tree in a single line
\r
596 public String print(boolean withbootstraps, boolean withdists,
\r
597 boolean printRootInfo) {
\r
598 synchronized (this) {
\r
599 boolean rootinfo = printRootInfo;
\r
600 this.printRootInfo = printRootInfo;
\r
602 String rv = print(withbootstraps, withdists);
\r
603 this.printRootInfo = rootinfo;
\r
612 * @return DOCUMENT ME!
\r
614 char getQuoteChar() {
\r
624 * @return DOCUMENT ME!
\r
626 char setQuoteChar(char c) {
\r
627 char old = QuoteChar;
\r
639 * @return DOCUMENT ME!
\r
641 private String nodeName(String name) {
\r
642 if (NodeSafeName[0].matcher(name).find()) {
\r
643 return QuoteChar + NodeSafeName[1].matcher(name).replaceAll("''")
\r
644 + QuoteChar; // quite
\r
646 return NodeSafeName[2].matcher(name).replaceAll("_"); // whitespace
\r
656 * @return DOCUMENT ME!
\r
658 private String printNodeField(SequenceNode c) {
\r
659 return //c.getNewickNodeName()
\r
660 ((c.getName() == null) ? "" : nodeName(c.getName()))
\r
661 + ((HasBootstrap) ? ((c.getBootstrap() > -1) ? (((c.getName()==null) ? " " : "") + c.getBootstrap())
\r
662 : "") : "") + ((HasDistances) ? (":" + c.dist) : "");
\r
671 * @return DOCUMENT ME!
\r
673 private String printRootField(SequenceNode root) {
\r
674 return (printRootInfo) ? (((root.getName() == null) ? "" : nodeName(root
\r
676 + ((HasBootstrap) ? ((root.getBootstrap() > -1) ? ((root.getName()!=null ? " " : "") + root
\r
677 .getBootstrap()) : "") : "") + ((RootHasDistance) ? (":" + root.dist)
\r
682 // Non recursive call deals with root node properties
\r
683 public void print(StringBuffer tf, SequenceNode root) {
\r
684 if (root != null) {
\r
685 if (root.isLeaf() && printRootInfo) {
\r
686 tf.append(printRootField(root));
\r
688 if (root.isDummy()) {
\r
689 _print(tf, (SequenceNode) root.right());
\r
690 _print(tf, (SequenceNode) root.left());
\r
693 _print(tf, (SequenceNode) root.right());
\r
695 if (root.left() != null) {
\r
699 _print(tf, (SequenceNode) root.left());
\r
700 tf.append(")" + printRootField(root));
\r
706 // Recursive call for non-root nodes
\r
707 public void _print(StringBuffer tf, SequenceNode c) {
\r
710 tf.append(printNodeField(c));
\r
713 _print(tf, (SequenceNode) c.left());
\r
714 if (c.left() != null) {
\r
717 _print(tf, (SequenceNode) c.right());
\r
720 _print(tf, (SequenceNode) c.right());
\r
722 if (c.left() != null) {
\r
726 _print(tf, (SequenceNode) c.left());
\r
727 tf.append(")" + printNodeField(c));
\r
734 public static void main(String[] args) {
\r
736 if (args == null || args.length != 1) {
\r
738 .println("Takes one argument - file name of a newick tree file.");
\r
742 File fn = new File(args[0]);
\r
744 StringBuffer newickfile = new StringBuffer();
\r
745 BufferedReader treefile = new BufferedReader(new FileReader(fn));
\r
748 while ((l = treefile.readLine()) != null) {
\r
749 newickfile.append(l);
\r
753 System.out.println("Read file :\n");
\r
754 NewickFile trf = new NewickFile(fn);
\r
756 System.out.println("Original file :\n");
\r
758 System.out.println(Pattern.compile("\n+").matcher(newickfile.toString())
\r
762 System.out.println("Parsed file.\n");
\r
763 System.out.println("Default output type for original input.\n");
\r
764 System.out.println(trf.print());
\r
765 System.out.println("Without bootstraps.\n");
\r
766 System.out.println(trf.print(false));
\r
767 System.out.println("Without distances.\n");
\r
768 System.out.println(trf.print(true, false));
\r
769 System.out.println("Without bootstraps but with distanecs.\n");
\r
770 System.out.println(trf.print(false, true));
\r
771 System.out.println("Without bootstraps or distanecs.\n");
\r
772 System.out.println(trf.print(false, false));
\r
773 System.out.println("With bootstraps and with distances.\n");
\r
774 System.out.println(trf.print(true, true));
\r
775 System.out.println("leaves.\n");
\r
776 Vector lvs = new Vector();
\r
777 trf.findLeaves(trf.root, lvs);
\r
778 Enumeration lv = lvs.elements();
\r
779 while (lv.hasMoreElements()) {
\r
780 BinaryNode leave = (BinaryNode) lv.nextElement();
\r
781 if (leave.getName() != null) {
\r
782 System.out.println("Node:'" + leave.getName() + "'");
\r
785 } catch (java.io.IOException e) {
\r
786 System.err.println("Exception\n" + e);
\r
787 e.printStackTrace();
\r
792 * Search for leaf nodes.
\r
794 * @param node root node to search from
\r
795 * @param leaves Vector of leaves to add leaf node objects too.
\r
797 * @return Vector of leaf nodes on binary tree
\r
799 public Vector findLeaves(SequenceNode node, Vector leaves) {
\r
800 if (node == null) {
\r
804 if ((node.left() == null) && (node.right() == null)) // Interior node detection
\r
806 leaves.addElement(node);
\r
810 /* TODO: Identify internal nodes... if (node.isSequenceLabel())
\r
812 leaves.addElement(node);
\r
814 findLeaves((SequenceNode) node.left(), leaves);
\r
815 findLeaves((SequenceNode) node.right(), leaves);
\r
822 * make tree node vector from a newick tree structure with associated vamsas objects
\r
824 * @return Treenode definitions for nodes with associated objects
\r
826 public Treenode[] makeTreeNodes() {
\r
827 return makeTreeNodes(true);
\r
831 * make treenode vector for a parsed tree with/out leaf node associations
\r
832 * @param ignoreplaceholders if true means only associated nodes are returned
\r
833 * @return treenode vector for associated or all leaves
\r
835 public Treenode[] makeTreeNodes(boolean ignoreplaceholders) {
\r
836 Vector leaves = new Vector();
\r
837 findLeaves(root, leaves);
\r
838 Vector tnv = new Vector();
\r
839 Enumeration l = leaves.elements();
\r
840 Hashtable nodespecs = new Hashtable();
\r
841 while (l.hasMoreElements()) {
\r
842 BinaryNode tnode = (BinaryNode) l.nextElement();
\r
843 if (tnode instanceof SequenceNode) {
\r
844 if (!(ignoreplaceholders && ((SequenceNode) tnode).isPlaceholder())) {
\r
845 Object assocseq = ((SequenceNode) tnode).element();
\r
846 if (assocseq instanceof Vobject) {
\r
847 Vobject vobj = (Vobject) assocseq;
\r
848 if (vobj != null) {
\r
849 Treenode node = new Treenode();
\r
850 node.setNodespec(makeNodeSpec(nodespecs, tnode));
\r
851 node.setName(tnode.getName());
\r
852 Vref vr = new Vref();
\r
855 tnv.addElement(node);
\r
857 System.err.println("WARNING: Unassociated treeNode "
\r
858 + tnode.element().toString()
\r
860 + ((tnode.getName() != null) ? " label " + tnode.getName()
\r
867 if (tnv.size() > 0) {
\r
868 Treenode[] tn = new Treenode[tnv.size()];
\r
872 return new Treenode[] {};
\r
875 private String makeNodeSpec(Hashtable nodespecs, BinaryNode tnode) {
\r
876 String nname = new String(tnode.getName());
\r
877 Integer nindx = (Integer) nodespecs.get(nname);
\r
878 if (nindx == null) {
\r
879 nindx = new Integer(1);
\r
881 nname = nindx.toString() + " " + nname;
\r
886 * call to match up Treenode specs to NJTree parsed from document object.
\r
890 * as returned from NJTree.findLeaves( .., ..) ..
\r
893 private BinaryNode findNodeSpec(String nodespec, Vector leaves) {
\r
894 int occurence = -1;
\r
895 String nspec = nodespec.substring(nodespec.indexOf(' ') + 1);
\r
896 String oval = nodespec.substring(0, nodespec.indexOf(' '));
\r
898 occurence = new Integer(oval).intValue();
\r
899 } catch (Exception e) {
\r
900 System.err.println("Invalid nodespec '" + nodespec + "'");
\r
903 BinaryNode bn = null;
\r
906 Enumeration en = leaves.elements();
\r
907 while (en.hasMoreElements() && nocc < occurence) {
\r
908 bn = (BinaryNode) en.nextElement();
\r
909 if (bn instanceof SequenceNode && bn.getName().equals(nspec)) {
\r
919 * re-decorate the newick node representation with the VorbaId of an object mapped by its corresponding TreeNode.
\r
920 * Note: this only takes the first object in the first set of references.
\r
922 * @return vector of mappings { treenode, SequenceNode, Vobject for VorbaId on sequence node }
\r
924 public Vector attachTreeMap(Treenode[] tn) {
\r
925 if (root != null || tn == null)
\r
927 Vector leaves = new Vector();
\r
928 Vector nodemap = new Vector();
\r
929 findLeaves(root, leaves);
\r
930 int sz = tn.length;
\r
934 Treenode node = tn[i++];
\r
935 BinaryNode mappednode = findNodeSpec(node.getNodespec(), leaves);
\r
936 if (mappednode != null && mappednode instanceof SequenceNode) {
\r
937 SequenceNode leaf = (SequenceNode) leaves.elementAt(i++);
\r
938 // check if we can make the specified association
\r
939 Vobject noderef = null;
\r
940 int vrf = 0, refv = 0;
\r
941 while (noderef == null && vrf < node.getVrefCount()) {
\r
942 if (refv < node.getVref(vrf).getRefsCount()) {
\r
943 Object ref = node.getVref(vrf).getRefs(refv++);
\r
944 if (ref instanceof Vobject) {
\r
945 noderef = (Vobject) ref;
\r
952 if (noderef != null) {
\r
953 nodemap.addElement(new Object[] { node, leaf, noderef });
\r
954 leaf.setElement(noderef);
\r
955 leaf.setPlaceholder(false);
\r
957 leaf.setPlaceholder(true);
\r