public String render(ReportNode node) {
    if (node != null) {
      StringBuffer text = new StringBuffer();
      text.append("<r c=\"")
          .append(StringEscapeUtils.escapeXml(node.getClass().getSimpleName()))
          .append("\" ");

      String icon = node.getIcon();
      if (icon != null) {
        text.append("i=\"").append(StringEscapeUtils.escapeXml(icon)).append("\" ");
      }

      if (node.getDate() != null) {
        text.append("t=\"").append("" + node.getDate().getTime()).append("\" ");
      }

      text.append("l=\"").append(StringEscapeUtils.escapeXml(node.getLevel())).append("\" ");

      text.append("d=\"").append(node.getDebug()).append("\" ");

      text.append(">");
      if (node.getTitle() != null) {
        text.append("<t>").append(StringEscapeUtils.escapeXml(node.getTitle())).append("</t>");
      }

      if (node.getHint() != null) {
        text.append("<h>").append(StringEscapeUtils.escapeXml(node.getHint())).append("</h>");
      }

      if (node instanceof TextReportNode) {
        TextReportNode textNode = (TextReportNode) node;
        if (textNode.getDetails() != null) {
          text.append("<d>")
              .append(StringEscapeUtils.escapeXml(textNode.getDetails()))
              .append("</d>");
        }
      } else if (node instanceof ExceptionReportNode) {
        ExceptionReportNode exceptionNode = (ExceptionReportNode) node;
        text.append("<e>")
            .append(serializeExceptionInfo(exceptionNode.getException()))
            .append("</e>");
      } else if (node instanceof BranchReportNode) {
        BranchReportNode branch = (BranchReportNode) node;
        text.append("<c>");
        if (branch.getChildNodes() != null) {
          for (ReportNode rn : branch.getChildNodes()) {
            text.append(render(rn));
          }
        }
        text.append("</c>");
      }

      text.append("</r>");
      return text.toString();
    } else {
      throw new IllegalArgumentException("Main report node should not be null");
    }
  }
  /**
   * Updates the status of all report nodes
   *
   * @param node
   */
  public int updateReport(ReportNode node, int step, IdGenerator idGenerator) {
    node.setId(idGenerator.makeUniqueId());

    Map<String, Object> metaData = new HashMap<String, Object>();
    new HashMap<String, Object>();
    metaData.put("collapsed", true);

    metaData.put("nodeClassName", node.getClass().getSimpleName());
    if (node.hasLevel(ReportNode.ERROR)) {
      metaData.put("hasError", true);
    } else if (node.hasLevel(ReportNode.WARN)) {
      metaData.put("hasWarn", true);
    }

    metaData.put("isSimple", false);
    metaData.put("hasChildren", false);

    if (node instanceof BranchReportNode) {
      metaData.put("type", "branch");
      BranchReportNode branchNode = (BranchReportNode) node;
      if (branchNode.getChildNodes() != null && branchNode.getChildNodes().size() > 0) {
        metaData.put("hasChildren", true);
        for (ReportNode childNode : branchNode.getChildNodes()) {
          step = updateReport(childNode, step, idGenerator);
        }
      } else {
        metaData.put("isSimple", true);
      }
    } else if (node instanceof TextReportNode) {
      step++;
      metaData.put("step", step);
      metaData.put("type", "text");
      TextReportNode textNode = (TextReportNode) node;
      if (textNode.getDetails() == null || textNode.getDetails().trim().isEmpty()) {
        metaData.put("isSimple", true);
      }
    } else if (node instanceof ExceptionReportNode) {
      metaData.put("type", "exception");
    }

    node.setMetaData(metaData);
    if (node.getParentBranch() != null) {
      metaData.put("parentId", node.getParentBranch().getId());
    }
    return step;
  }
  private ReportNode parse(Node xmlNode) throws Exception {
    if (!"r".equals(xmlNode.getNodeName())) throw new Exception("The report has errors");

    ReportNode rn;

    String classPath = NODE_CLASS_PATH + XmlUtils.getNodeAttribute(xmlNode, "c");

    Class<?> clazz = Class.forName(classPath);
    rn = (ReportNode) clazz.newInstance();

    rn.setIcon(XmlUtils.getNodeAttribute(xmlNode, "i"));
    rn.setLevel(XmlUtils.getNodeAttribute(xmlNode, "l"));
    rn.setTitle(XmlUtils.getChildNodeText(xmlNode, "t"));
    rn.setHint(XmlUtils.getChildNodeText(xmlNode, "h"));

    String time = XmlUtils.getNodeAttribute(xmlNode, "t");
    if (time != null) {
      rn.setDate(new Date(Long.parseLong(time)));
    }

    String debug = XmlUtils.getNodeAttribute(xmlNode, "d");
    if (debug != null) {
      rn.setDebug(Boolean.parseBoolean(debug));
    }

    if (rn instanceof TextReportNode) {
      TextReportNode textNode = (TextReportNode) rn;
      textNode.setDetails(XmlUtils.getChildNodeText(xmlNode, "d"));
    } else if (rn instanceof BranchReportNode) {
      BranchReportNode branchNode = (BranchReportNode) rn;
      Node childrenNodeContainer = XmlUtils.getChildNode(xmlNode, "c");
      branchNode.setChildNodes(new LinkedList<ReportNode>());

      if (childrenNodeContainer != null) {
        NodeList childNodes = childrenNodeContainer.getChildNodes();

        for (int i = 0; i < childNodes.getLength(); i++) {
          Node childNode = childNodes.item(i);
          if (childNode.getNodeType() == Node.ELEMENT_NODE && childNode.getNodeName().equals("r")) {
            branchNode.getChildNodes().add(parse(childNode));
          }
        }
      }
    }
    if (rn instanceof ExceptionReportNode) {
      ExceptionReportNode exceptionNode = (ExceptionReportNode) rn;
      Node exceptionInfoXmlNode = XmlUtils.getChildNode(xmlNode, "e");
      if (exceptionInfoXmlNode != null) {
        exceptionNode.setException(parseExceptionInfo(exceptionInfoXmlNode));
      }
    }

    return rn;
  }