LayoutlibClassLoader extends ClassLoader { @VisibleForTesting static void generate(@NotNull Class<?> originalBuildClass, @NotNull BiConsumer<String, byte[]> defineClass) { ClassLoader loader = originalBuildClass.getClassLoader(); String originalBuildClassName = originalBuildClass.getName(); String originalBuildBinaryClassName = toBinaryClassName(originalBuildClassName); Deque<String> pendingClasses = new LinkedList<>(); pendingClasses.push(originalBuildClassName); Remapper remapper = new Remapper() { @Override public String map(String typeName) { if (typeName.startsWith(originalBuildBinaryClassName)) { return "android/os/Build" + StringUtil.trimStart(typeName, originalBuildBinaryClassName); } return typeName; } }; while (!pendingClasses.isEmpty()) { String name = pendingClasses.pop(); String newName = "android.os.Build" + StringUtil.trimStart(name, originalBuildClassName); String binaryName = toBinaryClassName(name); try (InputStream is = loader.getResourceAsStream(binaryName + ".class")) { ClassWriter writer = new ClassWriter(0); ClassReader reader = new ClassReader(is); ClassRemapper classRemapper = new ClassRemapper(writer, remapper) { @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { if (outerName != null && outerName.startsWith(binaryName)) { pendingClasses.push(toClassName(name)); } super.visitInnerClass(name, outerName, innerName, access); } }; reader.accept(classRemapper, 0); defineClass.accept(newName, writer.toByteArray()); } catch (IOException e) { LOG.warn("Unable to define android.os.Build", e); } } } LayoutlibClassLoader(@NotNull ClassLoader parent);   }
@Test public void generateBuildFile() throws Exception { Map<String, String> definedClasses = new HashMap<>(); LayoutlibClassLoader.generate(TestBuild.class, (name, classBytes) -> { StringWriter writer = new StringWriter(); ClassReader reader = new ClassReader(classBytes); TraceClassVisitor visitor = new TraceClassVisitor(new PrintWriter(writer)); reader.accept(visitor, 0); definedClasses.put(name, simplify(writer.toString())); }); assertEquals(3, definedClasses.size()); assertEquals("public class android/os/Build {\n" + " public static INNERCLASS android/os/Build$InnerClass2 android/os/Build InnerClass2\n" + " public static INNERCLASS android/os/Build$InnerClass android/os/Build InnerClass\n" + " public final static Ljava/lang/String; TEST_FIELD = \"TestValue\"\n" + " public <init>()V\n" + " private static privateMethod()Ljava/lang/String;\n" + " public static getSerial()Ljava/lang/String;\n" + "}", definedClasses.get("android.os.Build")); assertEquals("public class android/os/Build$InnerClass {\n" + " public static INNERCLASS android/os/Build$InnerClass android/os/Build InnerClass\n" + " public final static Ljava/lang/String; TEST_INNER_FIELD = \"TestInnerValue\"\n" + " public final static I INNER_VALUE = 1\n" + " public <init>()V\n" + "}", definedClasses.get("android.os.Build$InnerClass")); assertEquals("public class android/os/Build$InnerClass2 {\n" + " public static INNERCLASS android/os/Build$InnerClass2 android/os/Build InnerClass2\n" + " public final static Ljava/lang/String; TEST_INNER_FIELD2\n" + " public <init>()V\n" + " static <clinit>()V\n" + "}", definedClasses.get("android.os.Build$InnerClass2")); }