/** Calculates graph validity */
  private boolean isValid(PathType searchTechnique) {
    // remove all arcs from queue
    extendedNodeQueue.clear();

    // initialize node queue with all nodes in graph
    basicNodeQueue.clear();
    basicNodeQueue.addAll(nodes);

    try {
      // check for all search technique
      if (searchTechnique == PathType.ALL) {
        for (Node n : nodes) {
          n.setRequirement(true);
        }
        for (Arc a : arcs) {
          a.setRequirement(true);
        }
      }

      // start by making initial graph consistent
      propagate();

      // search to test assigning a requirement setting
      // to tables not yet determined
      search(searchTechnique);

      return true;
    } catch (ConsistencyException cx) {
      logger.debug("failed to validate", cx);
      return false;
    }
  }
  /**
   * Called whenever a target node is altered
   *
   * @param n Node that was altered
   */
  public void graphElementChanged(GraphElement element) {
    List<GraphElement> searchDelta =
        (searchStack != null && searchStack.size() > 0) ? searchStack.getLast() : null;
    if (searchDelta != null) {
      searchDelta.add(element);
    }

    if (element instanceof Node) {
      Node n = (Node) element;
      basicNodeQueue.add(n);

      // for more complex arcs we need to do extended propagation checks
      if (n.getArcs().size() > 1) {
        extendedNodeQueue.add(n);
      }
    }
  }
  /**
   * Performs work of propagating changes from source nodes to target nodes until consistency is
   * reached
   *
   * @throws ConsistencyException When current graph cannot be made consistent
   */
  private void propagate() throws ConsistencyException {
    logger.debug("Beginning propagation");

    // first prune all non-required nodes
    for (Node n : nodes) {
      n.prune();
    }

    // process until queues are empty
    while (basicNodeQueue.size() > 0 || extendedNodeQueue.size() > 0) {

      // process basic node consistency enforcement because it's
      // faster than the extended arc propagation checks
      if (basicNodeQueue.size() > 0) {
        Node source = (Node) basicNodeQueue.remove();

        // check if source node is bound to a requirement setting
        // before processing arcs
        if (source.isRequirementKnown()) {

          // get list of arcs originating at node
          List<Arc> sourceArcs = source.getArcs();
          for (Arc arc : sourceArcs) {
            Node target = (arc.getLeft() == source) ? arc.getRight() : arc.getLeft();

            // if source is not required, arc is not required and
            // we can try and prune the target
            if (source.isNotRequired()) {
              arc.setRequirement(false);
              target.prune();
            }
          }
        }
      } else {
        // process extended enforcement of arc constraints on altered nodes
        // since we need to make sure that any node that is connected already
        Node source = (Node) extendedNodeQueue.remove();

        // enforce arc constraints on nodes
        List<Arc> sourceArcs = source.getArcs();
        for (Arc arc : sourceArcs) {
          arc.propagate(source);
        }
      }
    }

    // build required nodes list
    List<Node> requiredNodes = new LinkedList<Node>();
    for (Node n : nodes) {
      if (n.isRequired()) {
        requiredNodes.add(n);
      }
    }

    // make sure all required nodes can reach one another before returning
    // to ensure consistency
    if (requiredNodes.size() > 1) {
      List<Node> targetList = new LinkedList<Node>(requiredNodes);
      Node start = requiredNodes.remove(0);
      if (!start.canReachAllNodes(targetList)) {
        logger.debug(
            "Arc propagation completed, but not all targets could be reached from first node");
        throw new ConsistencyException(start);
      }
    }

    logger.debug("Propagation completed successfully");
  }