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}