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;
021import static io.ebean.enhance.common.EnhanceConstants.TRANSACTIONAL_ANNOTATION;
022import static io.ebean.enhance.common.EnhanceConstants.TYPEQUERYBEAN_ANNOTATION;
023
024/**
025 * Holds the meta data for an entity bean class that is being enhanced.
026 */
027public class ClassMeta {
028
029  private static final Logger logger = Logger.getLogger(ClassMeta.class.getName());
030
031  private final MessageOutput logout;
032
033  private final int logLevel;
034
035  private String className;
036
037  private String superClassName;
038
039  private ClassMeta superMeta;
040
041  /**
042  * Set to true if the class implements th GroovyObject interface.
043  */
044  private boolean hasGroovyInterface;
045
046  /**
047  * Set to true if the class implements the ScalaObject interface.
048  */
049  private boolean hasScalaInterface;
050
051  /**
052  * Set to true if the class already implements the EntityBean interface.
053  */
054  private boolean hasEntityBeanInterface;
055
056  private boolean alreadyEnhanced;
057
058  private boolean hasEqualsOrHashcode;
059
060  private boolean hasDefaultConstructor;
061
062  private boolean hasStaticInit;
063
064  private final LinkedHashMap<String, FieldMeta> fields = new LinkedHashMap<>();
065
066  private final HashSet<String> classAnnotation = new HashSet<>();
067
068  private final AnnotationInfo annotationInfo = new AnnotationInfo(null);
069
070  private final ArrayList<MethodMeta> methodMetaList = new ArrayList<>();
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(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    FieldMeta f = getFieldPersistent(fieldName);
199    return (f != null) && 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  private List<FieldMeta> getLocalFields() {
226
227    List<FieldMeta> list = new ArrayList<>();
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 void addInheritedFields(List<FieldMeta> list) {
243    if (superMeta != null) {
244      superMeta.addFieldsForInheritance(list);
245    }
246  }
247
248  /**
249  * Add all fields to the list.
250  */
251  private void addFieldsForInheritance(List<FieldMeta> list) {
252    if (isEntity()) {
253      list.addAll(0, fields.values());
254      if (superMeta != null) {
255        superMeta.addFieldsForInheritance(list);
256      }
257    }
258  }
259
260  /**
261  * Return true if the class contains persistent fields.
262  */
263  public boolean hasPersistentFields() {
264
265    for (FieldMeta fieldMeta : fields.values()) {
266      if (fieldMeta.isPersistent() || fieldMeta.isTransient()) {
267        return true;
268      }
269    }
270
271    return superMeta != null && superMeta.hasPersistentFields();
272  }
273
274  /**
275  * Return the list of all fields including ones inherited from entity super
276  * types and mappedSuperclasses.
277  */
278  public List<FieldMeta> getAllFields() {
279
280    if (allFields != null) {
281      return allFields;
282    }
283    List<FieldMeta> list = getLocalFields();
284    addInheritedFields(list);
285
286    this.allFields = list;
287    for (int i=0; i<allFields.size(); i++) {
288      allFields.get(i).setIndexPosition(i);
289    }
290
291    return list;
292  }
293
294  /**
295  * Add field level get set methods for each field.
296  */
297  public void addFieldGetSetMethods(ClassVisitor cv) {
298
299    if (isEntityEnhancementRequired()) {
300      for (FieldMeta fm : fields.values()) {
301        fm.addGetSetMethods(cv, this);
302      }
303    }
304  }
305
306  /**
307  * Return true if this is a mapped superclass.
308  */
309  boolean isMappedSuper() {
310    return classAnnotation.contains(EnhanceConstants.MAPPEDSUPERCLASS_ANNOTATION);
311  }
312
313  /**
314   * Return true if this is a query bean.
315   */
316  public boolean isQueryBean() {
317    return classAnnotation.contains(EnhanceConstants.TYPEQUERYBEAN_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  MethodVisitor createMethodVisitor(MethodVisitor mv, String name, String desc) {
382
383    MethodMeta methodMeta = new MethodMeta(annotationInfo, name, desc);
384    methodMetaList.add(methodMeta);
385    return new MethodReader(mv, methodMeta);
386  }
387
388  private static final class MethodReader extends MethodVisitor {
389
390    final MethodMeta methodMeta;
391
392    MethodReader(MethodVisitor mv, MethodMeta methodMeta) {
393      super(Opcodes.ASM7, mv);
394      this.methodMeta = methodMeta;
395    }
396
397    @Override
398    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
399      AnnotationVisitor av = null;
400      if (mv != null) {
401        av = mv.visitAnnotation(desc, visible);
402      }
403      if (!isInterestingAnnotation(desc)) {
404        return av;
405      }
406      return new AnnotationInfoVisitor(null, methodMeta.getAnnotationInfo(), av);
407    }
408
409    private boolean isInterestingAnnotation(String desc) {
410      return TRANSACTIONAL_ANNOTATION.equals(desc)
411        || TYPEQUERYBEAN_ANNOTATION.equals(desc);
412    }
413  }
414
415  /**
416  * Create and return a read only fieldVisitor for subclassing option.
417  */
418  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  private 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  private boolean hasGroovyInterface() {
492    return hasGroovyInterface;
493  }
494
495  public void setGroovyInterface(boolean hasGroovyInterface) {
496    this.hasGroovyInterface = hasGroovyInterface;
497  }
498
499}