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 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 = TQ_ROOT_BEAN.equals(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); 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 if (desc.equals(ASSOC_BEAN_BASIC_CONSTRUCTOR_DESC)) { 146 classInfo.setHasBasicConstructor(); 147 return new TypeQueryAssocBasicConstructor(superName, classInfo, cv, desc, signature); 148 } 149 if (desc.equals(ASSOC_BEAN_MAIN_CONSTRUCTOR_DESC)) { 150 classInfo.setHasMainConstructor(); 151 return new TypeQueryAssocMainConstructor(superName, 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 if (classInfo.isTypeQueryBean()) { 163 if (!typeQueryRootBean) { 164 classInfo.addAssocBeanExtras(cv, superName); 165 } else { 166 enhanceContext.summaryQueryBean(className); 167 } 168 TypeQueryAddMethods.add(cv, classInfo, typeQueryRootBean); 169 if (isLog(2)) { 170 classInfo.log("enhanced as query bean"); 171 } 172 } else if (classInfo.isFieldAccessUser()) { 173 enhanceContext.summaryFieldAccessUser(className); 174 } else if (classInfo.isTypeQueryUser()) { 175 if (isLog(4)) { 176 classInfo.log("enhanced - getfield calls replaced"); 177 } 178 } else { 179 throw new NoEnhancementRequiredException("No query bean enhancement"); 180 } 181 super.visitEnd(); 182 } 183 184 /** 185 * Add the marker annotation so that we don't enhance the type query bean twice. 186 */ 187 private void addMarkerAnnotation() { 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}