/* * Copyright 2007 the original author or 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 org.gradle.api.tasks.util; import com.google.common.collect.Sets; import groovy.lang.Closure; import org.gradle.api.Action; import org.gradle.api.Incubating; import org.gradle.api.file.FileTreeElement; import org.gradle.api.specs.Spec; import org.gradle.api.specs.Specs; import org.gradle.api.tasks.AntBuilderAware; import org.gradle.api.tasks.util.internal.PatternSetAntBuilderDelegate; import org.gradle.api.tasks.util.internal.PatternSpecFactory; import org.gradle.internal.typeconversion.NotationParser; import org.gradle.internal.typeconversion.NotationParserBuilder; import org.gradle.util.CollectionUtils; import java.util.Collections; import java.util.Set; /** * Standalone implementation of {@link PatternFilterable}. */ public class PatternSet implements AntBuilderAware, PatternFilterable { private static final NotationParser<Object, String> PARSER = NotationParserBuilder.toType(String.class).fromCharSequence().toComposite(); private final PatternSpecFactory patternSpecFactory; private final Set<String> includes = Sets.newLinkedHashSet(); private final Set<String> excludes = Sets.newLinkedHashSet(); private final Set<Spec<FileTreeElement>> includeSpecs = Sets.newLinkedHashSet(); private final Set<Spec<FileTreeElement>> excludeSpecs = Sets.newLinkedHashSet(); private boolean caseSensitive = true; public PatternSet() { this(PatternSpecFactory.INSTANCE); } @Incubating protected PatternSet(PatternSet patternSet) { this(patternSet.patternSpecFactory); } @Incubating protected PatternSet(PatternSpecFactory patternSpecFactory) { this.patternSpecFactory = patternSpecFactory; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof PatternSet)) { return false; } PatternSet that = (PatternSet) o; if (caseSensitive != that.caseSensitive) { return false; } if (excludeSpecs != null ? !excludeSpecs.equals(that.excludeSpecs) : that.excludeSpecs != null) { return false; } if (excludes != null ? !excludes.equals(that.excludes) : that.excludes != null) { return false; } if (includeSpecs != null ? !includeSpecs.equals(that.includeSpecs) : that.includeSpecs != null) { return false; } if (includes != null ? !includes.equals(that.includes) : that.includes != null) { return false; } return true; } @Override public int hashCode() { int result = includes != null ? includes.hashCode() : 0; result = 31 * result + (excludes != null ? excludes.hashCode() : 0); result = 31 * result + (includeSpecs != null ? includeSpecs.hashCode() : 0); result = 31 * result + (excludeSpecs != null ? excludeSpecs.hashCode() : 0); result = 31 * result + (caseSensitive ? 1 : 0); return result; } public PatternSet copyFrom(PatternFilterable sourcePattern) { return doCopyFrom((PatternSet) sourcePattern); } protected PatternSet doCopyFrom(PatternSet from) { includes.clear(); excludes.clear(); includeSpecs.clear(); excludeSpecs.clear(); caseSensitive = from.caseSensitive; if (from instanceof IntersectionPatternSet) { PatternSet other = ((IntersectionPatternSet) from).other; PatternSet otherCopy = new PatternSet(other).copyFrom(other); PatternSet intersectCopy = new IntersectionPatternSet(otherCopy); intersectCopy.includes.addAll(from.includes); intersectCopy.excludes.addAll(from.excludes); intersectCopy.includeSpecs.addAll(from.includeSpecs); intersectCopy.excludeSpecs.addAll(from.excludeSpecs); includeSpecs.add(intersectCopy.getAsSpec()); } else { includes.addAll(from.includes); excludes.addAll(from.excludes); includeSpecs.addAll(from.includeSpecs); excludeSpecs.addAll(from.excludeSpecs); } return this; } public PatternSet intersect() { if(isEmpty()) { return new PatternSet(this.patternSpecFactory); } else { return new IntersectionPatternSet(this); } } /** * The PatternSet is considered empty when no includes or excludes have been added. * * The Spec returned by getAsSpec method only contains the default excludes patterns * in this case. * * @return true when no includes or excludes have been added to this instance */ public boolean isEmpty() { return getExcludes().isEmpty() && getIncludes().isEmpty() && getExcludeSpecs().isEmpty() && getIncludeSpecs().isEmpty(); } private static class IntersectionPatternSet extends PatternSet { private final PatternSet other; public IntersectionPatternSet(PatternSet other) { super(other); this.other = other; } public Spec<FileTreeElement> getAsSpec() { return Specs.intersect(super.getAsSpec(), other.getAsSpec()); } public Object addToAntBuilder(Object node, String childNodeName) { return PatternSetAntBuilderDelegate.and(node, new Action<Object>() { public void execute(Object andNode) { IntersectionPatternSet.super.addToAntBuilder(andNode, null); other.addToAntBuilder(andNode, null); } }); } @Override public boolean isEmpty() { return other.isEmpty() && super.isEmpty(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } if (!super.equals(o)) { return false; } IntersectionPatternSet that = (IntersectionPatternSet) o; return other != null ? other.equals(that.other) : that.other == null; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (other != null ? other.hashCode() : 0); return result; } } public Spec<FileTreeElement> getAsSpec() { return patternSpecFactory.createSpec(this); } public Spec<FileTreeElement> getAsIncludeSpec() { return patternSpecFactory.createIncludeSpec(this); } public Spec<FileTreeElement> getAsExcludeSpec() { return patternSpecFactory.createExcludeSpec(this); } public Set<String> getIncludes() { return includes; } public Set<Spec<FileTreeElement>> getIncludeSpecs() { return includeSpecs; } public PatternSet setIncludes(Iterable<String> includes) { this.includes.clear(); return include(includes); } public PatternSet include(String... includes) { Collections.addAll(this.includes, includes); return this; } public PatternSet include(Iterable includes) { for (Object include : includes) { this.includes.add(PARSER.parseNotation(include)); } return this; } public PatternSet include(Spec<FileTreeElement> spec) { includeSpecs.add(spec); return this; } public Set<String> getExcludes() { return excludes; } public Set<Spec<FileTreeElement>> getExcludeSpecs() { return excludeSpecs; } public PatternSet setExcludes(Iterable<String> excludes) { this.excludes.clear(); return exclude(excludes); } public boolean isCaseSensitive() { return caseSensitive; } public void setCaseSensitive(boolean caseSensitive) { this.caseSensitive = caseSensitive; } /* This can't be called just include, because it has the same erasure as include(Iterable<String>). */ public PatternSet includeSpecs(Iterable<Spec<FileTreeElement>> includeSpecs) { CollectionUtils.addAll(this.includeSpecs, includeSpecs); return this; } public PatternSet include(Closure closure) { include(Specs.<FileTreeElement>convertClosureToSpec(closure)); return this; } public PatternSet exclude(String... excludes) { Collections.addAll(this.excludes, excludes); return this; } public PatternSet exclude(Iterable excludes) { for (Object exclude : excludes) { this.excludes.add(PARSER.parseNotation(exclude)); } return this; } public PatternSet exclude(Spec<FileTreeElement> spec) { excludeSpecs.add(spec); return this; } public PatternSet excludeSpecs(Iterable<Spec<FileTreeElement>> excludes) { CollectionUtils.addAll(this.excludeSpecs, excludes); return this; } public PatternSet exclude(Closure closure) { exclude(Specs.<FileTreeElement>convertClosureToSpec(closure)); return this; } public Object addToAntBuilder(Object node, String childNodeName) { if (!includeSpecs.isEmpty() || !excludeSpecs.isEmpty()) { throw new UnsupportedOperationException("Cannot add include/exclude specs to Ant node. Only include/exclude patterns are currently supported."); } return new PatternSetAntBuilderDelegate(includes, excludes, caseSensitive).addToAntBuilder(node, childNodeName); } }