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