public ActionDescriptor getAction(int id) {
    // check global actions
    for (Iterator iterator = globalActions.iterator(); iterator.hasNext(); ) {
      ActionDescriptor actionDescriptor = (ActionDescriptor) iterator.next();

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

    // check steps
    for (Iterator iterator = steps.iterator(); iterator.hasNext(); ) {
      StepDescriptor stepDescriptor = (StepDescriptor) iterator.next();
      ActionDescriptor actionDescriptor = stepDescriptor.getAction(id);

      if (actionDescriptor != null) {
        return actionDescriptor;
      }
    }

    // check initial actions, which we now must have unique id's
    for (Iterator iterator = initialActions.iterator(); iterator.hasNext(); ) {
      ActionDescriptor actionDescriptor = (ActionDescriptor) iterator.next();

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

    return null;
  }
  /**
   * Remove an action from this workflow completely.
   *
   * <p>This method will check global actions and all steps.
   *
   * @return true if the action was successfully removed, false if it was not found
   */
  public boolean removeAction(ActionDescriptor actionToRemove) {
    // global actions
    for (Iterator iterator = getGlobalActions().iterator(); iterator.hasNext(); ) {
      ActionDescriptor actionDescriptor = (ActionDescriptor) iterator.next();

      if (actionDescriptor.getId() == actionToRemove.getId()) {
        getGlobalActions().remove(actionDescriptor);

        return true;
      }
    }

    // steps
    for (Iterator iterator = getSteps().iterator(); iterator.hasNext(); ) {
      StepDescriptor stepDescriptor = (StepDescriptor) iterator.next();
      ActionDescriptor actionDescriptor = stepDescriptor.getAction(actionToRemove.getId());

      if (actionDescriptor != null) {
        stepDescriptor.getActions().remove(actionDescriptor);

        return true;
      }
    }

    return false;
  }
  /**
   * Add a step
   *
   * @param descriptor The step descriptor to add
   * @throws IllegalArgumentException if the descriptor's ID already exists in the workflow
   */
  public void addStep(StepDescriptor descriptor) {
    if (getStep(descriptor.getId()) != null) {
      throw new IllegalArgumentException("Step with id " + descriptor.getId() + " already exists");
    }

    steps.add(descriptor);
  }
  public StepDescriptor getStep(int id) {
    for (Iterator iterator = steps.iterator(); iterator.hasNext(); ) {
      StepDescriptor step = (StepDescriptor) iterator.next();

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

    return null;
  }
  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>");
  }
  public void validate() throws InvalidWorkflowDescriptorException {
    ValidationHelper.validate(this.getRegisters());
    ValidationHelper.validate(this.getTriggerFunctions().values());
    ValidationHelper.validate(this.getGlobalActions());
    ValidationHelper.validate(this.getInitialActions());
    ValidationHelper.validate(this.getCommonActions().values());
    ValidationHelper.validate(this.getSteps());
    ValidationHelper.validate(this.getSplits());
    ValidationHelper.validate(this.getJoins());

    Set actions = new HashSet();
    Iterator i = globalActions.iterator();

    while (i.hasNext()) {
      ActionDescriptor action = (ActionDescriptor) i.next();
      actions.add(new Integer(action.getId()));
    }

    i = getSteps().iterator();

    while (i.hasNext()) {
      StepDescriptor step = (StepDescriptor) i.next();
      Iterator j = step.getActions().iterator();

      while (j.hasNext()) {
        ActionDescriptor action = (ActionDescriptor) j.next();

        // check to see if it's a common action (dups are ok, since that's the point of common
        // actions!)
        if (!action.isCommon()) {
          if (!actions.add(new Integer(action.getId()))) {
            throw new InvalidWorkflowDescriptorException(
                "Duplicate occurance of action ID "
                    + action.getId()
                    + " found in step "
                    + step.getId());
          }
        }
      }
    }

    // now we have all our unique actions, let's check that no common action id's exist in them
    i = commonActions.keySet().iterator();

    while (i.hasNext()) {
      Integer action = (Integer) i.next();

      if (actions.contains(action)) {
        throw new InvalidWorkflowDescriptorException(
            "common-action ID " + action + " is duplicated in a step action");
      }
    }

    i = initialActions.iterator();

    while (i.hasNext()) {
      ActionDescriptor action = (ActionDescriptor) i.next();

      if (actions.contains(new Integer(action.getId()))) {
        throw new InvalidWorkflowDescriptorException(
            "initial-action ID " + action + " is duplicated in a step action");
      }
    }

    validateDTD();
  }