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