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