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}