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.entity.FieldMeta;
008import io.ebean.enhance.entity.LocalFieldVisitor;
009import io.ebean.enhance.entity.MessageOutput;
010import io.ebean.enhance.entity.MethodMeta;
011
012import java.util.ArrayList;
013import java.util.HashSet;
014import java.util.LinkedHashMap;
015import java.util.List;
016import java.util.logging.Level;
017import java.util.logging.Logger;
018
019import static io.ebean.enhance.Transformer.EBEAN_ASM_VERSION;
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 that we want to consume the init on.
204   */
205  public boolean isConsumeInitMany(String fieldName) {
206    FieldMeta f = getFieldPersistent(fieldName);
207    return (f != null && f.isPersistent() && f.isInitMany());
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  /**
389   * ACC_PUBLIC with maybe ACC_SYNTHETIC.
390   */
391  public int accPublic() {
392    return enhanceContext.accPublic();
393  }
394
395  /**
396   * ACC_PROTECTED with maybe ACC_SYNTHETIC.
397   */
398  public int accProtected() {
399    return enhanceContext.accProtected();
400  }
401
402  /**
403   * ACC_PRIVATE with maybe ACC_SYNTHETIC.
404   */
405  public int accPrivate() {
406    return enhanceContext.accPrivate();
407  }
408
409  public boolean isToManyGetField() {
410    return enhanceContext.isToManyGetField();
411  }
412
413  private static final class MethodReader extends MethodVisitor {
414
415    final MethodMeta methodMeta;
416
417    MethodReader(MethodVisitor mv, MethodMeta methodMeta) {
418      super(EBEAN_ASM_VERSION, mv);
419      this.methodMeta = methodMeta;
420    }
421
422    @Override
423    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
424      AnnotationVisitor av = null;
425      if (mv != null) {
426        av = mv.visitAnnotation(desc, visible);
427      }
428      if (!isInterestingAnnotation(desc)) {
429        return av;
430      }
431      return new AnnotationInfoVisitor(null, methodMeta.getAnnotationInfo(), av);
432    }
433
434    private boolean isInterestingAnnotation(String desc) {
435      return TRANSACTIONAL_ANNOTATION.equals(desc)
436        || TYPEQUERYBEAN_ANNOTATION.equals(desc);
437    }
438  }
439
440  /**
441   * Create and return a read only fieldVisitor for subclassing option.
442   */
443  FieldVisitor createLocalFieldVisitor(String name, String desc) {
444    return createLocalFieldVisitor(null, name, desc);
445  }
446
447  /**
448   * Create and return a new fieldVisitor for use when enhancing a class.
449   */
450  public FieldVisitor createLocalFieldVisitor(FieldVisitor fv, String name, String desc) {
451
452    FieldMeta fieldMeta = new FieldMeta(this, name, desc, className);
453    LocalFieldVisitor localField = new LocalFieldVisitor(fv, fieldMeta);
454    if (name.startsWith("_ebean")) {
455      // can occur when reading inheritance information on
456      // a entity that has already been enhanced
457      if (isLog(5)) {
458        log("... ignore field " + name);
459      }
460    } else {
461      fields.put(localField.getName(), fieldMeta);
462    }
463    return localField;
464  }
465
466  public void setAlreadyEnhanced(boolean alreadyEnhanced) {
467    this.alreadyEnhanced = alreadyEnhanced;
468  }
469
470  public boolean hasDefaultConstructor() {
471    return hasDefaultConstructor;
472  }
473
474  public void setHasDefaultConstructor(boolean hasDefaultConstructor) {
475    this.hasDefaultConstructor = hasDefaultConstructor;
476  }
477
478  public void setHasStaticInit(boolean hasStaticInit) {
479    this.hasStaticInit = hasStaticInit;
480  }
481
482  public boolean hasStaticInit() {
483    return hasStaticInit;
484  }
485
486  public String getDescription() {
487    StringBuilder sb = new StringBuilder();
488    appendDescription(sb);
489    return sb.toString();
490  }
491
492  private void appendDescription(StringBuilder sb) {
493    sb.append(className);
494    if (superMeta != null) {
495      sb.append(" : ");
496      superMeta.appendDescription(sb);
497    }
498  }
499
500  private boolean hasScalaInterface() {
501    return hasScalaInterface;
502  }
503
504  public void setScalaInterface(boolean hasScalaInterface) {
505    this.hasScalaInterface = hasScalaInterface;
506  }
507
508  public boolean hasEntityBeanInterface() {
509    return hasEntityBeanInterface;
510  }
511
512  public void setEntityBeanInterface(boolean hasEntityBeanInterface) {
513    this.hasEntityBeanInterface = hasEntityBeanInterface;
514  }
515
516  private boolean hasGroovyInterface() {
517    return hasGroovyInterface;
518  }
519
520  public void setGroovyInterface(boolean hasGroovyInterface) {
521    this.hasGroovyInterface = hasGroovyInterface;
522  }
523
524}