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