Implement --nocompress_suffixes option.
The planned replacement of the ApkBuilder with singlejar uses it.

--
MOS_MIGRATED_REVID=134290339
diff --git a/src/tools/singlejar/BUILD b/src/tools/singlejar/BUILD
index 63f6a45..cd02415 100644
--- a/src/tools/singlejar/BUILD
+++ b/src/tools/singlejar/BUILD
@@ -340,6 +340,7 @@
 java_library(
     name = "test1",
     resources = [
+        "options.cc",
         "zip_headers.h",
         "zlib_interface.h",
     ],
diff --git a/src/tools/singlejar/options.cc b/src/tools/singlejar/options.cc
index 7c609d8..11484c4 100644
--- a/src/tools/singlejar/options.cc
+++ b/src/tools/singlejar/options.cc
@@ -37,7 +37,8 @@
         tokens.MatchAndSet("--no_duplicates", &no_duplicates) ||
         tokens.MatchAndSet("--verbose", &verbose) ||
         tokens.MatchAndSet("--warn_duplicate_resources",
-                           &warn_duplicate_resources)) {
+                           &warn_duplicate_resources) ||
+        tokens.MatchAndSet("--nocompress_suffixes", &nocompress_suffixes)) {
       continue;
     } else if (tokens.MatchAndSet("--build_info_file", &optarg)) {
       build_info_files.push_back(optarg);
diff --git a/src/tools/singlejar/options.h b/src/tools/singlejar/options.h
index 753f0a2..cc01cd8 100644
--- a/src/tools/singlejar/options.h
+++ b/src/tools/singlejar/options.h
@@ -44,6 +44,7 @@
   std::vector<std::string> build_info_files;
   std::vector<std::string> build_info_lines;
   std::vector<std::string> include_prefixes;
+  std::vector<std::string> nocompress_suffixes;
   bool exclude_build_data;
   bool force_compression;
   bool normalize_timestamps;
diff --git a/src/tools/singlejar/options_test.cc b/src/tools/singlejar/options_test.cc
index 4a4d53a..14e1d4e 100644
--- a/src/tools/singlejar/options_test.cc
+++ b/src/tools/singlejar/options_test.cc
@@ -84,7 +84,8 @@
                         "--resources", "res1", "res2",
                         "--classpath_resources", "cpres1", "cpres2",
                         "--sources", "jar3",
-                        "--include_prefixes", "prefix1", "prefix2"};
+                        "--include_prefixes", "prefix1", "prefix2",
+                        "--nocompress_suffixes", ".png", ".so"};
   Options options;
   options.ParseCommandLine(arraysize(args), args);
 
@@ -101,6 +102,9 @@
   ASSERT_EQ(2, options.include_prefixes.size());
   EXPECT_EQ("prefix1", options.include_prefixes[0]);
   EXPECT_EQ("prefix2", options.include_prefixes[1]);
+  EXPECT_EQ(2, options.nocompress_suffixes.size());
+  EXPECT_EQ(".png", options.nocompress_suffixes[0]);
+  EXPECT_EQ(".so", options.nocompress_suffixes[1]);
 }
 
 TEST(OptionsTest, EmptyMultiOptargs) {
diff --git a/src/tools/singlejar/output_jar.cc b/src/tools/singlejar/output_jar.cc
index d5e2c10..77880a1 100644
--- a/src/tools/singlejar/output_jar.cc
+++ b/src/tools/singlejar/output_jar.cc
@@ -200,7 +200,19 @@
 
   // Then classpath resources.
   for (auto &classpath_resource : classpath_resources_) {
-    WriteEntry(classpath_resource->OutputEntry(compress));
+    bool do_compress = compress;
+    if (do_compress && !options_->nocompress_suffixes.empty()) {
+      for (auto &suffix : options_->nocompress_suffixes) {
+        auto entry_name = classpath_resource->filename();
+        if (entry_name.length() >= suffix.size() &&
+            !entry_name.compare(entry_name.length() - suffix.size(),
+                                suffix.size(), suffix)) {
+          do_compress = false;
+          break;
+        }
+      }
+    }
+    WriteEntry(classpath_resource->OutputEntry(do_compress));
   }
 
   // Then copy source files' contents.
@@ -340,30 +352,32 @@
       }
     }
 
-    // For the file entries and unless preserve_compression option is set,
-    // decide what to do with an entry depending on force_compress option
-    // and entry's current state:
-    //   force_compress    preserve_compress   compressed    Action
-    //         N                  N                 N        Copy
-    //         N                  N                 Y        Decompress
-    //         N                  Y                 *        Copy
-    //         Y                  *                 N        Compress
-    //         Y                  N                 Y        Copy
-    //         Y                  Y      can't be
-    if (is_file &&
-        !options_->preserve_compression &&
-        ((options_->force_compression &&
-          jar_entry->compression_method() == Z_NO_COMPRESSION) ||
-         (!options_->force_compression && !options_->preserve_compression &&
-          jar_entry->compression_method() == Z_DEFLATED))) {
-      // Change compression.
-      Concatenator combiner(jar_entry->file_name_string());
-      if (!combiner.Merge(jar_entry, lh)) {
-        diag_err(1, "%s:%d: cannot add %.*s", __FILE__, __LINE__,
-                 jar_entry->file_name_length(), jar_entry->file_name());
+    // For the file entries, decide whether output should be compressed.
+    if (is_file) {
+      bool input_compressed =
+          jar_entry->compression_method() != Z_NO_COMPRESSION;
+      bool output_compressed =
+          options_->force_compression ||
+          (options_->preserve_compression && input_compressed);
+      if (output_compressed && !options_->nocompress_suffixes.empty()) {
+        for (auto &suffix : options_->nocompress_suffixes) {
+          if (file_name_length >= suffix.size() &&
+              !strncmp(file_name + file_name_length - suffix.size(),
+                       suffix.c_str(), suffix.size())) {
+            output_compressed = false;
+            break;
+          }
+        }
       }
-      WriteEntry(combiner.OutputEntry(options_->force_compression));
-      continue;
+      if (input_compressed != output_compressed) {
+        Concatenator combiner(jar_entry->file_name_string());
+        if (!combiner.Merge(jar_entry, lh)) {
+          diag_err(1, "%s:%d: cannot add %.*s", __FILE__, __LINE__,
+                   jar_entry->file_name_length(), jar_entry->file_name());
+        }
+        WriteEntry(combiner.OutputEntry(output_compressed));
+        continue;
+      }
     }
 
     // Now we have to copy:
diff --git a/src/tools/singlejar/output_jar_simple_test.cc b/src/tools/singlejar/output_jar_simple_test.cc
index 6bad8e3..5bd0eca 100644
--- a/src/tools/singlejar/output_jar_simple_test.cc
+++ b/src/tools/singlejar/output_jar_simple_test.cc
@@ -643,4 +643,36 @@
   EXPECT_EQ("build: foo", GetEntryContents(out_path, kBuildDataFile));
 }
 
+// Test that the entries with suffixes in --nocompressed_suffixes are
+// not compressed. This applies both to the source archives' entries and
+// standalone files.
+TEST_F(OutputJarSimpleTest, Nocompress) {
+  string res1_path =
+      CreateTextFile("resource.foo", "line1\nline2\nline3\nline4\n");
+  string res2_path =
+      CreateTextFile("resource.bar", "line1\nline2\nline3\nline4\n");
+  string out_path = OutputFilePath("out.jar");
+  CreateOutput(out_path,
+               {"--compression", "--sources",
+                DATA_DIR_TOP "src/tools/singlejar/libtest1.jar", "--resources",
+                res1_path, res2_path, "--nocompress_suffixes", ".foo", ".h"});
+  InputJar input_jar;
+  ASSERT_TRUE(input_jar.Open(out_path));
+  const LH *lh;
+  const CDH *cdh;
+  while ((cdh = input_jar.NextEntry(&lh))) {
+    const char *entry_name_end = lh->file_name() + lh->file_name_length();
+    if (!strncmp(entry_name_end - 4, ".foo", 4) ||
+        !strncmp(entry_name_end - 2, ".h", 2)) {
+      EXPECT_EQ(Z_NO_COMPRESSION, lh->compression_method())
+          << "Expected " << lh->file_name_string() << " uncompressed";
+    } else if (!strncmp(entry_name_end - 3, ".cc", 3) ||
+               !strncmp(entry_name_end - 4, ".bar", 4)) {
+      EXPECT_EQ(Z_DEFLATED, lh->compression_method())
+          << "Expected " << lh->file_name_string() << " compressed";
+    }
+  }
+  input_jar.Close();
+}
+
 }  // namespace