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