/**
   * Add a join
   *
   * @param descriptor The join descriptor to add
   * @throws IllegalArgumentException if the descriptor's ID already exists in the workflow
   */
  public void addJoin(JoinDescriptor descriptor) {
    if (getJoin(descriptor.getId()) != null) {
      throw new IllegalArgumentException("Join with id " + descriptor.getId() + " already exists");
    }

    joins.add(descriptor);
  }
  public JoinDescriptor getJoin(int id) {
    for (Iterator iterator = joins.iterator(); iterator.hasNext(); ) {
      JoinDescriptor joinDescriptor = (JoinDescriptor) iterator.next();

      if (joinDescriptor.getId() == id) {
        return joinDescriptor;
      }
    }

    return null;
  }
  protected void init(Element root) {
    NodeList children = root.getChildNodes();

    for (int i = 0; i < children.getLength(); i++) {
      Node child = children.item(i);

      if (child.getNodeName().equals("meta")) {
        Element meta = (Element) child;
        String value = XMLUtil.getText(meta);
        this.metaAttributes.put(meta.getAttribute("name"), value);
      }
    }

    // handle registers - OPTIONAL
    Element r = XMLUtil.getChildElement(root, "registers");

    if (r != null) {
      List registers = XMLUtil.getChildElements(r, "register");

      for (int i = 0; i < registers.size(); i++) {
        Element register = (Element) registers.get(i);
        RegisterDescriptor registerDescriptor =
            DescriptorFactory.getFactory().createRegisterDescriptor(register);
        registerDescriptor.setParent(this);
        this.registers.add(registerDescriptor);
      }
    }

    // handle global-conditions - OPTIONAL
    Element globalConditionsElement = XMLUtil.getChildElement(root, "global-conditions");

    if (globalConditionsElement != null) {
      Element globalConditions = XMLUtil.getChildElement(globalConditionsElement, "conditions");

      ConditionsDescriptor conditionsDescriptor =
          DescriptorFactory.getFactory().createConditionsDescriptor(globalConditions);
      conditionsDescriptor.setParent(this);
      this.globalConditions = conditionsDescriptor;
    }

    // handle initial-steps - REQUIRED
    Element intialActionsElement = XMLUtil.getChildElement(root, "initial-actions");
    List initialActions = XMLUtil.getChildElements(intialActionsElement, "action");

    for (int i = 0; i < initialActions.size(); i++) {
      Element initialAction = (Element) initialActions.get(i);
      ActionDescriptor actionDescriptor =
          DescriptorFactory.getFactory().createActionDescriptor(initialAction);
      actionDescriptor.setParent(this);
      this.initialActions.add(actionDescriptor);
    }

    // handle global-actions - OPTIONAL
    Element globalActionsElement = XMLUtil.getChildElement(root, "global-actions");

    if (globalActionsElement != null) {
      List globalActions = XMLUtil.getChildElements(globalActionsElement, "action");

      for (int i = 0; i < globalActions.size(); i++) {
        Element globalAction = (Element) globalActions.get(i);
        ActionDescriptor actionDescriptor =
            DescriptorFactory.getFactory().createActionDescriptor(globalAction);
        actionDescriptor.setParent(this);
        this.globalActions.add(actionDescriptor);
      }
    }

    // handle common-actions - OPTIONAL
    //   - Store actions in HashMap for now. When parsing Steps, we'll resolve
    //      any common actions into local references.
    Element commonActionsElement = XMLUtil.getChildElement(root, "common-actions");

    if (commonActionsElement != null) {
      List commonActions = XMLUtil.getChildElements(commonActionsElement, "action");

      for (int i = 0; i < commonActions.size(); i++) {
        Element commonAction = (Element) commonActions.get(i);
        ActionDescriptor actionDescriptor =
            DescriptorFactory.getFactory().createActionDescriptor(commonAction);
        actionDescriptor.setParent(this);
        addCommonAction(actionDescriptor);
      }
    }

    // handle timer-functions - OPTIONAL
    Element timerFunctionsElement = XMLUtil.getChildElement(root, "trigger-functions");

    if (timerFunctionsElement != null) {
      List timerFunctions = XMLUtil.getChildElements(timerFunctionsElement, "trigger-function");

      for (int i = 0; i < timerFunctions.size(); i++) {
        Element timerFunction = (Element) timerFunctions.get(i);
        Integer id = new Integer(timerFunction.getAttribute("id"));
        FunctionDescriptor function =
            DescriptorFactory.getFactory()
                .createFunctionDescriptor(XMLUtil.getChildElement(timerFunction, "function"));
        function.setParent(this);
        this.timerFunctions.put(id, function);
      }
    }

    // handle steps - REQUIRED
    Element stepsElement = XMLUtil.getChildElement(root, "steps");
    List steps = XMLUtil.getChildElements(stepsElement, "step");

    for (int i = 0; i < steps.size(); i++) {
      Element step = (Element) steps.get(i);
      StepDescriptor stepDescriptor =
          DescriptorFactory.getFactory().createStepDescriptor(step, this);
      this.steps.add(stepDescriptor);
    }

    // handle splits - OPTIONAL
    Element splitsElement = XMLUtil.getChildElement(root, "splits");

    if (splitsElement != null) {
      List split = XMLUtil.getChildElements(splitsElement, "split");

      for (int i = 0; i < split.size(); i++) {
        Element s = (Element) split.get(i);
        SplitDescriptor splitDescriptor = DescriptorFactory.getFactory().createSplitDescriptor(s);
        splitDescriptor.setParent(this);
        this.splits.add(splitDescriptor);
      }
    }

    // handle joins - OPTIONAL:
    Element joinsElement = XMLUtil.getChildElement(root, "joins");

    if (joinsElement != null) {
      List join = XMLUtil.getChildElements(joinsElement, "join");

      for (int i = 0; i < join.size(); i++) {
        Element s = (Element) join.get(i);
        JoinDescriptor joinDescriptor = DescriptorFactory.getFactory().createJoinDescriptor(s);
        joinDescriptor.setParent(this);
        this.joins.add(joinDescriptor);
      }
    }
  }
  public void writeXML(PrintWriter out, int indent) {
    XMLUtil.printIndent(out, indent++);
    out.println("<workflow>");

    Iterator iter = metaAttributes.entrySet().iterator();

    while (iter.hasNext()) {
      Map.Entry entry = (Map.Entry) iter.next();
      XMLUtil.printIndent(out, indent);
      out.print("<meta name=\"");
      out.print(XMLUtil.encode(entry.getKey()));
      out.print("\">");
      out.print(XMLUtil.encode(entry.getValue()));
      out.println("</meta>");
    }

    if (registers.size() > 0) {
      XMLUtil.printIndent(out, indent++);
      out.println("<registers>");

      for (int i = 0; i < registers.size(); i++) {
        RegisterDescriptor register = (RegisterDescriptor) registers.get(i);
        register.writeXML(out, indent);
      }

      XMLUtil.printIndent(out, --indent);
      out.println("</registers>");
    }

    if (timerFunctions.size() > 0) {
      XMLUtil.printIndent(out, indent++);
      out.println("<trigger-functions>");

      Iterator iterator = timerFunctions.entrySet().iterator();

      while (iterator.hasNext()) {
        Map.Entry entry = (Map.Entry) iterator.next();
        XMLUtil.printIndent(out, indent++);
        out.println("<trigger-function id=\"" + entry.getKey() + "\">");

        FunctionDescriptor trigger = (FunctionDescriptor) entry.getValue();
        trigger.writeXML(out, indent);
        XMLUtil.printIndent(out, --indent);
        out.println("</trigger-function>");
      }

      while (iterator.hasNext()) {}

      XMLUtil.printIndent(out, --indent);
      out.println("</trigger-functions>");
    }

    if (getGlobalConditions() != null) {
      XMLUtil.printIndent(out, indent++);
      out.println("<global-conditions>");

      getGlobalConditions().writeXML(out, indent);

      out.println("</global-conditions>");
    }

    XMLUtil.printIndent(out, indent++);
    out.println("<initial-actions>");

    for (int i = 0; i < initialActions.size(); i++) {
      ActionDescriptor action = (ActionDescriptor) initialActions.get(i);
      action.writeXML(out, indent);
    }

    XMLUtil.printIndent(out, --indent);
    out.println("</initial-actions>");

    if (globalActions.size() > 0) {
      XMLUtil.printIndent(out, indent++);
      out.println("<global-actions>");

      for (int i = 0; i < globalActions.size(); i++) {
        ActionDescriptor action = (ActionDescriptor) globalActions.get(i);
        action.writeXML(out, indent);
      }

      XMLUtil.printIndent(out, --indent);
      out.println("</global-actions>");
    }

    if (commonActions.size() > 0) {
      XMLUtil.printIndent(out, indent++);
      out.println("<common-actions>");

      Iterator iterator = getCommonActions().values().iterator();

      while (iterator.hasNext()) {
        ActionDescriptor action = (ActionDescriptor) iterator.next();
        action.writeXML(out, indent);
      }

      XMLUtil.printIndent(out, --indent);
      out.println("</common-actions>");
    }

    XMLUtil.printIndent(out, indent++);
    out.println("<steps>");

    for (int i = 0; i < steps.size(); i++) {
      StepDescriptor step = (StepDescriptor) steps.get(i);
      step.writeXML(out, indent);
    }

    XMLUtil.printIndent(out, --indent);
    out.println("</steps>");

    if (splits.size() > 0) {
      XMLUtil.printIndent(out, indent++);
      out.println("<splits>");

      for (int i = 0; i < splits.size(); i++) {
        SplitDescriptor split = (SplitDescriptor) splits.get(i);
        split.writeXML(out, indent);
      }

      XMLUtil.printIndent(out, --indent);
      out.println("</splits>");
    }

    if (joins.size() > 0) {
      XMLUtil.printIndent(out, indent++);
      out.println("<joins>");

      for (int i = 0; i < joins.size(); i++) {
        JoinDescriptor join = (JoinDescriptor) joins.get(i);
        join.writeXML(out, indent);
      }

      XMLUtil.printIndent(out, --indent);
      out.println("</joins>");
    }

    XMLUtil.printIndent(out, --indent);
    out.println("</workflow>");
  }