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

com.google.gwt.dev.resource.impl.PathPrefixSet Maven / Gradle / Ivy

/*
 * Copyright 2008 Google Inc.
 *
 * 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.gwt.dev.resource.impl;

import com.google.gwt.dev.resource.impl.PathPrefix.Judgement;
import com.google.gwt.dev.util.StringInterner;
import com.google.gwt.dev.util.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Lists;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * Combines the information conveyed about a set of path prefixes to quickly
 * answer questions regarding an entire set of path prefixes.
 */
public class PathPrefixSet {
  /*
   * (1) TODO(amitmanjhi): Improve the api of the PathPrefixSet so that with one
   * trie-traversal, it could be found out which resources rooted at a directory
   * are allowed?
   */

  private class TrieNode {
    // TODO(amitmanjhi): Consider the memory-speed tradeoff here
    private Map children = Maps.create();
    private final String part;
    private List prefixes = Lists.newArrayList();
    private boolean hasPrefixes = false;

    public TrieNode(String part) {
      this.part = StringInterner.get().intern(part);
    }

    public TrieNode addChild(String part) {
      part = StringInterner.get().intern(part);
      TrieNode newChild = new TrieNode(part);
      assert !children.containsKey(part);
      children = Maps.put(children, part, newChild);
      return newChild;
    }

    public void addPathPrefix(PathPrefix prefix) {
      hasPrefixes = true;
      if (mergePathPrefixes) {
        if (prefixes.isEmpty()) {
          prefixes.add(prefix);
        } else {
          prefixes.get(0).merge(prefix);
        }
      } else {
        prefixes.add(prefix);
      }
    }

    public TrieNode findChild(String part) {
      return children.get(part);
    }

    public List getPathPrefixes() {
      return prefixes;
    }

    @Override
    public String toString() {
      StringBuilder sb = new StringBuilder();
      toString(sb, "");
      return sb.toString();
    }

    private void toString(StringBuilder sb, String indent) {
      if (sb.length() > 0) {
        sb.append('\n');
      }
      sb.append(indent);
      sb.append(' ');
      sb.append(part);
      for (TrieNode child : children.values()) {
        child.toString(sb, indent + "  ");
      }
    }
  }

  /**
   * Whether or not to merge prefixes that are added. Merged prefixes perform better during resource
   * scanning but at the cost of not being able to tell which module(s) are responsible for a
   * particular resource inclusion.
   */
  private boolean mergePathPrefixes = true;

  /**
   * List of all path prefixes in priority order.
   */
  private final List prefixes = new ArrayList();

  private final TrieNode rootTrieNode = new TrieNode("/");

  public PathPrefixSet() {
    this(true);
  }

  public PathPrefixSet(boolean mergePathPrefixes) {
    this.mergePathPrefixes = mergePathPrefixes;
  }

  /**
   * @param prefix the prefix to add
   * @return true if the prefix was not already in the set;
   *         otherwise, it merged with one having the same prefix, which has
   *         the effect of expanding the filter (the merge works as
   *         union(includes - skips) - union(excludes))
   */
  public boolean add(PathPrefix prefix) {
    prefix.setPriority(prefixes.size());
    prefixes.add(prefix);

    String pathPrefix = prefix.getPrefix();

    /*
     * An empty prefix means we have no prefix requirement, but we do attach the
     * prefix to the root so that we can apply the filter.
     */
    if ("".equals(pathPrefix)) {
      rootTrieNode.addPathPrefix(prefix);
      return false;
    }

    // TODO(bruce): consider not using split for speed
    String[] parts = pathPrefix.split("/");
    TrieNode parentNode = rootTrieNode;
    boolean didAdd = false;
    for (String part : parts) {
      TrieNode childNode = parentNode.findChild(part);
      if (childNode != null) {
        // Follow existing branch.
        parentNode = childNode;
      } else {
        // Add a new branch.
        parentNode = parentNode.addChild(part);
        didAdd = true;
      }
    }
    assert (parentNode != null);
    parentNode.addPathPrefix(prefix);
    return didAdd;
  }

  public int getSize() {
    return prefixes.size();
  }

  /**
   * Determines whether or not a directory might have resources that could be
   * included. The primary purpose of this method is to allow
   * {@link ClassPathEntry} subclasses to avoid descending into directory
   * hierarchies that could not possibly contain resources that would be
   * included by {@link #includesResource(String)}
   *
   * @param dirPath must be a valid abstract directory name (must not be an
   *          empty string)
   * @return true if some PathPrefix allows the directory
   */
  public boolean includesDirectory(String dirPath) {
    assertValidAbstractDirectoryPathName(dirPath);

    /*
     * There are four cases:
     *
     * (1) The empty string was specified as a prefix, which causes everything
     * to be included.
     *
     * (2) As we walk the parts of dirPath, we see a path prefix attached to one
     * of the trie nodes we encounter. This means that there was a specified
     * prefix that this dirPath falls underneath, so it is included.
     *
     * (3) dirPath is longer than the trie, but we never encounter a path prefix
     * as we walk the trie. This indicates that this directory doesn't fall into
     * any of the specified prefixes.
     *
     * (4) dirPath is not longer than the trie and stays on the trie the whole
     * time, which means it is included (since at least some longer prefix
     * includes it).
     */

    if (rootTrieNode.hasPrefixes) {
      // Case (1).
      return true;
    }

    TrieNode parentNode = rootTrieNode;
    String[] parts = dirPath.split("/");
    for (String part : parts) {
      assert (!"".equals(part));
      TrieNode childNode = parentNode.findChild(part);
      if (childNode != null) {
        if (childNode.hasPrefixes) {
          // Case (2).
          return true;
        }

        // Haven't found a path prefix yet, so keep walking.
        parentNode = childNode;
      } else {
        // Case (3).
        return false;
      }
    }

    // Case (4).
    return true;
  }

  /**
   * Determines whether or not a given resource should be allowed by this path
   * prefix set and the corresponding filters.
   *
   * @param resourceAbstractPathName
   * @return matching PathPrefix if the resource matches some
   *         specified prefix and any associated filters don't exclude it.
   *         Otherwise, returns null. So it returns null if either no prefixes
   *         match or the most specific prefix excludes the resource.
   */
  public ResourceResolution includesResource(String resourceAbstractPathName) {
    String[] parts = resourceAbstractPathName.split("/");
    return includesResource(resourceAbstractPathName, parts);
  }

  /**
   * Dives down the package hierarchy looking for the most specific
   * package that applies to this resource. The filter of the most specific
   * package is the final determiner of inclusion/exclusion, such that more
   * specific subpackages can override the filter settings on less specific
   * superpackages.
   */
  public ResourceResolution includesResource(String resourceAbstractPathName,
      String[] parts) {
    assertValidAbstractResourcePathName(resourceAbstractPathName);

    ResourceResolution resourceResolution = new ResourceResolution();
    TrieNode currentNode = rootTrieNode;
    List mostSpecificPrefixes = rootTrieNode.getPathPrefixes();

    // Walk all but the last path part, which is assumed to be a file name.
    for (String part : parts) {
      assert (!"".equals(part));
      TrieNode childNode = currentNode.findChild(part);
      if (childNode == null) {
        break;
      }

      // We found a more specific node.
      if (childNode.hasPrefixes) {
        List  moreSpecificPrefixes = childNode.getPathPrefixes();
        // If PathPrefix->Module associations are accurate because PathPrefixes haven't been merged.
        if (!mergePathPrefixes) {
          // Record the module name of every PathPrefix that would allow this
          // resource. This enables detailed dependency validity checking.
          for (PathPrefix candidatePrefix : moreSpecificPrefixes) {
            if (candidatePrefix.getJudgement(
                resourceAbstractPathName).isInclude()) {
              resourceResolution.addSourceModuleName(
                  candidatePrefix.getModuleName());
            }
          }
        }

        mostSpecificPrefixes = moreSpecificPrefixes;
      }
      currentNode = childNode;
    }

    PathPrefix chiefPrefix = null;
    Judgement chiefJudgement = null;
    for (PathPrefix candidatePrefix : mostSpecificPrefixes) {
      Judgement judgement = candidatePrefix.getJudgement(
          resourceAbstractPathName);

      // EXCLUSION_EXCLUDE > FILTER_INCLUDE > IMPLICIT_EXCLUDE
      if (chiefJudgement == null ||
          judgement.getPriority() > chiefJudgement.getPriority()) {
        chiefPrefix = candidatePrefix;
        chiefJudgement = judgement;
      }
    }

    if (chiefPrefix == null || !chiefJudgement.isInclude()) {
      return null;
    }

    resourceResolution.setPathPrefix(chiefPrefix);
    return resourceResolution;
  }

  public boolean mergePathPrefixes() {
    return mergePathPrefixes;
  }

  @Override
  public String toString() {
    return rootTrieNode.toString();
  }

  public Collection values() {
    return Collections.unmodifiableCollection(prefixes);
  }

  private void assertValidAbstractDirectoryPathName(String name) {
    assert (name != null);
    // assert ("".equals(name) || (!name.startsWith("/") &&
    // name.endsWith("/")));
    assert (!name.startsWith("/") && name.endsWith("/"));
  }

  private void assertValidAbstractResourcePathName(String name) {
    assert (name != null);
    assert (!"".equals(name));
    assert (!name.startsWith("/") && !name.endsWith("/"));
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy