AI 146870: am: CL 146865 Final pass on stubber: properly filter fields and methods.
  Original author: raphael
  Merged from: //branches/cupcake/...

Automated import of CL 146870
diff --git a/tools/mkstubs/src/com/android/mkstubs/AsmAnalyzer.java b/tools/mkstubs/src/com/android/mkstubs/AsmAnalyzer.java
index c023cf2..0a37f29 100644
--- a/tools/mkstubs/src/com/android/mkstubs/AsmAnalyzer.java
+++ b/tools/mkstubs/src/com/android/mkstubs/AsmAnalyzer.java
@@ -19,9 +19,7 @@
 import org.objectweb.asm.ClassReader;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Enumeration;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
@@ -68,66 +66,14 @@
         }
     }
 
-    public void filter(
-            Map<String, ClassReader> classes,
-            ArrayList<String> inclusions,
-            ArrayList<String> exclusions) {
+    public void filter(Map<String, ClassReader> classes, Filter filter) {
 
-        ArrayList<String> inPrefix = new ArrayList<String>();
-        HashSet  <String> inFull   = new HashSet  <String>();
-        ArrayList<String> exPrefix = new ArrayList<String>();
-        HashSet  <String> exFull   = new HashSet  <String>();
-        
-        for (String in : inclusions) {
-            if (in.endsWith("*")) {
-                inPrefix.add(in.substring(0, in.length() - 1));
-            } else {
-                inFull.add(in);
-            }
-        }
-        
-        for (String ex : exclusions) {
-            if (ex.endsWith("*")) {
-                exPrefix.add(ex.substring(0, ex.length() - 1));
-            } else {
-                exFull.add(ex);
-            }
-        }
-        
-        
         Set<String> keys = classes.keySet();
         for(Iterator<String> it = keys.iterator(); it.hasNext(); ) {
             String key = it.next();
 
-            
-            // Check if it can be included.
-            boolean keep = inFull.contains(key);
-            if (!keep) {
-                // Check for a prefix inclusion
-                for (String prefix : inPrefix) {
-                    if (key.startsWith(prefix)) {
-                        keep = true;
-                        break;
-                    }
-                }
-            }
-            
-            if (keep) {
-                // check for a full exclusion
-                keep = !exFull.contains(key);
-            }
-            if (keep) {
-                // or check for prefix exclusion
-                for (String prefix : exPrefix) {
-                    if (key.startsWith(prefix)) {
-                        keep = false;
-                        break;
-                    }
-                }
-            }
-            
             // remove if we don't keep it
-            if (!keep) {
+            if (!filter.accept(key)) {
                 System.out.println("- Remove class " + key);
                 it.remove();
             }
diff --git a/tools/mkstubs/src/com/android/mkstubs/Filter.java b/tools/mkstubs/src/com/android/mkstubs/Filter.java
new file mode 100644
index 0000000..c566c6b
--- /dev/null
+++ b/tools/mkstubs/src/com/android/mkstubs/Filter.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * 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.android.mkstubs;
+
+import java.util.TreeSet;
+
+/**
+ * 
+ */
+class Filter {
+    private TreeSet<String> mIncludePrefix = new TreeSet<String>();
+    private TreeSet<String> mIncludeFull   = new TreeSet<String>();
+    private TreeSet<String> mExcludePrefix = new TreeSet<String>();
+    private TreeSet<String> mExcludeFull   = new TreeSet<String>();
+
+    public TreeSet<String> getIncludeFull() {
+        return mIncludeFull;
+    }
+    
+    public TreeSet<String> getIncludePrefix() {
+        return mIncludePrefix;
+    }
+    
+    public TreeSet<String> getExcludeFull() {
+        return mExcludeFull;
+    }
+    
+    public TreeSet<String> getExcludePrefix() {
+        return mExcludePrefix;
+    }
+    
+    public boolean accept(String s) {
+        
+        // Check if it can be included.
+        boolean accept = mIncludeFull.contains(s);
+        if (!accept) {
+            // Check for a prefix inclusion
+            for (String prefix : mIncludePrefix) {
+                if (s.startsWith(prefix)) {
+                    accept = true;
+                    break;
+                }
+            }
+        }
+        
+        if (accept) {
+            // check for a full exclusion
+            accept = !mExcludeFull.contains(s);
+        }
+        if (accept) {
+            // or check for prefix exclusion
+            for (String prefix : mExcludePrefix) {
+                if (s.startsWith(prefix)) {
+                    accept = false;
+                    break;
+                }
+            }
+        }
+
+        return accept;
+    }    
+}
diff --git a/tools/mkstubs/src/com/android/mkstubs/FilterClassAdapter.java b/tools/mkstubs/src/com/android/mkstubs/FilterClassAdapter.java
index 71c55f8..c3585a8 100644
--- a/tools/mkstubs/src/com/android/mkstubs/FilterClassAdapter.java
+++ b/tools/mkstubs/src/com/android/mkstubs/FilterClassAdapter.java
@@ -24,27 +24,24 @@
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
-import java.util.List;
-
 /**
  * A class visitor that filters out all the referenced exclusions
  */
 class FilterClassAdapter extends ClassAdapter {
 
-    private final List<String> mExclusions;
+    private final Filter mFilter;
+    private String mClassName;
 
-    public FilterClassAdapter(ClassVisitor writer, List<String> exclusions) {
+    public FilterClassAdapter(ClassVisitor writer, Filter filter) {
         super(writer);
-        mExclusions = exclusions;
+        mFilter = filter;
     }
 
     @Override
     public void visit(int version, int access, String name, String signature,
             String superName, String[] interfaces) {
 
-        // TODO filter super type
-        // TODO filter interfaces 
-        
+        mClassName = name;
         super.visit(version, access, name, signature, superName, interfaces);
     }
 
@@ -71,7 +68,15 @@
             return null;
         }
         
-        // TODO filter on name
+        // filter on field name
+        String filterName = String.format("%s#%s", mClassName, name);
+
+        if (!mFilter.accept(filterName)) {
+            System.out.println("- Remove field " + filterName);
+            return null;
+        }
+        
+        // TODO we should produce an error if a filtered desc/signature is being used.
 
         return super.visitField(access, name, desc, signature, value);
     }
@@ -95,9 +100,25 @@
             return null;
         }
         
-        // TODO filter exceptions: error if filtered exception is being used
+        // filter on method name using the non-generic descriptor
+        String filterName = String.format("%s#%s%s", mClassName, name, desc);
 
-        // TODO filter on name; error if filtered desc or signatures is being used
+        if (!mFilter.accept(filterName)) {
+            System.out.println("- Remove method " + filterName);
+            return null;
+        }
+
+        // filter on method name using the generic signature
+        if (signature != null) {
+            filterName = String.format("%s#%s%s", mClassName, name, signature);
+    
+            if (!mFilter.accept(filterName)) {
+                System.out.println("- Remove method " + filterName);
+                return null;
+            }
+        }
+
+        // TODO we should produce an error if a filtered desc/signature/exception is being used.
 
         return super.visitMethod(access, name, desc, signature, exceptions);
     }
@@ -105,7 +126,7 @@
     @Override
     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
         
-        // Filter on desc type
+        // TODO produce an error if a filtered annotation type is being used
         return super.visitAnnotation(desc, visible);
     }
 
@@ -122,14 +143,17 @@
             return;
         }
 
-        // TODO filter on name
+        // filter on name
+        if (!mFilter.accept(name)) {
+            return;
+        }
 
         super.visitInnerClass(name, outerName, innerName, access);
     }
 
     @Override
     public void visitOuterClass(String owner, String name, String desc) {
-        // TODO Auto-generated method stub
+        // pass
     }
 
     @Override
diff --git a/tools/mkstubs/src/com/android/mkstubs/Main.java b/tools/mkstubs/src/com/android/mkstubs/Main.java
index 5c6e209..fd8fa4d 100644
--- a/tools/mkstubs/src/com/android/mkstubs/Main.java
+++ b/tools/mkstubs/src/com/android/mkstubs/Main.java
@@ -22,7 +22,6 @@
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Map;
 
 
@@ -34,12 +33,12 @@
     static class Params {
         private String mInputJarPath;
         private String mOutputJarPath;
-        private ArrayList<String> mInclusions = new ArrayList<String>();
-        private ArrayList<String> mExclusions = new ArrayList<String>();
-
+        private Filter mFilter;
+        
         public Params(String inputJarPath, String outputJarPath) {
             mInputJarPath = inputJarPath;
             mOutputJarPath = outputJarPath;
+            mFilter = new Filter();
         }
         
         public String getInputJarPath() {
@@ -49,13 +48,9 @@
         public String getOutputJarPath() {
             return mOutputJarPath;
         }
-
-        public ArrayList<String> getExclusions() {
-            return mExclusions;
-        }
         
-        public ArrayList<String> getInclusions() {
-            return mInclusions;
+        public Filter getFilter() {
+            return mFilter;
         }
     }
     
@@ -82,19 +77,43 @@
         Params p = new Params(args[0], args[1]);
         
         for (int i = 2; i < args.length; i++) {
-            String s = args[i];
-            if (s.startsWith("@")) {
-                addStringsFromFile(p, s.substring(1));
-            } else if (s.startsWith("-")) {
-                p.getExclusions().add(s.substring(1));
-            } else if (s.startsWith("+")) {
-                p.getInclusions().add(s.substring(1));
-            }
+            addString(p, args[i]);
         }
         
         return p;
     }
 
+    private void addString(Params p, String s) throws IOException {
+        s = s.trim();
+
+        if (s.length() < 2) {
+            return;
+        }
+        
+        char mode = s.charAt(0);
+        s = s.substring(1).trim();
+
+        if (mode == '@') {
+            addStringsFromFile(p, s);
+            
+        } else if (mode == '-') {
+            s = s.replace('.', '/');  // transform FQCN into ASM internal name
+            if (s.endsWith("*")) {
+                p.getFilter().getExcludePrefix().add(s.substring(0, s.length() - 1));
+            } else {
+                p.getFilter().getExcludeFull().add(s);
+            }
+
+        } else if (mode == '+') {
+            s = s.replace('.', '/');  // transform FQCN into ASM internal name
+            if (s.endsWith("*")) {
+                p.getFilter().getIncludePrefix().add(s.substring(0, s.length() - 1));
+            } else {
+                p.getFilter().getIncludeFull().add(s);
+            }
+        }
+    }
+
     private void addStringsFromFile(Params p, String inputFile)
             throws IOException {
         BufferedReader br = null;
@@ -102,22 +121,7 @@
             br = new BufferedReader(new FileReader(inputFile));
             String line;
             while ((line = br.readLine()) != null) {
-                line = line.trim();
-                if (line.length() == 0) {
-                    continue;
-                }
-                char mode = line.charAt(0);
-                line = line.substring(1).trim();
-                
-                if (line.length() > 0) {
-                    // Keep all class names in ASM path-like format, e.g. android/view/View
-                    line = line.replace('.', '/');
-                    if (mode == '-') {
-                        p.getExclusions().add(line);
-                    } else if (mode == '+') {
-                        p.getInclusions().add(line);
-                    }
-                }
+                addString(p, line);
             }
         } finally {
             br.close();
@@ -143,18 +147,22 @@
     private void process(Params p) throws IOException {
         AsmAnalyzer aa = new AsmAnalyzer();
         Map<String, ClassReader> classes = aa.parseInputJar(p.getInputJarPath());
-     
-        aa.filter(classes, p.getInclusions(), p.getExclusions());
+
+        System.out.println(String.format("Classes loaded: %d", classes.size()));
+        
+        aa.filter(classes, p.getFilter());
+
+        System.out.println(String.format("Classes filtered: %d", classes.size()));
 
         // dump as Java source files, mostly for debugging
         SourceGenerator src_gen = new SourceGenerator();
         File dst_src_dir = new File(p.getOutputJarPath() + "_sources");
         dst_src_dir.mkdir();
-        src_gen.generateSource(dst_src_dir, classes, p.getExclusions());
+        src_gen.generateSource(dst_src_dir, classes, p.getFilter());
         
         // dump the stubbed jar
         StubGenerator stub_gen = new StubGenerator();
         File dst_jar = new File(p.getOutputJarPath());
-        stub_gen.generateStubbedJar(dst_jar, classes, p.getExclusions());
+        stub_gen.generateStubbedJar(dst_jar, classes, p.getFilter());
     }
 }
diff --git a/tools/mkstubs/src/com/android/mkstubs/SourceGenerator.java b/tools/mkstubs/src/com/android/mkstubs/SourceGenerator.java
index 3eb19d6..461a25f 100644
--- a/tools/mkstubs/src/com/android/mkstubs/SourceGenerator.java
+++ b/tools/mkstubs/src/com/android/mkstubs/SourceGenerator.java
@@ -26,7 +26,6 @@
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.Writer;
-import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
@@ -41,7 +40,7 @@
      */
     public void generateSource(File baseDir,
             Map<String, ClassReader> classes,
-            List<String> exclusions) throws IOException {
+            Filter filter) throws IOException {
         
         for (Entry<String, ClassReader> entry : classes.entrySet()) {
             ClassReader cr = entry.getValue();
@@ -51,7 +50,7 @@
             FileWriter fw = null;
             try {
                 fw = createWriter(baseDir, name);
-                visitClassSource(fw, cr, exclusions);
+                visitClassSource(fw, cr, filter);
             } finally {
                 fw.close();
             }
@@ -79,12 +78,12 @@
      * Generate a source equivalent to the stubbed version of the class reader,
      * minus all exclusions
      */
-    void visitClassSource(Writer fw, ClassReader cr, List<String> exclusions) {
+    void visitClassSource(Writer fw, ClassReader cr, Filter filter) {
         System.out.println("Dump " + cr.getClassName());
         
         ClassVisitor javaWriter = new ClassSourcer(new Output(fw));
-        ClassVisitor filter = new FilterClassAdapter(javaWriter, exclusions);
-        cr.accept(filter, 0 /*flags*/);
+        ClassVisitor classFilter = new FilterClassAdapter(javaWriter, filter);
+        cr.accept(classFilter, 0 /*flags*/);
     }
 
 }
diff --git a/tools/mkstubs/src/com/android/mkstubs/StubGenerator.java b/tools/mkstubs/src/com/android/mkstubs/StubGenerator.java
index 79855ac..0321dc3 100644
--- a/tools/mkstubs/src/com/android/mkstubs/StubGenerator.java
+++ b/tools/mkstubs/src/com/android/mkstubs/StubGenerator.java
@@ -25,7 +25,6 @@
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 import java.util.Map.Entry;
@@ -43,14 +42,14 @@
      */
     public void generateStubbedJar(File destJar,
             Map<String, ClassReader> classes,
-            List<String> exclusions) throws IOException {
+            Filter filter) throws IOException {
 
         TreeMap<String, byte[]> all = new TreeMap<String, byte[]>();
 
         for (Entry<String, ClassReader> entry : classes.entrySet()) {
             ClassReader cr = entry.getValue();
             
-            byte[] b = visitClassStubber(cr, exclusions);
+            byte[] b = visitClassStubber(cr, filter);
             String name = classNameToEntryPath(cr.getClassName());
             all.put(name, b);
         }
@@ -88,7 +87,7 @@
         jar.close();
     }
     
-    byte[] visitClassStubber(ClassReader cr, List<String> exclusions) {
+    byte[] visitClassStubber(ClassReader cr, Filter filter) {
         System.out.println("Stub " + cr.getClassName());
 
         // Rewrite the new class from scratch, without reusing the constant pool from the
@@ -96,8 +95,8 @@
         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
 
         ClassVisitor stubWriter = new ClassStubber(cw);
-        ClassVisitor filter = new FilterClassAdapter(stubWriter, exclusions);
-        cr.accept(filter, 0 /*flags*/);
+        ClassVisitor classFilter = new FilterClassAdapter(stubWriter, filter);
+        cr.accept(classFilter, 0 /*flags*/);
         return cw.toByteArray();
     }
 }
diff --git a/tools/mkstubs/tests/com/android/mkstubs/SourceGeneratorTest.java b/tools/mkstubs/tests/com/android/mkstubs/SourceGeneratorTest.java
index c413d12..5b1f3a9 100644
--- a/tools/mkstubs/tests/com/android/mkstubs/SourceGeneratorTest.java
+++ b/tools/mkstubs/tests/com/android/mkstubs/SourceGeneratorTest.java
@@ -24,7 +24,6 @@
 import org.objectweb.asm.ClassReader;
 
 import java.io.StringWriter;
-import java.util.ArrayList;
 
 /**
  * 
@@ -48,7 +47,7 @@
         StringWriter sw = new StringWriter();
         ClassReader cr = new ClassReader("data/TestBaseClass");
         
-        mGen.visitClassSource(sw, cr, new ArrayList<String>());
+        mGen.visitClassSource(sw, cr, new Filter());
         
         String s = sw.toString();
         Assert.assertNotNull(s);