Add support -adaptclassstrings [class_filter] (w/ different parsing).

First, user-given rules w/ that option in pg config will be converted
to ProguardClassNameList, which is in turn used to match classes.

After the normal minifier process is done, IdentifierMinifier, a newly
introduced minifier that uses aforementioned matched classes and naming
lense that is generated from the other minifiers, will walk through
candidate classes to find string literal usages:
1) in <clinit> (for static field initialization), and
2) in method's code_item.
Those string literals will be replaced with the renamed ones only if
1) they correspond to class identifiers; and
2) corresponding types are indeed renamed by the previous minifier.

Bug: 36799092
Change-Id: I3f09a22fa0d0eda6671f4c60d29b9363fb991df3
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
new file mode 100644
index 0000000..374f496
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -0,0 +1,99 @@
+// Copyright (c) 2017, 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 com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.ConstStringJumbo;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.shaking.ProguardClassNameList;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.Map;
+
+class IdentifierMinifier {
+
+  private final AppInfo appInfo;
+  private final ProguardClassNameList adaptClassStrings;
+  private final NamingLens lens;
+
+  IdentifierMinifier(
+      AppInfo appInfo,
+      ProguardClassNameList adaptClassStrings,
+      NamingLens lens) {
+    this.appInfo = appInfo;
+    this.adaptClassStrings = adaptClassStrings;
+    this.lens = lens;
+  }
+
+  void run() {
+    if (adaptClassStrings.size() != 0) {
+      handleAdaptClassStrings();
+    }
+    // TODO(b/36799092): Handle influx of string literals from call sites to annotated members.
+  }
+
+  private void handleAdaptClassStrings() {
+    appInfo.classes().forEach(clazz -> {
+      if (!adaptClassStrings.matches(clazz.type)) {
+        return;
+      }
+      clazz.forEachField(encodedField -> {
+        if (encodedField.staticValue instanceof DexValueString) {
+          DexString original = ((DexValueString) encodedField.staticValue).getValue();
+          DexString renamed = getRenamedStringLiteral(original);
+          if (renamed != original) {
+            encodedField.staticValue = new DexValueString(renamed);
+          }
+        }
+      });
+      clazz.forEachMethod(encodedMethod -> {
+        // Abstract methods do not have code_item.
+        if (encodedMethod.accessFlags.isAbstract()) {
+          return;
+        }
+        Code code = encodedMethod.getCode();
+        if (code == null || !code.isDexCode()) {
+          return;
+        }
+        DexCode dexCode = code.asDexCode();
+        for (Instruction instr : dexCode.instructions) {
+          if (instr instanceof ConstString) {
+            ConstString cnst = (ConstString) instr;
+            DexString dexString = cnst.getString();
+            cnst.BBBB = getRenamedStringLiteral(dexString);
+          } else if (instr instanceof ConstStringJumbo) {
+            ConstStringJumbo cnst = (ConstStringJumbo) instr;
+            DexString dexString = cnst.getString();
+            cnst.BBBBBBBB = getRenamedStringLiteral(dexString);
+          }
+        }
+      });
+    });
+  }
+
+  private DexString getRenamedStringLiteral(DexString originalLiteral) {
+    String originalString = originalLiteral.toString();
+    Map<String, DexType> renamedYetMatchedTypes =
+        lens.getRenamedItems(
+            DexType.class,
+            type -> type.toSourceString().equals(originalString),
+            DexType::toSourceString);
+    DexType type = renamedYetMatchedTypes.get(originalString);
+    if (type != null) {
+      DexString renamed = lens.lookupDescriptor(type);
+      // Create a new DexString only when the corresponding string literal will be replaced.
+      if (renamed != originalLiteral) {
+        return appInfo.dexItemFactory.createString(
+            DescriptorUtils.descriptorToJavaType(renamed.toString()));
+      }
+    }
+    return originalLiteral;
+  }
+
+}
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index fa85364..4181f7e 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -16,10 +16,13 @@
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
 
 public class Minifier {
 
@@ -47,7 +50,12 @@
     Map<DexField, DexString> fieldRenaming =
         new FieldNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
     timing.end();
-    return new MinifiedRenaming(classRenaming, methodRenaming, fieldRenaming, appInfo);
+    NamingLens lens = new MinifiedRenaming(classRenaming, methodRenaming, fieldRenaming, appInfo);
+    timing.begin("MinifyIdentifiers");
+    new IdentifierMinifier(appInfo, options.proguardConfiguration.getAdaptClassStrings(), lens)
+        .run();
+    timing.end();
+    return lens;
   }
 
   private static class MinifiedRenaming extends NamingLens {
@@ -84,6 +92,17 @@
       Iterables.filter(renaming.keySet(), DexType.class).forEach(consumer);
     }
 
+    @Override
+    <T extends DexItem> Map<String, T> getRenamedItems(
+        Class<T> clazz, Predicate<T> predicate, Function<T, String> namer) {
+      return renaming
+          .keySet()
+          .stream()
+          .filter(item -> (item.getClass() == clazz) && predicate.test(clazz.cast(item)))
+          .map(clazz::cast)
+          .collect(ImmutableMap.toImmutableMap(namer::apply, i -> i));
+    }
+
     /**
      * Checks whether the target is precise enough to be translated,
      * <p>
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index 69c165d..ca6c1c8 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -4,11 +4,16 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
 import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
 
 /**
  * Implements a translation of the Dex graph from original names to new names produced by
@@ -39,6 +44,9 @@
 
   abstract void forAllRenamedTypes(Consumer<DexType> consumer);
 
+  abstract <T extends DexItem> Map<String, T> getRenamedItems(
+      Class<T> clazz, Predicate<T> predicate, Function<T, String> namer);
+
   /**
    * Checks whether the target will be translated properly by this lense.
    * <p>
@@ -75,6 +83,12 @@
     }
 
     @Override
+    <T extends DexItem> Map<String, T> getRenamedItems(
+        Class<T> clazz, Predicate<T> predicate, Function<T, String> namer) {
+      return ImmutableMap.of();
+    }
+
+    @Override
     public boolean checkTargetCanBeTranslated(DexMethod item) {
       return true;
     }
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 9ad96e2..dc800a3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -44,6 +44,7 @@
     private Path packageObfuscationDictionary;
     private boolean useUniqueClassMemberNames;
     private boolean keepParameterNames;
+    private ProguardClassNameList adaptClassStrings = ProguardClassNameList.emptyList();
 
     private Builder(DexItemFactory dexItemFactory) {
       this.dexItemFactory = dexItemFactory;
@@ -201,6 +202,10 @@
       return keepParameterNames;
     }
 
+    public void setAdaptClassStrings(ProguardClassNameList adaptClassStrings) {
+      this.adaptClassStrings = adaptClassStrings;
+    }
+
     public ProguardConfiguration build() throws CompilationException {
       return new ProguardConfiguration(
           dexItemFactory,
@@ -229,7 +234,8 @@
           DictionaryReader.readAllNames(classObfuscationDictionary),
           DictionaryReader.readAllNames(packageObfuscationDictionary),
           useUniqueClassMemberNames,
-          keepParameterNames);
+          keepParameterNames,
+          adaptClassStrings);
     }
   }
 
@@ -260,6 +266,7 @@
   private final ImmutableList<String> packageObfuscationDictionary;
   private boolean useUniqueClassMemberNames;
   private boolean keepParameterNames;
+  private final ProguardClassNameList adaptClassStrings;
 
   private ProguardConfiguration(
       DexItemFactory factory,
@@ -288,7 +295,8 @@
       ImmutableList<String> classObfuscationDictionary,
       ImmutableList<String> packageObfuscationDictionary,
       boolean useUniqueClassMemberNames,
-      boolean keepParameterNames) {
+      boolean keepParameterNames,
+      ProguardClassNameList adaptClassStrings) {
     this.dexItemFactory = factory;
     this.injars = ImmutableList.copyOf(injars);
     this.libraryjars = ImmutableList.copyOf(libraryjars);
@@ -316,6 +324,7 @@
     this.packageObfuscationDictionary = packageObfuscationDictionary;
     this.useUniqueClassMemberNames = useUniqueClassMemberNames;
     this.keepParameterNames = keepParameterNames;
+    this.adaptClassStrings = adaptClassStrings;
   }
 
   /**
@@ -438,11 +447,17 @@
     return keepParameterNames;
   }
 
+  public ProguardClassNameList getAdaptClassStrings() {
+    return adaptClassStrings;
+  }
+
   public static ProguardConfiguration defaultConfiguration(DexItemFactory dexItemFactory) {
     try {
       return builderInitializedWithDefaults(dexItemFactory).build();
     } catch(CompilationException e) {
-      throw new RuntimeException(); // Building a builder initialized with defaults will not throw CompilationException because DictionaryReader is called with empty lists.
+      // Building a builder initialized with defaults will not throw CompilationException because
+      // DictionaryReader is called with empty lists.
+      throw new RuntimeException();
     }
   }
 
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 50dae72..36f7efd 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -279,13 +279,13 @@
       } else if (acceptString("useuniqueclassmembernames")) {
         configurationBuilder.setUseUniqueClassMemberNames(true);
       } else if (acceptString("adaptclassstrings")) {
+        skipWhitespace();
         if (isOptionalArgumentGiven()) {
-          configurationBuilder.addRule(parseAdaptClassStrings());
+          configurationBuilder.setAdaptClassStrings(parseClassNames());
         } else {
-          configurationBuilder.addRule(ProguardIdentifierNameStringRule.defaultAllRule());
+          configurationBuilder.setAdaptClassStrings(
+              ProguardClassNameList.singletonList(ProguardTypeMatcher.defaultAllMatcher()));
         }
-        // TODO(b/36799092): warn until it is fully implemented.
-        warnIgnoringOptions("adaptclassstrings");
       } else if (acceptString("identifiernamestring")) {
         configurationBuilder.addRule(parseIdentifierNameStringRule());
         // TODO(b/36799092): warn until it is fully implemented.
@@ -464,14 +464,6 @@
       return keepRuleBuilder.build();
     }
 
-    private ProguardIdentifierNameStringRule parseAdaptClassStrings()
-        throws ProguardRuleParserException {
-      ProguardIdentifierNameStringRule.Builder keepRuleBuilder =
-          ProguardIdentifierNameStringRule.builder();
-      keepRuleBuilder.setClassNames(parseClassNames());
-      return keepRuleBuilder.build();
-    }
-
     private ProguardIdentifierNameStringRule parseIdentifierNameStringRule()
         throws ProguardRuleParserException {
       ProguardIdentifierNameStringRule.Builder keepRuleBuilder =
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
index 44e7078..6cfd154 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
@@ -43,7 +43,7 @@
     return "identifiernamestring";
   }
 
-  public static ProguardIdentifierNameStringRule defaultAllRule() {
+  public static ProguardIdentifierNameStringRule defaultAdaptAllRule() {
     ProguardIdentifierNameStringRule.Builder builder = ProguardIdentifierNameStringRule.builder();
     builder.setClassNames(
         ProguardClassNameList.singletonList(ProguardTypeMatcher.defaultAllMatcher()));
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index aa9c360..b55a7a0 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -243,8 +243,17 @@
     } finally {
       application.timing.end();
     }
-    return new RootSet(noShrinking, noOptimization, noObfuscation, reasonAsked, keepPackageName,
-        checkDiscarded, alwaysInline, noSideEffects, assumedValues, dependentNoShrinking);
+    return new RootSet(
+        noShrinking,
+        noOptimization,
+        noObfuscation,
+        reasonAsked,
+        keepPackageName,
+        checkDiscarded,
+        alwaysInline,
+        noSideEffects,
+        assumedValues,
+        dependentNoShrinking);
   }
 
   private void markMatchingVisibleMethods(DexClass clazz,
@@ -325,17 +334,14 @@
   }
 
   /**
-   * Checks whether the given rule is satisfied bu this clazz, not taking superclasses into
+   * Checks whether the given rule is satisfied by this clazz, not taking superclasses into
    * account.
    */
   private boolean ruleSatisfied(ProguardMemberRule rule, DexClass clazz) {
-    if (ruleSatisfiedByMethods(rule, clazz.directMethods()) ||
-        ruleSatisfiedByMethods(rule, clazz.virtualMethods()) ||
-        ruleSatisfiedByFields(rule, clazz.staticFields()) ||
-        ruleSatisfiedByFields(rule, clazz.instanceFields())) {
-      return true;
-    }
-    return false;
+    return ruleSatisfiedByMethods(rule, clazz.directMethods())
+        || ruleSatisfiedByMethods(rule, clazz.virtualMethods())
+        || ruleSatisfiedByFields(rule, clazz.staticFields())
+        || ruleSatisfiedByFields(rule, clazz.instanceFields());
   }
 
   private boolean ruleSatisfiedByMethods(ProguardMemberRule rule, DexEncodedMethod[] methods) {
@@ -536,10 +542,15 @@
       return true;
     }
 
-    private RootSet(Map<DexItem, ProguardKeepRule> noShrinking,
-        Set<DexItem> noOptimization, Set<DexItem> noObfuscation, Set<DexItem> reasonAsked,
-        Set<DexItem> keepPackageName, Set<DexItem> checkDiscarded,
-        Set<DexItem> alwaysInline, Map<DexItem, ProguardMemberRule> noSideEffects,
+    private RootSet(
+        Map<DexItem, ProguardKeepRule> noShrinking,
+        Set<DexItem> noOptimization,
+        Set<DexItem> noObfuscation,
+        Set<DexItem> reasonAsked,
+        Set<DexItem> keepPackageName,
+        Set<DexItem> checkDiscarded,
+        Set<DexItem> alwaysInline,
+        Map<DexItem, ProguardMemberRule> noSideEffects,
         Map<DexItem, ProguardMemberRule> assumedValues,
         Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking) {
       this.noShrinking = Collections.unmodifiableMap(noShrinking);
diff --git a/src/test/examples/adaptclassstrings/A.java b/src/test/examples/adaptclassstrings/A.java
new file mode 100644
index 0000000..a1d95ef
--- /dev/null
+++ b/src/test/examples/adaptclassstrings/A.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2017, 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 adaptclassstrings;
+
+public class A {
+  protected static final String ITSELF = "Ladaptclassstrings/A;";
+  protected static final String OTHER = "adaptclassstrings.C";
+
+  protected int f;
+
+  A(int f) {
+    this.f = f;
+  }
+
+  protected int foo() {
+    return f * 9;
+  }
+
+  void bar() {
+    System.out.println("adaptclassstrings.C");
+    System.out.println("adaptclassstrings.D");
+  }
+}
diff --git a/src/test/examples/adaptclassstrings/AA.java b/src/test/examples/adaptclassstrings/AA.java
new file mode 100644
index 0000000..0a03efa
--- /dev/null
+++ b/src/test/examples/adaptclassstrings/AA.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2017, 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 adaptclassstrings;
+
+public class AA extends A {
+
+  AA(int f) {
+    super(f);
+  }
+
+  @Override
+  protected int foo() {
+    return super.foo() - f;
+  }
+}
diff --git a/src/test/examples/adaptclassstrings/C.java b/src/test/examples/adaptclassstrings/C.java
new file mode 100644
index 0000000..c5c8fe6
--- /dev/null
+++ b/src/test/examples/adaptclassstrings/C.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2017, 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 adaptclassstrings;
+
+public class C {
+  static final String OTHER = "adaptclassstrings.A";
+  static final String ITSELF = "adaptclassstrings.C";
+}
diff --git a/src/test/examples/adaptclassstrings/Main.java b/src/test/examples/adaptclassstrings/Main.java
new file mode 100644
index 0000000..4cee1d5
--- /dev/null
+++ b/src/test/examples/adaptclassstrings/Main.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2017, 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 adaptclassstrings;
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    int f = 3;
+    A a = new A(f);
+    AA aa = new AA(f);
+    assert a.foo() != aa.foo();
+
+    a.bar();
+
+    Object a_foo =
+        Class.forName("adaptclassstrings.A").getMethod("foo").invoke(a);
+    Object aa_foo =
+        Class.forName("adaptclassstrings.AA").getMethod("foo").invoke(aa);
+    assert !a_foo.equals(aa_foo);
+
+    Object c_to_a_foo =
+        Class.forName((String)
+            Class.forName("adaptclassstrings.C").getField("OTHER").get(null))
+            .getMethod("foo").invoke(a);
+    assert a_foo.equals(c_to_a_foo);
+
+    String cName = (String) Class.forName(C.ITSELF).getField("ITSELF").get(null);
+    assert cName.equals(A.OTHER);
+  }
+}
diff --git a/src/test/examples/adaptclassstrings/keep-rules-1.txt b/src/test/examples/adaptclassstrings/keep-rules-1.txt
new file mode 100644
index 0000000..72562ed
--- /dev/null
+++ b/src/test/examples/adaptclassstrings/keep-rules-1.txt
@@ -0,0 +1,12 @@
+# Copyright (c) 2017, 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class adaptclassstrings.Main {
+  public static void main(...);
+}
+
+-dontshrink
+-dontoptimize
diff --git a/src/test/examples/adaptclassstrings/keep-rules-2.txt b/src/test/examples/adaptclassstrings/keep-rules-2.txt
new file mode 100644
index 0000000..fc8be9d
--- /dev/null
+++ b/src/test/examples/adaptclassstrings/keep-rules-2.txt
@@ -0,0 +1,14 @@
+# Copyright (c) 2017, 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class adaptclassstrings.Main {
+  public static void main(...);
+}
+
+-dontshrink
+-dontoptimize
+
+-adaptclassstrings *.*A
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
new file mode 100644
index 0000000..191f6cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -0,0 +1,202 @@
+// Copyright (c) 2017, 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.ConstStringJumbo;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.ListUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IdentifierMinifierTest {
+
+  private final String appFileName;
+  private final List<String> keepRulesFiles;
+  private final Consumer<DexInspector> inspection;
+
+  @Rule
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  public IdentifierMinifierTest(
+      String test,
+      List<String> keepRulesFiles,
+      Consumer<DexInspector> inspection) {
+    this.appFileName = ToolHelper.EXAMPLES_BUILD_DIR + test + "/classes.dex";
+    this.keepRulesFiles = keepRulesFiles;
+    this.inspection = inspection;
+  }
+
+  @Before
+  public void generateR8ProcessedApp()
+      throws IOException, ExecutionException, ProguardRuleParserException, CompilationException {
+    Path out = temp.getRoot().toPath();
+    R8Command command =
+        R8Command.builder()
+            .setOutputPath(out)
+            .addProgramFiles(Paths.get(appFileName))
+            .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
+            .addProguardConfigurationConsumer(builder -> {
+              builder.setPrintMapping(true);
+              builder.setPrintMappingFile(out.resolve(AndroidApp.DEFAULT_PROGUARD_MAP_FILE));
+            })
+            .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()))
+            .build();
+    ToolHelper.runR8(command);
+  }
+
+  @Test
+  public void identiferMinifierTest() throws Exception {
+    Path out = temp.getRoot().toPath();
+    DexInspector dexInspector =
+        new DexInspector(
+            out.resolve("classes.dex"),
+            out.resolve(AndroidApp.DEFAULT_PROGUARD_MAP_FILE).toString());
+    inspection.accept(dexInspector);
+  }
+
+  @Parameters(name = "test: {0} keep: {1}")
+  public static Collection<Object[]> data() {
+    List<String> tests = Arrays.asList("adaptclassstrings");
+
+    Map<String, Consumer<DexInspector>> inspections = new HashMap<>();
+    inspections.put("adaptclassstrings:keep-rules-1.txt", IdentifierMinifierTest::test1_rule1);
+    inspections.put("adaptclassstrings:keep-rules-2.txt", IdentifierMinifierTest::test1_rule2);
+
+    return NamingTestBase.createTests(tests, inspections);
+  }
+
+  // Without -adaptclassstrings
+  private static void test1_rule1(DexInspector inspector) {
+    ClassSubject mainClass = inspector.clazz("adaptclassstrings.Main");
+    MethodSubject main = mainClass.method(DexInspector.MAIN);
+    Code mainCode = main.getMethod().getCode();
+    verifyPresenceOfConstString(mainCode.asDexCode().instructions);
+    int renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
+    assertEquals(0, renamedYetFoundIdentifierCount);
+
+    ClassSubject aClass = inspector.clazz("adaptclassstrings.A");
+    MethodSubject bar = aClass.method("void", "bar", ImmutableList.of());
+    Code barCode = bar.getMethod().getCode();
+    verifyPresenceOfConstString(barCode.asDexCode().instructions);
+    renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, barCode.asDexCode().instructions);
+    assertEquals(0, renamedYetFoundIdentifierCount);
+
+    renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, aClass.getDexClass().staticFields());
+    assertEquals(0, renamedYetFoundIdentifierCount);
+  }
+
+  // With -adaptclassstrings *.*A
+  private static void test1_rule2(DexInspector inspector) {
+    ClassSubject mainClass = inspector.clazz("adaptclassstrings.Main");
+    MethodSubject main = mainClass.method(DexInspector.MAIN);
+    Code mainCode = main.getMethod().getCode();
+    verifyPresenceOfConstString(mainCode.asDexCode().instructions);
+    int renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
+    assertEquals(0, renamedYetFoundIdentifierCount);
+
+    ClassSubject aClass = inspector.clazz("adaptclassstrings.A");
+    MethodSubject bar = aClass.method("void", "bar", ImmutableList.of());
+    Code barCode = bar.getMethod().getCode();
+    verifyPresenceOfConstString(barCode.asDexCode().instructions);
+    renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, barCode.asDexCode().instructions);
+    assertEquals(1, renamedYetFoundIdentifierCount);
+
+    renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, aClass.getDexClass().staticFields());
+    assertEquals(1, renamedYetFoundIdentifierCount);
+  }
+
+  private static void verifyPresenceOfConstString(Instruction[] instructions) {
+    boolean presence =
+        Arrays.stream(instructions)
+            .anyMatch(instr -> instr instanceof ConstString || instr instanceof ConstStringJumbo);
+    assertTrue(presence);
+  }
+
+  private static String retrieveString(Instruction instr) {
+    if (instr instanceof ConstString) {
+      ConstString cnst = (ConstString) instr;
+      return cnst.getString().toString();
+    } else if (instr instanceof ConstStringJumbo) {
+      ConstStringJumbo cnst = (ConstStringJumbo) instr;
+      return cnst.getString().toString();
+    }
+    return null;
+  }
+
+  private static int countRenamedClassIdentifier(
+      DexInspector inspector, Instruction[] instructions) {
+    return Arrays.stream(instructions)
+        .filter(instr -> instr instanceof ConstString || instr instanceof ConstStringJumbo)
+        .reduce(0, (cnt, instr) -> {
+          String cnstString = retrieveString(instr);
+          if (!DescriptorUtils.isClassDescriptor(cnstString)) {
+            ClassSubject classSubject = inspector.clazz(cnstString);
+            if (classSubject.isRenamed()
+                && DescriptorUtils.descriptorToJavaType(classSubject.getFinalDescriptor())
+                .equals(cnstString)) {
+              return cnt + 1;
+            }
+          }
+          return cnt;
+        }, Integer::sum);
+  }
+
+  private static int countRenamedClassIdentifier(
+      DexInspector inspector, DexEncodedField[] fields) {
+    return Arrays.stream(fields)
+        .filter(encodedField -> encodedField.staticValue instanceof DexValueString)
+        .reduce(0, (cnt, encodedField) -> {
+          String cnstString = ((DexValueString) encodedField.staticValue).getValue().toString();
+          if (!DescriptorUtils.isClassDescriptor(cnstString)) {
+            ClassSubject classSubject = inspector.clazz(cnstString);
+            if (classSubject.isRenamed()
+                && DescriptorUtils.descriptorToJavaType(classSubject.getFinalDescriptor())
+                .equals(cnstString)) {
+              return cnt + 1;
+            }
+          }
+          return cnt;
+        }, Integer::sum);
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index cb76b5d..fb3c88a 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -347,30 +347,15 @@
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(dexItemFactory, diagnosticsHandler);
-    String config1 = "-adaptclassstrings !foobar,*bar";
-    String config2 = "-adaptclassstrings !a.b.c.nope,a.b.**";
-    parser.parse( new ProguardConfigurationSourceStrings(ImmutableList.of(config1, config2)));
+    String adaptClassStrings = "-adaptclassstrings !foobar,*bar";
+    parser.parse( new ProguardConfigurationSourceStrings(ImmutableList.of(adaptClassStrings)));
     ProguardConfiguration config = parser.getConfig();
-    List<ProguardConfigurationRule> rules = config.getRules();
-    assertEquals(2, rules.size());
-    assertEquals(0, rules.get(0).getMemberRules().size());
-    assertEquals(2, rules.get(0).getClassNames().size());
     assertFalse(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lboobaz;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
     assertTrue(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lboobar;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobar;")));
     assertFalse(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lfoobar;")));
-    assertEquals(0, rules.get(1).getMemberRules().size());
-    assertEquals(2, rules.get(1).getClassNames().size());
-    assertFalse(
-        rules.get(1).getClassNames().matches(dexItemFactory.createType("Lx/y/z;")));
-    assertTrue(
-        rules.get(1).getClassNames().matches(dexItemFactory.createType("La/b/c/d;")));
-    assertTrue(
-        rules.get(1).getClassNames().matches(dexItemFactory.createType("La/b/p/q;")));
-    assertFalse(
-        rules.get(1).getClassNames().matches(dexItemFactory.createType("La/b/c/nope;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
   }
 
   @Test
@@ -381,14 +366,12 @@
     String adaptAll = "-adaptclassstrings *";
     parser.parse(new ProguardConfigurationSourceStrings(ImmutableList.of(adaptAll)));
     ProguardConfiguration config = parser.getConfig();
-    List<ProguardConfigurationRule> rules = config.getRules();
-    assertEquals(1, rules.size());
     assertTrue(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lboobaz;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
     assertTrue(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lboobaz;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
     assertTrue(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lfoobar;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
   }
 
   @Test
@@ -399,14 +382,12 @@
     String adaptAll = "-adaptclassstrings";
     parser.parse(new ProguardConfigurationSourceStrings(ImmutableList.of(adaptAll)));
     ProguardConfiguration config = parser.getConfig();
-    List<ProguardConfigurationRule> rules = config.getRules();
-    assertEquals(1, rules.size());
     assertTrue(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lboobaz;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
     assertTrue(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lboobaz;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
     assertTrue(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lfoobar;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 26de96c..612043e 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -697,6 +697,8 @@
     public abstract DexEncodedField getField();
 
     public abstract DexValue getStaticValue();
+
+    public abstract boolean isRenamed();
   }
 
   public class AbsentFieldSubject extends FieldSubject {
@@ -727,6 +729,11 @@
     }
 
     @Override
+    public boolean isRenamed() {
+      return false;
+    }
+
+    @Override
     public Signature getOriginalSignature() {
       return null;
     }
@@ -787,6 +794,12 @@
       return true;
     }
 
+    @Override
+    public boolean isRenamed() {
+      return clazz.naming == null || !getFinalSignature().name.equals(getOriginalSignature().name);
+    }
+
+
     public TypeSubject type() {
       return new TypeSubject(dexField.field.type);
     }