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.ASM6, 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                // this will hide the field on the super object...
176                // but being in a different ClassLoader means we don't
177                // get access to those 'real' private fields
178                FieldVisitor fv = super.visitField(access, name, desc, signature, value);
179                
180                return classMeta.createLocalFieldVisitor(fv, name, desc);
181        }
182
183        /**
184         * Replace the method code with field interception.
185         */
186        @Override
187        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
188                
189                if (firstMethod){
190      if (classMeta.isAlreadyEnhanced()) {
191        throw new NoEnhancementRequiredException();
192      }
193
194                        if (classMeta.hasEntityBeanInterface()){
195                                log("Enhancing when EntityBean interface already exists!");
196                        }
197                        
198                        // always add the marker field on every enhanced class
199                        String marker = MarkerField.addField(cv, classMeta.getClassName());
200                        if (isLog(4)){
201                                log("... add marker field \""+marker+"\"");                                     
202                        }
203                        
204                        IndexFieldWeaver.addPropertiesField(cv);
205                        if (isLog(4)){
206        log("... add _ebean_props field");         
207      }
208                        
209                        if (!classMeta.isSuperClassEntity()){
210                                // only add the intercept and identity fields if 
211                                // the superClass is not also enhanced
212                                if (isLog(4)){
213                                        log("... add intercept and identity fields");                                   
214                                }
215                                InterceptField.addField(cv, enhanceContext.isTransientInternalFields());
216                                MethodEquals.addIdentityField(cv);
217                                
218                        }
219                        firstMethod = false;
220                }
221
222
223                classMeta.addExistingMethod(name, desc);
224                
225    if (isLog(4)) {
226      log("--- #### method name["+name+"] desc["+desc+"] sig["+signature+"]");
227    }
228    
229    if (isConstructor(name, desc)) {
230                        if (desc.equals("()V")) {
231                                // ensure public access on the default constructor
232                                access = Opcodes.ACC_PUBLIC;
233                        }
234      MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
235      return new ConstructorAdapter(mv, classMeta, desc);
236    }
237
238    if (isStaticInit(name, desc)) {
239      if (isLog(4)) {
240        log("... --- #### enhance existing static init method");
241      }
242      MethodVisitor mv = super.visitMethod(Opcodes.ACC_STATIC, name, desc, signature, exceptions);
243      return new MethodStaticInitAdapter(mv, classMeta);
244    }
245        
246          MethodVisitor mv =  super.visitMethod(access, name, desc, signature, exceptions);
247
248                if (interceptEntityMethod(access, name, desc)) {
249                        // change the method replacing the relevant GETFIELD PUTFIELD with
250                        // our special field methods with interception... 
251                        return new MethodFieldAdapter(mv, classMeta, name+" "+desc);                            
252                }
253                
254                // just leave as is, no interception etc
255                return mv;
256        }
257
258        /**
259         * Add methods to get and set the entityBeanIntercept. Also add the
260         * writeReplace method to control serialisation.
261         */
262        @Override
263        public void visitEnd() {
264
265                if (!classMeta.isEntityEnhancementRequired()){
266                        throw new NoEnhancementRequiredException();
267                }
268                
269                if (!classMeta.hasStaticInit()) {
270                  IndexFieldWeaver.addPropertiesInit(cv, classMeta);
271                }
272                
273                if (!classMeta.hasDefaultConstructor()){
274                  DefaultConstructor.add(cv, classMeta);
275                }
276
277                MarkerField.addGetMarker(cv, classMeta.getClassName());
278                
279                if (isLog(4)){
280                  log("... add _ebean_getPropertyNames() and _ebean_getPropertyName()");
281                }
282                IndexFieldWeaver.addGetPropertyNames(cv, classMeta);
283                IndexFieldWeaver.addGetPropertyName(cv, classMeta);
284    
285                if (!classMeta.isSuperClassEntity()){
286            if (isLog(8)){
287              log("... add _ebean_getIntercept() and _ebean_setIntercept()");
288            }
289                        InterceptField.addGetterSetter(cv, classMeta.getClassName());
290                }
291                
292                // Add the field set/get methods which are used in place
293                // of GETFIELD PUTFIELD instructions
294                classMeta.addFieldGetSetMethods(cv);
295                
296                //Add the getField(index) and setField(index) methods
297                IndexFieldWeaver.addMethods(cv, classMeta);
298                
299                MethodSetEmbeddedLoaded.addMethod(cv, classMeta);
300                MethodIsEmbeddedNewOrDirty.addMethod(cv, classMeta);
301    MethodNewInstance.addMethod(cv, classMeta);
302                
303                // register with the agentContext
304                enhanceContext.addClassMeta(classMeta);
305                
306                super.visitEnd();
307        }
308
309        private boolean isConstructor(String name, String desc){
310
311                if (name.equals("<init>")) {
312                        if (desc.equals("()V")) {
313                                classMeta.setHasDefaultConstructor(true);
314                        }
315                        return true;
316                }
317                return false;
318        }
319        
320  private boolean isStaticInit(String name, String desc) {
321
322    if (name.equals("<clinit>") && desc.equals("()V")) {
323      classMeta.setHasStaticInit(true);
324      return true;
325    }
326
327    return false;
328  }
329        
330        private boolean interceptEntityMethod(int access, String name, String desc) {
331
332                if ((access & Opcodes.ACC_STATIC) != 0) {
333                        // no interception of static methods?
334                        if (isLog(4)){
335                                log("Skip intercepting static method "+name);                                   
336                        }
337                        return false;
338                }
339
340                if (name.equals("hashCode") && desc.equals("()I")) {
341                        classMeta.setHasEqualsOrHashcode(true);
342                        return true;
343                }
344
345                if (name.equals("equals") && desc.equals("(Ljava/lang/Object;)Z")) {
346                        classMeta.setHasEqualsOrHashcode(true);
347                        return true;
348                }
349                
350                if (name.equals("toString") && desc.equals("()Ljava/lang/String;")) {
351                        // don't intercept toString as its is used
352                        // during debugging etc
353                        return false;
354                }
355
356                return true;
357        }
358}