001package io.ebean.enhance.querybean;
002
003import io.ebean.enhance.common.AlreadyEnhancedException;
004import io.ebean.enhance.common.EnhanceContext;
005import io.ebean.enhance.common.NoEnhancementRequiredException;
006import io.ebean.enhance.asm.AnnotationVisitor;
007import io.ebean.enhance.asm.ClassVisitor;
008import io.ebean.enhance.asm.ClassWriter;
009import io.ebean.enhance.asm.FieldVisitor;
010import io.ebean.enhance.asm.MethodVisitor;
011import io.ebean.enhance.asm.Opcodes;
012
013/**
014 * Reads/visits the class and performs the appropriate enhancement if necessary.
015 */
016public class TypeQueryClassAdapter extends ClassVisitor implements Constants {
017
018  private final EnhanceContext enhanceContext;
019
020  private boolean typeQueryRootBean;
021  private String className;
022  private String signature;
023  private ClassInfo classInfo;
024
025  public TypeQueryClassAdapter(ClassWriter cw, EnhanceContext enhanceContext) {
026    super(Opcodes.ASM6, cw);
027    this.enhanceContext = enhanceContext;
028  }
029
030  @Override
031  public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
032
033    super.visit(version, access, name, signature, superName, interfaces);
034    if ((access & Opcodes.ACC_INTERFACE) != 0) {
035      throw new NoEnhancementRequiredException("Not enhancing interface");
036    }
037    this.typeQueryRootBean = TQ_ROOT_BEAN.equals(superName);
038    this.className = name;
039    this.signature = signature;
040    this.classInfo = new ClassInfo(enhanceContext, name);
041  }
042
043  /**
044   * Extract and return the associated entity bean class from the signature.
045   */
046  protected String getDomainClass() {
047    int posStart = signature.indexOf('<');
048    int posEnd = signature.indexOf(';', posStart + 1);
049    return signature.substring(posStart + 2, posEnd);
050  }
051
052  /**
053   * Look for TypeQueryBean annotation.
054   */
055  @Override
056  public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
057    classInfo.checkTypeQueryAnnotation(desc);
058    return super.visitAnnotation(desc, visible);
059  }
060
061  @Override
062  public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
063    if (classInfo.isAlreadyEnhanced()) {
064      throw new AlreadyEnhancedException(className);
065    }
066    if (classInfo.isTypeQueryBean()) {
067      // collect type query bean fields
068      classInfo.addField(access, name, desc, signature);
069    }
070    return super.visitField(access, name, desc, signature, value);
071  }
072
073  @Override
074  public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
075
076    if (classInfo.isTypeQueryBean()) {
077      if ((access & Opcodes.ACC_STATIC) != 0) {
078        if (isLog(5)) {
079          log("ignore static methods on type query bean " +name + " " + desc);
080        }
081        return super.visitMethod(access, name, desc, signature, exceptions);
082      }
083      if (classInfo.addMarkerAnnotation()) {
084        addMarkerAnnotation();
085      }
086      if (name.equals("<init>")) {
087        if (!typeQueryRootBean) {
088          return handleAssocBeanConstructor(access, name, desc, signature, exceptions);
089        }
090        if (isLog(3)) {
091          log("replace constructor code <init> " + desc);
092        }
093        if (desc.equals("(Z)V")) {
094          // Constructor for alias initialises all the properties/fields
095          return new TypeQueryConstructorForAlias(classInfo, cv);
096        }
097        return new TypeQueryConstructorAdapter(classInfo, getDomainClass(), cv, desc, signature);
098      }
099      if (!desc.startsWith("()L")) {
100        if (isLog(5)) {
101          log("leaving method as is - " + name + " " + desc + " " + signature);
102        }
103        return super.visitMethod(access, name, desc, signature, exceptions);
104      }
105      MethodDesc methodDesc = new MethodDesc(access, name, desc, signature, exceptions);
106      if (methodDesc.isGetter()) {
107        if (isLog(3)) {
108          log("overwrite getter method - " + name + " " + desc + " " + signature);
109        }
110        return new TypeQueryGetterAdapter(cv, classInfo, methodDesc);
111      }
112    }
113
114    if (isLog(8)) {
115      log("... checking method " + name + " " + desc);
116    }
117    MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
118    return new MethodAdapter(mv, enhanceContext, classInfo);
119  }
120
121  /**
122   * Handle the constructors for assoc type query beans.
123   */
124  private MethodVisitor handleAssocBeanConstructor(int access, String name, String desc, String signature, String[] exceptions) {
125
126    if (desc.equals(ASSOC_BEAN_BASIC_CONSTRUCTOR_DESC)) {
127      classInfo.setHasBasicConstructor();
128      return new TypeQueryAssocBasicConstructor(classInfo, cv, desc, signature);
129    }
130    if (desc.equals(ASSOC_BEAN_MAIN_CONSTRUCTOR_DESC)) {
131      classInfo.setHasMainConstructor();
132      return new TypeQueryAssocMainConstructor(classInfo, cv, desc, signature);
133    }
134    // leave as is
135    return super.visitMethod(access, name, desc, signature, exceptions);
136  }
137
138  @Override
139  public void visitEnd() {
140    if (classInfo.isAlreadyEnhanced()) {
141      throw new AlreadyEnhancedException(className);
142    }
143
144    if (classInfo.isTypeQueryBean()) {
145      if (!typeQueryRootBean) {
146        classInfo.addAssocBeanExtras(cv);
147      }
148      TypeQueryAddMethods.add(cv, classInfo, typeQueryRootBean);
149      if (isLog(2)) {
150        classInfo.log("enhanced as type query bean");
151      }
152    } else if (classInfo.isTypeQueryUser()) {
153      if (isLog(1)) {
154        classInfo.log("enhanced - getfield calls replaced");
155      }
156    } else {
157      throw new NoEnhancementRequiredException("Not a type bean or caller of type beans");
158    }
159    super.visitEnd();
160  }
161
162  /**
163   * Add the marker annotation so that we don't enhance the type query bean twice.
164   */
165  private void addMarkerAnnotation() {
166
167    if (isLog(4)) {
168      log("... adding marker annotation");
169    }
170    AnnotationVisitor av = cv.visitAnnotation(ANNOTATION_ALREADY_ENHANCED_MARKER, true);
171    if (av != null) {
172      av.visitEnd();
173    }
174  }
175
176  public boolean isLog(int level) {
177    return enhanceContext.isLog(level);
178  }
179
180  public void log(String msg) {
181    enhanceContext.log(className, msg);
182  }
183}