1/* 2 * Copyright (C) 2008 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 18package com.android.tools.layoutlib.create; 19 20 21import static org.junit.Assert.assertArrayEquals; 22import static org.junit.Assert.assertFalse; 23import static org.junit.Assert.assertTrue; 24 25import org.junit.After; 26import org.junit.Before; 27import org.junit.Test; 28import org.objectweb.asm.ClassReader; 29import org.objectweb.asm.ClassVisitor; 30import org.objectweb.asm.FieldVisitor; 31import org.objectweb.asm.MethodVisitor; 32import org.objectweb.asm.Opcodes; 33import org.objectweb.asm.Type; 34 35import java.io.File; 36import java.io.IOException; 37import java.io.InputStream; 38import java.net.URL; 39import java.util.ArrayList; 40import java.util.Collections; 41import java.util.Enumeration; 42import java.util.HashSet; 43import java.util.Map; 44import java.util.Set; 45import java.util.TreeMap; 46import java.util.zip.ZipEntry; 47import java.util.zip.ZipFile; 48 49/** 50 * Unit tests for some methods of {@link AsmGenerator}. 51 */ 52public class AsmGeneratorTest { 53 54 private MockLog mLog; 55 private ArrayList<String> mOsJarPath; 56 private String mOsDestJar; 57 private File mTempFile; 58 59 // ASM internal name for the the class in java package that should be refactored. 60 private static final String JAVA_CLASS_NAME = "java/lang/JavaClass"; 61 62 @Before 63 public void setUp() throws Exception { 64 mLog = new MockLog(); 65 URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar"); 66 67 mOsJarPath = new ArrayList<String>(); 68 mOsJarPath.add(url.getFile()); 69 70 mTempFile = File.createTempFile("mock", ".jar"); 71 mOsDestJar = mTempFile.getAbsolutePath(); 72 mTempFile.deleteOnExit(); 73 } 74 75 @After 76 public void tearDown() throws Exception { 77 if (mTempFile != null) { 78 mTempFile.delete(); 79 mTempFile = null; 80 } 81 } 82 83 @Test 84 public void testClassRenaming() throws IOException, LogAbortException { 85 86 ICreateInfo ci = new ICreateInfo() { 87 @Override 88 public Class<?>[] getInjectedClasses() { 89 // classes to inject in the final JAR 90 return new Class<?>[0]; 91 } 92 93 @Override 94 public String[] getDelegateMethods() { 95 return new String[0]; 96 } 97 98 @Override 99 public String[] getDelegateClassNatives() { 100 return new String[0]; 101 } 102 103 @Override 104 public String[] getOverriddenMethods() { 105 // methods to force override 106 return new String[0]; 107 } 108 109 @Override 110 public String[] getRenamedClasses() { 111 // classes to rename (so that we can replace them) 112 return new String[] { 113 "mock_android.view.View", "mock_android.view._Original_View", 114 "not.an.actual.ClassName", "anoter.fake.NewClassName", 115 }; 116 } 117 118 @Override 119 public String[] getJavaPkgClasses() { 120 return new String[0]; 121 } 122 123 @Override 124 public Set<String> getExcludedClasses() { 125 return null; 126 } 127 128 @Override 129 public String[] getDeleteReturns() { 130 // methods deleted from their return type. 131 return new String[0]; 132 } 133 }; 134 135 AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci); 136 137 AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen, 138 null, // derived from 139 new String[] { // include classes 140 "**" 141 }, 142 new HashSet<String>(0) /* excluded classes */, 143 new String[]{} /* include files */); 144 aa.analyze(); 145 agen.generate(); 146 147 Set<String> notRenamed = agen.getClassesNotRenamed(); 148 assertArrayEquals(new String[] { "not/an/actual/ClassName" }, notRenamed.toArray()); 149 150 } 151 152 @Test 153 public void testClassRefactoring() throws IOException, LogAbortException { 154 ICreateInfo ci = new ICreateInfo() { 155 @Override 156 public Class<?>[] getInjectedClasses() { 157 // classes to inject in the final JAR 158 return new Class<?>[] { 159 com.android.tools.layoutlib.create.dataclass.JavaClass.class 160 }; 161 } 162 163 @Override 164 public String[] getDelegateMethods() { 165 return new String[0]; 166 } 167 168 @Override 169 public String[] getDelegateClassNatives() { 170 return new String[0]; 171 } 172 173 @Override 174 public String[] getOverriddenMethods() { 175 // methods to force override 176 return new String[0]; 177 } 178 179 @Override 180 public String[] getRenamedClasses() { 181 // classes to rename (so that we can replace them) 182 return new String[0]; 183 } 184 185 @Override 186 public String[] getJavaPkgClasses() { 187 // classes to refactor (so that we can replace them) 188 return new String[] { 189 "java.lang.JavaClass", "com.android.tools.layoutlib.create.dataclass.JavaClass", 190 }; 191 } 192 193 @Override 194 public Set<String> getExcludedClasses() { 195 return Collections.singleton("java.lang.JavaClass"); 196 } 197 198 @Override 199 public String[] getDeleteReturns() { 200 // methods deleted from their return type. 201 return new String[0]; 202 } 203 }; 204 205 AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci); 206 207 AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen, 208 null, // derived from 209 new String[] { // include classes 210 "**" 211 }, 212 new HashSet<String>(1), 213 new String[] { /* include files */ 214 "mock_android/data/data*" 215 }); 216 aa.analyze(); 217 agen.generate(); 218 Map<String, ClassReader> output = new TreeMap<String, ClassReader>(); 219 Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); 220 parseZip(mOsDestJar, output, filesFound); 221 boolean injectedClassFound = false; 222 for (ClassReader cr: output.values()) { 223 TestClassVisitor cv = new TestClassVisitor(); 224 cr.accept(cv, 0); 225 injectedClassFound |= cv.mInjectedClassFound; 226 } 227 assertTrue(injectedClassFound); 228 assertArrayEquals(new String[] {"mock_android/data/dataFile"}, 229 filesFound.keySet().toArray()); 230 } 231 232 @Test 233 public void testClassExclusion() throws IOException, LogAbortException { 234 ICreateInfo ci = new ICreateInfo() { 235 @Override 236 public Class<?>[] getInjectedClasses() { 237 return new Class<?>[0]; 238 } 239 240 @Override 241 public String[] getDelegateMethods() { 242 return new String[0]; 243 } 244 245 @Override 246 public String[] getDelegateClassNatives() { 247 return new String[0]; 248 } 249 250 @Override 251 public String[] getOverriddenMethods() { 252 // methods to force override 253 return new String[0]; 254 } 255 256 @Override 257 public String[] getRenamedClasses() { 258 // classes to rename (so that we can replace them) 259 return new String[0]; 260 } 261 262 @Override 263 public String[] getJavaPkgClasses() { 264 // classes to refactor (so that we can replace them) 265 return new String[0]; 266 } 267 268 @Override 269 public Set<String> getExcludedClasses() { 270 Set<String> set = new HashSet<String>(2); 271 set.add("mock_android.dummy.InnerTest"); 272 set.add("java.lang.JavaClass"); 273 return set; 274 } 275 276 @Override 277 public String[] getDeleteReturns() { 278 // methods deleted from their return type. 279 return new String[0]; 280 } 281 }; 282 283 AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci); 284 Set<String> excludedClasses = ci.getExcludedClasses(); 285 AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen, 286 null, // derived from 287 new String[] { // include classes 288 "**" 289 }, 290 excludedClasses, 291 new String[] { /* include files */ 292 "mock_android/data/data*" 293 }); 294 aa.analyze(); 295 agen.generate(); 296 Map<String, ClassReader> output = new TreeMap<String, ClassReader>(); 297 Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); 298 parseZip(mOsDestJar, output, filesFound); 299 for (String s : output.keySet()) { 300 assertFalse(excludedClasses.contains(s)); 301 } 302 assertArrayEquals(new String[] {"mock_android/data/dataFile"}, 303 filesFound.keySet().toArray()); 304 } 305 306 private void parseZip(String jarPath, 307 Map<String, ClassReader> classes, 308 Map<String, InputStream> filesFound) throws IOException { 309 310 ZipFile zip = new ZipFile(jarPath); 311 Enumeration<? extends ZipEntry> entries = zip.entries(); 312 ZipEntry entry; 313 while (entries.hasMoreElements()) { 314 entry = entries.nextElement(); 315 if (entry.getName().endsWith(".class")) { 316 ClassReader cr = new ClassReader(zip.getInputStream(entry)); 317 String className = classReaderToClassName(cr); 318 classes.put(className, cr); 319 } else { 320 filesFound.put(entry.getName(), zip.getInputStream(entry)); 321 } 322 } 323 324 } 325 326 private String classReaderToClassName(ClassReader classReader) { 327 if (classReader == null) { 328 return null; 329 } else { 330 return classReader.getClassName().replace('/', '.'); 331 } 332 } 333 334 private class TestClassVisitor extends ClassVisitor { 335 336 boolean mInjectedClassFound = false; 337 338 TestClassVisitor() { 339 super(Opcodes.ASM4); 340 } 341 342 @Override 343 public void visit(int version, int access, String name, String signature, 344 String superName, String[] interfaces) { 345 assertTrue(!getBase(name).equals(JAVA_CLASS_NAME)); 346 if (name.equals("com/android/tools/layoutlib/create/dataclass/JavaClass")) { 347 mInjectedClassFound = true; 348 } 349 super.visit(version, access, name, signature, superName, interfaces); 350 } 351 352 @Override 353 public FieldVisitor visitField(int access, String name, String desc, 354 String signature, Object value) { 355 assertTrue(testType(Type.getType(desc))); 356 return super.visitField(access, name, desc, signature, value); 357 } 358 359 @SuppressWarnings("hiding") 360 @Override 361 public MethodVisitor visitMethod(int access, String name, String desc, 362 String signature, String[] exceptions) { 363 MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); 364 return new MethodVisitor(Opcodes.ASM4, mv) { 365 366 @Override 367 public void visitFieldInsn(int opcode, String owner, String name, 368 String desc) { 369 assertTrue(!getBase(owner).equals(JAVA_CLASS_NAME)); 370 assertTrue(testType(Type.getType(desc))); 371 super.visitFieldInsn(opcode, owner, name, desc); 372 } 373 374 @Override 375 public void visitLdcInsn(Object cst) { 376 if (cst instanceof Type) { 377 assertTrue(testType((Type)cst)); 378 } 379 super.visitLdcInsn(cst); 380 } 381 382 @Override 383 public void visitTypeInsn(int opcode, String type) { 384 assertTrue(!getBase(type).equals(JAVA_CLASS_NAME)); 385 super.visitTypeInsn(opcode, type); 386 } 387 388 @Override 389 public void visitMethodInsn(int opcode, String owner, String name, 390 String desc) { 391 assertTrue(!getBase(owner).equals(JAVA_CLASS_NAME)); 392 assertTrue(testType(Type.getType(desc))); 393 super.visitMethodInsn(opcode, owner, name, desc); 394 } 395 396 }; 397 } 398 399 private boolean testType(Type type) { 400 int sort = type.getSort(); 401 if (sort == Type.OBJECT) { 402 assertTrue(!getBase(type.getInternalName()).equals(JAVA_CLASS_NAME)); 403 } else if (sort == Type.ARRAY) { 404 assertTrue(!getBase(type.getElementType().getInternalName()) 405 .equals(JAVA_CLASS_NAME)); 406 } else if (sort == Type.METHOD) { 407 boolean r = true; 408 for (Type t : type.getArgumentTypes()) { 409 r &= testType(t); 410 } 411 return r & testType(type.getReturnType()); 412 } 413 return true; 414 } 415 416 private String getBase(String className) { 417 if (className == null) { 418 return null; 419 } 420 int pos = className.indexOf('$'); 421 if (pos > 0) { 422 return className.substring(0, pos); 423 } 424 return className; 425 } 426 } 427} 428