blob: 35bc9219d98b1df60f2016a04c1f11ed59e4f9bc [file] [log] [blame]
/*
* 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.thirdparty.apache.ant.types.ZipScanner;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Represents the abstract path prefix that goes between the
* {@link ClassPathEntry} and the rest of resource's abstract path. This concept
* allows us to specify subsets of path hierarchies orthogonally from path
* roots. For example, a path root might be <code>/home/gwt/src/</code> and an
* abstract path prefix might be <code>module/client/</code>. Importantly,
* you can apply the same abstract path prefix to multiple path roots and find
* more than one set of resources residing in disjoint locations yet occupying
* the same logical hierarchy. Sorry this explanation is so abstract; it's how
* we model things like the GWT module's client source path, public path, and
* super source path.
*/
public final class PathPrefix {
/**
* Represents whether or not a PathPrefix includes a particular file as well
* as an indicator of the inclusion/exclusion priority. The priority is
* needed because there can be multiple PathPrefixes for a given directory
* and the highest priority judgement must be found and honored.
*/
public enum Judgement {
EXCLUSION_EXCLUDE(false, 3), FILTER_INCLUDE(true, 2),
IMPLICIT_EXCLUDE(false, 1);
private final boolean include;
private final int priority;
private Judgement(boolean include, int priority) {
this.include = include;
this.priority = priority;
}
public int getPriority() {
return priority;
}
public boolean isInclude() {
return include;
}
}
private final Set<String> exclusions;
private ZipScanner exclusionScanner;
private final List<ResourceFilter> filters;
private final String prefix;
private int priority = -1;
private final boolean shouldReroot;
private final String moduleName;
/**
* Construct a non-rerooting prefix.
*
* @param prefix a string prefix that (1) is the empty string or (2) begins
* with something other than a slash and ends with a slash
* @param filter the resource filter to use, or <code>null</code> for no
* filter; note that the filter must always return the same answer
* for the same candidate path (doing otherwise will produce
* inconsistent behavior in identifying available resources)
*/
public PathPrefix(String prefix, ResourceFilter filter) {
this("", prefix, filter, false, null);
}
/**
* Construct a prefix without global exclusions.
*
* @param prefix a string prefix that (1) is the empty string or (2) begins
* with something other than a slash and ends with a slash
* @param filter the resource filter to use, or <code>null</code> for no
* filter; note that the filter must always return the same answer
* for the same candidate path (doing otherwise will produce
* inconsistent behavior in identifying available resources)
* @param shouldReroot if <code>true</code>, any matching {@link Resource}
* for this prefix will be rerooted to not include the initial prefix
* path; if <code>false</code>, the prefix will be included in a
* matching resource's path.
*/
public PathPrefix(String prefix, ResourceFilter filter, boolean shouldReroot) {
this("", prefix, filter, shouldReroot, null);
}
/**
* Construct a prefix.
*
* @param moduleName the name of the module that contained the Source or
* Public entry that this PathPrefix represents
* @param prefix a string prefix that (1) is the empty string or (2) begins
* with something other than a slash and ends with a slash
* @param filter the resource filter to use, or <code>null</code> for no
* filter; note that the filter must always return the same answer
* for the same candidate path (doing otherwise will produce
* inconsistent behavior in identifying available resources)
* @param shouldReroot if <code>true</code>, any matching {@link Resource}
* for this prefix will be rerooted to not include the initial prefix
* path; if <code>false</code>, the prefix will be included in a
* matching resource's path.
* @param excludeList list of globs that should be removed from <i>any</i>
* module's resources.
*/
public PathPrefix(String moduleName, String prefix, ResourceFilter filter,
boolean shouldReroot, String[] excludeList) {
assertValidPrefix(prefix);
this.moduleName = moduleName;
this.prefix = prefix;
this.filters = new ArrayList<ResourceFilter>(1);
this.filters.add(filter);
this.shouldReroot = shouldReroot;
this.exclusions = new HashSet<String>();
if (excludeList != null) {
for (String exclude : excludeList) {
exclusions.add(exclude);
}
}
}
/**
* Determines the inclusion/exclusion status and priority of a given path.
* <p>
* Determination is made using the prefix path, list of exclusions and list
* of filters (which are constructed from "includes" and "skips" entries in
* the xml).
*/
public Judgement getJudgement(String path) {
if (!path.startsWith(prefix)) {
return Judgement.IMPLICIT_EXCLUDE;
}
if (filters.size() == 0 && exclusions.size() == 0) {
return Judgement.FILTER_INCLUDE;
}
if (shouldReroot) {
path = getRerootedPath(path);
}
createExcludeFilter();
if (exclusionScanner != null && exclusionScanner.match(path)) {
return Judgement.EXCLUSION_EXCLUDE;
}
for (ResourceFilter filter : filters) {
if (filter == null || filter.allows(path)) {
return Judgement.FILTER_INCLUDE;
}
}
return Judgement.IMPLICIT_EXCLUDE;
}
/**
* Equality is based on prefixes representing the same string. Importantly,
* the filter does not affect equality.
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof PathPrefix) {
if (prefix.equals(((PathPrefix) obj).prefix)) {
return true;
}
}
return false;
}
/**
* The prefix.
*
* @return the result is guaranteed to be non-<code>null</code>, and
* either be the empty string or it will not begin with a slash and
* will end with a slash; these guarantees are very useful when
* concatenating paths that incorporate prefixes
*/
public String getPrefix() {
return prefix;
}
public String getRerootedPath(String path) {
assert (path.startsWith(prefix));
if (shouldReroot) {
return path.substring(prefix.length());
} else {
return path;
}
}
@Override
public int hashCode() {
return prefix.hashCode();
}
/**
* Consolidate a given {@code PathPrefix} with this one, such that resources
* excluded by neither prefix and included by either are allowed.
*
* @param pathPrefix
*/
public void merge(PathPrefix pathPrefix) {
assert prefix.equals(pathPrefix.prefix);
for (ResourceFilter filter : pathPrefix.filters) {
filters.add(filter);
}
exclusions.addAll(pathPrefix.exclusions);
if (exclusionScanner != null && !exclusions.isEmpty()) {
exclusionScanner = null; // lose the stale one; we'll recreate later
}
}
public boolean shouldReroot() {
return shouldReroot;
}
@Override
public String toString() {
return prefix + (shouldReroot ? "**" : "*") + (filters.size() == 0 ? "" : "?");
}
public String getModuleName() {
return moduleName;
}
int getPriority() {
return priority;
}
void setPriority(int priority) {
assert (this.priority == -1);
this.priority = priority;
}
private void assertValidPrefix(String prefix) {
assert (prefix != null);
assert ("".equals(prefix) || (!prefix.startsWith("/") && prefix.endsWith("/")))
&& !prefix.endsWith("//") : "malformed prefix";
}
private void createExcludeFilter() {
if (exclusionScanner == null && !exclusions.isEmpty()) {
exclusionScanner = new ZipScanner();
exclusionScanner.setIncludes(exclusions.toArray(new String[exclusions.size()]));
exclusionScanner.init();
exclusions.clear();
}
}
}