1 /* 2 * Copyright (C) 2019 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.create.dataclass.OuterClass; 20 import com.android.tools.layoutlib.create.dataclass.UsageClass; 21 22 import org.junit.Test; 23 import org.objectweb.asm.ClassReader; 24 import org.objectweb.asm.ClassWriter; 25 26 import java.io.IOException; 27 import java.lang.reflect.Method; 28 import java.util.HashMap; 29 import java.util.Map; 30 import java.util.Map.Entry; 31 import java.util.Set; 32 33 import static org.junit.Assert.assertEquals; 34 import static org.junit.Assert.assertNotNull; 35 36 public class RefactorClassAdapterTest { 37 private static final String OUTER_CLASS_NAME = OuterClass.class.getName(); 38 private static final String USAGE_CLASS_NAME = UsageClass.class.getName(); 39 40 @Test testRefactorWithLambdas()41 public void testRefactorWithLambdas() throws Exception { 42 Map<String, String> refactorMap = new HashMap<>(); 43 String originalClassName = OUTER_CLASS_NAME.replace('.', '/'); 44 String newClassName = originalClassName + "Refactored"; 45 refactorMap.put(originalClassName, newClassName); 46 47 ClassWriter outerCw = new ClassWriter(0 /*flags*/); 48 RefactorClassAdapter cv = new RefactorClassAdapter(outerCw, refactorMap); 49 ClassReader cr = new ClassReader(OUTER_CLASS_NAME); 50 cr.accept(cv, 0 /* flags */); 51 52 ClassWriter usageCw = new ClassWriter(0 /*flags*/); 53 cv = new RefactorClassAdapter(usageCw, refactorMap); 54 cr = new ClassReader(USAGE_CLASS_NAME); 55 cr.accept(cv, 0 /* flags */); 56 57 ClassLoader2 cl2 = new ClassLoader2() { 58 @Override 59 public void testModifiedInstance() throws Exception { 60 Class<?> clazz2 = loadClass(USAGE_CLASS_NAME); 61 Object usage = clazz2.newInstance(); 62 assertNotNull(usage); 63 assertEquals(17, callUsage(usage)); 64 } 65 }; 66 cl2.add(OUTER_CLASS_NAME + "Refactored", outerCw); 67 cl2.add(USAGE_CLASS_NAME, usageCw); 68 cl2.testModifiedInstance(); 69 } 70 71 //------- 72 73 /** 74 * A class loader than can define and instantiate our modified classes. 75 * <p/> 76 * Trying to do so in the original class loader generates all sort of link issues because 77 * there are 2 different definitions of the same class name. This class loader will 78 * define and load the class when requested by name and provide helpers to access the 79 * instance methods via reflection. 80 */ 81 private abstract static class ClassLoader2 extends ClassLoader { 82 83 private final Map<String, byte[]> mClassDefs = new HashMap<>(); 84 ClassLoader2()85 public ClassLoader2() { 86 super(null); 87 } 88 add(String className, ClassWriter rewrittenClass)89 private void add(String className, ClassWriter rewrittenClass) { 90 mClassDefs.put(className, rewrittenClass.toByteArray()); 91 } 92 getByteCode()93 private Set<Entry<String, byte[]>> getByteCode() { 94 return mClassDefs.entrySet(); 95 } 96 97 @Override findClass(String name)98 protected Class<?> findClass(String name) { 99 try { 100 return super.findClass(name); 101 } catch (ClassNotFoundException e) { 102 103 byte[] def = mClassDefs.get(name); 104 if (def != null) { 105 // Load the modified class from its bytes representation. 106 return defineClass(name, def, 0, def.length); 107 } 108 109 try { 110 // Load everything else from the original definition into the new class loader. 111 ClassReader cr = new ClassReader(name); 112 ClassWriter cw = new ClassWriter(0); 113 cr.accept(cw, 0); 114 byte[] bytes = cw.toByteArray(); 115 return defineClass(name, bytes, 0, bytes.length); 116 117 } catch (IOException ioe) { 118 throw new RuntimeException(ioe); 119 } 120 } 121 } 122 123 /** 124 * Accesses {@link UsageClass#doSomething} via reflection. 125 */ callUsage(Object instance)126 public int callUsage(Object instance) throws Exception { 127 Method m = instance.getClass().getMethod("doSomething"); 128 129 Object result = m.invoke(instance); 130 return (Integer) result; 131 } 132 testModifiedInstance()133 public abstract void testModifiedInstance() throws Exception; 134 } 135 } 136