001package io.ebean.enhance.common;
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.entity.FieldMeta;
009import io.ebean.enhance.entity.LocalFieldVisitor;
010import io.ebean.enhance.entity.MessageOutput;
011import io.ebean.enhance.entity.MethodMeta;
012
013import java.util.ArrayList;
014import java.util.HashSet;
015import java.util.LinkedHashMap;
016import java.util.List;
017import java.util.logging.Level;
018import java.util.logging.Logger;
019
020/**
021 * Holds the meta data for an entity bean class that is being enhanced.
022 */
023public class ClassMeta {
024
025        private static final Logger logger = Logger.getLogger(ClassMeta.class.getName());
026
027        private static final String OBJECT_CLASS = Object.class.getName().replace('.', '/');
028        
029        private final MessageOutput logout;
030
031        private final int logLevel;
032
033        private String className;
034
035        private String superClassName;
036
037        private ClassMeta superMeta;
038
039        /**
040         * Set to true if the class implements th GroovyObject interface.
041         */
042        private boolean hasGroovyInterface;
043
044        /**
045         * Set to true if the class implements the ScalaObject interface.
046         */
047        private boolean hasScalaInterface;
048
049        /**
050         * Set to true if the class already implements the EntityBean interface.
051         */
052        private boolean hasEntityBeanInterface;
053        
054        private boolean alreadyEnhanced;
055
056        private boolean hasEqualsOrHashcode;
057
058        private boolean hasDefaultConstructor;
059        
060        private boolean hasStaticInit;
061
062        private HashSet<String> existingMethods = new HashSet<String>();
063
064        private LinkedHashMap<String, FieldMeta> fields = new LinkedHashMap<String, FieldMeta>();
065
066        private HashSet<String> classAnnotation = new HashSet<String>();
067
068        private AnnotationInfo annotationInfo = new AnnotationInfo(null);
069
070        private ArrayList<MethodMeta> methodMetaList = new ArrayList<MethodMeta>();
071
072        private final EnhanceContext enhanceContext;
073        
074  private List<FieldMeta> allFields;
075  
076        public ClassMeta(EnhanceContext enhanceContext, int logLevel, MessageOutput logout) {
077                this.enhanceContext = enhanceContext;
078                this.logLevel = logLevel;
079                this.logout = logout;
080        }
081        
082        /**
083         * Return the enhance context which has options for enhancement.
084         */
085        public EnhanceContext getEnhanceContext() {
086        return enhanceContext;
087    }
088        
089        /**
090         * Return the AnnotationInfo collected on methods. 
091         * Used to determine Transactional method enhancement.
092         */
093        public AnnotationInfo getAnnotationInfo() {
094                return annotationInfo;
095        }
096
097        /**
098         * Return the transactional annotation information for a matching interface method.
099         */
100        public AnnotationInfo getInterfaceTransactionalInfo(String methodName, String methodDesc) {
101
102                AnnotationInfo annotationInfo = null;
103
104                for (int i = 0; i < methodMetaList.size(); i++) {
105                        MethodMeta meta = methodMetaList.get(i);
106                        if (meta.isMatch(methodName, methodDesc)) {
107                                if (annotationInfo != null) {
108                                        String msg = "Error in [" + className + "] searching the transactional methods[" + methodMetaList
109                                                        + "] found more than one match for the transactional method:" + methodName + " "
110                                                        + methodDesc;
111                                        
112                                        logger.log(Level.SEVERE, msg);
113                                        log(msg);
114                                        
115                                } else {
116                                        annotationInfo = meta.getAnnotationInfo();
117                                        if (isLog(9)){
118                                                log("... found transactional info from interface "+className+" "+methodName+" "+methodDesc);
119                                        }
120                                }
121                        }
122                }
123
124                return annotationInfo;
125        }
126
127        public boolean isCheckSuperClassForEntity() {
128    return !superClassName.equals(OBJECT_CLASS) && isCheckEntity();
129  }
130
131        public String toString() {
132                return className;
133        }
134
135        public boolean isTransactional() {
136    return classAnnotation.contains(EnhanceConstants.AVAJE_TRANSACTIONAL_ANNOTATION);
137  }
138
139        public void setClassName(String className, String superClassName) {
140                this.className = className;
141                this.superClassName = superClassName;
142        }
143
144        public String getSuperClassName() {
145                return superClassName;
146        }
147
148        public boolean isLog(int level) {
149                return level <= logLevel;
150        }
151
152        public void log(String msg) {
153                if (className != null) {
154                        msg = "cls: " + className + "  msg: " + msg;
155                }
156                logout.println("ebean-enhance> " + msg);
157        }
158        
159        public void logEnhanced() {
160                String m = "enhanced ";
161                if (hasScalaInterface()){
162                        m += " (scala)";
163                }
164                if (hasGroovyInterface()){
165                        m += " (groovy)";
166                }
167                log(m);
168        }
169
170        public void setSuperMeta(ClassMeta superMeta) {
171                this.superMeta = superMeta;
172        }
173
174        /**
175         * Set to true if the class has an existing equals() or hashcode() method.
176         */
177        public void setHasEqualsOrHashcode(boolean hasEqualsOrHashcode) {
178                this.hasEqualsOrHashcode = hasEqualsOrHashcode;
179        }
180
181        /**
182         * Return true if Equals/hashCode is implemented on this class or a super class.
183         */
184        public boolean hasEqualsOrHashCode() {
185          if (hasEqualsOrHashcode) {
186            return true;
187            
188          } else {
189      return (superMeta != null && superMeta.hasEqualsOrHashCode());
190    }
191        }
192
193        /**
194         * Return true if the field is a persistent field.
195         */
196        public boolean isFieldPersistent(String fieldName) {
197
198                FieldMeta f = getFieldPersistent(fieldName);
199                return (f == null) ? false: f.isPersistent();
200        }
201
202  /**
203   * Return true if the field is a persistent many field.
204   */
205  public boolean isFieldPersistentMany(String fieldName) {
206    FieldMeta f = getFieldPersistent(fieldName);
207    return (f != null && f.isPersistent() && f.isMany());
208  }
209
210  /**
211   * Return the field - null when not found.
212   */
213  public FieldMeta getFieldPersistent(String fieldName) {
214
215    FieldMeta f = fields.get(fieldName);
216    if (f != null) {
217      return f;
218    }
219    return (superMeta == null) ? null : superMeta.getFieldPersistent(fieldName);
220  }
221
222        /**
223         * Return the list of fields local to this type (not inherited).
224         */
225        public List<FieldMeta> getLocalFields() {
226
227                ArrayList<FieldMeta> list = new ArrayList<FieldMeta>();
228
229    for (FieldMeta fm : fields.values()) {
230      if (!fm.isObjectArray()) {
231        // add field local to this entity type
232        list.add(fm);
233      }
234    }
235
236                return list;
237        }
238
239        /**
240         * Return the list of fields inherited from super types that are entities.
241         */
242        private List<FieldMeta> getInheritedFields(List<FieldMeta> list) {
243
244                if (list == null){
245                        list = new ArrayList<FieldMeta>();
246                }
247
248                if (superMeta != null) {
249                        superMeta.addFieldsForInheritance(list);
250                }
251                return list;
252        }
253
254        /**
255         * Add all fields to the list.
256         */
257        private void addFieldsForInheritance(List<FieldMeta> list) {
258                if (isEntity()) {
259                        list.addAll(0, fields.values());
260                        if (superMeta != null) {
261                                superMeta.addFieldsForInheritance(list);
262                        }
263                }
264        }
265        
266        /**
267         * Return true if the class contains persistent fields.
268         */
269  public boolean hasPersistentFields() {
270    
271    for (FieldMeta fieldMeta : fields.values()) {
272      if (fieldMeta.isPersistent() || fieldMeta.isTransient()) {
273        return true;
274      }
275    }
276
277    return superMeta != null && superMeta.hasPersistentFields();
278  }
279  
280        /**
281         * Return the list of all fields including ones inherited from entity super
282         * types and mappedSuperclasses.
283         */
284        public List<FieldMeta> getAllFields() {
285
286          if (allFields != null) {
287            return allFields;
288          }
289                List<FieldMeta> list = getLocalFields();
290                getInheritedFields(list);
291                
292                this.allFields = list;
293                for (int i=0; i<allFields.size(); i++) {
294                  allFields.get(i).setIndexPosition(i);
295                }
296                
297                return list;
298        }
299
300        /**
301         * Add field level get set methods for each field.
302         */
303        public void addFieldGetSetMethods(ClassVisitor cv) {
304
305                if (isEntityEnhancementRequired()) {
306      for (FieldMeta fm : fields.values()) {
307        fm.addGetSetMethods(cv, this);
308      }
309                }
310        }
311
312        /**
313         * Return true if the class has an Entity, Embeddable, MappedSuperclass (with persistent fields).
314         */
315        public boolean isEntity() {
316                if (!EntityCheck.hasEntityAnnotation(classAnnotation)) {
317                        return false;
318                }
319                if (classAnnotation.contains(EnhanceConstants.MAPPEDSUPERCLASS_ANNOTATION)) {
320                  // only 'interesting' if it has persistent fields or equals/hashCode.
321                  // Some MappedSuperclass like com.avaje.ebean.Model don't need any enhancement
322                  boolean shouldEnhance = hasEqualsOrHashCode() || hasPersistentFields();
323                  if (isLog(8)) {
324                    log("mappedSuperClass with equals/hashCode or persistentFields: "+shouldEnhance);
325                  }
326                  return shouldEnhance;
327                }
328                return true;
329        }
330
331  /**
332   * Return true if the class has an Entity, Embeddable, or MappedSuperclass.
333   */
334  private boolean isCheckEntity() {
335                return EntityCheck.hasEntityAnnotation(classAnnotation);
336  }
337
338        /**
339         * Return true for classes not already enhanced and yet annotated with entity, embeddable or mappedSuperclass.
340         */
341        public boolean isEntityEnhancementRequired() {
342    return !alreadyEnhanced && isEntity();
343  }
344
345  /**
346   * Return true if the bean is already enhanced.
347   */
348  public boolean isAlreadyEnhanced() {
349    return alreadyEnhanced;
350  }
351
352  /**
353         * Return the className of this entity class.
354         */
355        public String getClassName() {
356                return className;
357        }
358
359        /**
360         * Return true if this entity bean has a super class that is an entity.
361         */
362        public boolean isSuperClassEntity() {
363    return superMeta != null && superMeta.isEntity();
364        }
365
366        /**
367         * Add a class annotation.
368         */
369        public void addClassAnnotation(String desc) {
370                classAnnotation.add(desc);
371        }
372
373        /**
374         * Add an existing method.
375         */
376        public void addExistingMethod(String methodName, String methodDesc) {
377                existingMethods.add(methodName + methodDesc);
378        }
379
380        /**
381         * Return true if the method already exists on the bean.
382         */
383        public boolean isExistingMethod(String methodName, String methodDesc) {
384                return existingMethods.contains(methodName + methodDesc);
385        }
386
387        public MethodVisitor createMethodVisitor(MethodVisitor mv, int access, String name, String desc) {
388
389                MethodMeta methodMeta = new MethodMeta(annotationInfo, access, name, desc);
390                methodMetaList.add(methodMeta);
391
392                return new MethodReader(mv, methodMeta);
393        }
394
395        private static final class MethodReader extends MethodVisitor {
396
397    final MethodMeta methodMeta;
398
399                MethodReader(MethodVisitor mv, MethodMeta methodMeta) {
400      super(Opcodes.ASM6, mv);
401                        this.methodMeta = methodMeta;
402                }
403
404                @Override
405                public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
406      if (mv == null) {
407        return null;
408      }
409      AnnotationVisitor av = mv.visitAnnotation(desc, visible);
410      return new AnnotationInfoVisitor(null, methodMeta.getAnnotationInfo(), av);
411                }
412
413        }
414
415        /**
416         * Create and return a read only fieldVisitor for subclassing option.
417         */
418        public FieldVisitor createLocalFieldVisitor(String name, String desc) {
419                return createLocalFieldVisitor(null, name, desc);
420        }
421
422        /**
423         * Create and return a new fieldVisitor for use when enhancing a class.
424         */
425        public FieldVisitor createLocalFieldVisitor(FieldVisitor fv, String name, String desc) {
426
427                FieldMeta fieldMeta = new FieldMeta(this, name, desc, className);
428                LocalFieldVisitor localField = new LocalFieldVisitor(fv, fieldMeta);
429                if (name.startsWith("_ebean")) {
430                        // can occur when reading inheritance information on
431                        // a entity that has already been enhanced
432                        if (isLog(5)) {
433                                log("... ignore field " + name);
434                        }
435                } else {
436                        fields.put(localField.getName(), fieldMeta);
437                }
438                return localField;
439        }
440
441        public void setAlreadyEnhanced(boolean alreadyEnhanced) {
442                this.alreadyEnhanced = alreadyEnhanced;
443        }
444
445        public boolean hasDefaultConstructor() {
446                return hasDefaultConstructor;
447        }
448
449        public void setHasDefaultConstructor(boolean hasDefaultConstructor) {
450                this.hasDefaultConstructor = hasDefaultConstructor;
451        }
452
453  public void setHasStaticInit(boolean hasStaticInit) {
454    this.hasStaticInit = hasStaticInit;
455  }
456
457        public boolean hasStaticInit() {
458          return hasStaticInit;
459        }
460
461        public String getDescription() {
462                StringBuilder sb = new StringBuilder();
463                appendDescription(sb);
464                return sb.toString();
465        }
466
467        private void appendDescription(StringBuilder sb) {
468                sb.append(className);
469                if (superMeta != null) {
470                        sb.append(" : ");
471                        superMeta.appendDescription(sb);
472                }
473        }
474
475        public boolean hasScalaInterface() {
476                return hasScalaInterface;
477        }
478
479        public void setScalaInterface(boolean hasScalaInterface) {
480                this.hasScalaInterface = hasScalaInterface;
481        }
482
483        public boolean hasEntityBeanInterface() {
484                return hasEntityBeanInterface;
485        }
486
487        public void setEntityBeanInterface(boolean hasEntityBeanInterface) {
488                this.hasEntityBeanInterface = hasEntityBeanInterface;
489        }
490
491        public boolean hasGroovyInterface() {
492                return hasGroovyInterface;
493        }
494
495        public void setGroovyInterface(boolean hasGroovyInterface) {
496                this.hasGroovyInterface = hasGroovyInterface;
497        }
498        
499}