All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.graph.CheckPathsBetweenNodes Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs.

There is a newer version: v20240317
Show newest version
/*
 * Copyright 2008 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.javascript.jscomp.graph;

import com.google.common.base.Predicate;
import com.google.javascript.jscomp.graph.DiGraph.DiGraphEdge;
import com.google.javascript.jscomp.graph.DiGraph.DiGraphNode;
import org.jspecify.nullness.Nullable;

/**
 * See constructor, {@link #CheckPathsBetweenNodes(DiGraph,
 * DiGraphNode, DiGraphNode, Predicate, Predicate)}, for a
 * description of this algorithm.
 *
 * @param  The node type.
 * @param  The edge type.
 */
public final class CheckPathsBetweenNodes {

  private final Predicate nodePredicate;
  private final Predicate> edgePredicate;
  private final boolean inclusive;

  // This algorithm works in two stages. First, the depth-first search (DFS)
  // tree is calculated with A as the root. During when constructing the DFS
  // tree, back edges are recorded. A back edge is a non-tree edge (X -> Y)
  // where X is an descendant of Y in the DFS tree. The second step does a
  // recursive traversal of the graph. Back edges are ignored during the
  // recursive traversal, thus no cycles are encountered. Any recursive branch
  // that encounters B without yet satisfying the predicate represents a path
  // from the entry node to the exit without any nodes that satisfy the
  // predicate.
  //
  // The implementation of discoverBackEdges follows the DFS-Visit algorithm in
  // "Introduction to Algorithms" by Cormen, Leiseron, Rivest, and Stein, 2nd
  // ed., on page 541. The calculation of back edges is described on page 546.

  // A non-tree edge in the DFS that connects a node to one of its ancestors.
  private static final Annotation BACK_EDGE = new Annotation() {};
  private static final Annotation VISITED_EDGE = new Annotation() {};

  // Not yet visited.
  private static final @Nullable Annotation WHITE = null;
  // Being visited.
  private static final Annotation GRAY = new Annotation() {};
  // Finished visiting.
  private static final Annotation BLACK = new Annotation() {};

  private final DiGraph graph;
  private final DiGraphNode start;
  private final DiGraphNode end;

  /**
   * Given a graph G with nodes A and B, this algorithm determines if all paths from A to B contain
   * at least one node satisfying a given predicate.
   *
   * 

Note that nodePredicate is not necessarily called for all nodes in G nor is edgePredicate * called for all edges in G. * * @param graph Graph G to analyze. * @param a The node A. * @param b The node B. * @param nodePredicate Predicate which at least one node on each path from an A node to B * (inclusive) must match. * @param edgePredicate Edges to consider as part of the graph. Edges in graph that don't match * edgePredicate will be ignored. * @param inclusive Includes node A and B in the test for the node predicate. */ public CheckPathsBetweenNodes( DiGraph graph, DiGraphNode a, DiGraphNode b, Predicate nodePredicate, Predicate> edgePredicate, boolean inclusive) { this.graph = graph; this.start = a; this.end = b; this.nodePredicate = nodePredicate; this.edgePredicate = edgePredicate; this.inclusive = inclusive; } /** * Inclusive check. */ public CheckPathsBetweenNodes(DiGraph graph, DiGraphNode a, DiGraphNode b, Predicate nodePredicate, Predicate> edgePredicate) { this(graph, a, b, nodePredicate, edgePredicate, true); } /** * @return true iff all paths contain at least one node that satisfy the * predicate */ public boolean allPathsSatisfyPredicate() { setUp(); boolean result = checkAllPathsWithoutBackEdges(start, end); tearDown(); return result; } /** * @return true iff some paths contain at least one node that satisfy the * predicate */ public boolean somePathsSatisfyPredicate() { setUp(); boolean result = checkSomePathsWithoutBackEdges(start, end); tearDown(); return result; } private void setUp() { graph.pushNodeAnnotations(); graph.pushEdgeAnnotations(); discoverBackEdges(this.start); } private void tearDown() { graph.popNodeAnnotations(); graph.popEdgeAnnotations(); } private void discoverBackEdges(DiGraphNode u) { u.setAnnotation(GRAY); for (DiGraphEdge e : u.getOutEdges()) { if (ignoreEdge(e)) { continue; } DiGraphNode v = e.getDestination(); if (v.getAnnotation() == WHITE) { discoverBackEdges(v); } else if (v.getAnnotation() == GRAY) { e.setAnnotation(BACK_EDGE); } } u.setAnnotation(BLACK); } private boolean ignoreEdge(DiGraphEdge e) { return !edgePredicate.apply(e); } /** * Verify that all non-looping paths from {@code a} to {@code b} pass * through at least one node where {@code nodePredicate} is true. */ private boolean checkAllPathsWithoutBackEdges(DiGraphNode a, DiGraphNode b) { if (nodePredicate.apply(a.getValue()) && (inclusive || (a != start && a != end))) { return true; } if (a == b) { return false; } for (DiGraphEdge e : a.getOutEdges()) { // Once we visited that edge once, we no longer need to // re-visit it again. if (e.getAnnotation() == VISITED_EDGE) { continue; } e.setAnnotation(VISITED_EDGE); if (ignoreEdge(e)) { continue; } if (e.getAnnotation() == BACK_EDGE) { continue; } DiGraphNode next = e.getDestination(); if (!checkAllPathsWithoutBackEdges(next, b)) { return false; } } return true; } /** * Verify that some non-looping paths from {@code a} to {@code b} pass * through at least one node where {@code nodePredicate} is true. */ private boolean checkSomePathsWithoutBackEdges(DiGraphNode a, DiGraphNode b) { if (nodePredicate.apply(a.getValue()) && (inclusive || (a != start && a != end))) { return true; } if (a == b) { return false; } for (DiGraphEdge e : a.getOutEdges()) { // Once we visited that edge once, we no longer need to // re-visit it again. if (e.getAnnotation() == VISITED_EDGE) { continue; } e.setAnnotation(VISITED_EDGE); if (ignoreEdge(e)) { continue; } if (e.getAnnotation() == BACK_EDGE) { continue; } DiGraphNode next = e.getDestination(); if (checkSomePathsWithoutBackEdges(next, b)) { return true; } } return false; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy