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}