Merge "Support for -adaptresourcefilecontents"
diff --git a/src/main/java/com/android/tools/r8/DataEntryResource.java b/src/main/java/com/android/tools/r8/DataEntryResource.java
index aa9c73f..b6443a4 100644
--- a/src/main/java/com/android/tools/r8/DataEntryResource.java
+++ b/src/main/java/com/android/tools/r8/DataEntryResource.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.origin.ArchiveEntryOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -21,6 +22,10 @@
   /** Get the bytes of the data entry resource. */
   InputStream getByteStream() throws ResourceException;
 
+  static DataEntryResource fromBytes(byte[] bytes, String name, Origin origin) {
+    return new ByteDataEntryResource(bytes, name, origin);
+  }
+
   static DataEntryResource fromFile(Path dir, Path file) {
     return new LocalDataEntryResource(dir.resolve(file).toFile(),
         file.toString().replace(File.separatorChar, SEPARATOR));
@@ -30,6 +35,34 @@
     return new ZipDataEntryResource(zip, entry);
   }
 
+  class ByteDataEntryResource implements DataEntryResource {
+
+    private final byte[] bytes;
+    private final String name;
+    private final Origin origin;
+
+    public ByteDataEntryResource(byte[] bytes, String name, Origin origin) {
+      this.bytes = bytes;
+      this.name = name;
+      this.origin = origin;
+    }
+
+    @Override
+    public InputStream getByteStream() {
+      return new ByteArrayInputStream(bytes);
+    }
+
+    @Override
+    public String getName() {
+      return name;
+    }
+
+    @Override
+    public Origin getOrigin() {
+      return origin;
+    }
+  }
+
   class ZipDataEntryResource implements DataEntryResource {
     private final ZipFile zip;
     private final ZipEntry entry;
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 736cb3f..3d18ea3 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -316,21 +316,26 @@
           .filter(Objects::nonNull)
           .collect(Collectors.toList());
 
+      ResourceAdapter resourceAdapter =
+          new ResourceAdapter(application.dexItemFactory, graphLense, namingLens, options);
+
       for (DataResourceProvider dataResourceProvider : dataResourceProviders) {
         try {
-          dataResourceProvider.accept(new Visitor() {
-            @Override
-            public void visit(DataDirectoryResource directory) {
-              dataResourceConsumer.accept(directory, options.reporter);
-              options.reporter.failIfPendingErrors();
-            }
+          dataResourceProvider.accept(
+              new Visitor() {
+                @Override
+                public void visit(DataDirectoryResource directory) {
+                  dataResourceConsumer.accept(directory, options.reporter);
+                  options.reporter.failIfPendingErrors();
+                }
 
-            @Override
-            public void visit(DataEntryResource file) {
-              dataResourceConsumer.accept(file, options.reporter);
-              options.reporter.failIfPendingErrors();
-            }
-          });
+                @Override
+                public void visit(DataEntryResource file) {
+                  dataResourceConsumer.accept(
+                      resourceAdapter.adaptFileContentsIfNeeded(file), options.reporter);
+                  options.reporter.failIfPendingErrors();
+                }
+              });
         } catch (ResourceException e) {
           throw new CompilationError(e.getMessage(), e);
         }
diff --git a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
new file mode 100644
index 0000000..e325933
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -0,0 +1,192 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.dex;
+
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.ProguardPathFilter;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.io.ByteStreams;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+
+public class ResourceAdapter {
+
+  private final DexItemFactory dexItemFactory;
+  private final GraphLense graphLense;
+  private final NamingLens namingLense;
+  private final InternalOptions options;
+
+  public ResourceAdapter(
+      DexItemFactory dexItemFactory,
+      GraphLense graphLense,
+      NamingLens namingLense,
+      InternalOptions options) {
+    this.dexItemFactory = dexItemFactory;
+    this.graphLense = graphLense;
+    this.namingLense = namingLense;
+    this.options = options;
+  }
+
+  public DataEntryResource adaptFileContentsIfNeeded(DataEntryResource file) {
+    ProguardPathFilter filter = options.proguardConfiguration.getAdaptResourceFileContents();
+    return filter.isEnabled()
+            && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
+            && filter.matches(file.getName())
+        ? adaptFileContents(file)
+        : file;
+  }
+
+  // According to the Proguard documentation, the resource files should be parsed and written using
+  // the platform's default character set.
+  private DataEntryResource adaptFileContents(DataEntryResource file) {
+    try (InputStream in = file.getByteStream()) {
+      byte[] bytes = ByteStreams.toByteArray(in);
+      String contents = new String(bytes, Charset.defaultCharset());
+
+      FileContentsAdapter adapter = new FileContentsAdapter(contents);
+      if (adapter.run()) {
+        return DataEntryResource.fromBytes(
+            adapter.getResult().getBytes(Charset.defaultCharset()),
+            file.getName(),
+            file.getOrigin());
+      }
+    } catch (ResourceException e) {
+      options.reporter.error(
+          new StringDiagnostic("Failed to open input: " + e.getMessage(), file.getOrigin()));
+    } catch (Exception e) {
+      options.reporter.error(new ExceptionDiagnostic(e, file.getOrigin()));
+    }
+    return file;
+  }
+
+  private class FileContentsAdapter {
+
+    private final String contents;
+    private final StringBuilder result = new StringBuilder();
+
+    // If any type names in `contents` have been updated. If this flag is still true in the end,
+    // then we can simply use the resource as it was.
+    private boolean changed = false;
+    private int outputFrom = 0;
+    private int position = 0;
+
+    public FileContentsAdapter(String contents) {
+      this.contents = contents;
+    }
+
+    public boolean run() {
+      do {
+        handleMisc();
+        handleJavaType();
+      } while (!eof());
+      if (changed) {
+        // At least one type was renamed. We need to flush all characters in `contents` that follow
+        // the last type that was renamed.
+        outputRangeFromInput(outputFrom, contents.length());
+      } else {
+        // No types were renamed. In this case the adapter should simply have scanned through
+        // `contents`, without outputting anything to `result`.
+        assert outputFrom == 0;
+        assert result.toString().isEmpty();
+      }
+      return changed;
+    }
+
+    public String getResult() {
+      assert changed;
+      return result.toString();
+    }
+
+    // Forwards the cursor until the current character is a Java identifier part.
+    private void handleMisc() {
+      while (!eof() && !Character.isJavaIdentifierPart(contents.charAt(position))) {
+        position++;
+      }
+    }
+
+    // Reads a Java type from the current position in `contents`, and then checks if the given
+    // type has been renamed.
+    private void handleJavaType() {
+      if (eof()) {
+        return;
+      }
+
+      assert Character.isJavaIdentifierPart(contents.charAt(position));
+      int start = position++;
+      while (!eof()) {
+        char currentChar = contents.charAt(position);
+        if (Character.isJavaIdentifierPart(currentChar)) {
+          position++;
+          continue;
+        }
+        if (currentChar == '.'
+            && !eof(position + 1)
+            && Character.isJavaIdentifierPart(contents.charAt(position + 1))) {
+          // Consume the dot and the Java identifier part that follows the dot.
+          position += 2;
+          continue;
+        }
+
+        // Not a valid extension of the type name.
+        break;
+      }
+
+      if ((start > 0 && isDashOrDot(contents.charAt(start - 1)))
+          || (!eof(position) && isDashOrDot(contents.charAt(position)))) {
+        // The Java type starts with '-' or '.', and should not be renamed.
+        return;
+      }
+
+      String javaType = contents.substring(start, position);
+      DexString descriptor =
+          dexItemFactory.lookupString(
+              DescriptorUtils.javaTypeToDescriptorIgnorePrimitives(javaType));
+      DexType dexType = descriptor != null ? dexItemFactory.lookupType(descriptor) : null;
+      if (dexType != null) {
+        DexString renamedDescriptor = namingLense.lookupDescriptor(graphLense.lookupType(dexType));
+        if (!descriptor.equals(renamedDescriptor)) {
+          // Need to flush all changes up to and excluding 'start', and then output the renamed
+          // type.
+          outputRangeFromInput(outputFrom, start);
+          outputString(DescriptorUtils.descriptorToJavaType(renamedDescriptor.toSourceString()));
+          outputFrom = position;
+          changed = true;
+        }
+      }
+    }
+
+    private boolean isDashOrDot(char c) {
+      return c == '.' || c == '-';
+    }
+
+    private void outputRangeFromInput(int from, int toExclusive) {
+      if (from < toExclusive) {
+        result.append(contents.substring(from, toExclusive));
+      }
+    }
+
+    private void outputString(String s) {
+      result.append(s);
+    }
+
+    private boolean eof() {
+      return eof(position);
+    }
+
+    private boolean eof(int position) {
+      return position == contents.length();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 91b1dc7..3f02009 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -573,6 +573,10 @@
     return canonicalize(strings, new DexString(source));
   }
 
+  public DexString lookupString(String source) {
+    return strings.get(new DexString(source));
+  }
+
   // TODO(b/67934123) Unify into one method,
   public DexItemBasedString createItemBasedString(DexType type) {
     assert !sorted;
@@ -635,7 +639,7 @@
     return createType(createString(descriptor));
   }
 
-  synchronized public DexType lookupType(DexString descriptor) {
+  public DexType lookupType(DexString descriptor) {
     return types.get(descriptor);
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 7526e3f..05539fe 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -57,7 +57,7 @@
     private final ProguardClassFilter.Builder adaptClassStrings = ProguardClassFilter.builder();
     private final ProguardPathFilter.Builder adaptResourceFilenames = ProguardPathFilter.builder();
     private final ProguardPathFilter.Builder adaptResourceFileContents =
-        ProguardPathFilter.builder();
+        ProguardPathFilter.builder().disable();
     private final ProguardPathFilter.Builder keepDirectories = ProguardPathFilter.builder();
     private boolean forceProguardCompatibility = false;
     private boolean overloadAggressively;
@@ -235,6 +235,10 @@
       adaptResourceFilenames.addPattern(pattern);
     }
 
+    public void enableAdaptResourceFileContents() {
+      adaptResourceFileContents.enable();
+    }
+
     public void addAdaptResourceFileContents(ProguardPathList pattern) {
       adaptResourceFileContents.addPattern(pattern);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index b9c3012..855ba98 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -370,10 +370,7 @@
         }
         parsePathFilter(configurationBuilder::addAdaptResourceFilenames);
       } else if (acceptString("adaptresourcefilecontents")) {
-        // TODO(36847655): Report an error until it's fully supported.
-        if (failOnPartiallyImplementedOptions) {
-          failPartiallyImplementedOption("-adaptresourcefilecontents", optionStart);
-        }
+        configurationBuilder.enableAdaptResourceFileContents();
         parsePathFilter(configurationBuilder::addAdaptResourceFileContents);
       } else if (acceptString("identifiernamestring")) {
         configurationBuilder.addRule(parseIdentifierNameStringRule(optionStart));
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardPathFilter.java b/src/main/java/com/android/tools/r8/shaking/ProguardPathFilter.java
index cb2e117..b3771ef 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardPathFilter.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardPathFilter.java
@@ -7,6 +7,7 @@
 import com.google.common.collect.ImmutableList;
 
 public class ProguardPathFilter {
+  private final boolean enabled;
   private final ImmutableList<ProguardPathList> patterns;
 
   public static Builder builder() {
@@ -14,6 +15,7 @@
   }
 
   public static class Builder {
+    private boolean enabled = true;
     private final ImmutableList.Builder<ProguardPathList> patterns = ImmutableList.builder();
 
     private Builder() {
@@ -24,23 +26,41 @@
       return this;
     }
 
+    public Builder disable() {
+      enabled = false;
+      return this;
+    }
+
+    public Builder enable() {
+      enabled = true;
+      return this;
+    }
+
     ProguardPathFilter build() {
-      return new ProguardPathFilter(patterns.build());
+      return new ProguardPathFilter(patterns.build(), enabled);
     }
   }
 
-  private ProguardPathFilter(ImmutableList<ProguardPathList> patterns) {
+  private ProguardPathFilter(ImmutableList<ProguardPathList> patterns, boolean enabled) {
+    this.enabled = enabled;
     if (patterns.isEmpty()) {
       this.patterns = ImmutableList.of(ProguardPathList.emptyList());
     } else {
+      assert enabled;
       this.patterns = patterns;
     }
   }
 
+  public boolean isEnabled() {
+    return enabled;
+  }
+
   public boolean matches(String path) {
-    for (ProguardPathList pattern : patterns) {
-      if (pattern.matches(path)) {
-        return true;
+    if (enabled) {
+      for (ProguardPathList pattern : patterns) {
+        if (pattern.matches(path)) {
+          return true;
+        }
       }
     }
     return false;
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 1b026c0..acf9b62 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -10,6 +10,8 @@
 import com.android.tools.r8.ArchiveClassFileProvider;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceProvider;
 import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DirectoryClassFileProvider;
@@ -267,6 +269,7 @@
 
     private final List<ProgramResourceProvider> programResourceProviders = new ArrayList<>();
     private final List<ProgramResource> programResources = new ArrayList<>();
+    private final List<DataEntryResource> dataResources = new ArrayList<>();
     private final Map<ProgramResource, String> programResourcesMainDescriptor = new HashMap<>();
     private final List<ClassFileResourceProvider> classpathResourceProviders = new ArrayList<>();
     private final List<ClassFileResourceProvider> libraryResourceProviders = new ArrayList<>();
@@ -464,6 +467,12 @@
       return this;
     }
 
+    /** Add resource data. */
+    public Builder addDataResource(byte[] bytes, String name, Origin origin) {
+      addDataResources(DataEntryResource.fromBytes(bytes, name, origin));
+      return this;
+    }
+
     /**
      * Set proguard-map output data.
      *
@@ -529,17 +538,34 @@
      * Build final AndroidApp.
      */
     public AndroidApp build() {
-      if (!programResources.isEmpty()) {
+      if (!programResources.isEmpty() || !dataResources.isEmpty()) {
         // If there are individual program resources move them to a dedicated provider.
-        final List<ProgramResource> resources = ImmutableList.copyOf(programResources);
+        final List<ProgramResource> finalProgramResources = ImmutableList.copyOf(programResources);
+        final List<DataEntryResource> finalDataResources = ImmutableList.copyOf(dataResources);
         programResourceProviders.add(
             new ProgramResourceProvider() {
               @Override
-              public Collection<ProgramResource> getProgramResources() throws ResourceException {
-                return resources;
+              public Collection<ProgramResource> getProgramResources() {
+                return finalProgramResources;
+              }
+
+              @Override
+              public DataResourceProvider getDataResourceProvider() {
+                if (!finalDataResources.isEmpty()) {
+                  return new DataResourceProvider() {
+                    @Override
+                    public void accept(Visitor visitor) {
+                      for (DataEntryResource dataResource : finalDataResources) {
+                        visitor.visit(dataResource);
+                      }
+                    }
+                  };
+                }
+                return null;
               }
             });
         programResources.clear();
+        dataResources.clear();
       }
       return new AndroidApp(
           ImmutableList.copyOf(programResourceProviders),
@@ -576,6 +602,14 @@
       programResources.addAll(resources);
     }
 
+    private void addDataResources(DataEntryResource... resources) {
+      addDataResources(Arrays.asList(resources));
+    }
+
+    private void addDataResources(Collection<DataEntryResource> resources) {
+      this.dataResources.addAll(resources);
+    }
+
     private void addClasspathOrLibraryProvider(
         Path file, List<ClassFileResourceProvider> providerList) throws IOException {
       if (!Files.exists(file)) {
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
new file mode 100644
index 0000000..7b96286
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -0,0 +1,298 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DataDirectoryResource;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class AdaptResourceFileContentsTest extends TestBase {
+
+  private static class CustomDataResourceConsumer implements DataResourceConsumer {
+
+    private final Map<String, ImmutableList<String>> resources = new HashMap<>();
+
+    @Override
+    public void accept(DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+      try {
+        byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+        String contents = new String(bytes, Charset.defaultCharset());
+        resources.put(file.getName(), ImmutableList.copyOf(contents.split(System.lineSeparator())));
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    @Override
+    public void finished(DiagnosticsHandler handler) {}
+
+    public ImmutableList<String> get(String name) {
+      return resources.get(name);
+    }
+  }
+
+  private static final ImmutableList<String> originalAllChangedResource =
+      ImmutableList.of(
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A<java.lang.String>",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A<"
+              + "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A>",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          // Test property values are rewritten.
+          "property=com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A",
+          // Test XML content is rewritten.
+          "<tag>com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A</tag>",
+          "<tag attr=\"com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A\"></tag>",
+          // Test single-quote literals are rewritten.
+          "'com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A'");
+
+  private static final ImmutableList<String> originalAllPresentResource =
+      ImmutableList.of(
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B");
+
+  private static final ImmutableList<String> originalAllUnchangedResource =
+      ImmutableList.of(
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass",
+          // Test there is no renaming for the method on A.
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A.method",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A.method()",
+          // Test there is no renaming for the methods on B.
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B.method",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B.method()",
+          // Test various prefixes.
+          "42com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          "WithIdentifierPrefixcom.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          "-com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          "WithDashPrefix-com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          "$com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          "WithDollarPrefix$com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          ".com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          "WithDotPrefix.com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          // Test various suffixes.
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B42",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$BWithIdentifierSuffix",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B-",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B-WithDashSuffix",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B$",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B$WithDollarSuffix",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B.",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B.WithDotSuffix");
+
+  private static String getProguardConfig(
+      boolean enableAdaptResourceFileContents, String adaptResourceFileContentsPathFilter) {
+    String adaptResourceFileContentsRule;
+    if (enableAdaptResourceFileContents) {
+      adaptResourceFileContentsRule = "-adaptresourcefilecontents";
+      if (adaptResourceFileContentsPathFilter != null) {
+        adaptResourceFileContentsRule += " " + adaptResourceFileContentsPathFilter;
+      }
+    } else {
+      adaptResourceFileContentsRule = "";
+    }
+    return String.join(
+        System.lineSeparator(),
+        adaptResourceFileContentsRule,
+        "-keep class " + AdaptResourceFileContentsTestClass.class.getName() + " {",
+        "  public static void main(...);",
+        "}",
+        "-neverinline class com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B {",
+        "  public void method();",
+        "}");
+  }
+
+  @Test
+  public void testEnabled() throws Exception {
+    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    AndroidApp out = compileWithR8(getProguardConfig(true, null), dataResourceConsumer);
+
+    // Check that the data resources have changed as expected.
+    checkAllAreChanged(
+        dataResourceConsumer.get("resource-all-changed.md"), originalAllChangedResource);
+    checkAllAreChanged(
+        dataResourceConsumer.get("resource-all-changed.txt"), originalAllChangedResource);
+
+    // Check that the new names are consistent with the actual application code.
+    checkAllArePresent(
+        dataResourceConsumer.get("resource-all-present.txt"), new CodeInspector(out));
+
+    // Check that the data resources have not changed unexpectedly.
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.class"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.cLaSs"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-unchanged.txt"), originalAllUnchangedResource);
+  }
+
+  @Ignore("b/36847655")
+  @Test
+  public void testProguardBehavior() throws Exception {
+    // TODO(christofferqa): Run Proguard on the example and check that R8 behaves the same way.
+  }
+
+  @Test
+  public void testEnabledWithFilter() throws Exception {
+    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    compileWithR8(getProguardConfig(true, "*.md"), dataResourceConsumer);
+
+    // Check that the file matching the filter has changed as expected.
+    checkAllAreChanged(
+        dataResourceConsumer.get("resource-all-changed.md"), originalAllChangedResource);
+
+    // Check that all the other data resources are unchanged.
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.class"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.cLaSs"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.txt"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-present.txt"), originalAllPresentResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-unchanged.txt"), originalAllUnchangedResource);
+  }
+
+  @Test
+  public void testDisabled() throws Exception {
+    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    compileWithR8(getProguardConfig(false, null), dataResourceConsumer);
+
+    // Check that all data resources are unchanged.
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.class"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.cLaSs"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.md"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.txt"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-present.txt"), originalAllPresentResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-unchanged.txt"), originalAllUnchangedResource);
+  }
+
+  private static void checkAllAreChanged(
+      ImmutableList<String> adaptedLines, ImmutableList<String> originalLines) {
+    assertEquals(adaptedLines.size(), originalLines.size());
+    for (int i = 0; i < originalLines.size(); i++) {
+      assertNotEquals(originalLines.get(i), adaptedLines.get(i));
+    }
+  }
+
+  private static void checkAllArePresent(ImmutableList<String> lines, CodeInspector inspector) {
+    for (String line : lines) {
+      assertThat(inspector.clazz(line), isPresent());
+    }
+  }
+
+  private static void checkAllAreUnchanged(
+      ImmutableList<String> adaptedLines, ImmutableList<String> originalLines) {
+    assertEquals(adaptedLines.size(), originalLines.size());
+    for (int i = 0; i < originalLines.size(); i++) {
+      assertEquals(originalLines.get(i), adaptedLines.get(i));
+    }
+  }
+
+  private AndroidApp compileWithR8(String proguardConfig, DataResourceConsumer dataResourceConsumer)
+      throws CompilationFailedException, IOException {
+    AndroidApp app =
+        AndroidApp.builder()
+            .addProgramFiles(
+                ToolHelper.getClassFileForTestClass(AdaptResourceFileContentsTestClass.class),
+                ToolHelper.getClassFileForTestClass(AdaptResourceFileContentsTestClass.A.class),
+                ToolHelper.getClassFileForTestClass(AdaptResourceFileContentsTestClass.B.class))
+            .addDataResource(
+                String.join(System.lineSeparator(), originalAllChangedResource).getBytes(),
+                "resource-all-changed.class",
+                Origin.unknown())
+            .addDataResource(
+                String.join(System.lineSeparator(), originalAllChangedResource).getBytes(),
+                "resource-all-changed.cLaSs",
+                Origin.unknown())
+            .addDataResource(
+                String.join(System.lineSeparator(), originalAllChangedResource).getBytes(),
+                "resource-all-changed.md",
+                Origin.unknown())
+            .addDataResource(
+                String.join(System.lineSeparator(), originalAllChangedResource).getBytes(),
+                "resource-all-changed.txt",
+                Origin.unknown())
+            .addDataResource(
+                String.join(System.lineSeparator(), originalAllPresentResource).getBytes(),
+                "resource-all-present.txt",
+                Origin.unknown())
+            .addDataResource(
+                String.join(System.lineSeparator(), originalAllUnchangedResource).getBytes(),
+                "resource-all-unchanged.txt",
+                Origin.unknown())
+            .build();
+    R8Command command =
+        ToolHelper.allowTestProguardOptions(
+                ToolHelper.prepareR8CommandBuilder(app)
+                    .addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown()))
+            .build();
+    return ToolHelper.runR8(
+        command,
+        options -> {
+          // TODO(christofferqa): Class inliner should respect -neverinline.
+          options.enableClassInlining = false;
+          options.enableClassMerging = true;
+          options.dataResourceConsumer = dataResourceConsumer;
+        });
+  }
+}
+
+class AdaptResourceFileContentsTestClass {
+
+  public static void main(String[] args) {
+    B obj = new B();
+    obj.method();
+  }
+
+  static class A {
+
+    public void method() {
+      System.out.println("In A.method()");
+    }
+  }
+
+  static class B extends A {
+
+    public void method() {
+      System.out.println("In B.method()");
+      super.method();
+    }
+  }
+}