001package io.ebean.enhance.common; 002 003import io.ebean.enhance.asm.ClassReader; 004import io.ebean.enhance.asm.ClassVisitor; 005import io.ebean.enhance.asm.ClassWriter; 006import io.ebean.enhance.asm.Opcodes; 007 008import java.io.IOException; 009import java.io.InputStream; 010import java.util.ArrayList; 011import java.util.HashMap; 012import java.util.HashSet; 013import java.util.List; 014import java.util.Map; 015import java.util.Set; 016 017import static io.ebean.enhance.Transformer.EBEAN_ASM_VERSION; 018 019/** 020 * ClassWriter without class loading. Fixes problems on dynamic enhancement mentioned here: 021 * <a href="https://github.com/ebean-orm/ebean-agent/issues/59">https://github.com/ebean-orm/ebean-agent/issues/59</a> 022 * 023 * Idea taken from here: 024 * 025 * <a href="https://github.com/zygote1984/AspectualAdapters/blob/master/ALIA4J-NOIRIn-all/src/org/alia4j/noirin/transform/ClassWriterWithoutClassLoading.java">https://github.com/zygote1984/AspectualAdapters/blob/master/ALIA4J-NOIRIn-all/src/org/alia4j/noirin/transform/ClassWriterWithoutClassLoading.java</a> 026 * 027 * @author praml 028 */ 029public class ClassWriterWithoutClassLoading extends ClassWriter { 030 031 private final Map<String, Set<String>> type2instanceOfs = new HashMap<>(); 032 private final Map<String, String> type2superclass = new HashMap<>(); 033 private final Map<String, Boolean> type2isInterface = new HashMap<>(); 034 private final ClassLoader classLoader; 035 private final List<CommonSuperUnresolved> unresolved = new ArrayList<>(); 036 037 public ClassWriterWithoutClassLoading(ClassReader classReader, int flags, ClassLoader classLoader) { 038 super(classReader, flags); 039 this.classLoader = classLoader; 040 } 041 042 public ClassWriterWithoutClassLoading(int flags, ClassLoader classLoader) { 043 super(flags); 044 this.classLoader = classLoader; 045 } 046 047 public List<CommonSuperUnresolved> getUnresolved() { 048 return unresolved; 049 } 050 051 /** 052 * Returns the common super type of the two given types. 053 * 054 * @param type1 the internal name of a class. 055 * @param type2 the internal name of another class. 056 * @return the internal name of the common super class of the two given classes. 057 */ 058 @Override 059 protected String getCommonSuperClass(final String type1, final String type2) { 060 try { 061 if (getInstanceOfs(type2).contains(type1)) { 062 return type1; 063 } 064 if (getInstanceOfs(type1).contains(type2)) { 065 return type2; 066 } 067 if (isInterface(type1) || isInterface(type2)) { 068 return "java/lang/Object"; 069 } else { 070 String type = type1; 071 do { 072 type = getSuperclass(type); 073 } while (!getInstanceOfs(type2).contains(type)); 074 return type; 075 } 076 } catch (Exception e) { 077 unresolved.add(new CommonSuperUnresolved(type1, type2, e.toString())); 078 return "java/lang/Object"; 079 } 080 } 081 082 private String getSuperclass(String type) { 083 if (!type2superclass.containsKey(type)) { 084 initializeTypeHierarchyFor(type); 085 } 086 return type2superclass.get(type); 087 } 088 089 private boolean isInterface(String type) { 090 if (!type2isInterface.containsKey(type)) { 091 initializeTypeHierarchyFor(type); 092 } 093 return type2isInterface.get(type); 094 } 095 096 private Set<String> getInstanceOfs(String type) { 097 if (!type2instanceOfs.containsKey(type)) { 098 initializeTypeHierarchyFor(type); 099 } 100 return type2instanceOfs.get(type); 101 } 102 103 /** 104 * Here we read the class at bytecode-level. 105 */ 106 private void initializeTypeHierarchyFor(final String internalTypeName) { 107 if (classLoader == null) { 108 // Bug in Zulu JDK for jdk classes (which we should skip anyway) 109 throw new IllegalStateException("ClassLoader is null?"); 110 } 111 try (InputStream classBytes = classLoader.getResourceAsStream(internalTypeName + ".class")){ 112 ClassReader classReader = new ClassReader(classBytes); 113 classReader.accept(new ClassVisitor(EBEAN_ASM_VERSION) { 114 115 @Override 116 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 117 super.visit(version, access, name, signature, superName, interfaces); 118 type2superclass.put(internalTypeName, superName); 119 type2isInterface.put(internalTypeName, (access & Opcodes.ACC_INTERFACE) > 0); 120 121 Set<String> instanceOfs = new HashSet<>(); 122 instanceOfs.add(internalTypeName); // we are instance of ourself 123 if (superName != null) { 124 instanceOfs.add(superName); 125 instanceOfs.addAll(getInstanceOfs(superName)); 126 } 127 for (String superInterface : interfaces) { 128 instanceOfs.add(superInterface); 129 instanceOfs.addAll(getInstanceOfs(superInterface)); 130 } 131 type2instanceOfs.put(internalTypeName, instanceOfs); 132 } 133 }, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); 134 } catch (IOException e) { 135 throw new IllegalArgumentException(e); 136 } 137 } 138 139}