private static Collection<OutputValue> getOutputValues(
      final ProcessorGraphNode<Object, Object> node) {
    final Map<String, String> outputMapper = node.getOutputMapper();
    final Set<OutputValue> values = Sets.newHashSet();

    final Set<String> mappings = outputMapper.keySet();
    final Class<?> paramType = node.getProcessor().getOutputType();
    verifyAllMappingsMatchParameter(
        mappings,
        paramType,
        "One or more of the output mapping keys of '"
            + node
            + "' do not match an "
            + "output parameter.  The bad mappings are: ");
    final Collection<Field> allProperties = getAllAttributes(paramType);
    for (Field field : allProperties) {
      // if the field is annotated with @DebugValue, it can be renamed automatically in a
      // mapping in case of a conflict.
      final boolean canBeRenamed = field.getAnnotation(InternalValue.class) != null;
      String name =
          ProcessorUtils.getOutputValueName(
              node.getProcessor().getOutputPrefix(), outputMapper, field);
      values.add(new OutputValue(name, canBeRenamed, field.getType()));
    }

    return values;
  }
  private String getOriginalKey(
      final ProcessorGraphNode<Object, Object> node, final String mappedKey) {
    String inputName = mappedKey;
    if (node.getInputMapper().containsKey(mappedKey)) {
      inputName = node.getInputMapper().get(mappedKey);
    }

    return inputName;
  }
  private String getMappedKey(
      final ProcessorGraphNode<Object, Object> node, final String requiredInput) {
    String inputName = requiredInput;
    if (node.getInputMapper().containsValue(requiredInput)) {
      inputName = node.getInputMapper().inverse().get(requiredInput);
    }

    return inputName;
  }
  private void checkExternalDependencies(
      final List<ProcessorDependency> allDependencies,
      final ProcessorGraphNode<Object, Object> node,
      final List<ProcessorGraphNode<Object, Object>> nodes) {
    for (ProcessorDependency dependency : allDependencies) {
      if (dependency.getRequired().equals(node.getProcessor().getClass())) {
        // this node is required by another processor type, let's see if there
        // is an actual processor of this type
        for (ProcessorGraphNode<Object, Object> dependentNode : nodes) {
          if (dependency.getDependent().equals(dependentNode.getProcessor().getClass())) {
            // this is the right processor type, let's check if the processors should have
            // some inputs in common
            if (dependency.getCommonInputs().isEmpty()) {
              // no inputs in common required, just create the dependency
              node.addDependency(dependentNode);
            } else {
              // we have to check if the two processors have the given inputs in common.
              // for example if the input "map" is required, the mapped name for "map" for
              // processor 1 is retrieved, e.g. "mapDef1". if processor 2 also has a mapped
              // input with name "mapDef1", we add a dependency.
              boolean allRequiredInputsInCommon = true;
              for (String requiredInput : dependency.getCommonInputs()) {
                // to make things more complicated: the common input attributes might have
                // different names in the two nodes. e.g. for `CreateOverviewMapProcessor`
                // the overview map is called `overviewMap`, but on the `SetStyleProcessor`
                // the map is simply called `map`.
                final String requiredNodeInput = getRequiredNodeInput(requiredInput);
                final String dependentNodeInput = getDependentNodeInput(requiredInput);

                final String mappedKey = getMappedKey(node, requiredNodeInput);
                if (!getOriginalKey(dependentNode, mappedKey).equals(dependentNodeInput)) {
                  allRequiredInputsInCommon = false;
                  break;
                }
              }

              if (allRequiredInputsInCommon) {
                node.addDependency(dependentNode);
              }
            }
          }
        }
      }
    }
  }
  private static Set<InputValue> getInputs(final ProcessorGraphNode<Object, Object> node) {
    final BiMap<String, String> inputMapper = node.getInputMapper();
    final Set<InputValue> inputs = Sets.newHashSet();

    final Object inputParameter = node.getProcessor().createInputParameter();
    if (inputParameter != null) {
      verifyAllMappingsMatchParameter(
          inputMapper.values(),
          inputParameter.getClass(),
          "One or more of the input mapping values of '"
              + node
              + "'  do not match an input parameter.  The bad mappings are");

      final Collection<Field> allProperties = getAllAttributes(inputParameter.getClass());
      for (Field field : allProperties) {
        String name =
            ProcessorUtils.getInputValueName(
                node.getProcessor().getInputPrefix(), inputMapper, field.getName());
        inputs.add(new InputValue(name, field.getType()));
      }
    }

    return inputs;
  }
  /**
   * Create a {@link ProcessorDependencyGraph}.
   *
   * @param processors the processors that will be part of the graph
   * @return a {@link org.mapfish.print.processor.ProcessorDependencyGraph} constructed from the
   *     passed in processors
   */
  @SuppressWarnings("unchecked")
  public ProcessorDependencyGraph build(final List<? extends Processor> processors) {
    ProcessorDependencyGraph graph = new ProcessorDependencyGraph();

    final Map<String, ProcessorGraphNode<Object, Object>> provideBy =
        new HashMap<String, ProcessorGraphNode<Object, Object>>();
    final Map<String, Class<?>> outputTypes = new HashMap<String, Class<?>>();
    final List<ProcessorGraphNode<Object, Object>> nodes =
        new ArrayList<ProcessorGraphNode<Object, Object>>(processors.size());

    for (Processor<Object, Object> processor : processors) {
      final ProcessorGraphNode<Object, Object> node =
          new ProcessorGraphNode<Object, Object>(processor, this.metricRegistry);
      for (OutputValue value : getOutputValues(node)) {
        String outputName = value.getName();
        if (provideBy.containsKey(outputName)) {
          // there is already an output with the same name
          if (value.canBeRenamed()) {
            // if this is just a debug output, we can simply rename it
            outputName = outputName + "_" + UUID.randomUUID().toString();
          } else {
            throw new IllegalArgumentException(
                "Multiple processors provide the same output mapping: '"
                    + processor
                    + "' and '"
                    + provideBy.get(outputName)
                    + "' both provide: '"
                    + outputName
                    + "'.  You have to rename one of the outputs and the corresponding input so that"
                    + " there is no ambiguity with regards to the input a processor consumes.");
          }
        }

        provideBy.put(outputName, node);
        outputTypes.put(outputName, value.getType());
      }
      nodes.add(node);
    }

    ArrayList<ProcessorDependency> allDependencies = Lists.newArrayList(this.dependencies);
    for (ProcessorGraphNode<Object, Object> node : nodes) {
      if (node.getProcessor() instanceof CustomDependencies) {
        CustomDependencies custom = (CustomDependencies) node.getProcessor();
        allDependencies.addAll(custom.createDependencies(nodes));
      }
    }
    final SetMultimap<ProcessorGraphNode<Object, Object>, InputValue> inputsForNodes =
        cacheInputsForNodes(nodes);
    for (ProcessorGraphNode<Object, Object> node : nodes) {
      final Set<InputValue> inputs = inputsForNodes.get(node);

      // check explicit, external dependencies between nodes
      checkExternalDependencies(allDependencies, node, nodes);

      // check input/output value dependencies
      for (InputValue input : inputs) {
        final ProcessorGraphNode<Object, Object> solution = provideBy.get(input.getName());
        if (solution != null && solution != node) {
          // check that the provided output has the same type
          final Class<?> inputType = input.getType();
          final Class<?> outputType = outputTypes.get(input.getName());
          if (inputType.isAssignableFrom(outputType)) {
            solution.addDependency(node);
          } else {
            throw new IllegalArgumentException(
                "Type conflict: Processor '"
                    + solution.getName()
                    + "' provides an output with name '"
                    + input.getName()
                    + "' and of type '"
                    + outputType
                    + " ', while "
                    + "processor '"
                    + node.getName()
                    + "' expects an input of that name with type '"
                    + inputType
                    + "'! Please rename one of the attributes in the mappings of the processors.");
          }
        }
      }
    }

    // once all dependencies are discovered, select the root nodes
    for (ProcessorGraphNode<Object, Object> node : nodes) {
      // a root node is a node that has no requirements (that is no other node
      // should be executed before the node) and that has only external inputs
      if (!node.hasRequirements()
          && hasNoneOrOnlyExternalInput(node, inputsForNodes.get(node), provideBy)) {
        graph.addRoot(node);
      }
    }

    Assert.isTrue(
        graph.getAllProcessors().containsAll(processors),
        "'" + graph + "' does not contain all the processors: " + processors);

    return graph;
  }