prototype of Newick string parsing code and node binding utilities. Will be updated...
authorjprocter <jprocter@compbio.dundee.ac.uk>
Thu, 28 Jun 2007 14:40:49 +0000 (14:40 +0000)
committerjprocter <jprocter@compbio.dundee.ac.uk>
Thu, 28 Jun 2007 14:40:49 +0000 (14:40 +0000)
git-svn-id: https://svn.lifesci.dundee.ac.uk/svn/repository/trunk@416 be28352e-c001-0410-b1a7-c7978e42abec

src/uk/ac/vamsas/objects/utils/NewickFile.java [new file with mode: 0644]

diff --git a/src/uk/ac/vamsas/objects/utils/NewickFile.java b/src/uk/ac/vamsas/objects/utils/NewickFile.java
new file mode 100644 (file)
index 0000000..058bca9
--- /dev/null
@@ -0,0 +1,1134 @@
+/*\r
+ * Jalview - A Sequence Alignment Editor and Viewer\r
+ * Copyright (C) 2007 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle\r
+ *\r
+ * This program is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License\r
+ * as published by the Free Software Foundation; either version 2\r
+ * of the License, or (at your option) any later version.\r
+ *\r
+ * This program is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+ * GNU General Public License for more details.\r
+ *\r
+ * You should have received a copy of the GNU General Public License\r
+ * along with this program; if not, write to the Free Software\r
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\r
+ */\r
+\r
+// NewickFile.java\r
+// Tree I/O\r
+// http://evolution.genetics.washington.edu/phylip/newick_doc.html\r
+// TODO: Implement Basic NHX tag parsing and preservation\r
+// TODO: http://evolution.genetics.wustl.edu/eddy/forester/NHX.html\r
+// TODO: Extended SequenceNodeI to hold parsed NHX strings\r
+package uk.ac.vamsas.objects.utils;\r
+\r
+import java.io.*;\r
+import java.util.regex.Matcher;\r
+import java.util.regex.Pattern;\r
+\r
+import uk.ac.vamsas.client.Vobject;\r
+import uk.ac.vamsas.client.VorbaId;\r
+import uk.ac.vamsas.objects.core.SequenceType;\r
+\r
+/**\r
+ * DOCUMENT ME!\r
+ * \r
+ * @author $author$\r
+ * @version $Revision: 1.12 $\r
+ */\r
+public class NewickFile {\r
+  public class BinaryNode {\r
+    VorbaId element;\r
+\r
+    String name;\r
+\r
+    BinaryNode left;\r
+\r
+    BinaryNode right;\r
+\r
+    BinaryNode parent;\r
+\r
+    /** DOCUMENT ME!! */\r
+    public int bootstrap;\r
+\r
+    /**\r
+     * Creates a new BinaryNode object.\r
+     */\r
+    public BinaryNode() {\r
+      left = right = parent = null;\r
+      bootstrap = 0;\r
+    }\r
+\r
+    /**\r
+     * Creates a new BinaryNode object.\r
+     * \r
+     * @param element\r
+     *          DOCUMENT ME!\r
+     * @param parent\r
+     *          DOCUMENT ME!\r
+     * @param name\r
+     *          DOCUMENT ME!\r
+     */\r
+    public BinaryNode(VorbaId element, BinaryNode parent, String name) {\r
+      this.element = element;\r
+      this.parent = parent;\r
+      this.name = name;\r
+\r
+      left = right = null;\r
+    }\r
+\r
+    /**\r
+     * DOCUMENT ME!\r
+     * \r
+     * @return DOCUMENT ME!\r
+     */\r
+    public VorbaId element() {\r
+      return element;\r
+    }\r
+\r
+    /**\r
+     * DOCUMENT ME!\r
+     * \r
+     * @param v\r
+     *          DOCUMENT ME!\r
+     * \r
+     * @return DOCUMENT ME!\r
+     */\r
+    public VorbaId setElement(VorbaId v) {\r
+      return element = v;\r
+    }\r
+\r
+    /**\r
+     * DOCUMENT ME!\r
+     * \r
+     * @return DOCUMENT ME!\r
+     */\r
+    public BinaryNode left() {\r
+      return left;\r
+    }\r
+\r
+    /**\r
+     * DOCUMENT ME!\r
+     * \r
+     * @param n\r
+     *          DOCUMENT ME!\r
+     * \r
+     * @return DOCUMENT ME!\r
+     */\r
+    public BinaryNode setLeft(BinaryNode n) {\r
+      return left = n;\r
+    }\r
+\r
+    /**\r
+     * DOCUMENT ME!\r
+     * \r
+     * @return DOCUMENT ME!\r
+     */\r
+    public BinaryNode right() {\r
+      return right;\r
+    }\r
+\r
+    /**\r
+     * DOCUMENT ME!\r
+     * \r
+     * @param n\r
+     *          DOCUMENT ME!\r
+     * \r
+     * @return DOCUMENT ME!\r
+     */\r
+    public BinaryNode setRight(BinaryNode n) {\r
+      return right = n;\r
+    }\r
+\r
+    /**\r
+     * DOCUMENT ME!\r
+     * \r
+     * @return DOCUMENT ME!\r
+     */\r
+    public BinaryNode parent() {\r
+      return parent;\r
+    }\r
+\r
+    /**\r
+     * DOCUMENT ME!\r
+     * \r
+     * @param n\r
+     *          DOCUMENT ME!\r
+     * \r
+     * @return DOCUMENT ME!\r
+     */\r
+    public BinaryNode setParent(BinaryNode n) {\r
+      return parent = n;\r
+    }\r
+\r
+    /**\r
+     * DOCUMENT ME!\r
+     * \r
+     * @return DOCUMENT ME!\r
+     */\r
+    public boolean isLeaf() {\r
+      return (left == null) && (right == null);\r
+    }\r
+\r
+    /**\r
+     * attaches FIRST and SECOND node arguments as the LEFT and RIGHT children\r
+     * of this node (removing any old references) a null parameter DOES NOT mean\r
+     * that the pointer to the corresponding child node is set to NULL - you\r
+     * should use setChild(null), or detach() for this.\r
+     * \r
+     */\r
+    public void SetChildren(BinaryNode leftchild, BinaryNode rightchild) {\r
+      if (leftchild != null) {\r
+        this.setLeft(leftchild);\r
+        leftchild.detach();\r
+        leftchild.setParent(this);\r
+      }\r
+\r
+      if (rightchild != null) {\r
+        this.setRight(rightchild);\r
+        rightchild.detach();\r
+        rightchild.setParent(this);\r
+      }\r
+    }\r
+\r
+    /**\r
+     * Detaches the node from the binary tree, along with all its child nodes.\r
+     * \r
+     * @return BinaryNode The detached node.\r
+     */\r
+    public BinaryNode detach() {\r
+      if (this.parent != null) {\r
+        if (this.parent.left == this) {\r
+          this.parent.left = null;\r
+        } else {\r
+          if (this.parent.right == this) {\r
+            this.parent.right = null;\r
+          }\r
+        }\r
+      }\r
+\r
+      this.parent = null;\r
+\r
+      return this;\r
+    }\r
+\r
+    /**\r
+     * Traverses up through the tree until a node with a free leftchild is\r
+     * discovered.\r
+     * \r
+     * @return BinaryNode\r
+     */\r
+    public BinaryNode ascendLeft() {\r
+      BinaryNode c = this;\r
+\r
+      do {\r
+        c = c.parent();\r
+      } while ((c != null) && (c.left() != null) && !c.left().isLeaf());\r
+\r
+      return c;\r
+    }\r
+\r
+    /**\r
+     * Traverses up through the tree until a node with a free rightchild is\r
+     * discovered. Jalview builds trees by descent on the left, so this may be\r
+     * unused.\r
+     * \r
+     * @return BinaryNode\r
+     */\r
+    public BinaryNode ascendRight() {\r
+      BinaryNode c = this;\r
+\r
+      do {\r
+        c = c.parent();\r
+      } while ((c != null) && (c.right() != null) && !c.right().isLeaf());\r
+\r
+      return c;\r
+    }\r
+\r
+    /**\r
+     * DOCUMENT ME!\r
+     * \r
+     * @param name\r
+     *          DOCUMENT ME!\r
+     */\r
+    public void setName(String name) {\r
+      this.name = name;\r
+    }\r
+\r
+    /**\r
+     * DOCUMENT ME!\r
+     * \r
+     * @return DOCUMENT ME!\r
+     */\r
+    public String getName() {\r
+      return this.name;\r
+    }\r
+\r
+    /**\r
+     * DOCUMENT ME!\r
+     * \r
+     * @param boot\r
+     *          DOCUMENT ME!\r
+     */\r
+    public void setBootstrap(int boot) {\r
+      this.bootstrap = boot;\r
+    }\r
+\r
+    /**\r
+     * DOCUMENT ME!\r
+     * \r
+     * @return DOCUMENT ME!\r
+     */\r
+    public int getBootstrap() {\r
+      return bootstrap;\r
+    }\r
+    /**\r
+     * \r
+     * @return unquoted string of form VorbaId|v|node name string\r
+     */\r
+    public String getNewickNodeName() {\r
+      if (element!=null || name!=null)\r
+        {\r
+        return ((element!=null) ? element.getId()+"|v|" : "")\r
+          + ((name == null) ? "" : name);\r
+        }\r
+      return "";\r
+    }\r
+    public boolean parseNodeNameString(uk.ac.vamsas.client.ClientDocument binder)\r
+    {\r
+      \r
+      if (element==null && name!=null && name.indexOf("|v|")>-1)\r
+      {\r
+        element = binder.getObject(null).getVorbaId(); // TODO: fix THIS ! name.substring(0, name.indexOf("|v")));\r
+        \r
+      }\r
+      return false;\r
+    }\r
+  }\r
+\r
+  public class SequenceNode extends BinaryNode {\r
+    /** DOCUMENT ME!! */\r
+    public float dist;\r
+\r
+    /** DOCUMENT ME!! */\r
+    public boolean dummy = false;\r
+\r
+    private boolean placeholder = false;\r
+\r
+    /**\r
+     * Creates a new SequenceNode object.\r
+     */\r
+    public SequenceNode() {\r
+      super();\r
+    }\r
+\r
+    /**\r
+     * Creates a new SequenceNode object.\r
+     * \r
+     * @param val\r
+     *          DOCUMENT ME!\r
+     * @param parent\r
+     *          DOCUMENT ME!\r
+     * @param dist\r
+     *          DOCUMENT ME!\r
+     * @param name\r
+     *          DOCUMENT ME!\r
+     */\r
+    public SequenceNode(VorbaId val, SequenceNode parent, float dist, String name) {\r
+      super(val, parent, name);\r
+      this.dist = dist;\r
+    }\r
+    public SequenceNode(Vobject val, SequenceNode parent, float dist, String name) {\r
+      super(val.getVorbaId(), parent, name);\r
+      this.dist = dist;\r
+    }\r
+\r
+    /**\r
+     * Creates a new SequenceNode object.\r
+     * \r
+     * @param val\r
+     *          DOCUMENT ME!\r
+     * @param parent\r
+     *          DOCUMENT ME!\r
+     * @param name\r
+     *          DOCUMENT ME!\r
+     * @param dist\r
+     *          DOCUMENT ME!\r
+     * @param bootstrap\r
+     *          DOCUMENT ME!\r
+     * @param dummy\r
+     *          DOCUMENT ME!\r
+     */\r
+    public SequenceNode(Vobject val, SequenceNode parent, String name,\r
+        float dist, int bootstrap, boolean dummy) {\r
+      super(val.getVorbaId(), parent, name);\r
+      this.dist = dist;\r
+      this.bootstrap = bootstrap;\r
+      this.dummy = dummy;\r
+    }\r
+\r
+    /**\r
+     * @param dummy\r
+     *          true if node is created for the representation of polytomous\r
+     *          trees\r
+     */\r
+    public boolean isDummy() {\r
+      return dummy;\r
+    }\r
+\r
+    /*\r
+     * @param placeholder is true if the sequence refered to in the element node\r
+     * is not actually present in the associated alignment\r
+     */\r
+    public boolean isPlaceholder() {\r
+      return placeholder;\r
+    }\r
+\r
+    /**\r
+     * DOCUMENT ME!\r
+     * \r
+     * @param newstate\r
+     *          DOCUMENT ME!\r
+     * \r
+     * @return DOCUMENT ME!\r
+     */\r
+    public boolean setDummy(boolean newstate) {\r
+      boolean oldstate = dummy;\r
+      dummy = newstate;\r
+\r
+      return oldstate;\r
+    }\r
+\r
+    /**\r
+     * DOCUMENT ME!\r
+     * \r
+     * @param Placeholder\r
+     *          DOCUMENT ME!\r
+     */\r
+    public void setPlaceholder(boolean Placeholder) {\r
+      this.placeholder = Placeholder;\r
+    }\r
+\r
+    /**\r
+     * ascends the tree but doesn't stop until a non-dummy node is discovered.\r
+     * This will probably break if the tree is a mixture of BinaryNodes and\r
+     * SequenceNodes.\r
+     */\r
+    public SequenceNode AscendTree() {\r
+      SequenceNode c = this;\r
+\r
+      do {\r
+        c = (SequenceNode) c.parent();\r
+      } while ((c != null) && c.dummy);\r
+\r
+      return c;\r
+    }\r
+\r
+  }\r
+\r
+  SequenceNode root;\r
+\r
+  private boolean HasBootstrap = false;\r
+\r
+  private boolean HasDistances = false;\r
+\r
+  private boolean RootHasDistance = false;\r
+\r
+  // File IO Flags\r
+  boolean ReplaceUnderscores = false;\r
+\r
+  boolean printRootInfo = false;\r
+\r
+  private Pattern[] NodeSafeName = new Pattern[] {\r
+      Pattern.compile("[\\[,:'()]"), // test for requiring quotes\r
+      Pattern.compile("'"), // escaping quote characters\r
+      Pattern.compile("/w") // unqoted whitespace transformation\r
+  };\r
+\r
+  char QuoteChar = '\'';\r
+\r
+  String newickFile = null;\r
+\r
+  /**\r
+   * Creates a new NewickFile object.\r
+   * \r
+   * @param inStr\r
+   *          DOCUMENT ME!\r
+   * \r
+   * @throws IOException\r
+   *           DOCUMENT ME!\r
+   */\r
+  public NewickFile(String inStr) throws IOException {\r
+    newickFile = inStr;\r
+    parse();\r
+  }\r
+  public NewickFile(File inFile) throws IOException {\r
+    errormessage = "Problem's reading file "+inFile;\r
+    dataIn = new java.io.BufferedReader(new InputStreamReader(new java.io.FileInputStream(inFile)));\r
+    parse();\r
+  }\r
+  /**\r
+   * Creates a new NewickFile object.\r
+   * \r
+   * @param newtree\r
+   *          DOCUMENT ME!\r
+   */\r
+  public NewickFile(SequenceNode newtree) {\r
+    root = newtree;\r
+  }\r
+\r
+  /**\r
+   * Creates a new NewickFile object.\r
+   * \r
+   * @param newtree\r
+   *          DOCUMENT ME!\r
+   * @param bootstrap\r
+   *          DOCUMENT ME!\r
+   */\r
+  public NewickFile(SequenceNode newtree, boolean bootstrap) {\r
+    HasBootstrap = bootstrap;\r
+    root = newtree;\r
+  }\r
+\r
+  /**\r
+   * Creates a new NewickFile object.\r
+   * \r
+   * @param newtree\r
+   *          DOCUMENT ME!\r
+   * @param bootstrap\r
+   *          DOCUMENT ME!\r
+   * @param distances\r
+   *          DOCUMENT ME!\r
+   */\r
+  public NewickFile(SequenceNode newtree, boolean bootstrap, boolean distances) {\r
+    root = newtree;\r
+    HasBootstrap = bootstrap;\r
+    HasDistances = distances;\r
+  }\r
+\r
+  /**\r
+   * Creates a new NewickFile object.\r
+   * \r
+   * @param newtree\r
+   *          DOCUMENT ME!\r
+   * @param bootstrap\r
+   *          DOCUMENT ME!\r
+   * @param distances\r
+   *          DOCUMENT ME!\r
+   * @param rootdistance\r
+   *          DOCUMENT ME!\r
+   */\r
+  public NewickFile(SequenceNode newtree, boolean bootstrap, boolean distances,\r
+      boolean rootdistance) {\r
+    root = newtree;\r
+    HasBootstrap = bootstrap;\r
+    HasDistances = distances;\r
+    RootHasDistance = rootdistance;\r
+  }\r
+\r
+  /**\r
+   * DOCUMENT ME!\r
+   * \r
+   * @param Error\r
+   *          Message prefix\r
+   * @param Er\r
+   *          Message qualifier (ie the incorrect data)\r
+   * @param r\r
+   *          range of characters either side to dump\r
+   * @param p\r
+   *          character position\r
+   * @param s\r
+   *          newick string being parsed\r
+   * \r
+   * @return composed error message\r
+   */\r
+  private String ErrorStringrange(String Error, String Er, int r, int p,\r
+      String s) {\r
+    return ((Error == null) ? "" : Error)\r
+        + Er\r
+        + " at position "\r
+        + p\r
+        + " ( "\r
+        + s.substring(((p - r) < 0) ? 0 : (p - r), ((p + r) > s.length()) ? s\r
+            .length() : (p + r)) + " )\n";\r
+  }\r
+\r
+  /**\r
+   * \r
+   * @return true if tree has bootstrap values\r
+   */\r
+  public boolean HasBootstrap() {\r
+    return HasBootstrap;\r
+  }\r
+\r
+  /**\r
+   * \r
+   * @return true if tree has distances on branches\r
+   */\r
+  public boolean HasDistances() {\r
+    return HasDistances;\r
+  }\r
+\r
+  /**\r
+   * \r
+   * @return true if root has a stem distance\r
+   */\r
+  public boolean HasRootDistance() {\r
+    return RootHasDistance;\r
+  }\r
+  /*\r
+   * hacked out of jalview code\r
+   */\r
+  boolean error;\r
+  String errormessage;\r
+  java.io.BufferedReader dataIn=null;\r
+  public String nextLine() throws IOException {\r
+    if (dataIn==null)\r
+    {\r
+      dataIn = new BufferedReader(new StringReader(newickFile));\r
+      error=false;\r
+    }\r
+    else\r
+      throw new IOException("IMPLEMENTATION ERROR: NewickFile has not been initialised for reading a newick string.");\r
+    if (!error)\r
+      return dataIn.readLine();\r
+    throw new IOException("Invalid Source Stream:" + errormessage);\r
+  }\r
+  /**\r
+   * call this to convert the newick string into a binary node linked tree\r
+   * \r
+   * @throws IOException\r
+   *           if the newick string cannot be parsed.\r
+   */\r
+  public void parse() throws IOException {\r
+    String nf;\r
+    if (newickFile==null)\r
+    {\r
+      // fill nf with complete tree file\r
+\r
+      StringBuffer file = new StringBuffer();\r
+\r
+      while ((nf = nextLine()) != null) {\r
+        file.append(nf);\r
+      }\r
+\r
+      nf = file.toString();\r
+    } else\r
+    {\r
+      nf = newickFile;\r
+    }\r
+    \r
+\r
+    root = new SequenceNode();\r
+\r
+    SequenceNode realroot = null;\r
+    SequenceNode c = root;\r
+\r
+    int d = -1;\r
+    int cp = 0;\r
+    // int flen = nf.length();\r
+\r
+    String Error = null;\r
+    String nodename = null;\r
+\r
+    float DefDistance = (float) 0.001; // @param Default distance for a node -\r
+                                        // very very small\r
+    int DefBootstrap = 0; // @param Default bootstrap for a node\r
+\r
+    float distance = DefDistance;\r
+    int bootstrap = DefBootstrap;\r
+\r
+    boolean ascending = false; // flag indicating that we are leaving the\r
+                                // current node\r
+\r
+    Pattern majorsyms = Pattern.compile(\r
+        "[(\\['),;]");\r
+\r
+    Matcher mjsyms = majorsyms.matcher(nf);\r
+    while (mjsyms.find(cp) && (Error == null)) {\r
+      int fcp = mjsyms.start();\r
+\r
+      switch (nf.charAt(fcp)) {\r
+      case '[': // Comment or structured/extended NH format info\r
+\r
+\r
+        if (nf.indexOf(']',fcp)>-1) {\r
+          // Skip the comment field\r
+          cp = nf.indexOf(']',fcp);\r
+        } else {\r
+          Error = ErrorStringrange(Error, "Unterminated comment", 3, fcp, nf);\r
+        }\r
+\r
+        ;\r
+\r
+        break;\r
+\r
+      case '(':\r
+\r
+        // ascending should not be set\r
+        // New Internal node\r
+        if (ascending) {\r
+          Error = ErrorStringrange(Error, "Unexpected '('", 7, fcp, nf);\r
+\r
+          continue;\r
+        }\r
+\r
+        ;\r
+        d++;\r
+\r
+        if (c.right() == null) {\r
+          c.setRight(new SequenceNode(null, c, null, DefDistance, DefBootstrap,\r
+              false));\r
+          c = (SequenceNode) c.right();\r
+        } else {\r
+          if (c.left() != null) {\r
+            // Dummy node for polytomy - keeps c.left free for new node\r
+            SequenceNode tmpn = new SequenceNode(null, c, null, 0, 0, true);\r
+            tmpn.SetChildren(c.left(), c.right());\r
+            c.setRight(tmpn);\r
+          }\r
+\r
+          c.setLeft(new SequenceNode(null, c, null, DefDistance, DefBootstrap,\r
+              false));\r
+          c = (SequenceNode) c.left();\r
+        }\r
+\r
+        if (realroot == null) {\r
+          realroot = c;\r
+        }\r
+\r
+        nodename = null;\r
+        distance = DefDistance;\r
+        bootstrap = DefBootstrap;\r
+        cp = fcp + 1;\r
+\r
+        break;\r
+\r
+      // Deal with quoted fields\r
+      case '\'':\r
+\r
+        Matcher qnodename = Pattern.compile("([^']|'')+'").matcher(nf);\r
+        if (qnodename.find(fcp)) {\r
+          nodename = new String(qnodename.group(1));\r
+          cp = qnodename.end(0);\r
+        } else {\r
+          Error = ErrorStringrange(Error, "Unterminated quotes for nodename",\r
+              7, fcp, nf);\r
+        }\r
+\r
+        break;\r
+\r
+      case ';':\r
+\r
+        if (d != -1) {\r
+          Error = ErrorStringrange(Error,\r
+              "Wayward semicolon (depth=" + d + ")", 7, fcp, nf);\r
+        }\r
+\r
+        // cp advanced at the end of default\r
+      default:\r
+\r
+        // Parse simpler field strings\r
+        String fstring = nf.substring(cp, fcp);\r
+        Matcher uqnodename = Pattern.compile("\\b([^' :;\\](),]+)").matcher(fstring);\r
+        if (uqnodename.matches()\r
+            && ((uqnodename.start(1) == 0) || (fstring.charAt(uqnodename\r
+                .start(1) - 1) != ':'))) // JBPNote HACK!\r
+        {\r
+          if (nodename == null) {\r
+            if (ReplaceUnderscores) {\r
+              nodename = uqnodename.group(1).replace('_', ' ');\r
+            } else {\r
+              nodename = uqnodename.group(1);\r
+            }\r
+          } else {\r
+            Error = ErrorStringrange(Error,\r
+                "File has broken algorithm - overwritten nodename", 10, fcp, nf);\r
+          }\r
+        }\r
+        \r
+        Matcher nbootstrap = Pattern.compile("\\S+([0-9+]+)\\S*:").matcher(fstring);\r
+\r
+\r
+        if (nbootstrap.matches()\r
+            && (nbootstrap.start(1) > uqnodename.end(1))) {\r
+          try {\r
+            bootstrap = (new Integer(nbootstrap.group(1))).intValue();\r
+            HasBootstrap = true;\r
+          } catch (Exception e) {\r
+            Error = ErrorStringrange(Error, "Can't parse bootstrap value", 4,\r
+                cp + nbootstrap.start(0), nf);\r
+          }\r
+        }\r
+\r
+        Matcher ndist = Pattern.compile(\r
+        ":([-0-9Ee.+]+)").matcher(fstring);\r
+        boolean nodehasdistance = false;\r
+\r
+        if (ndist.matches()) {\r
+          try {\r
+            distance = (new Float(ndist.group(1))).floatValue();\r
+            HasDistances = true;\r
+            nodehasdistance = true;\r
+          } catch (Exception e) {\r
+            Error = ErrorStringrange(Error, "Can't parse node distance value",\r
+                7, cp + ndist.start(0), nf);\r
+          }\r
+        }\r
+\r
+        if (ascending) {\r
+          // Write node info here\r
+          c.setName(nodename);\r
+          // Trees without distances still need a render distance\r
+          c.dist = (HasDistances) ? distance : DefDistance;\r
+          // be consistent for internal bootstrap defaults too\r
+          c.setBootstrap((HasBootstrap) ? bootstrap : DefBootstrap);\r
+          if (c == realroot) {\r
+            RootHasDistance = nodehasdistance; // JBPNote This is really\r
+                                                // UGLY!!! Ensure root node gets\r
+                                                // its given distance\r
+          }\r
+        } else {\r
+          // Find a place to put the leaf\r
+          SequenceNode newnode = new SequenceNode(null, c, nodename,\r
+              (HasDistances) ? distance : DefDistance,\r
+              (HasBootstrap) ? bootstrap : DefBootstrap, false);\r
+\r
+          if (c.right() == null) {\r
+            c.setRight(newnode);\r
+          } else {\r
+            if (c.left() == null) {\r
+              c.setLeft(newnode);\r
+            } else {\r
+              // Insert a dummy node for polytomy\r
+              // dummy nodes have distances\r
+              SequenceNode newdummy = new SequenceNode(null, c, null,\r
+                  (HasDistances ? 0 : DefDistance), 0, true);\r
+              newdummy.SetChildren(c.left(), newnode);\r
+              c.setLeft(newdummy);\r
+            }\r
+          }\r
+        }\r
+\r
+        if (ascending) {\r
+          // move back up the tree from preceding closure\r
+          c = c.AscendTree();\r
+\r
+          if ((d > -1) && (c == null)) {\r
+            Error = ErrorStringrange(\r
+                Error,\r
+                "File broke algorithm: Lost place in tree (is there an extra ')' ?)",\r
+                7, fcp, nf);\r
+          }\r
+        }\r
+\r
+        if (nf.charAt(fcp) == ')') {\r
+          d--;\r
+          ascending = true;\r
+        } else {\r
+          if (nf.charAt(fcp) == ',') {\r
+            if (ascending) {\r
+              ascending = false;\r
+            } else {\r
+              // Just advance focus, if we need to\r
+              if ((c.left() != null) && (!c.left().isLeaf())) {\r
+                c = (SequenceNode) c.left();\r
+              }\r
+            }\r
+          }\r
+\r
+          // else : We do nothing if ';' is encountered.\r
+        }\r
+\r
+        // Reset new node properties to obvious fakes\r
+        nodename = null;\r
+        distance = DefDistance;\r
+        bootstrap = DefBootstrap;\r
+\r
+        cp = fcp + 1;\r
+      }\r
+    }\r
+\r
+    if (Error != null) {\r
+      throw (new IOException("NewickFile: " + Error + "\n"));\r
+    }\r
+\r
+    root = (SequenceNode) root.right().detach(); // remove the imaginary root.\r
+\r
+    if (!RootHasDistance) {\r
+      root.dist = (HasDistances) ? 0 : DefDistance;\r
+    }\r
+  }\r
+\r
+  /**\r
+   * DOCUMENT ME!\r
+   * \r
+   * @return DOCUMENT ME!\r
+   */\r
+  public SequenceNode getTree() {\r
+    return root;\r
+  }\r
+  public uk.ac.vamsas.objects.core.Treenode[] matchTreeNodeNames(String[] names, Vobject[] boundObjects)\r
+  {\r
+    // todo!\r
+    // also - need to reconstruct a names object id mapping (or BInaryNode) mapping for the parsed tree file\r
+    return null;\r
+  }\r
+  /**\r
+   * Generate a newick format tree according to internal flags for bootstraps,\r
+   * distances and root distances.\r
+   * \r
+   * @return new hampshire tree in a single line\r
+   */\r
+  public String print() {\r
+    synchronized (this) {\r
+      StringBuffer tf = new StringBuffer();\r
+      print(tf, root);\r
+\r
+      return (tf.append(";").toString());\r
+    }\r
+  }\r
+\r
+  /**\r
+   * \r
+   * \r
+   * Generate a newick format tree according to internal flags for distances and\r
+   * root distances and user specificied writing of bootstraps.\r
+   * \r
+   * @param withbootstraps\r
+   *          controls if bootstrap values are explicitly written.\r
+   * \r
+   * @return new hampshire tree in a single line\r
+   */\r
+  public String print(boolean withbootstraps) {\r
+    synchronized (this) {\r
+      boolean boots = this.HasBootstrap;\r
+      this.HasBootstrap = withbootstraps;\r
+\r
+      String rv = print();\r
+      this.HasBootstrap = boots;\r
+\r
+      return rv;\r
+    }\r
+  }\r
+\r
+  /**\r
+   * \r
+   * Generate newick format tree according to internal flags for writing root\r
+   * node distances.\r
+   * \r
+   * @param withbootstraps\r
+   *          explicitly write bootstrap values\r
+   * @param withdists\r
+   *          explicitly write distances\r
+   * \r
+   * @return new hampshire tree in a single line\r
+   */\r
+  public String print(boolean withbootstraps, boolean withdists) {\r
+    synchronized (this) {\r
+      boolean dists = this.HasDistances;\r
+      this.HasDistances = withdists;\r
+\r
+      String rv = print(withbootstraps);\r
+      this.HasDistances = dists;\r
+\r
+      return rv;\r
+    }\r
+  }\r
+\r
+  /**\r
+   * Generate newick format tree according to user specified flags\r
+   * \r
+   * @param withbootstraps\r
+   *          explicitly write bootstrap values\r
+   * @param withdists\r
+   *          explicitly write distances\r
+   * @param printRootInfo\r
+   *          explicitly write root distance\r
+   * \r
+   * @return new hampshire tree in a single line\r
+   */\r
+  public String print(boolean withbootstraps, boolean withdists,\r
+      boolean printRootInfo) {\r
+    synchronized (this) {\r
+      boolean rootinfo = printRootInfo;\r
+      this.printRootInfo = printRootInfo;\r
+\r
+      String rv = print(withbootstraps, withdists);\r
+      this.printRootInfo = rootinfo;\r
+\r
+      return rv;\r
+    }\r
+  }\r
+\r
+  /**\r
+   * DOCUMENT ME!\r
+   * \r
+   * @return DOCUMENT ME!\r
+   */\r
+  char getQuoteChar() {\r
+    return QuoteChar;\r
+  }\r
+\r
+  /**\r
+   * DOCUMENT ME!\r
+   * \r
+   * @param c\r
+   *          DOCUMENT ME!\r
+   * \r
+   * @return DOCUMENT ME!\r
+   */\r
+  char setQuoteChar(char c) {\r
+    char old = QuoteChar;\r
+    QuoteChar = c;\r
+\r
+    return old;\r
+  }\r
+\r
+  /**\r
+   * DOCUMENT ME!\r
+   * \r
+   * @param name\r
+   *          DOCUMENT ME!\r
+   * \r
+   * @return DOCUMENT ME!\r
+   */\r
+  private String nodeName(String name) {\r
+    if (NodeSafeName[0].matcher(name).find()) {\r
+      return QuoteChar + NodeSafeName[1].matcher(name).replaceAll("''") + QuoteChar; // quite \r
+    } else {\r
+      return NodeSafeName[2].matcher(name).replaceAll("_"); // whitespace\r
+    }\r
+  }\r
+\r
+  /**\r
+   * DOCUMENT ME!\r
+   * \r
+   * @param c\r
+   *          DOCUMENT ME!\r
+   * \r
+   * @return DOCUMENT ME!\r
+   */\r
+  private String printNodeField(SequenceNode c) {\r
+    return c.getNewickNodeName()\r
+        + ((HasBootstrap) ? ((c.getBootstrap() > -1) ? (" " + c.getBootstrap())\r
+            : "") : "") + ((HasDistances) ? (":" + c.dist) : "");\r
+  }\r
+\r
+  /**\r
+   * DOCUMENT ME!\r
+   * \r
+   * @param root\r
+   *          DOCUMENT ME!\r
+   * \r
+   * @return DOCUMENT ME!\r
+   */\r
+  private String printRootField(SequenceNode root) {\r
+    return (printRootInfo) ? (((root.getName() == null) ? "" : nodeName(root\r
+        .getName()))\r
+        + ((HasBootstrap) ? ((root.getBootstrap() > -1) ? (" " + root\r
+            .getBootstrap()) : "") : "") + ((RootHasDistance) ? (":" + root.dist)\r
+        : ""))\r
+        : "";\r
+  }\r
+\r
+  // Non recursive call deals with root node properties\r
+  public void print(StringBuffer tf, SequenceNode root) {\r
+    if (root != null) {\r
+      if (root.isLeaf() && printRootInfo) {\r
+        tf.append(printRootField(root));\r
+      } else {\r
+        if (root.isDummy()) {\r
+          _print(tf, (SequenceNode) root.right());\r
+          _print(tf, (SequenceNode) root.left());\r
+        } else {\r
+          tf.append("(");\r
+          _print(tf, (SequenceNode) root.right());\r
+\r
+          if (root.left() != null) {\r
+            tf.append(",");\r
+          }\r
+\r
+          _print(tf, (SequenceNode) root.left());\r
+          tf.append(")" + printRootField(root));\r
+        }\r
+      }\r
+    }\r
+  }\r
+\r
+  // Recursive call for non-root nodes\r
+  public void _print(StringBuffer tf, SequenceNode c) {\r
+    if (c != null) {\r
+      if (c.isLeaf()) {\r
+        tf.append(printNodeField(c));\r
+      } else {\r
+        if (c.isDummy()) {\r
+          _print(tf, (SequenceNode) c.left());\r
+          if (c.left() != null) {\r
+            tf.append(",");\r
+          }\r
+          _print(tf, (SequenceNode) c.right());\r
+        } else {\r
+          tf.append("(");\r
+          _print(tf, (SequenceNode) c.right());\r
+\r
+          if (c.left() != null) {\r
+            tf.append(",");\r
+          }\r
+\r
+          _print(tf, (SequenceNode) c.left());\r
+          tf.append(")" + printNodeField(c));\r
+        }\r
+      }\r
+    }\r
+  }\r
+\r
+  // Test\r
+  public static void main(String[] args) {\r
+    try {\r
+      if (args == null || args.length != 1) {\r
+        System.err\r
+            .println("Takes one argument - file name of a newick tree file.");\r
+        System.exit(0);\r
+      }\r
+\r
+      File fn = new File(args[0]);\r
+\r
+      StringBuffer newickfile = new StringBuffer();\r
+      BufferedReader treefile = new BufferedReader(new FileReader(fn));\r
+      String l;\r
+\r
+      while ((l = treefile.readLine()) != null) {\r
+        newickfile.append(l);\r
+      }\r
+\r
+      treefile.close();\r
+      System.out.println("Read file :\n");\r
+      NewickFile trf = new NewickFile(fn);\r
+      // trf.parse();\r
+      System.out.println("Original file :\n");\r
+\r
+      System.out.println(Pattern.compile("\n+").matcher(newickfile.toString()).replaceAll("") + "\n");\r
+\r
+      System.out.println("Parsed file.\n");\r
+      System.out.println("Default output type for original input.\n");\r
+      System.out.println(trf.print());\r
+      System.out.println("Without bootstraps.\n");\r
+      System.out.println(trf.print(false));\r
+      System.out.println("Without distances.\n");\r
+      System.out.println(trf.print(true, false));\r
+      System.out.println("Without bootstraps but with distanecs.\n");\r
+      System.out.println(trf.print(false, true));\r
+      System.out.println("Without bootstraps or distanecs.\n");\r
+      System.out.println(trf.print(false, false));\r
+      System.out.println("With bootstraps and with distances.\n");\r
+      System.out.println(trf.print(true, true));\r
+    } catch (java.io.IOException e) {\r
+      System.err.println("Exception\n" + e);\r
+      e.printStackTrace();\r
+    }\r
+  }\r
+}\r