001package io.ebean.enhance.entity;
002
003import io.ebean.enhance.asm.AnnotationVisitor;
004import io.ebean.enhance.asm.ClassVisitor;
005import io.ebean.enhance.asm.FieldVisitor;
006import io.ebean.enhance.asm.MethodVisitor;
007import io.ebean.enhance.asm.Opcodes;
008import io.ebean.enhance.common.ClassMeta;
009import io.ebean.enhance.common.EnhanceConstants;
010import io.ebean.enhance.common.EnhanceContext;
011import io.ebean.enhance.common.NoEnhancementRequiredException;
012
013/**
014 * ClassAdapter for enhancing entities.
015 * <p>
016 * Used for javaagent or ant etc to modify the class with field interception.
017 * </p>
018 * <p>
019 * This is NOT used for subclass generation.
020 * </p>
021 */
022public class ClassAdapterEntity extends ClassVisitor implements EnhanceConstants {
023
024  private final EnhanceContext enhanceContext;
025
026  private final ClassLoader classLoader;
027
028  private final ClassMeta classMeta;
029
030  private boolean firstMethod = true;
031
032  public ClassAdapterEntity(ClassVisitor cv, ClassLoader classLoader, EnhanceContext context) {
033    super(Opcodes.ASM7, cv);
034    this.classLoader = classLoader;
035    this.enhanceContext = context;
036    this.classMeta = context.createClassMeta();
037  }
038
039  /**
040  * Log that the class has been enhanced.
041  */
042  public void logEnhanced() {
043    classMeta.logEnhanced();
044  }
045
046  public boolean isLog(int level){
047    return classMeta.isLog(level);
048  }
049
050  public void log(String msg){
051    classMeta.log(msg);
052  }
053
054  /**
055  * Create the class definition replacing the className and super class.
056  */
057  @Override
058  public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
059
060    classMeta.setClassName(name, superName);
061
062    int n = 1 + interfaces.length;
063    String[] c = new String[n];
064    for (int i = 0; i < interfaces.length; i++) {
065      c[i] = interfaces[i];
066      if (c[i].equals(C_ENTITYBEAN)) {
067        classMeta.setEntityBeanInterface(true);
068      }
069      if (c[i].equals(C_SCALAOBJECT)) {
070        classMeta.setScalaInterface(true);
071      }
072      if (c[i].equals(C_GROOVYOBJECT)) {
073        classMeta.setGroovyInterface(true);
074      }
075    }
076
077    if (classMeta.hasEntityBeanInterface()){
078      // Just use the original interfaces
079      c = interfaces;
080    } else {
081      // Add the EntityBean interface
082      c[c.length - 1] = C_ENTITYBEAN;
083      if (classMeta.isLog(8)) {
084        classMeta.log("... add EntityBean interface");
085      }
086    }
087
088    if (!superName.equals("java/lang/Object")){
089      // read information about superClasses...
090      if (classMeta.isLog(7)){
091        classMeta.log("read information about superClasses " + superName + " to see if it is entity/embedded/mappedSuperclass");
092      }
093      ClassMeta superMeta = enhanceContext.getSuperMeta(superName, classLoader);
094      if (superMeta != null && superMeta.isEntity()){
095        // the superClass is an entity/embedded/mappedSuperclass...
096        classMeta.setSuperMeta(superMeta);
097        if (classMeta.isLog(3)){
098          classMeta.log("entity extends "+superMeta.getDescription());
099        }
100      } else {
101        if (classMeta.isLog(7)){
102          if (superMeta == null){
103            classMeta.log("unable to read superMeta for "+superName);
104          } else {
105            classMeta.log("superMeta "+superName+" is not an entity/embedded/mappedsuperclass (with equals or persistent fields)");
106          }
107        }
108      }
109    }
110
111    super.visit(version, access, name, signature, superName, c);
112  }
113
114  @Override
115  public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
116    classMeta.addClassAnnotation(desc);
117    return super.visitAnnotation(desc, visible);
118  }
119
120  /**
121  * Return true if this is the enhancement marker field.
122  * <p>
123  * The existence of this field is used to confirm that the class has been
124  * enhanced (rather than solely relying on the EntityBean interface).
125  * <p>
126  */
127  private boolean isEbeanFieldMarker(String name) {
128    return name.equals(MarkerField._EBEAN_MARKER);
129  }
130
131  private boolean isPropertyChangeListenerField(String desc) {
132    return desc.equals("Ljava/beans/PropertyChangeSupport;");
133  }
134
135  /**
136  * The ebeanIntercept field is added once but thats all. Note the other
137  * fields are defined in the superclass.
138  */
139  @Override
140  public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
141
142    if ((access & Opcodes.ACC_STATIC) != 0) {
143      // static field...
144      if (isEbeanFieldMarker(name)) {
145        classMeta.setAlreadyEnhanced(true);
146        if (isLog(4)) {
147          log("Found ebean marker field " + name + " " + value);
148        }
149      } else {
150        if (isLog(4)) {
151          log("Skip intercepting static field " + name);
152        }
153      }
154
155      // no interception of static fields
156      return super.visitField(access, name, desc, signature, value);
157    }
158
159    if (isPropertyChangeListenerField(desc)) {
160      if (isLog(2)){
161        classMeta.log("Found existing PropertyChangeSupport field "+name);
162      }
163      // no interception on PropertyChangeSupport field
164      return super.visitField(access, name, desc, signature, value);
165    }
166
167    if ((access & Opcodes.ACC_TRANSIENT) != 0) {
168      if (isLog(3)){
169        log("Skip intercepting transient field "+name);
170      }
171      // no interception of transient fields
172      return super.visitField(access, name, desc, signature, value);
173    }
174
175    if ((access & Opcodes.ACC_FINAL) != 0) {
176      // remove final modifier from fields (for lazy loading partials in Java9+)
177      access = (access ^ Opcodes.ACC_FINAL);
178    }
179
180    FieldVisitor fv = super.visitField(access, name, desc, signature, value);
181    return classMeta.createLocalFieldVisitor(fv, name, desc);
182  }
183
184  /**
185  * Replace the method code with field interception.
186  */
187  @Override
188  public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
189
190    if (firstMethod){
191      if (classMeta.isAlreadyEnhanced()) {
192        throw new NoEnhancementRequiredException();
193      }
194
195      if (classMeta.hasEntityBeanInterface()){
196        log("Enhancing when EntityBean interface already exists!");
197      }
198
199      // always add the marker field on every enhanced class
200      String marker = MarkerField.addField(cv, classMeta.getClassName());
201      if (isLog(4)){
202        log("... add marker field \""+marker+"\"");
203      }
204
205      IndexFieldWeaver.addPropertiesField(cv);
206      if (isLog(4)){
207        log("... add _ebean_props field");
208      }
209
210      if (!classMeta.isSuperClassEntity()){
211        // only add the intercept and identity fields if
212        // the superClass is not also enhanced
213        if (isLog(4)){
214          log("... add intercept and identity fields");
215        }
216        InterceptField.addField(cv, enhanceContext.isTransientInternalFields());
217        MethodEquals.addIdentityField(cv);
218
219      }
220      firstMethod = false;
221    }
222
223
224    classMeta.addExistingMethod(name, desc);
225
226    if (isLog(4)) {
227      log("--- #### method name["+name+"] desc["+desc+"] sig["+signature+"]");
228    }
229
230    if (isConstructor(name, desc)) {
231      if (desc.equals(NOARG_VOID)) {
232        // ensure public access on the default constructor
233        access = Opcodes.ACC_PUBLIC;
234      }
235      MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
236      return new ConstructorAdapter(mv, classMeta, desc);
237    }
238
239    if (isStaticInit(name, desc)) {
240      if (isLog(4)) {
241        log("... --- #### enhance existing static init method");
242      }
243      MethodVisitor mv = super.visitMethod(Opcodes.ACC_STATIC, name, desc, signature, exceptions);
244      return new MethodStaticInitAdapter(mv, classMeta);
245    }
246
247    MethodVisitor mv =  super.visitMethod(access, name, desc, signature, exceptions);
248
249    if (interceptEntityMethod(access, name, desc)) {
250      // change the method replacing the relevant GETFIELD PUTFIELD with
251      // our special field methods with interception...
252      return new MethodFieldAdapter(mv, classMeta, name+" "+desc);
253    }
254
255    // just leave as is, no interception etc
256    return mv;
257  }
258
259  /**
260  * Add methods to get and set the entityBeanIntercept. Also add the
261  * writeReplace method to control serialisation.
262  */
263  @Override
264  public void visitEnd() {
265
266    if (!classMeta.isEntityEnhancementRequired()){
267      throw new NoEnhancementRequiredException();
268    }
269
270    if (!classMeta.hasStaticInit()) {
271      IndexFieldWeaver.addPropertiesInit(cv, classMeta);
272    }
273
274    if (!classMeta.hasDefaultConstructor()){
275      DefaultConstructor.add(cv, classMeta);
276    }
277
278    MarkerField.addGetMarker(cv, classMeta.getClassName());
279
280    if (isLog(4)){
281      log("... add _ebean_getPropertyNames() and _ebean_getPropertyName()");
282    }
283    IndexFieldWeaver.addGetPropertyNames(cv, classMeta);
284    IndexFieldWeaver.addGetPropertyName(cv, classMeta);
285
286    if (!classMeta.isSuperClassEntity()){
287      if (isLog(8)){
288        log("... add _ebean_getIntercept() and _ebean_setIntercept()");
289      }
290      InterceptField.addGetterSetter(cv, classMeta.getClassName());
291    }
292
293    // Add the field set/get methods which are used in place
294    // of GETFIELD PUTFIELD instructions
295    classMeta.addFieldGetSetMethods(cv);
296
297    //Add the getField(index) and setField(index) methods
298    IndexFieldWeaver.addMethods(cv, classMeta);
299
300    MethodSetEmbeddedLoaded.addMethod(cv, classMeta);
301    MethodIsEmbeddedNewOrDirty.addMethod(cv, classMeta);
302    MethodNewInstance.addMethod(cv, classMeta);
303
304    // register with the agentContext
305    enhanceContext.addClassMeta(classMeta);
306
307    super.visitEnd();
308  }
309
310  private boolean isConstructor(String name, String desc){
311
312    if (name.equals(INIT)) {
313      if (desc.equals(NOARG_VOID)) {
314        classMeta.setHasDefaultConstructor(true);
315      }
316      return true;
317    }
318    return false;
319  }
320
321  private boolean isStaticInit(String name, String desc) {
322
323    if (name.equals(CLINIT) && desc.equals(NOARG_VOID)) {
324      classMeta.setHasStaticInit(true);
325      return true;
326    }
327
328    return false;
329  }
330
331  private boolean interceptEntityMethod(int access, String name, String desc) {
332
333    if ((access & Opcodes.ACC_STATIC) != 0) {
334      // no interception of static methods?
335      if (isLog(4)){
336        log("Skip intercepting static method "+name);
337      }
338      return false;
339    }
340
341    if (name.equals("hashCode") && desc.equals("()I")) {
342      classMeta.setHasEqualsOrHashcode(true);
343      return true;
344    }
345
346    if (name.equals("equals") && desc.equals("(Ljava/lang/Object;)Z")) {
347      classMeta.setHasEqualsOrHashcode(true);
348      return true;
349    }
350
351    if (name.equals("toString") && desc.equals("()Ljava/lang/String;")) {
352      // don't intercept toString as its is used
353      // during debugging etc
354      return false;
355    }
356
357    return true;
358  }
359}