1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.mkstubs;
18 
19 import com.android.mkstubs.Main.Logger;
20 import com.android.mkstubs.stubber.ClassStubber;
21 
22 import org.objectweb.asm.ClassReader;
23 import org.objectweb.asm.ClassVisitor;
24 import org.objectweb.asm.ClassWriter;
25 
26 import java.io.File;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.util.Map;
30 import java.util.Map.Entry;
31 import java.util.TreeMap;
32 import java.util.jar.JarEntry;
33 import java.util.jar.JarOutputStream;
34 
35 /**
36  * Given a set of already filtered classes, this filters out all private members,
37  * stubs the remaining classes and then generates a Jar out of them.
38  * <p/>
39  * This is an helper extracted for convenience. Callers just need to use
40  * {@link #generateStubbedJar(File, Map, Filter)}.
41  */
42 class StubGenerator {
43 
44     private Logger mLog;
45 
StubGenerator(Logger log)46     public StubGenerator(Logger log) {
47         mLog = log;
48     }
49 
50     /**
51      * Generate source for the stubbed classes, mostly for debug purposes.
52      * @throws IOException
53      */
generateStubbedJar(File destJar, Map<String, ClassReader> classes, Filter filter)54     public void generateStubbedJar(File destJar,
55             Map<String, ClassReader> classes,
56             Filter filter) throws IOException {
57 
58         TreeMap<String, byte[]> all = new TreeMap<>();
59 
60         for (Entry<String, ClassReader> entry : classes.entrySet()) {
61             ClassReader cr = entry.getValue();
62 
63             byte[] b = visitClassStubber(cr, filter);
64             String name = classNameToEntryPath(cr.getClassName());
65             all.put(name, b);
66         }
67 
68         createJar(new FileOutputStream(destJar), all);
69 
70         mLog.debug("Wrote %s", destJar.getPath());
71     }
72 
73     /**
74      * Utility method that converts a fully qualified java name into a JAR entry path
75      * e.g. for the input "android.view.View" it returns "android/view/View.class"
76      */
classNameToEntryPath(String className)77     String classNameToEntryPath(String className) {
78         return className.replaceAll("\\.", "/").concat(".class");
79     }
80 
81     /**
82      * Writes the JAR file.
83      *
84      * @param outStream The file output stream were to write the JAR.
85      * @param all The map of all classes to output.
86      * @throws IOException if an I/O error has occurred
87      */
createJar(FileOutputStream outStream, Map<String,byte[]> all)88     void createJar(FileOutputStream outStream, Map<String,byte[]> all) throws IOException {
89         JarOutputStream jar = new JarOutputStream(outStream);
90         for (Entry<String, byte[]> entry : all.entrySet()) {
91             String name = entry.getKey();
92             JarEntry jar_entry = new JarEntry(name);
93             jar.putNextEntry(jar_entry);
94             jar.write(entry.getValue());
95             jar.closeEntry();
96         }
97         jar.flush();
98         jar.close();
99     }
100 
visitClassStubber(ClassReader cr, Filter filter)101     byte[] visitClassStubber(ClassReader cr, Filter filter) {
102         mLog.debug("Stub " + cr.getClassName());
103 
104         // Rewrite the new class from scratch, without reusing the constant pool from the
105         // original class reader.
106         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
107 
108         ClassVisitor stubWriter = new ClassStubber(cw);
109         ClassVisitor classFilter = new FilterClassAdapter(stubWriter, filter, mLog);
110         cr.accept(classFilter, 0 /*flags*/);
111         return cw.toByteArray();
112     }
113 }
114