diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMeta.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMeta.java index 0bcb35f7ca..6ed8075b6d 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMeta.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMeta.java @@ -8,6 +8,7 @@ package org.hibernate.jpamodelgen.annotation; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; +import org.hibernate.jpamodelgen.Context; import org.hibernate.jpamodelgen.model.MetaAttribute; import org.hibernate.jpamodelgen.model.Metamodel; import org.hibernate.jpamodelgen.util.Constants; @@ -18,6 +19,7 @@ import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; import java.util.List; import static java.util.Collections.emptySet; @@ -94,7 +96,8 @@ public abstract class AnnotationMeta implements Metamodel { // If we are in the scope of @CheckHQL, semantic errors in the // query result in compilation errors. Otherwise, they only // result in warnings, so we don't break working code. - new WarningErrorHandler( mirror, value, hql, reportErrors, checkHql ), + new WarningErrorHandler( getContext(), getElement(), mirror, value, hql, + reportErrors, checkHql ), ProcessorSessionFactory.create( getContext().getProcessingEnvironment() ) ); if ( statement instanceof SqmSelectStatement @@ -158,12 +161,29 @@ public abstract class AnnotationMeta implements Metamodel { abstract void putMember(String name, MetaAttribute nameMetaAttribute); - private class WarningErrorHandler extends ErrorHandler { + /** + * Adjusts the severity of validation errors that occur type-checking + * a named HQL query, depending on whether we are within the scope + * of a {@link org.hibernate.annotations.processing.CheckHQL} annotation. + *

+ * We always type-check; but when there's no {@code CheckHQL} in scope, + * we report problems as warnings only. This is necessary, since we don't + * yet accept absolutely everything that is legal: we don't know about + * XML, we don't handle converters, and we don't handle {@code enum} + * types very well. + */ + private static class WarningErrorHandler extends ErrorHandler { private final boolean reportErrors; private final boolean checkHql; - public WarningErrorHandler(AnnotationMirror mirror, AnnotationValue value, String hql, boolean reportErrors, boolean checkHql) { - super( AnnotationMeta.this.getElement(), mirror, value, hql, AnnotationMeta.this.getContext() ); + private WarningErrorHandler( + Context context, + Element element, + AnnotationMirror mirror, + AnnotationValue value, String hql, + boolean reportErrors, + boolean checkHql) { + super(context, element, mirror, value, hql); this.reportErrors = reportErrors; this.checkHql = checkHql; } @@ -188,7 +208,9 @@ public abstract class AnnotationMeta implements Metamodel { } @Override - public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String message, RecognitionException e) { + public void syntaxError( + Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, + String message, RecognitionException e) { if (reportErrors) { super.syntaxError( recognizer, offendingSymbol, line, charPositionInLine, message, e ); } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java index bdca68e837..042a527caf 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java @@ -56,6 +56,10 @@ import static org.hibernate.jpamodelgen.util.TypeUtils.getAnnotationValueRef; /** * Class used to collect meta information about an annotated type (entity, embeddable or mapped superclass). + * Also repurposed for any type with "auxiliary" annotations like {@code @NamedQuery}, {@code @FetchProfile}, + * {@code @Find}, or {@code @HQL}. We do not distinguish these two kinds of thing, since an entity class may + * {@code @NamedQuery} or {@code @FetchProfile} annotations. Entities may not, however, have methods annotated + * {@code @Find} or {@code @HQL}, since entity classes are usually concrete classes. * * @author Max Andersen * @author Hardy Ferentschik @@ -74,19 +78,19 @@ public class AnnotationMetaEntity extends AnnotationMeta { /** * Whether the members of this type have already been initialized or not. *

- * Embeddables and mapped superclasses need to be lazily initialized since the access type may be determined by - * the class which is embedding or subclassing the entity or superclass. This might not be known until + * Embeddables and mapped superclasses need to be lazily initialized since the access type may be determined + * by the class which is embedding or subclassing the entity or superclass. This might not be known until * annotations are processed. *

* Also note, that if two different classes with different access types embed this entity or extend this mapped - * super-class, the access type of the embeddable/superclass will be the one of the last embedding/subclassing + * superclass, the access type of the embeddable/superclass will be the one of the last embedding/subclassing * entity processed. The result is not determined (that's ok according to the spec). */ private boolean initialized; /** * Another meta entity for the same type which should be merged lazily with this meta entity. Doing the merge - * lazily is required for embeddedables and mapped supertypes to only pull in those members matching the access + * lazily is required for embeddables and mapped supertypes, to only pull in those members matching the access * type as configured via the embedding entity or subclass (also see METAGEN-85). */ private Metamodel entityToMerge; @@ -95,6 +99,10 @@ public class AnnotationMetaEntity extends AnnotationMeta { * True if this "metamodel class" is actually an instantiable DAO-style repository. */ private boolean dao = false; + + /** + * The type of the "session getter" method of a DAO-style repository. + */ private String sessionType = Constants.ENTITY_MANAGER; public AnnotationMetaEntity(TypeElement element, Context context) { @@ -235,23 +243,16 @@ public class AnnotationMetaEntity extends AnnotationMeta { final List methodsOfClass = methodsIn( element.getEnclosedElements() ); final List gettersAndSettersOfClass = new ArrayList<>(); final List queryMethods = new ArrayList<>(); - for ( ExecutableElement rawMethodOfClass: methodsOfClass ) { - if ( isGetterOrSetter( rawMethodOfClass ) ) { - gettersAndSettersOfClass.add( rawMethodOfClass ); + for ( ExecutableElement method: methodsOfClass ) { + if ( isGetterOrSetter( method ) ) { + gettersAndSettersOfClass.add( method ); } - else if ( containsAnnotation( rawMethodOfClass, Constants.HQL, Constants.SQL, Constants.FIND ) ) { - queryMethods.add( rawMethodOfClass ); + else if ( containsAnnotation( method, Constants.HQL, Constants.SQL, Constants.FIND ) ) { + queryMethods.add( method ); } } - if ( !containsAnnotation( element, Constants.ENTITY ) ) { - for ( ExecutableElement getterOrSetter : gettersAndSettersOfClass ) { - if ( isSessionGetter( getterOrSetter ) ) { - dao = true; - sessionType = addDaoConstructor( getterOrSetter ); - } - } - } + findSessionGetter( methodsOfClass ); addPersistentMembers( fieldsOfClass, AccessType.FIELD ); addPersistentMembers( gettersAndSettersOfClass, AccessType.PROPERTY ); @@ -265,6 +266,22 @@ public class AnnotationMetaEntity extends AnnotationMeta { initialized = true; } + private void findSessionGetter(List methodsOfClass) { + if ( !containsAnnotation( element, Constants.ENTITY ) ) { + for ( ExecutableElement method : methodsOfClass ) { + if ( isSessionGetter( method ) ) { + dao = true; + sessionType = addDaoConstructor( method ); + } + } + } + } + + /** + * If there is a session getter method, we generate an instance + * variable backing it, together with a constructor that initializes + * it. + */ private String addDaoConstructor(ExecutableElement getterOrSetter) { String name = getterOrSetter.getSimpleName().toString(); String typeName = element.getSimpleName().toString() + '_'; @@ -274,16 +291,23 @@ public class AnnotationMetaEntity extends AnnotationMeta { return sessionType; } - private static boolean isSessionGetter(ExecutableElement getterOrSetter) { - final TypeMirror type = getterOrSetter.getReturnType(); - if ( type.getKind() == TypeKind.DECLARED ) { - final DeclaredType declaredType = (DeclaredType) type; - final Element element = declaredType.asElement(); - if ( element.getKind() == ElementKind.INTERFACE ) { - final Name name = ((TypeElement) element).getQualifiedName(); - return name.contentEquals(Constants.HIB_SESSION) - || name.contentEquals(Constants.HIB_STATELESS_SESSION) - || name.contentEquals(Constants.ENTITY_MANAGER); + /** + * The session getter method doesn't have to be a JavaBeans-style + * getter. It can be any method with no parameters and one of the + * needed return types. + */ + private static boolean isSessionGetter(ExecutableElement method) { + if ( method.getParameters().isEmpty() ) { + final TypeMirror type = method.getReturnType(); + if ( type.getKind() == TypeKind.DECLARED ) { + final DeclaredType declaredType = (DeclaredType) type; + final Element element = declaredType.asElement(); + if ( element.getKind() == ElementKind.INTERFACE ) { + final Name name = ((TypeElement) element).getQualifiedName(); + return name.contentEquals(Constants.HIB_SESSION) + || name.contentEquals(Constants.HIB_STATELESS_SESSION) + || name.contentEquals(Constants.ENTITY_MANAGER); + } } } return false; @@ -684,7 +708,7 @@ public class AnnotationMetaEntity extends AnnotationMeta { hql, false, true, emptySet(), emptySet(), - new ErrorHandler( method, mirror, value, hql, context ), + new ErrorHandler(context, method, mirror, value, hql), ProcessorSessionFactory.create( context.getProcessingEnvironment() ) ); } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/ErrorHandler.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/ErrorHandler.java index cadeebaac5..ce43b7cddb 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/ErrorHandler.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/ErrorHandler.java @@ -23,6 +23,9 @@ import java.util.BitSet; import static org.hibernate.query.hql.internal.StandardHqlTranslator.prettifyAntlrError; /** + * Responsible for forwarding errors from the HQL typechecker to the Java compiler, + * via {@link Context#message}. + * * @author Gavin King */ class ErrorHandler implements Validation.Handler { @@ -33,11 +36,11 @@ class ErrorHandler implements Validation.Handler { private final Context context; private int errorCount; - public ErrorHandler(Element element, AnnotationMirror mirror, AnnotationValue value, String queryString, Context context) { + public ErrorHandler(Context context, Element element, AnnotationMirror mirror, AnnotationValue value, String hql) { this.element = element; this.mirror = mirror; this.value = value; - this.queryString = queryString; + this.queryString = hql; this.context = context; }