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}