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