/**
   * This method is responsible for saving a macro as an XML document. The document follows the
   * structure of the macro tree.
   */
  public static void saveMacro(final MacroDefinition macroDef, final String saveLocation)
      throws NullPointerException, IOException, ParserConfigurationException, DOMException,
          TransformerConfigurationException, TransformerException {

    MacroWriter.macro = macroDef;
    File macroFile = null;

    if (saveLocation.equals("")) {
      // test if macro save location exists
      File macroSaveLocation = new File(MacroManager.macroSaveLocation);
      if (!macroSaveLocation.exists()) macroSaveLocation.mkdir();

      String macroPath = MacroManager.macroSaveLocation + MacroWriter.macro.getName() + ".xml";
      macroFile = new File(macroPath);
    } else {
      macroFile = new File(saveLocation);
    }

    Document ptDOM = null;
    StreamSource xsltSource = null;
    Transformer transformer = null;

    try {
      // Build a PTML Document
      DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
      DocumentBuilder builder = builderFactory.newDocumentBuilder();
      ptDOM = builder.newDocument();

      // PTML Element
      Element ptmlElement = ptDOM.createElement("ptml");
      ptDOM.appendChild(ptmlElement);

      // Macro Element
      Element macroElement = ptDOM.createElement("macro");
      macroElement.setAttribute("name", MacroWriter.macro.getName());
      macroElement.setAttribute("description", MacroWriter.macro.getDescription());
      macroElement.setAttribute("returntype", MacroWriter.macro.getReturnType());
      ptmlElement.appendChild(macroElement);

      // put nodes into HashMap, so that we can check whether they have
      // been processed already
      // (in case of recursion when child nodes are processed)
      ArrayList<PerformanceTreeNode> nodesArray = MacroWriter.macro.getMacroNodes();
      Iterator<PerformanceTreeNode> i = nodesArray.iterator();
      while (i.hasNext()) {
        PerformanceTreeNode nodeNotProcessedYet = i.next();
        String nodeID = nodeNotProcessedYet.getId();
        MacroWriter.nodesProcessed.put(nodeID, "false");
      }

      // serialise node and their arcs
      i = nodesArray.iterator();
      while (i.hasNext()) {
        PerformanceTreeNode nodeToSerialise = i.next();
        String nodeToSerialiseID = nodeToSerialise.getId();
        boolean nodeProcessedAlready = false;
        if ((MacroWriter.nodesProcessed.get(nodeToSerialiseID)).equals("true")) {
          nodeProcessedAlready = true;
        } else if ((MacroWriter.nodesProcessed.get(nodeToSerialiseID)).equals("false")) {
          nodeProcessedAlready = false;
        }

        if (!nodeProcessedAlready)
          MacroWriter.createNodeElement(nodeToSerialise, macroElement, ptDOM);
      }

      // serialise state and action labels
      MacroWriter.serialiseStateAndActionLabels(macroElement, ptDOM);

      ptDOM.normalize();

      // Create Transformer with XSL Source File
      xsltSource =
          new StreamSource(
              Thread.currentThread()
                  .getContextClassLoader()
                  .getResourceAsStream(
                      "pipe"
                          + System.getProperty("file.separator")
                          + "modules"
                          + System.getProperty("file.separator")
                          + "queryeditor"
                          + System.getProperty("file.separator")
                          + "io"
                          + System.getProperty("file.separator")
                          + "WriteMacroXML.xsl"));
      transformer = TransformerFactory.newInstance().newTransformer(xsltSource);

      // Write file and do XSLT transformation to generate correct PTML
      File outputObjectArrayList = macroFile;
      DOMSource source = new DOMSource(ptDOM);
      StreamResult result = new StreamResult(outputObjectArrayList);
      transformer.transform(source, result);
    } catch (DOMException e) {
      System.out.println(
          "DOMException thrown in saveMacro()"
              + " : MacroWriter Class : modules.queryeditor.io Package"
              + " : filename=\""
              + macroFile.getCanonicalPath()
              + "\" xslt=\""
              + xsltSource.getSystemId()
              + "\" transformer=\""
              + transformer.getURIResolver()
              + "\"");
    } catch (ParserConfigurationException e) {
      System.out.println(
          "ParserConfigurationException thrown in saveMacro()"
              + " : MacroWriter Class : modules.queryeditor.io Package"
              + " : filename=\""
              + macroFile.getCanonicalPath()
              + "\" xslt=\""
              + "\" transformer=\""
              + "\"");
    } catch (TransformerConfigurationException e) {
      System.out.println(
          "TransformerConfigurationException thrown in saveMacro()"
              + " : MacroWriter Class : modules.queryeditor.io Package"
              + " : filename=\""
              + macroFile.getCanonicalPath()
              + "\" xslt=\""
              + xsltSource.getSystemId()
              + "\" transformer=\""
              + transformer.getURIResolver()
              + "\"");
    } catch (TransformerException e) {
      System.out.println(
          "TransformerException thrown in saveMacro()"
              + " : MacroWriter Class : modules.queryeditor.io Package"
              + " : filename=\""
              + macroFile.getCanonicalPath()
              + "\" xslt=\""
              + xsltSource.getSystemId()
              + "\" transformer=\""
              + transformer.getURIResolver()
              + "\"");
    }
  }
  /**
   * This method serialises the information contained within a PerformanceTreeNode and recurses down
   * to serialise info about its arcs
   */
  private static void createNodeElement(
      final PerformanceTreeNode inputNode, final Element parentElement, final Document document) {

    Element nodeElement = document.createElement("node");

    // common data to serialise for all PT nodes
    String nodeID = inputNode.getId();
    Double nodePositionX = inputNode.getPositionXObject();
    Double nodePositionY = inputNode.getPositionYObject();
    PTNode nodeType = inputNode.getNodeType();
    String nodeIncomingArcID = inputNode.getIncomingArcID();
    nodeElement.setAttribute("id", nodeID);
    nodeElement.setAttribute("type", nodeType.toString());
    nodeElement.setAttribute("x", String.valueOf(nodePositionX));
    nodeElement.setAttribute("y", String.valueOf(nodePositionY));
    nodeElement.setAttribute("incomingArc", nodeIncomingArcID);

    // indicate that this node has been processed already
    MacroWriter.nodesProcessed.put(nodeID, "true");

    if (nodeIncomingArcID == null || nodeIncomingArcID.equals("")) {
      Element tree = document.createElement("tree");
      parentElement.appendChild(tree);
      tree.appendChild(nodeElement);
    } else {
      parentElement.appendChild(nodeElement);
    }

    // special cases
    if (inputNode instanceof ValueNode) {
      String nodeLabel;
      if (((ValueNode) inputNode).getNodeLabelObject() != null)
        nodeLabel = ((ValueNode) inputNode).getNodeLabel();
      else nodeLabel = "";
      nodeElement.setAttribute("label", nodeLabel);
    } else if (inputNode instanceof OperationNode) {
      String nodeOperation = ((OperationNode) inputNode).getOperation();
      nodeElement.setAttribute("operation", nodeOperation);
      Element outgoingArcsElement = document.createElement("outgoingArcs");
      nodeElement.appendChild(outgoingArcsElement);
      Collection nodeOutgoingArcIDs = ((OperationNode) inputNode).getOutgoingArcIDs();
      Iterator i = nodeOutgoingArcIDs.iterator();
      while (i.hasNext()) {
        String outgoingArcID = (String) i.next();
        PerformanceTreeArc nodeArc = MacroWriter.macro.getMacroArc(outgoingArcID);
        MacroWriter.createArcElement(nodeArc, outgoingArcsElement, document);
      }

      Element childNodesElement = document.createElement("childNodes");
      nodeElement.appendChild(childNodesElement);
      i = nodeOutgoingArcIDs.iterator();
      while (i.hasNext()) {
        String outgoingArcID = (String) i.next();
        PerformanceTreeArc nodeArc = MacroWriter.macro.getMacroArc(outgoingArcID);
        if (nodeArc.getTarget() != null) {
          PerformanceTreeNode childNode = nodeArc.getTarget();
          boolean nodeProcessedAlready = false;
          if ((MacroWriter.nodesProcessed.get(childNode.getId())).equals("true")) {
            nodeProcessedAlready = true;
          } else if ((MacroWriter.nodesProcessed.get(childNode.getId())).equals("false")) {
            nodeProcessedAlready = false;
          }
          if (!nodeProcessedAlready)
            MacroWriter.createNodeElement(childNode, childNodesElement, document);
        }
      }
    }
  }