From 2ad8c6978f786795b501dd4e6fa6b94cd910a485 Mon Sep 17 00:00:00 2001 From: ajmichael Date: Tue, 31 Oct 2017 16:05:33 -0400 Subject: Open source ZipFilterAction for use in Android testing. https://github.com/bazelbuild/bazel/issues/903 RELNOTES: None PiperOrigin-RevId: 174079202 --- .../java/com/google/devtools/build/android/BUILD | 13 + .../build/android/ZipFilterActionTest.java | 296 +++++++++++++++++++++ .../java/com/google/devtools/build/android/BUILD | 10 + .../devtools/build/android/ZipFilterAction.java | 211 +++++++++++++++ .../build/android/ZipFilterEntryFilter.java | 71 +++++ 5 files changed, 601 insertions(+) create mode 100644 src/test/java/com/google/devtools/build/android/ZipFilterActionTest.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/ZipFilterAction.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/ZipFilterEntryFilter.java (limited to 'src') diff --git a/src/test/java/com/google/devtools/build/android/BUILD b/src/test/java/com/google/devtools/build/android/BUILD index 04b2194719..cb5f346c9b 100644 --- a/src/test/java/com/google/devtools/build/android/BUILD +++ b/src/test/java/com/google/devtools/build/android/BUILD @@ -262,6 +262,19 @@ java_test( ], ) +java_test( + name = "ZipFilterActionTest", + size = "small", + srcs = ["ZipFilterActionTest.java"], + deps = [ + "//src/java_tools/singlejar/java/com/google/devtools/build/singlejar:libSingleJar", + "//src/tools/android/java/com/google/devtools/build/android:android_builder_lib", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + java_library( name = "test_utils", testonly = 1, diff --git a/src/test/java/com/google/devtools/build/android/ZipFilterActionTest.java b/src/test/java/com/google/devtools/build/android/ZipFilterActionTest.java new file mode 100644 index 0000000000..a59f2cb58d --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/ZipFilterActionTest.java @@ -0,0 +1,296 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// 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.devtools.build.android; + +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Multimap; +import com.google.devtools.build.singlejar.ZipEntryFilter.CustomMergeStrategy; +import com.google.devtools.build.singlejar.ZipEntryFilter.StrategyCallback; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Date; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link ZipFilterAction}. */ +@RunWith(JUnit4.class) +public class ZipFilterActionTest { + + private static final class Entry { + private final String name; + private final String contents; + + public Entry(String name, String contents) { + this.name = name; + this.contents = contents; + } + + public String getName() { + return name; + } + + public String getContents() { + return contents; + } + } + + private enum FilterOperation { + SKIP, + RENAME, + CUSTOM_MERGE, + COPY + } + + private static final class TestingStrategyCallback implements StrategyCallback { + private FilterOperation operation; + + public void assertOp(FilterOperation operation) { + assertThat(this.operation).isEqualTo(operation); + } + + @Override + public void skip() throws IOException { + operation = FilterOperation.SKIP; + } + + @Override + public void rename(String filename, Date date) throws IOException { + operation = FilterOperation.RENAME; + } + + @Override + public void customMerge(Date date, CustomMergeStrategy strategy) throws IOException { + operation = FilterOperation.CUSTOM_MERGE; + } + + @Override + public void copy(Date date) throws IOException { + operation = FilterOperation.COPY; + } + } + + @Rule public ExpectedException thrown = ExpectedException.none(); + @Rule public TemporaryFolder tmp = new TemporaryFolder(); + + private int fileCount; + private TestingStrategyCallback callback; + + private Path createZip(String... filenames) throws IOException { + Entry[] entries = new Entry[filenames.length]; + int i = 0; + for (String filename : filenames) { + entries[i++] = new Entry(filename, "" + fileCount++); + } + return createZip(entries); + } + + private Path createZip(Entry... entries) throws IOException { + File zip = tmp.newFile(); + try (ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(zip))) { + for (Entry entry : entries) { + ZipEntry e = new ZipEntry(entry.getName()); + zout.putNextEntry(e); + zout.write(entry.getContents().getBytes(UTF_8)); + zout.closeEntry(); + } + } + return zip.toPath(); + } + + @Before public void setup() { + callback = new TestingStrategyCallback(); + } + + @Test public void testCreateFilter() throws IOException { + ImmutableSet filters = ImmutableSet.of( + createZip("foo.class", "bar.java"), + createZip("foo.java", "bar.java", "baz.class")); + ImmutableSet types = ImmutableSet.of(".class"); + Multimap filterFiles = ZipFilterAction.getEntriesToOmit(filters, types); + assertThat(filterFiles.keySet()).containsExactly("foo.class", "baz.class"); + assertThat(filterFiles).valuesForKey("foo.class").hasSize(1); + assertThat(filterFiles).valuesForKey("baz.class").hasSize(1); + } + + @Test public void testCreateFilter_NoZips() throws IOException { + ImmutableSet filters = ImmutableSet.of(); + ImmutableSet types = ImmutableSet.of(".class"); + Multimap filterFiles = ZipFilterAction.getEntriesToOmit(filters, types); + assertThat(filterFiles).isEmpty(); + } + + @Test public void testCreateFilter_NoTypes() throws IOException { + ImmutableSet filters = ImmutableSet.of( + createZip("foo.class", "bar.java"), + createZip("foo.java", "bar.java", "baz.class")); + ImmutableSet types = ImmutableSet.of(); + Multimap filterFiles = ZipFilterAction.getEntriesToOmit(filters, types); + assertThat(filterFiles.keySet()) + .containsExactly("foo.class", "bar.java", "foo.java", "baz.class"); + } + + @Test public void testCreateFilter_MultipleTypes() throws IOException { + ImmutableSet filters = ImmutableSet.of( + createZip("foo.class", "bar.java"), + createZip("foo.java", "bar.java", "baz.class")); + ImmutableSet types = ImmutableSet.of(".class", "bar.java"); + Multimap filterFiles = ZipFilterAction.getEntriesToOmit(filters, types); + assertThat(filterFiles.keySet()).containsExactly("foo.class", "baz.class", "bar.java"); + assertThat(filterFiles).valuesForKey("foo.class").hasSize(1); + assertThat(filterFiles).valuesForKey("bar.java").hasSize(2); + } + + @Test public void testZipEntryFilter() throws Exception { + ZipFilterEntryFilter filter = new ZipFilterEntryFilter(".*R.class.*", + ImmutableSetMultimap.of("foo.class", 1L, "baz.class", 2L), + ImmutableMap.of("foo.class", 1L, "bar.class", 2L, "baz.class", 3L, "res/R.class", 4L), + false); + filter.accept("foo.class", callback); + callback.assertOp(FilterOperation.SKIP); + filter.accept("bar.class", callback); + callback.assertOp(FilterOperation.COPY); + filter.accept("baz.class", callback); + callback.assertOp(FilterOperation.COPY); + filter.accept("res/R.class", callback); + callback.assertOp(FilterOperation.SKIP); + } + + @Test public void testZipEntryFilter_ErrorOnMismatch() throws Exception { + ZipFilterEntryFilter filter = new ZipFilterEntryFilter(".*R.class.*", + ImmutableSetMultimap.of("foo.class", 1L, "baz.class", 2L), + ImmutableMap.of("foo.class", 1L, "bar.class", 2L, "baz.class", 3L, "res/R.class", 4L), + true); + filter.accept("foo.class", callback); + callback.assertOp(FilterOperation.SKIP); + filter.accept("bar.class", callback); + callback.assertOp(FilterOperation.COPY); + filter.accept("res/R.class", callback); + callback.assertOp(FilterOperation.SKIP); + thrown.expect(IllegalStateException.class); + thrown.expectMessage("name matches but the hash does not."); + filter.accept("baz.class", callback); + } + + @Test public void testFlags() throws Exception { + File input = tmp.newFile("input"); + File output = tmp.newFile("output"); + output.delete(); + File filter1 = tmp.newFile("filter1"); + File filter2 = tmp.newFile("filter2"); + + ImmutableList args = ImmutableList.of( + "--inputZip", input.getPath(), + "--outputZip", output.getPath(), + "--filterZips", Joiner.on(",").join(filter1.getPath(), filter2.getPath(), + filter1.getPath()), + "--filterTypes", Joiner.on(",").join(".class", ".class", ".java"), + "--explicitFilters", Joiner.on(",").join("R\\.class", "R\\$.*\\.class"), + "--outputMode", "DONT_CARE", + "--noerrorOnHashMismatch"); + thrown.expect(ZipException.class); + thrown.expectMessage("Zip file 'filter1' is malformed"); + ZipFilterAction.main(args.toArray(new String[0])); + } + + @Test public void testFullIntegration() throws IOException { + Path input = createZip(new Entry("foo.java", "foo"), new Entry("bar.class", "bar"), + new Entry("baz.class", "baz"), new Entry("1.class", "1"), new Entry("2.class", "2"), + new Entry("R.class", "r"), new Entry("Read.class", "read")); + File output = tmp.newFile(); + output.delete(); + Path filter1 = createZip(new Entry("1.class", "1"), new Entry("b.class", "b")); + Path filter2 = createZip(new Entry("1.class", "2"), new Entry("2.class", "1"), + new Entry("foo.java", "foo"), new Entry("bar.class", "bar")); + ImmutableList args = ImmutableList.of( + "--inputZip", input.toFile().getPath(), + "--outputZip", output.getPath(), + "--filterZips", Joiner.on(",").join(filter1.toFile().getPath(), filter2.toFile().getPath(), + filter1.toFile().getPath()), + "--filterTypes", ".class", + "--explicitFilters", Joiner.on(",").join("R\\.class", "R\\$.*\\.class"), + "--outputMode", "DONT_CARE"); + ZipFilterAction.main(args.toArray(new String[0])); + List filteredEntries = new ArrayList<>(); + try (ZipFile zip = new ZipFile(output)) { + Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + filteredEntries.add(entries.nextElement().getName()); + } + } + assertThat(filteredEntries).containsExactly("foo.java", "baz.class", "2.class", "Read.class"); + } + + @Test public void testFullIntegrationErrorsOnHash() throws IOException { + Path input = createZip("foo.java", "bar.class", "baz.class"); + File output = tmp.newFile(); + output.delete(); + Path filter = createZip("foo.java", "bar.class"); + ImmutableList args = ImmutableList.of( + "--inputZip", input.toFile().getPath(), + "--outputZip", output.getPath(), + "--filterZips", filter.toFile().getPath(), + "--filterTypes", ".class", + "--outputMode", "DONT_CARE", + "--errorOnHashMismatch"); + thrown.expect(IllegalStateException.class); + thrown.expectMessage("name matches but the hash does not"); + ZipFilterAction.main(args.toArray(new String[0])); + } + + @Test public void testFullIntegrationErrorsOnHash_WithExplicitOverride() + throws IOException { + Path input = createZip("foo.java", "bar.class", "baz.class"); + File output = tmp.newFile(); + output.delete(); + Path filter = createZip("foo.java", "bar.class"); + ImmutableList args = ImmutableList.of( + "--inputZip", input.toFile().getPath(), + "--outputZip", output.getPath(), + "--filterZips", filter.toFile().getPath(), + "--filterTypes", ".class", + "--explicitFilters", "bar\\.class", + "--outputMode", "DONT_CARE", + "--errorOnHashMismatch"); + ZipFilterAction.main(args.toArray(new String[0])); + List filteredEntries = new ArrayList<>(); + try (ZipFile zip = new ZipFile(output)) { + Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + filteredEntries.add(entries.nextElement().getName()); + } + } + assertThat(filteredEntries).containsExactly("foo.java", "baz.class"); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/BUILD b/src/tools/android/java/com/google/devtools/build/android/BUILD index 916252726e..6bf42f53e3 100644 --- a/src/tools/android/java/com/google/devtools/build/android/BUILD +++ b/src/tools/android/java/com/google/devtools/build/android/BUILD @@ -38,6 +38,13 @@ java_binary( ], ) +java_binary( + name = "ZipFilterAction", + main_class = "com.google.devtools.build.android.ZipFilterAction", + visibility = ["//visibility:private"], + runtime_deps = [":android_builder_lib"], +) + java_library( name = "android_builder_lib", srcs = glob([ @@ -46,6 +53,8 @@ java_library( "aapt2/*.java", ]), deps = [ + "//src/java_tools/singlejar/java/com/google/devtools/build/singlejar:libSingleJar", + "//src/java_tools/singlejar/java/com/google/devtools/build/zip", "//src/main/java/com/google/devtools/common/options", "//src/tools/android/java/com/google/devtools/build/android/junctions", "//src/tools/android/java/com/google/devtools/build/android/proto:serialize_format_java_pb", @@ -56,6 +65,7 @@ java_library( "//third_party:jsr305", "//third_party/java/android_databinding:exec", "//third_party/java/aosp_gradle_core", + "//third_party/java/jcommander", "//third_party/protobuf:protobuf_java", ], ) diff --git a/src/tools/android/java/com/google/devtools/build/android/ZipFilterAction.java b/src/tools/android/java/com/google/devtools/build/android/ZipFilterAction.java new file mode 100644 index 0000000000..f7225eb6d4 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/ZipFilterAction.java @@ -0,0 +1,211 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// 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.devtools.build.android; + +import com.beust.jcommander.IStringConverter; +import com.beust.jcommander.IValueValidator; +import com.beust.jcommander.JCommander; +import com.beust.jcommander.Parameter; +import com.beust.jcommander.ParameterException; +import com.beust.jcommander.Parameters; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Multimap; +import com.google.devtools.build.singlejar.ZipCombiner; +import com.google.devtools.build.singlejar.ZipCombiner.OutputMode; +import com.google.devtools.build.singlejar.ZipEntryFilter; +import com.google.devtools.build.zip.ZipFileEntry; +import com.google.devtools.build.zip.ZipReader; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +/** + * Action to filter entries out of a Zip file. + * + *

The entries to remove are determined from the filterZips and filterTypes. All entries from the + * filter Zip files that have an extension listed in filterTypes will be removed. If no filterZips + * are specified, no entries will be removed. Specifying no filterTypes is treated as if an + * extension of '.*' was specified. + * + *

Assuming each Zip as a set of entries, the result is: + *

 outputZip = inputZip - union[x intersect filterTypes for x in filterZips]
+ * + *

+ * Example Usage:
+ *   java/com/google/build/android/ZipFilterAction\
+ *      --inputZip path/to/inputZip
+ *      --outputZip path/to/outputZip
+ *      --filterZips [path/to/filterZip[,path/to/filterZip]...]
+ *      --filterTypes [fileExtension[,fileExtension]...]
+ *      --explicitFilters [fileRegex[,fileRegex]...]
+ *      --outputMode [DONT_CARE|FORCE_DEFLATE|FORCE_STORED]
+ *      --errorOnHashMismatch
+ * 
+ */ +public class ZipFilterAction { + + private static final Logger logger = Logger.getLogger(ZipFilterAction.class.getName()); + + @Parameters(optionPrefixes = "--") + static class Options { + @Parameter( + names = "--inputZip", + description = "Path of input zip.", + converter = PathFlagConverter.class, + validateValueWith = PathExistsValidator.class + ) + Path inputZip; + + @Parameter( + names = "--outputZip", + description = "Path to write output zip.", + converter = PathFlagConverter.class + ) + Path outputZip; + + @Parameter( + names = "--filterZips", + description = "Filter zips.", + converter = PathFlagConverter.class, + validateValueWith = AllPathsExistValidator.class + ) + List filterZips = ImmutableList.of(); + + @Parameter(names = "--filterTypes", description = "Filter file types.") + List filterTypes = ImmutableList.of(); + + @Parameter(names = "--explicitFilters", description = "Explicitly specified filters.") + List explicitFilters = ImmutableList.of(); + + @Parameter(names = "--outputMode", description = "Output zip compression mode.") + OutputMode outputMode = OutputMode.DONT_CARE; + + @Parameter( + names = "--errorOnHashMismatch", + description = "Error on entry filter with hash mismatch." + ) + boolean errorOnHashMismatch = false; + + // This is a hack to support existing users of --noerrorOnHashMismatch. JCommander does not + // support setting boolean flags with "--no", so instead we set the default to false and just + // ignore anyone who passes --noerrorOnHashMismatch. + @Parameter(names = "--noerrorOnHashMismatch") + boolean ignored = false; + } + + /** Converts string flags to paths. Public because JCommander invokes this by reflection. */ + public static class PathFlagConverter implements IStringConverter { + + @Override + public Path convert(String text) { + return FileSystems.getDefault().getPath(text); + } + } + + /** Validates that a path exists. Public because JCommander invokes this by reflection. */ + public static class PathExistsValidator implements IValueValidator { + + @Override + public void validate(String s, Path path) { + if (!Files.exists(path)) { + throw new ParameterException(String.format("%s is not a valid path.", path.toString())); + } + } + } + + /** Validates that a set of paths exist. Public because JCommander invokes this by reflection. */ + public static class AllPathsExistValidator implements IValueValidator> { + + @Override + public void validate(String s, List paths) { + for (Path path : paths) { + if (!Files.exists(path)) { + throw new ParameterException(String.format("%s is not a valid path.", path.toString())); + } + } + } + } + + @VisibleForTesting + static Multimap getEntriesToOmit( + Collection filterZips, Collection filterTypes) throws IOException { + // Escape filter types to prevent regex abuse + Set escapedFilterTypes = new HashSet<>(); + for (String filterType : filterTypes) { + escapedFilterTypes.add(Pattern.quote(filterType)); + } + // Match any string that ends with any of the filter file types + String filterRegex = String.format(".*(%s)$", Joiner.on("|").join(escapedFilterTypes)); + + ImmutableSetMultimap.Builder entriesToOmit = ImmutableSetMultimap.builder(); + for (Path filterZip : filterZips) { + try (ZipReader zip = new ZipReader(filterZip.toFile())) { + for (ZipFileEntry entry : zip.entries()) { + if (filterTypes.isEmpty() || entry.getName().matches(filterRegex)) { + entriesToOmit.put(entry.getName(), entry.getCrc()); + } + } + } + } + return entriesToOmit.build(); + } + + public static void main(String[] args) throws IOException { + Options options = new Options(); + new JCommander(options).parse(args); + logger.fine( + String.format( + "Creating filter from entries of type %s, in zip files %s.", + options.filterTypes, options.filterZips)); + + final Stopwatch timer = Stopwatch.createStarted(); + final Multimap entriesToOmit = + getEntriesToOmit(options.filterZips, options.filterTypes); + final String explicitFilter = + options.explicitFilters.isEmpty() + ? "" + : String.format(".*(%s).*", Joiner.on("|").join(options.explicitFilters)); + logger.fine(String.format("Filter created in %dms", timer.elapsed(TimeUnit.MILLISECONDS))); + + ImmutableMap.Builder inputEntries = ImmutableMap.builder(); + try (ZipReader input = new ZipReader(options.inputZip.toFile())) { + for (ZipFileEntry entry : input.entries()) { + inputEntries.put(entry.getName(), entry.getCrc()); + } + } + ZipEntryFilter entryFilter = + new ZipFilterEntryFilter( + explicitFilter, entriesToOmit, inputEntries.build(), options.errorOnHashMismatch); + + try (OutputStream out = Files.newOutputStream(options.outputZip); + ZipCombiner combiner = new ZipCombiner(options.outputMode, entryFilter, out)) { + combiner.addZip(options.inputZip.toFile()); + } + logger.fine(String.format("Filtering completed in %dms", timer.elapsed(TimeUnit.MILLISECONDS))); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/ZipFilterEntryFilter.java b/src/tools/android/java/com/google/devtools/build/android/ZipFilterEntryFilter.java new file mode 100644 index 0000000000..f6e556b626 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/ZipFilterEntryFilter.java @@ -0,0 +1,71 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// 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.devtools.build.android; + +import com.google.common.collect.Multimap; +import com.google.devtools.build.singlejar.ZipEntryFilter; + +import java.io.IOException; +import java.util.Map; + +/** + * A {@link ZipEntryFilter} for use with {@link ZipFilterAction}. Filters out entries that match the + * provided regular expression or are in the list to omit. + */ +class ZipFilterEntryFilter implements ZipEntryFilter { + + private final String explicitFilter; + private final Multimap entriesToOmit; + private final Map inputEntries; + private final boolean errorOnHashMismatch; + + /** + * Creates a new filter. + * + * @param explicitFilter a regular expression to match against entry filenames + * @param entriesToOmit a map of filename and CRC-32 of entries to omit + * @param inputEntries a map of filename and CRC-32 of entries in the input Zip file + * @param errorOnHashMistmatch whether to error or warn when there is a CRC-32 mismatch + */ + public ZipFilterEntryFilter(String explicitFilter, Multimap entriesToOmit, + Map inputEntries, boolean errorOnHashMistmatch) { + this.explicitFilter = explicitFilter; + this.entriesToOmit = entriesToOmit; + this.inputEntries = inputEntries; + this.errorOnHashMismatch = errorOnHashMistmatch; + } + + @Override + public void accept(String filename, StrategyCallback callback) throws IOException { + if (filename.matches(explicitFilter)) { + callback.skip(); + } else if (entriesToOmit.containsKey(filename)) { + Long entryCrc = inputEntries.get(filename); + if (entriesToOmit.containsEntry(filename, entryCrc)) { + callback.skip(); + } else { + if (errorOnHashMismatch) { + throw new IllegalStateException(String.format("Requested to filter entries of name " + + "'%s'; name matches but the hash does not. Aborting", filename)); + } else { + System.out.printf("\u001b[35mWARNING:\u001b[0m Requested to filter entries of name " + + "'%s'; name matches but the hash does not. Copying anyway.\n", filename); + callback.copy(null); + } + } + } else { + callback.copy(null); + } + } +} -- cgit v1.2.3