• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.tools.layoutlib.create;
18 
19 import com.android.tools.layoutlib.java.LinkedHashMap_Delegate;
20 import com.android.tools.layoutlib.java.System_Delegate;
21 
22 import org.objectweb.asm.ClassVisitor;
23 import org.objectweb.asm.MethodVisitor;
24 import org.objectweb.asm.Opcodes;
25 import org.objectweb.asm.Type;
26 
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.HashSet;
30 import java.util.LinkedHashMap;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Map;
34 import java.util.Set;
35 
36 /**
37  * Replaces calls to certain methods that do not exist in the Desktop VM. Useful for methods in the
38  * "java" package.
39  */
40 public class ReplaceMethodCallsAdapter extends ClassVisitor {
41 
42     /**
43      * Descriptors for specialized versions {@link System#arraycopy} that are not present on the
44      * Desktop VM.
45      */
46     private static Set<String> ARRAYCOPY_DESCRIPTORS = new HashSet<>(Arrays.asList(
47             "([CI[CII)V", "([BI[BII)V", "([SI[SII)V", "([II[III)V",
48             "([JI[JII)V", "([FI[FII)V", "([DI[DII)V", "([ZI[ZII)V"));
49 
50     private static final List<MethodReplacer> METHOD_REPLACERS = new ArrayList<>(5);
51 
52     private static final String ANDROID_LOCALE_CLASS =
53             "com/android/layoutlib/bridge/android/AndroidLocale";
54 
55     private static final String JAVA_LOCALE_CLASS = Type.getInternalName(java.util.Locale.class);
56     private static final Type STRING = Type.getType(String.class);
57 
58     private static final String JAVA_LANG_SYSTEM = Type.getInternalName(System.class);
59 
60     // Static initialization block to initialize METHOD_REPLACERS.
61     static {
62         // Case 1: java.lang.System.arraycopy()
METHOD_REPLACERS.add(new MethodReplacer() { @Override public boolean isNeeded(String owner, String name, String desc, String sourceClass) { return JAVA_LANG_SYSTEM.equals(owner) && "arraycopy".equals(name) && ARRAYCOPY_DESCRIPTORS.contains(desc); } @Override public void replace(MethodInformation mi) { mi.desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V"; } })63         METHOD_REPLACERS.add(new MethodReplacer() {
64             @Override
65             public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
66                 return JAVA_LANG_SYSTEM.equals(owner) && "arraycopy".equals(name) &&
67                         ARRAYCOPY_DESCRIPTORS.contains(desc);
68             }
69 
70             @Override
71             public void replace(MethodInformation mi) {
72                 mi.desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
73             }
74         });
75 
76         // Case 2: java.util.Locale.adjustLanguageCode() or java.util.Locale.getDefault()
METHOD_REPLACERS.add(new MethodReplacer() { private final String STRING_TO_STRING = Type.getMethodDescriptor(STRING, STRING); private final String VOID_TO_LOCALE = Type.getMethodDescriptor(Type.getType(Locale.class)); @Override public boolean isNeeded(String owner, String name, String desc, String sourceClass) { return JAVA_LOCALE_CLASS.equals(owner) && ("adjustLanguageCode".equals(name) && desc.equals(STRING_TO_STRING) || "getDefault".equals(name) && desc.equals(VOID_TO_LOCALE)); } @Override public void replace(MethodInformation mi) { mi.owner = ANDROID_LOCALE_CLASS; } })77         METHOD_REPLACERS.add(new MethodReplacer() {
78 
79             private final String STRING_TO_STRING = Type.getMethodDescriptor(STRING, STRING);
80             private final String VOID_TO_LOCALE =
81                     Type.getMethodDescriptor(Type.getType(Locale.class));
82 
83             @Override
84             public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
85                 return JAVA_LOCALE_CLASS.equals(owner) &&
86                         ("adjustLanguageCode".equals(name) && desc.equals(STRING_TO_STRING) ||
87                         "getDefault".equals(name) && desc.equals(VOID_TO_LOCALE));
88             }
89 
90             @Override
91             public void replace(MethodInformation mi) {
92                 mi.owner = ANDROID_LOCALE_CLASS;
93             }
94         });
95 
96         // Case 3: java.lang.System.log?()
METHOD_REPLACERS.add(new MethodReplacer() { @Override public boolean isNeeded(String owner, String name, String desc, String sourceClass) { return JAVA_LANG_SYSTEM.equals(owner) && name.length() == 4 && name.startsWith("log"); } @Override public void replace(MethodInformation mi) { assert mi.desc.equals("(Ljava/lang/String;Ljava/lang/Throwable;)V") || mi.desc.equals("(Ljava/lang/String;)V"); mi.name = "log"; mi.owner = Type.getInternalName(System_Delegate.class); } })97         METHOD_REPLACERS.add(new MethodReplacer() {
98             @Override
99             public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
100                 return JAVA_LANG_SYSTEM.equals(owner) && name.length() == 4
101                         && name.startsWith("log");
102             }
103 
104             @Override
105             public void replace(MethodInformation mi) {
106                 assert mi.desc.equals("(Ljava/lang/String;Ljava/lang/Throwable;)V")
107                         || mi.desc.equals("(Ljava/lang/String;)V");
108                 mi.name = "log";
109                 mi.owner = Type.getInternalName(System_Delegate.class);
110             }
111         });
112 
113         // Case 4: java.lang.System time calls
METHOD_REPLACERS.add(new MethodReplacer() { @Override public boolean isNeeded(String owner, String name, String desc, String sourceClass) { return JAVA_LANG_SYSTEM.equals(owner) && name.equals("nanoTime"); } @Override public void replace(MethodInformation mi) { mi.name = "nanoTime"; mi.owner = Type.getInternalName(System_Delegate.class); } })114         METHOD_REPLACERS.add(new MethodReplacer() {
115             @Override
116             public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
117                 return JAVA_LANG_SYSTEM.equals(owner) && name.equals("nanoTime");
118             }
119 
120             @Override
121             public void replace(MethodInformation mi) {
122                 mi.name = "nanoTime";
123                 mi.owner = Type.getInternalName(System_Delegate.class);
124             }
125         });
METHOD_REPLACERS.add(new MethodReplacer() { @Override public boolean isNeeded(String owner, String name, String desc, String sourceClass) { return JAVA_LANG_SYSTEM.equals(owner) && name.equals("currentTimeMillis"); } @Override public void replace(MethodInformation mi) { mi.name = "currentTimeMillis"; mi.owner = Type.getInternalName(System_Delegate.class); } })126         METHOD_REPLACERS.add(new MethodReplacer() {
127             @Override
128             public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
129                 return JAVA_LANG_SYSTEM.equals(owner) && name.equals("currentTimeMillis");
130             }
131 
132             @Override
133             public void replace(MethodInformation mi) {
134                 mi.name = "currentTimeMillis";
135                 mi.owner = Type.getInternalName(System_Delegate.class);
136             }
137         });
138 
139         // Case 5: java.util.LinkedHashMap.eldest()
METHOD_REPLACERS.add(new MethodReplacer() { private final String VOID_TO_MAP_ENTRY = Type.getMethodDescriptor(Type.getType(Map.Entry.class)); private final String LINKED_HASH_MAP = Type.getInternalName(LinkedHashMap.class); @Override public boolean isNeeded(String owner, String name, String desc, String sourceClass) { return LINKED_HASH_MAP.equals(owner) && "eldest".equals(name) && VOID_TO_MAP_ENTRY.equals(desc); } @Override public void replace(MethodInformation mi) { mi.opcode = Opcodes.INVOKESTATIC; mi.owner = Type.getInternalName(LinkedHashMap_Delegate.class); mi.desc = Type.getMethodDescriptor( Type.getType(Map.Entry.class), Type.getType(LinkedHashMap.class)); } })140         METHOD_REPLACERS.add(new MethodReplacer() {
141 
142             private final String VOID_TO_MAP_ENTRY =
143                     Type.getMethodDescriptor(Type.getType(Map.Entry.class));
144             private final String LINKED_HASH_MAP = Type.getInternalName(LinkedHashMap.class);
145 
146             @Override
147             public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
148                 return LINKED_HASH_MAP.equals(owner) &&
149                         "eldest".equals(name) &&
150                         VOID_TO_MAP_ENTRY.equals(desc);
151             }
152 
153             @Override
154             public void replace(MethodInformation mi) {
155                 mi.opcode = Opcodes.INVOKESTATIC;
156                 mi.owner = Type.getInternalName(LinkedHashMap_Delegate.class);
157                 mi.desc = Type.getMethodDescriptor(
158                         Type.getType(Map.Entry.class), Type.getType(LinkedHashMap.class));
159             }
160         });
161 
162         // Case 6: android.content.Context.getClassLoader() in LayoutInflater
METHOD_REPLACERS.add(new MethodReplacer() { private final String VOID_TO_CLASS_LOADER = Type.getMethodDescriptor(Type.getType(ClassLoader.class)); @Override public boolean isNeeded(String owner, String name, String desc, String sourceClass) { return owner.equals("android/content/Context") && sourceClass.equals("android/view/LayoutInflater") && name.equals("getClassLoader") && desc.equals(VOID_TO_CLASS_LOADER); } @Override public void replace(MethodInformation mi) { mi.name = "getFrameworkClassLoader"; } })163         METHOD_REPLACERS.add(new MethodReplacer() {
164             // When LayoutInflater asks for a class loader, we must return the class loader that
165             // cannot return app's custom views/classes. This is so that in case of any failure
166             // or exception when instantiating the views, the IDE can replace it with a mock view
167             // and have proper error handling. However, if a custom view asks for the class
168             // loader, we must return a class loader that can find app's custom views as well.
169             // Thus, we rewrite the call to get class loader in LayoutInflater to
170             // getFrameworkClassLoader and inject a new method in Context. This leaves the normal
171             // method: Context.getClassLoader() free to be used by the apps.
172             private final String VOID_TO_CLASS_LOADER =
173                     Type.getMethodDescriptor(Type.getType(ClassLoader.class));
174 
175             @Override
176             public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
177                 return owner.equals("android/content/Context") &&
178                         sourceClass.equals("android/view/LayoutInflater") &&
179                         name.equals("getClassLoader") &&
180                         desc.equals(VOID_TO_CLASS_LOADER);
181             }
182 
183             @Override
184             public void replace(MethodInformation mi) {
185                 mi.name = "getFrameworkClassLoader";
186             }
187         });
188     }
189 
190     /**
191      * If a method some.package.Class.Method(args) is called from some.other.Class,
192      * @param owner some/package/Class
193      * @param name Method
194      * @param desc (args)returnType
195      * @param sourceClass some/other/Class
196      * @return if the method invocation needs to be replaced by some other class.
197      */
isReplacementNeeded(String owner, String name, String desc, String sourceClass)198     public static boolean isReplacementNeeded(String owner, String name, String desc,
199             String sourceClass) {
200         for (MethodReplacer replacer : METHOD_REPLACERS) {
201             if (replacer.isNeeded(owner, name, desc, sourceClass)) {
202                 return true;
203             }
204         }
205         return false;
206     }
207 
208     private final String mOriginalClassName;
209 
ReplaceMethodCallsAdapter(ClassVisitor cv, String originalClassName)210     public ReplaceMethodCallsAdapter(ClassVisitor cv, String originalClassName) {
211         super(Main.ASM_VERSION, cv);
212         mOriginalClassName = originalClassName;
213     }
214 
215     @Override
visitMethod(int access, String name, String desc, String signature, String[] exceptions)216     public MethodVisitor visitMethod(int access, String name, String desc, String signature,
217             String[] exceptions) {
218         return new MyMethodVisitor(super.visitMethod(access, name, desc, signature, exceptions));
219     }
220 
221     private class MyMethodVisitor extends MethodVisitor {
222 
MyMethodVisitor(MethodVisitor mv)223         public MyMethodVisitor(MethodVisitor mv) {
224             super(Main.ASM_VERSION, mv);
225         }
226 
227         @Override
visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf)228         public void visitMethodInsn(int opcode, String owner, String name, String desc,
229                 boolean itf) {
230             for (MethodReplacer replacer : METHOD_REPLACERS) {
231                 if (replacer.isNeeded(owner, name, desc, mOriginalClassName)) {
232                     MethodInformation mi = new MethodInformation(opcode, owner, name, desc);
233                     replacer.replace(mi);
234                     opcode = mi.opcode;
235                     owner = mi.owner;
236                     name = mi.name;
237                     desc = mi.desc;
238                     break;
239                 }
240             }
241             super.visitMethodInsn(opcode, owner, name, desc, itf);
242         }
243     }
244 
245     private static class MethodInformation {
246         public int opcode;
247         public String owner;
248         public String name;
249         public String desc;
250 
MethodInformation(int opcode, String owner, String name, String desc)251         public MethodInformation(int opcode, String owner, String name, String desc) {
252             this.opcode = opcode;
253             this.owner = owner;
254             this.name = name;
255             this.desc = desc;
256         }
257     }
258 
259     private interface MethodReplacer {
isNeeded(String owner, String name, String desc, String sourceClass)260         boolean isNeeded(String owner, String name, String desc, String sourceClass);
261 
262         /**
263          * Updates the MethodInformation with the new values of the method attributes -
264          * opcode, owner, name and desc.
265          */
replace(MethodInformation mi)266         void replace(MethodInformation mi);
267     }
268 }
269