/**
   * Traverses the node, including its children (recursive), and gathers all the node ids.
   *
   * @param node the target node
   * @param set set to store ids, if <tt>null</tt> a new set will be created
   * @param onlyCustomId whether to only store custom assigned ids (ie. {@link
   *     org.apache.camel.model.OptionalIdentifiedDefinition#hasCustomIdAssigned()}
   * @param includeAbstract whether to include abstract nodes (ie. {@link
   *     org.apache.camel.model.ProcessorDefinition#isAbstract()}
   * @return the set with the found ids.
   */
  public static Set<String> gatherAllNodeIds(
      ProcessorDefinition<?> node, Set<String> set, boolean onlyCustomId, boolean includeAbstract) {
    if (node == null) {
      return set;
    }

    // skip abstract
    if (node.isAbstract() && !includeAbstract) {
      return set;
    }

    if (set == null) {
      set = new LinkedHashSet<String>();
    }

    // add ourselves
    if (node.getId() != null) {
      if (!onlyCustomId || node.hasCustomIdAssigned() && onlyCustomId) {
        set.add(node.getId());
      }
    }

    // traverse outputs and recursive children as well
    List<ProcessorDefinition<?>> children = node.getOutputs();
    if (children != null && !children.isEmpty()) {
      for (ProcessorDefinition<?> child : children) {
        // traverse children also
        gatherAllNodeIds(child, set, onlyCustomId, includeAbstract);
      }
    }

    return set;
  }
  @SuppressWarnings({"unchecked", "rawtypes"})
  private static <T> void doFindType(
      List<ProcessorDefinition<?>> outputs, Class<T> type, List<T> found) {
    if (outputs == null || outputs.isEmpty()) {
      return;
    }

    for (ProcessorDefinition out : outputs) {
      if (type.isInstance(out)) {
        found.add((T) out);
      }

      // send is much common
      if (out instanceof SendDefinition) {
        SendDefinition send = (SendDefinition) out;
        List<ProcessorDefinition<?>> children = send.getOutputs();
        doFindType(children, type, found);
      }

      // special for choice
      if (out instanceof ChoiceDefinition) {
        ChoiceDefinition choice = (ChoiceDefinition) out;
        for (WhenDefinition when : choice.getWhenClauses()) {
          List<ProcessorDefinition<?>> children = when.getOutputs();
          doFindType(children, type, found);
        }

        // otherwise is optional
        if (choice.getOtherwise() != null) {
          List<ProcessorDefinition<?>> children = choice.getOtherwise().getOutputs();
          doFindType(children, type, found);
        }
      }

      // special for try ... catch ... finally
      if (out instanceof TryDefinition) {
        TryDefinition doTry = (TryDefinition) out;
        List<ProcessorDefinition<?>> doTryOut = doTry.getOutputsWithoutCatches();
        doFindType(doTryOut, type, found);

        List<CatchDefinition> doTryCatch = doTry.getCatchClauses();
        for (CatchDefinition doCatch : doTryCatch) {
          doFindType(doCatch.getOutputs(), type, found);
        }

        if (doTry.getFinallyClause() != null) {
          doFindType(doTry.getFinallyClause().getOutputs(), type, found);
        }

        // do not check children as we already did that
        continue;
      }

      // try children as well
      List<ProcessorDefinition<?>> children = out.getOutputs();
      doFindType(children, type, found);
    }
  }
  /**
   * Is the given child the first in the outputs from the parent?
   *
   * @param parentType the type the parent must be
   * @param node the node
   * @return <tt>true</tt> if first child, <tt>false</tt> otherwise
   */
  public static boolean isFirstChildOfType(Class<?> parentType, ProcessorDefinition<?> node) {
    if (node == null || node.getParent() == null) {
      return false;
    }

    if (node.getParent().getOutputs().isEmpty()) {
      return false;
    }

    if (!(node.getParent().getClass().equals(parentType))) {
      return false;
    }

    return node.getParent().getOutputs().get(0).equals(node);
  }
  /**
   * Is the given node parent(s) of the given type
   *
   * @param parentType the parent type
   * @param node the current node
   * @param recursive whether or not to check grand parent(s) as well
   * @return <tt>true</tt> if parent(s) is of given type, <tt>false</tt> otherwise
   */
  public static boolean isParentOfType(
      Class<?> parentType, ProcessorDefinition<?> node, boolean recursive) {
    if (node == null || node.getParent() == null) {
      return false;
    }

    if (parentType.isAssignableFrom(node.getParent().getClass())) {
      return true;
    } else if (recursive) {
      // recursive up the tree of parents
      return isParentOfType(parentType, node.getParent(), true);
    } else {
      // no match
      return false;
    }
  }
  /**
   * Gets the route definition the given node belongs to.
   *
   * @param node the node
   * @return the route, or <tt>null</tt> if not possible to find
   */
  public static RouteDefinition getRoute(ProcessorDefinition<?> node) {
    if (node == null) {
      return null;
    }

    ProcessorDefinition def = node;
    // drill to the top
    while (def != null && def.getParent() != null) {
      def = def.getParent();
    }

    if (def instanceof RouteDefinition) {
      return (RouteDefinition) def;
    } else {
      // not found
      return null;
    }
  }
 /**
  * Is there any outputs in the given list.
  *
  * <p>Is used for check if the route output has any real outputs (non abstracts)
  *
  * @param outputs the outputs
  * @param excludeAbstract whether or not to exclude abstract outputs (e.g. skip onException etc.)
  * @return <tt>true</tt> if has outputs, otherwise <tt>false</tt> is returned
  */
 @SuppressWarnings("unchecked")
 public static boolean hasOutputs(List<ProcessorDefinition> outputs, boolean excludeAbstract) {
   if (outputs == null || outputs.isEmpty()) {
     return false;
   }
   if (!excludeAbstract) {
     return !outputs.isEmpty();
   }
   for (ProcessorDefinition output : outputs) {
     if (output instanceof TransactedDefinition || output instanceof PolicyDefinition) {
       // special for those as they wrap entire output, so we should just check its output
       return hasOutputs(output.getOutputs(), excludeAbstract);
     }
     if (!output.isAbstract()) {
       return true;
     }
   }
   return false;
 }
  @SuppressWarnings("unchecked")
  private static <T> void doFindType(
      List<ProcessorDefinition> outputs, Class<T> type, List<T> found) {
    if (outputs == null || outputs.isEmpty()) {
      return;
    }

    for (ProcessorDefinition out : outputs) {
      if (type.isInstance(out)) {
        found.add((T) out);
      }

      // send is much common
      if (out instanceof SendDefinition) {
        SendDefinition send = (SendDefinition) out;
        List<ProcessorDefinition> children = send.getOutputs();
        doFindType(children, type, found);
      }

      // special for choice
      if (out instanceof ChoiceDefinition) {
        ChoiceDefinition choice = (ChoiceDefinition) out;
        for (WhenDefinition when : choice.getWhenClauses()) {
          List<ProcessorDefinition> children = when.getOutputs();
          doFindType(children, type, found);
        }

        // otherwise is optional
        if (choice.getOtherwise() != null) {
          List<ProcessorDefinition> children = choice.getOtherwise().getOutputs();
          doFindType(children, type, found);
        }
      }

      // try children as well
      List<ProcessorDefinition> children = out.getOutputs();
      doFindType(children, type, found);
    }
  }