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