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 17package com.android.tools.layoutlib.create; 18 19import com.android.tools.layoutlib.java.LinkedHashMap_Delegate; 20import com.android.tools.layoutlib.java.System_Delegate; 21 22import org.objectweb.asm.ClassVisitor; 23import org.objectweb.asm.MethodVisitor; 24import org.objectweb.asm.Opcodes; 25import org.objectweb.asm.Type; 26 27import java.util.ArrayList; 28import java.util.Arrays; 29import java.util.HashSet; 30import java.util.LinkedHashMap; 31import java.util.List; 32import java.util.Locale; 33import java.util.Map; 34import 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 */ 40public 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<String>(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<MethodReplacer>(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() 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.toLanguageTag() and java.util.Locale.getScript() 77 METHOD_REPLACERS.add(new MethodReplacer() { 78 79 private final String LOCALE_TO_STRING = 80 Type.getMethodDescriptor(STRING, Type.getType(Locale.class)); 81 82 @Override 83 public boolean isNeeded(String owner, String name, String desc, String sourceClass) { 84 return JAVA_LOCALE_CLASS.equals(owner) && "()Ljava/lang/String;".equals(desc) && 85 ("toLanguageTag".equals(name) || "getScript".equals(name)); 86 } 87 88 @Override 89 public void replace(MethodInformation mi) { 90 mi.opcode = Opcodes.INVOKESTATIC; 91 mi.owner = ANDROID_LOCALE_CLASS; 92 mi.desc = LOCALE_TO_STRING; 93 } 94 }); 95 96 // Case 3: java.util.Locale.adjustLanguageCode() or java.util.Locale.forLanguageTag() or 97 // java.util.Locale.getDefault() 98 METHOD_REPLACERS.add(new MethodReplacer() { 99 100 private final String STRING_TO_STRING = Type.getMethodDescriptor(STRING, STRING); 101 private final String STRING_TO_LOCALE = Type.getMethodDescriptor( 102 Type.getType(Locale.class), STRING); 103 private final String VOID_TO_LOCALE = 104 Type.getMethodDescriptor(Type.getType(Locale.class)); 105 106 @Override 107 public boolean isNeeded(String owner, String name, String desc, String sourceClass) { 108 return JAVA_LOCALE_CLASS.equals(owner) && 109 ("adjustLanguageCode".equals(name) && desc.equals(STRING_TO_STRING) || 110 "forLanguageTag".equals(name) && desc.equals(STRING_TO_LOCALE) || 111 "getDefault".equals(name) && desc.equals(VOID_TO_LOCALE)); 112 } 113 114 @Override 115 public void replace(MethodInformation mi) { 116 mi.owner = ANDROID_LOCALE_CLASS; 117 } 118 }); 119 120 // Case 4: java.lang.System.log?() 121 METHOD_REPLACERS.add(new MethodReplacer() { 122 @Override 123 public boolean isNeeded(String owner, String name, String desc, String sourceClass) { 124 return JAVA_LANG_SYSTEM.equals(owner) && name.length() == 4 125 && name.startsWith("log"); 126 } 127 128 @Override 129 public void replace(MethodInformation mi) { 130 assert mi.desc.equals("(Ljava/lang/String;Ljava/lang/Throwable;)V") 131 || mi.desc.equals("(Ljava/lang/String;)V"); 132 mi.name = "log"; 133 mi.owner = Type.getInternalName(System_Delegate.class); 134 } 135 }); 136 137 // Case 5: java.util.LinkedHashMap.eldest() 138 METHOD_REPLACERS.add(new MethodReplacer() { 139 140 private final String VOID_TO_MAP_ENTRY = 141 Type.getMethodDescriptor(Type.getType(Map.Entry.class)); 142 private final String LINKED_HASH_MAP = Type.getInternalName(LinkedHashMap.class); 143 144 @Override 145 public boolean isNeeded(String owner, String name, String desc, String sourceClass) { 146 return LINKED_HASH_MAP.equals(owner) && 147 "eldest".equals(name) && 148 VOID_TO_MAP_ENTRY.equals(desc); 149 } 150 151 @Override 152 public void replace(MethodInformation mi) { 153 mi.opcode = Opcodes.INVOKESTATIC; 154 mi.owner = Type.getInternalName(LinkedHashMap_Delegate.class); 155 mi.desc = Type.getMethodDescriptor( 156 Type.getType(Map.Entry.class), Type.getType(LinkedHashMap.class)); 157 } 158 }); 159 160 // Case 6: android.content.Context.getClassLoader() in LayoutInflater 161 METHOD_REPLACERS.add(new MethodReplacer() { 162 // When LayoutInflater asks for a class loader, we must return the class loader that 163 // cannot return app's custom views/classes. This is so that in case of any failure 164 // or exception when instantiating the views, the IDE can replace it with a mock view 165 // and have proper error handling. However, if a custom view asks for the class 166 // loader, we must return a class loader that can find app's custom views as well. 167 // Thus, we rewrite the call to get class loader in LayoutInflater to 168 // getFrameworkClassLoader and inject a new method in Context. This leaves the normal 169 // method: Context.getClassLoader() free to be used by the apps. 170 private final String VOID_TO_CLASS_LOADER = 171 Type.getMethodDescriptor(Type.getType(ClassLoader.class)); 172 173 @Override 174 public boolean isNeeded(String owner, String name, String desc, String sourceClass) { 175 return owner.equals("android/content/Context") && 176 sourceClass.equals("android/view/LayoutInflater") && 177 name.equals("getClassLoader") && 178 desc.equals(VOID_TO_CLASS_LOADER); 179 } 180 181 @Override 182 public void replace(MethodInformation mi) { 183 mi.name = "getFrameworkClassLoader"; 184 } 185 }); 186 } 187 188 /** 189 * If a method some.package.Class.Method(args) is called from some.other.Class, 190 * @param owner some/package/Class 191 * @param name Method 192 * @param desc (args)returnType 193 * @param sourceClass some/other/Class 194 * @return if the method invocation needs to be replaced by some other class. 195 */ 196 public static boolean isReplacementNeeded(String owner, String name, String desc, 197 String sourceClass) { 198 for (MethodReplacer replacer : METHOD_REPLACERS) { 199 if (replacer.isNeeded(owner, name, desc, sourceClass)) { 200 return true; 201 } 202 } 203 return false; 204 } 205 206 private final String mOriginalClassName; 207 208 public ReplaceMethodCallsAdapter(ClassVisitor cv, String originalClassName) { 209 super(Opcodes.ASM4, cv); 210 mOriginalClassName = originalClassName; 211 } 212 213 @Override 214 public MethodVisitor visitMethod(int access, String name, String desc, String signature, 215 String[] exceptions) { 216 return new MyMethodVisitor(super.visitMethod(access, name, desc, signature, exceptions)); 217 } 218 219 private class MyMethodVisitor extends MethodVisitor { 220 221 public MyMethodVisitor(MethodVisitor mv) { 222 super(Opcodes.ASM4, mv); 223 } 224 225 @Override 226 public void visitMethodInsn(int opcode, String owner, String name, String desc) { 227 for (MethodReplacer replacer : METHOD_REPLACERS) { 228 if (replacer.isNeeded(owner, name, desc, mOriginalClassName)) { 229 MethodInformation mi = new MethodInformation(opcode, owner, name, desc); 230 replacer.replace(mi); 231 opcode = mi.opcode; 232 owner = mi.owner; 233 name = mi.name; 234 desc = mi.desc; 235 break; 236 } 237 } 238 super.visitMethodInsn(opcode, owner, name, desc); 239 } 240 } 241 242 private static class MethodInformation { 243 public int opcode; 244 public String owner; 245 public String name; 246 public String desc; 247 248 public MethodInformation(int opcode, String owner, String name, String desc) { 249 this.opcode = opcode; 250 this.owner = owner; 251 this.name = name; 252 this.desc = desc; 253 } 254 } 255 256 private interface MethodReplacer { 257 boolean isNeeded(String owner, String name, String desc, String sourceClass); 258 259 /** 260 * Updates the MethodInformation with the new values of the method attributes - 261 * opcode, owner, name and desc. 262 */ 263 void replace(MethodInformation mi); 264 } 265} 266