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}