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 or MappedSuperclass.
322   */
323  public boolean isEntity() {
324    return EntityCheck.hasEntityAnnotation(classAnnotation);
325  }
326
327  /**
328   * Return true if the class has an Entity, Embeddable, or MappedSuperclass.
329   */
330  private boolean isCheckEntity() {
331    return EntityCheck.hasEntityAnnotation(classAnnotation);
332  }
333
334  /**
335   * Return true for classes not already enhanced and yet annotated with entity, embeddable or mappedSuperclass.
336   */
337  public boolean isEntityEnhancementRequired() {
338    return !alreadyEnhanced && isEntity();
339  }
340
341  /**
342   * Return true if the bean is already enhanced.
343   */
344  public boolean isAlreadyEnhanced() {
345    return alreadyEnhanced;
346  }
347
348  /**
349   * Return the className of this entity class.
350   */
351  public String getClassName() {
352    return className;
353  }
354
355  /**
356   * Return true if this entity bean has a super class that is an entity.
357   */
358  public boolean isSuperClassEntity() {
359    return superMeta != null && superMeta.isEntity();
360  }
361
362  /**
363   * Add a class annotation.
364   */
365  public void addClassAnnotation(String desc) {
366    classAnnotation.add(desc);
367  }
368
369  MethodVisitor createMethodVisitor(MethodVisitor mv, String name, String desc) {
370
371    MethodMeta methodMeta = new MethodMeta(annotationInfo, name, desc);
372    methodMetaList.add(methodMeta);
373    return new MethodReader(mv, methodMeta);
374  }
375
376  /**
377   * ACC_PUBLIC with maybe ACC_SYNTHETIC.
378   */
379  public int accPublic() {
380    return enhanceContext.accPublic();
381  }
382
383  /**
384   * ACC_PROTECTED with maybe ACC_SYNTHETIC.
385   */
386  public int accProtected() {
387    return enhanceContext.accProtected();
388  }
389
390  /**
391   * ACC_PRIVATE with maybe ACC_SYNTHETIC.
392   */
393  public int accPrivate() {
394    return enhanceContext.accPrivate();
395  }
396
397  public boolean isToManyGetField() {
398    return enhanceContext.isToManyGetField();
399  }
400
401  /**
402   * Return the EntityBeanIntercept type that will be new'ed up for the EntityBean.
403   * For version 140+ EntityBeanIntercept is an interface and instead we new up InterceptReadWrite.
404   */
405  public String interceptNew() {
406    return enhanceContext.interceptNew();
407  }
408
409  /**
410   * Invoke a method on EntityBeanIntercept.
411   * For version 140+ EntityBeanIntercept is an interface and this uses INVOKEINTERFACE.
412   */
413  public void visitMethodInsnIntercept(MethodVisitor mv, String name, String desc) {
414    enhanceContext.visitMethodInsnIntercept(mv, name, desc);
415  }
416
417  /**
418   * If 141+ Add InterceptReadOnly support.
419   */
420  public boolean interceptAddReadOnly() {
421    return enhanceContext.interceptAddReadOnly();
422  }
423
424  private static final class MethodReader extends MethodVisitor {
425
426    final MethodMeta methodMeta;
427
428    MethodReader(MethodVisitor mv, MethodMeta methodMeta) {
429      super(EBEAN_ASM_VERSION, mv);
430      this.methodMeta = methodMeta;
431    }
432
433    @Override
434    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
435      AnnotationVisitor av = null;
436      if (mv != null) {
437        av = mv.visitAnnotation(desc, visible);
438      }
439      if (!isInterestingAnnotation(desc)) {
440        return av;
441      }
442      return new AnnotationInfoVisitor(null, methodMeta.getAnnotationInfo(), av);
443    }
444
445    private boolean isInterestingAnnotation(String desc) {
446      return TRANSACTIONAL_ANNOTATION.equals(desc)
447        || TYPEQUERYBEAN_ANNOTATION.equals(desc);
448    }
449  }
450
451  /**
452   * Create and return a read only fieldVisitor for subclassing option.
453   */
454  FieldVisitor createLocalFieldVisitor(String name, String desc) {
455    return createLocalFieldVisitor(null, name, desc);
456  }
457
458  /**
459   * Create and return a new fieldVisitor for use when enhancing a class.
460   */
461  public FieldVisitor createLocalFieldVisitor(FieldVisitor fv, String name, String desc) {
462
463    FieldMeta fieldMeta = new FieldMeta(this, name, desc, className);
464    LocalFieldVisitor localField = new LocalFieldVisitor(fv, fieldMeta);
465    if (name.startsWith("_ebean")) {
466      // can occur when reading inheritance information on
467      // a entity that has already been enhanced
468      if (isLog(5)) {
469        log("... ignore field " + name);
470      }
471    } else {
472      fields.put(localField.getName(), fieldMeta);
473    }
474    return localField;
475  }
476
477  public void setAlreadyEnhanced(boolean alreadyEnhanced) {
478    this.alreadyEnhanced = alreadyEnhanced;
479  }
480
481  public boolean hasDefaultConstructor() {
482    return hasDefaultConstructor;
483  }
484
485  public void setHasDefaultConstructor(boolean hasDefaultConstructor) {
486    this.hasDefaultConstructor = hasDefaultConstructor;
487  }
488
489  public void setHasStaticInit(boolean hasStaticInit) {
490    this.hasStaticInit = hasStaticInit;
491  }
492
493  public boolean hasStaticInit() {
494    return hasStaticInit;
495  }
496
497  public String getDescription() {
498    StringBuilder sb = new StringBuilder();
499    appendDescription(sb);
500    return sb.toString();
501  }
502
503  private void appendDescription(StringBuilder sb) {
504    sb.append(className);
505    if (superMeta != null) {
506      sb.append(" : ");
507      superMeta.appendDescription(sb);
508    }
509  }
510
511  private boolean hasScalaInterface() {
512    return hasScalaInterface;
513  }
514
515  public void setScalaInterface(boolean hasScalaInterface) {
516    this.hasScalaInterface = hasScalaInterface;
517  }
518
519  public boolean hasEntityBeanInterface() {
520    return hasEntityBeanInterface;
521  }
522
523  public void setEntityBeanInterface(boolean hasEntityBeanInterface) {
524    this.hasEntityBeanInterface = hasEntityBeanInterface;
525  }
526
527  private boolean hasGroovyInterface() {
528    return hasGroovyInterface;
529  }
530
531  public void setGroovyInterface(boolean hasGroovyInterface) {
532    this.hasGroovyInterface = hasGroovyInterface;
533  }
534
535}