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
020import static io.ebean.enhance.common.EnhanceConstants.C_OBJECT;
021
022/**
023 * Holds the meta data for an entity bean class that is being enhanced.
024 */
025public class ClassMeta {
026
027  private static final Logger logger = Logger.getLogger(ClassMeta.class.getName());
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<>();
065
066  private HashSet<String> classAnnotation = new HashSet<>();
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(C_OBJECT) && isCheckEntity();
129  }
130
131  @Override
132  public String toString() {
133    return className;
134  }
135
136  public boolean isTransactional() {
137    return classAnnotation.contains(EnhanceConstants.AVAJE_TRANSACTIONAL_ANNOTATION);
138  }
139
140  public void setClassName(String className, String superClassName) {
141    this.className = className;
142    this.superClassName = superClassName;
143  }
144
145  public String getSuperClassName() {
146    return superClassName;
147  }
148
149  public boolean isLog(int level) {
150    return level <= logLevel;
151  }
152
153  public void log(String msg) {
154    if (className != null) {
155      msg = "cls: " + className + "  msg: " + msg;
156    }
157    logout.println("ebean-enhance> " + msg);
158  }
159
160  public void logEnhanced() {
161    String m = "enhanced ";
162    if (hasScalaInterface()){
163      m += " (scala)";
164    }
165    if (hasGroovyInterface()){
166      m += " (groovy)";
167    }
168    log(m);
169  }
170
171  public void setSuperMeta(ClassMeta superMeta) {
172    this.superMeta = superMeta;
173  }
174
175  /**
176  * Set to true if the class has an existing equals() or hashcode() method.
177  */
178  public void setHasEqualsOrHashcode(boolean hasEqualsOrHashcode) {
179    this.hasEqualsOrHashcode = hasEqualsOrHashcode;
180  }
181
182  /**
183  * Return true if Equals/hashCode is implemented on this class or a super class.
184  */
185  public boolean hasEqualsOrHashCode() {
186    if (hasEqualsOrHashcode) {
187      return true;
188
189    } else {
190      return (superMeta != null && superMeta.hasEqualsOrHashCode());
191    }
192  }
193
194  /**
195  * Return true if the field is a persistent field.
196  */
197  public boolean isFieldPersistent(String fieldName) {
198
199    FieldMeta f = getFieldPersistent(fieldName);
200    return (f == null) ? false: f.isPersistent();
201  }
202
203  /**
204  * Return true if the field is a persistent many field.
205  */
206  public boolean isFieldPersistentMany(String fieldName) {
207    FieldMeta f = getFieldPersistent(fieldName);
208    return (f != null && f.isPersistent() && f.isMany());
209  }
210
211  /**
212  * Return the field - null when not found.
213  */
214  public FieldMeta getFieldPersistent(String fieldName) {
215
216    FieldMeta f = fields.get(fieldName);
217    if (f != null) {
218      return f;
219    }
220    return (superMeta == null) ? null : superMeta.getFieldPersistent(fieldName);
221  }
222
223  /**
224  * Return the list of fields local to this type (not inherited).
225  */
226  public List<FieldMeta> getLocalFields() {
227
228    ArrayList<FieldMeta> list = new ArrayList<FieldMeta>();
229
230    for (FieldMeta fm : fields.values()) {
231      if (!fm.isObjectArray()) {
232        // add field local to this entity type
233        list.add(fm);
234      }
235    }
236
237    return list;
238  }
239
240  /**
241  * Return the list of fields inherited from super types that are entities.
242  */
243  private List<FieldMeta> getInheritedFields(List<FieldMeta> list) {
244
245    if (list == null){
246      list = new ArrayList<FieldMeta>();
247    }
248
249    if (superMeta != null) {
250      superMeta.addFieldsForInheritance(list);
251    }
252    return list;
253  }
254
255  /**
256  * Add all fields to the list.
257  */
258  private void addFieldsForInheritance(List<FieldMeta> list) {
259    if (isEntity()) {
260      list.addAll(0, fields.values());
261      if (superMeta != null) {
262        superMeta.addFieldsForInheritance(list);
263      }
264    }
265  }
266
267  /**
268  * Return true if the class contains persistent fields.
269  */
270  public boolean hasPersistentFields() {
271
272    for (FieldMeta fieldMeta : fields.values()) {
273      if (fieldMeta.isPersistent() || fieldMeta.isTransient()) {
274        return true;
275      }
276    }
277
278    return superMeta != null && superMeta.hasPersistentFields();
279  }
280
281  /**
282  * Return the list of all fields including ones inherited from entity super
283  * types and mappedSuperclasses.
284  */
285  public List<FieldMeta> getAllFields() {
286
287    if (allFields != null) {
288      return allFields;
289    }
290    List<FieldMeta> list = getLocalFields();
291    getInheritedFields(list);
292
293    this.allFields = list;
294    for (int i=0; i<allFields.size(); i++) {
295      allFields.get(i).setIndexPosition(i);
296    }
297
298    return list;
299  }
300
301  /**
302  * Add field level get set methods for each field.
303  */
304  public void addFieldGetSetMethods(ClassVisitor cv) {
305
306    if (isEntityEnhancementRequired()) {
307      for (FieldMeta fm : fields.values()) {
308        fm.addGetSetMethods(cv, this);
309      }
310    }
311  }
312
313  /**
314  * Return true if this is a mapped superclass.
315  */
316  public boolean isMappedSuper() {
317    return classAnnotation.contains(EnhanceConstants.MAPPEDSUPERCLASS_ANNOTATION);
318  }
319
320  /**
321  * Return true if the class has an Entity, Embeddable, MappedSuperclass (with persistent fields).
322  */
323  public boolean isEntity() {
324    if (!EntityCheck.hasEntityAnnotation(classAnnotation)) {
325      return false;
326    }
327    if (classAnnotation.contains(EnhanceConstants.MAPPEDSUPERCLASS_ANNOTATION)) {
328      // only 'interesting' if it has persistent fields or equals/hashCode.
329      // Some MappedSuperclass like com.avaje.ebean.Model don't need any enhancement
330      boolean shouldEnhance = hasEqualsOrHashCode() || hasPersistentFields();
331      if (isLog(8)) {
332        log("mappedSuperClass with equals/hashCode or persistentFields: "+shouldEnhance);
333      }
334      return shouldEnhance;
335    }
336    return true;
337  }
338
339  /**
340  * Return true if the class has an Entity, Embeddable, or MappedSuperclass.
341  */
342  private boolean isCheckEntity() {
343    return EntityCheck.hasEntityAnnotation(classAnnotation);
344  }
345
346  /**
347  * Return true for classes not already enhanced and yet annotated with entity, embeddable or mappedSuperclass.
348  */
349  public boolean isEntityEnhancementRequired() {
350    return !alreadyEnhanced && isEntity();
351  }
352
353  /**
354  * Return true if the bean is already enhanced.
355  */
356  public boolean isAlreadyEnhanced() {
357    return alreadyEnhanced;
358  }
359
360  /**
361  * Return the className of this entity class.
362  */
363  public String getClassName() {
364    return className;
365  }
366
367  /**
368  * Return true if this entity bean has a super class that is an entity.
369  */
370  public boolean isSuperClassEntity() {
371    return superMeta != null && superMeta.isEntity();
372  }
373
374  /**
375  * Add a class annotation.
376  */
377  public void addClassAnnotation(String desc) {
378    classAnnotation.add(desc);
379  }
380
381  /**
382  * Add an existing method.
383  */
384  public void addExistingMethod(String methodName, String methodDesc) {
385    existingMethods.add(methodName + methodDesc);
386  }
387
388  /**
389  * Return true if the method already exists on the bean.
390  */
391  public boolean isExistingMethod(String methodName, String methodDesc) {
392    return existingMethods.contains(methodName + methodDesc);
393  }
394
395  public MethodVisitor createMethodVisitor(MethodVisitor mv, int access, String name, String desc) {
396
397    MethodMeta methodMeta = new MethodMeta(annotationInfo, access, name, desc);
398    methodMetaList.add(methodMeta);
399
400    return new MethodReader(mv, methodMeta);
401  }
402
403  private static final class MethodReader extends MethodVisitor {
404
405    final MethodMeta methodMeta;
406
407    MethodReader(MethodVisitor mv, MethodMeta methodMeta) {
408      super(Opcodes.ASM7, mv);
409      this.methodMeta = methodMeta;
410    }
411
412    @Override
413    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
414      if (mv == null) {
415        return null;
416      }
417      AnnotationVisitor av = mv.visitAnnotation(desc, visible);
418      return new AnnotationInfoVisitor(null, methodMeta.getAnnotationInfo(), av);
419    }
420
421  }
422
423  /**
424  * Create and return a read only fieldVisitor for subclassing option.
425  */
426  public FieldVisitor createLocalFieldVisitor(String name, String desc) {
427    return createLocalFieldVisitor(null, name, desc);
428  }
429
430  /**
431  * Create and return a new fieldVisitor for use when enhancing a class.
432  */
433  public FieldVisitor createLocalFieldVisitor(FieldVisitor fv, String name, String desc) {
434
435    FieldMeta fieldMeta = new FieldMeta(this, name, desc, className);
436    LocalFieldVisitor localField = new LocalFieldVisitor(fv, fieldMeta);
437    if (name.startsWith("_ebean")) {
438      // can occur when reading inheritance information on
439      // a entity that has already been enhanced
440      if (isLog(5)) {
441        log("... ignore field " + name);
442      }
443    } else {
444      fields.put(localField.getName(), fieldMeta);
445    }
446    return localField;
447  }
448
449  public void setAlreadyEnhanced(boolean alreadyEnhanced) {
450    this.alreadyEnhanced = alreadyEnhanced;
451  }
452
453  public boolean hasDefaultConstructor() {
454    return hasDefaultConstructor;
455  }
456
457  public void setHasDefaultConstructor(boolean hasDefaultConstructor) {
458    this.hasDefaultConstructor = hasDefaultConstructor;
459  }
460
461  public void setHasStaticInit(boolean hasStaticInit) {
462    this.hasStaticInit = hasStaticInit;
463  }
464
465  public boolean hasStaticInit() {
466    return hasStaticInit;
467  }
468
469  public String getDescription() {
470    StringBuilder sb = new StringBuilder();
471    appendDescription(sb);
472    return sb.toString();
473  }
474
475  private void appendDescription(StringBuilder sb) {
476    sb.append(className);
477    if (superMeta != null) {
478      sb.append(" : ");
479      superMeta.appendDescription(sb);
480    }
481  }
482
483  public boolean hasScalaInterface() {
484    return hasScalaInterface;
485  }
486
487  public void setScalaInterface(boolean hasScalaInterface) {
488    this.hasScalaInterface = hasScalaInterface;
489  }
490
491  public boolean hasEntityBeanInterface() {
492    return hasEntityBeanInterface;
493  }
494
495  public void setEntityBeanInterface(boolean hasEntityBeanInterface) {
496    this.hasEntityBeanInterface = hasEntityBeanInterface;
497  }
498
499  public boolean hasGroovyInterface() {
500    return hasGroovyInterface;
501  }
502
503  public void setGroovyInterface(boolean hasGroovyInterface) {
504    this.hasGroovyInterface = hasGroovyInterface;
505  }
506
507}