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