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