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