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