HHH-16887 integrate full HQL typechecking into Metamodel Generator!

This commit is contained in:
Gavin King 2023-07-04 12:46:06 +02:00
parent 445f2cbdd8
commit f61e00c642
11 changed files with 2940 additions and 56 deletions

View File

@ -23,6 +23,7 @@ dependencies {
implementation jakartaLibs.jaxb
implementation libs.antlrRuntime
implementation project( ':hibernate-core' )
implementation libs.byteBuddy
xjc jakartaLibs.xjc
xjc jakartaLibs.jaxb

View File

@ -51,7 +51,19 @@
* @author Emmanuel Bernard
*/
@SupportedAnnotationTypes({
"jakarta.persistence.Entity", "jakarta.persistence.MappedSuperclass", "jakarta.persistence.Embeddable"
Constants.ENTITY,
Constants.MAPPED_SUPERCLASS,
Constants.EMBEDDABLE,
Constants.HQL,
Constants.SQL,
Constants.NAMED_QUERY,
Constants.NAMED_NATIVE_QUERY,
Constants.NAMED_ENTITY_GRAPH,
Constants.SQL_RESULT_SET_MAPPING,
Constants.HIB_FETCH_PROFILE,
Constants.HIB_FILTER_DEF,
Constants.HIB_NAMED_QUERY,
Constants.HIB_NAMED_NATIVE_QUERY
})
@SupportedOptions({
JPAMetaModelEntityProcessor.DEBUG_OPTION,

View File

@ -24,19 +24,12 @@
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;
import org.antlr.v4.runtime.ANTLRErrorListener;
import org.antlr.v4.runtime.BailErrorStrategy;
import org.antlr.v4.runtime.DefaultErrorStrategy;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.atn.ATNConfigSet;
import org.antlr.v4.runtime.atn.PredictionMode;
import org.antlr.v4.runtime.dfa.DFA;
import org.antlr.v4.runtime.misc.ParseCancellationException;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.grammars.hql.HqlLexer;
import org.hibernate.grammars.hql.HqlParser;
import org.hibernate.jpamodelgen.Context;
import org.hibernate.jpamodelgen.ImportContextImpl;
import org.hibernate.jpamodelgen.model.ImportContext;
@ -47,8 +40,10 @@
import org.hibernate.jpamodelgen.util.Constants;
import org.hibernate.jpamodelgen.util.NullnessUtil;
import org.hibernate.jpamodelgen.util.TypeUtils;
import org.hibernate.query.hql.internal.HqlParseTreeBuilder;
import org.hibernate.jpamodelgen.validation.ProcessorSessionFactory;
import org.hibernate.jpamodelgen.validation.Validation;
import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.toList;
import static org.hibernate.jpamodelgen.util.TypeUtils.containsAnnotation;
import static org.hibernate.jpamodelgen.util.TypeUtils.determineAnnotationSpecifiedAccessType;
@ -397,7 +392,14 @@ private void addQueryMethod(
checkParameters( method, paramNames, mirror, hql );
if ( !isNative ) {
checkHqlSyntax( method, mirror, hql );
// checkHqlSyntax( method, mirror, hql );
Validation.validate(
hql,
false,
emptySet(), emptySet(),
new ErrorHandler( method, mirror, hql ),
ProcessorSessionFactory.create( context.getProcessingEnvironment() )
);
}
}
}
@ -412,60 +414,88 @@ private void checkParameters(ExecutableElement method, List<String> paramNames,
}
}
private void checkHqlSyntax(ExecutableElement method, AnnotationMirror mirror, String queryString) {
ANTLRErrorListener errorListener = new ANTLRErrorListener() {
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String message, RecognitionException e) {
displayError( method, mirror, "illegal HQL syntax - "
+ prettifyAntlrError( offendingSymbol, line, charPositionInLine, message, e, queryString, false ) );
}
// private void checkHqlSyntax(ExecutableElement method, AnnotationMirror mirror, String queryString) {
// final ANTLRErrorListener errorListener = new ErrorHandler( method, mirror, queryString );
// final HqlLexer hqlLexer = HqlParseTreeBuilder.INSTANCE.buildHqlLexer( queryString );
// final HqlParser hqlParser = HqlParseTreeBuilder.INSTANCE.buildHqlParser( queryString, hqlLexer );
// hqlLexer.addErrorListener( errorListener );
// hqlParser.getInterpreter().setPredictionMode( PredictionMode.SLL );
// hqlParser.removeErrorListeners();
// hqlParser.addErrorListener( errorListener );
// hqlParser.setErrorHandler( new BailErrorStrategy() );
//
// try {
// hqlParser.statement();
// }
// catch ( ParseCancellationException e) {
// // reset the input token stream and parser state
// hqlLexer.reset();
// hqlParser.reset();
//
// // fall back to LL(k)-based parsing
// hqlParser.getInterpreter().setPredictionMode( PredictionMode.LL );
// hqlParser.setErrorHandler( new DefaultErrorStrategy() );
//
// hqlParser.statement();
// }
// }
@Override
public void reportAmbiguity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, boolean exact, BitSet ambigAlts, ATNConfigSet configs) {
}
@Override
public void reportAttemptingFullContext(Parser recognizer, DFA dfa, int startIndex, int stopIndex, BitSet conflictingAlts, ATNConfigSet configs) {
}
@Override
public void reportContextSensitivity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, int prediction, ATNConfigSet configs) {
}
};
final HqlLexer hqlLexer = HqlParseTreeBuilder.INSTANCE.buildHqlLexer( queryString );
final HqlParser hqlParser = HqlParseTreeBuilder.INSTANCE.buildHqlParser( queryString, hqlLexer );
hqlLexer.addErrorListener( errorListener );
hqlParser.getInterpreter().setPredictionMode( PredictionMode.SLL );
hqlParser.removeErrorListeners();
hqlParser.addErrorListener( errorListener );
hqlParser.setErrorHandler( new BailErrorStrategy() );
try {
hqlParser.statement();
}
catch ( ParseCancellationException e) {
// reset the input token stream and parser state
hqlLexer.reset();
hqlParser.reset();
// fall back to LL(k)-based parsing
hqlParser.getInterpreter().setPredictionMode( PredictionMode.LL );
hqlParser.setErrorHandler( new DefaultErrorStrategy() );
hqlParser.statement();
}
}
private void displayError(ExecutableElement method, String message) {
private void displayError(Element method, String message) {
context.getProcessingEnvironment().getMessager()
.printMessage( Diagnostic.Kind.ERROR, message, method );
}
private void displayError(ExecutableElement method, AnnotationMirror mirror, String message) {
private void displayError(Element method, AnnotationMirror mirror, String message) {
context.getProcessingEnvironment().getMessager()
.printMessage( Diagnostic.Kind.ERROR, message, method, mirror,
mirror.getElementValues().entrySet().stream()
.filter( entry -> entry.getKey().getSimpleName().toString().equals("value") )
.map(Map.Entry::getValue).findAny().orElseThrow() );
}
private class ErrorHandler implements Validation.Handler {
private final ExecutableElement method;
private final AnnotationMirror mirror;
private final String queryString;
private int errorcount;
public ErrorHandler(ExecutableElement method, AnnotationMirror mirror, String queryString) {
this.method = method;
this.mirror = mirror;
this.queryString = queryString;
}
@Override
public void error(int start, int end, String message) {
errorcount++;
displayError( method, mirror, message );
}
@Override
public void warn(int start, int end, String message) {
}
@Override
public int getErrorCount() {
return errorcount;
}
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String message, RecognitionException e) {
errorcount++;
displayError( method, mirror, "illegal HQL syntax - "
+ prettifyAntlrError( offendingSymbol, line, charPositionInLine, message, e, queryString, false ) );
}
@Override
public void reportAmbiguity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, boolean exact, BitSet ambigAlts, ATNConfigSet configs) {
}
@Override
public void reportAttemptingFullContext(Parser recognizer, DFA dfa, int startIndex, int stopIndex, BitSet conflictingAlts, ATNConfigSet configs) {
}
@Override
public void reportContextSensitivity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, int prediction, ATNConfigSet configs) {
}
}
}

View File

@ -0,0 +1,213 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.validation;
import org.hibernate.FetchMode;
import org.hibernate.QueryException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.CollectionType;
import org.hibernate.type.ListType;
import org.hibernate.type.MapType;
import org.hibernate.type.Type;
import java.io.Serializable;
import static org.hibernate.internal.util.StringHelper.root;
import static org.hibernate.jpamodelgen.validation.MockSessionFactory.typeConfiguration;
/**
* @author Gavin King
*/
@SuppressWarnings("nullness")
public abstract class MockCollectionPersister implements QueryableCollection {
private static final String[] ID_COLUMN = {"id"};
private static final String[] INDEX_COLUMN = {"pos"};
private final String role;
private final MockSessionFactory factory;
private final CollectionType collectionType;
private final String ownerEntityName;
private final Type elementType;
public MockCollectionPersister(String role, CollectionType collectionType, Type elementType, MockSessionFactory factory) {
this.role = role;
this.collectionType = collectionType;
this.elementType = elementType;
this.factory = factory;
this.ownerEntityName = root(role);
}
String getOwnerEntityName() {
return ownerEntityName;
}
@Override
public String getRole() {
return role;
}
@Override
public String getName() {
return role;
}
@Override
public CollectionType getCollectionType() {
return collectionType;
}
@Override
public EntityPersister getOwnerEntityPersister() {
return factory.getMetamodel().entityPersister(ownerEntityName);
}
abstract Type getElementPropertyType(String propertyPath);
@Override
public Type toType(String propertyName) throws QueryException {
if ("index".equals(propertyName)) {
//this is what AbstractCollectionPersister does!
//TODO: move it to FromElementType:626 or all
// the way to CollectionPropertyMapping
return getIndexType();
}
Type type = getElementPropertyType(propertyName);
if (type==null) {
throw new QueryException(elementType.getName()
+ " has no mapped "
+ propertyName);
}
else {
return type;
}
}
@Override
public Type getKeyType() {
return getOwnerEntityPersister().getIdentifierType();
}
@Override
public Type getIndexType() {
if (collectionType instanceof ListType) {
return typeConfiguration.getBasicTypeForJavaType(Integer.class);
}
else if (collectionType instanceof MapType) {
//TODO!!! this is incorrect, return the correct key type
return typeConfiguration.getBasicTypeForJavaType(String.class);
}
else {
return null;
}
}
@Override
public Type getElementType() {
return elementType;
}
@Override
public Type getIdentifierType() {
return typeConfiguration.getBasicTypeForJavaType(Long.class);
}
@Override
public boolean hasIndex() {
return getCollectionType() instanceof ListType
|| getCollectionType() instanceof MapType;
}
@Override
public EntityPersister getElementPersister() {
if (elementType.isEntityType()) {
return factory.getMetamodel()
.entityPersister(elementType.getName());
}
else {
return null;
}
}
@Override
public SessionFactoryImplementor getFactory() {
return factory;
}
@Override
public boolean isOneToMany() {
return elementType.isEntityType();
}
@Override
public Serializable[] getCollectionSpaces() {
return new Serializable[] {role};
}
@Override
public String getMappedByProperty() {
return null;
}
@Override
public String[] getIndexColumnNames() {
return INDEX_COLUMN;
}
@Override
public String[] getIndexColumnNames(String alias) {
return INDEX_COLUMN;
}
@Override
public String[] getIndexFormulas() {
return null;
}
@Override
public String[] getElementColumnNames(String alias) {
return new String[] {""};
}
@Override
public String[] getElementColumnNames() {
return new String[] {""};
}
@Override
public FetchMode getFetchMode() {
return FetchMode.DEFAULT;
}
@Override
public String getTableName() {
return role;
}
@Override
public String[] getKeyColumnNames() {
return ID_COLUMN;
}
@Override
public boolean isCollection() {
return true;
}
@Override
public boolean consumesCollectionAlias() {
return true;
}
@Override
public String[] toColumns(String propertyName) {
return new String[] {""};
}
}

View File

@ -0,0 +1,239 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.validation;
import jakarta.persistence.AccessType;
import org.hibernate.QueryException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.persister.entity.DiscriminatorMetadata;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.tuple.entity.EntityMetamodel;
import org.hibernate.type.Type;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static org.hibernate.jpamodelgen.validation.MockSessionFactory.typeConfiguration;
/**
* @author Gavin King
*/
@SuppressWarnings("nullness")
public abstract class MockEntityPersister implements EntityPersister, Queryable, DiscriminatorMetadata {
private static final String[] ID_COLUMN = {"id"};
private final String entityName;
private final MockSessionFactory factory;
private final List<MockEntityPersister> subclassPersisters = new ArrayList<>();
final AccessType defaultAccessType;
private final Map<String,Type> propertyTypesByName = new HashMap<>();
public MockEntityPersister(String entityName, AccessType defaultAccessType, MockSessionFactory factory) {
this.entityName = entityName;
this.factory = factory;
this.defaultAccessType = defaultAccessType;
}
void initSubclassPersisters() {
for (MockEntityPersister other: factory.getMockEntityPersisters()) {
other.addPersister(this);
this.addPersister(other);
}
}
private void addPersister(MockEntityPersister entityPersister) {
if (isSubclassPersister(entityPersister)) {
subclassPersisters.add(entityPersister);
}
}
private Type getSubclassPropertyType(String propertyPath) {
return subclassPersisters.stream()
.map(sp -> sp.getPropertyType(propertyPath))
.filter(Objects::nonNull)
.findAny()
.orElse(null);
}
abstract boolean isSubclassPersister(MockEntityPersister entityPersister);
@Override
public boolean isSubclassEntityName(String name) {
return isSubclassPersister(subclassPersisters.stream()
.filter(persister -> persister.entityName.equals(name))
.findFirst().orElseThrow());
}
@Override
public SessionFactoryImplementor getFactory() {
return factory;
}
@Override
public EntityMetamodel getEntityMetamodel() {
throw new UnsupportedOperationException();
}
@Override
public String getEntityName() {
return entityName;
}
@Override
public String getName() {
return entityName;
}
@Override
public final Type getPropertyType(String propertyPath) {
Type result = propertyTypesByName.get(propertyPath);
if (result!=null) {
return result;
}
result = createPropertyType(propertyPath);
if (result == null) {
//check subclasses, needed for treat()
result = getSubclassPropertyType(propertyPath);
}
if (result!=null) {
propertyTypesByName.put(propertyPath, result);
}
return result;
}
abstract Type createPropertyType(String propertyPath);
@Override
public Type getIdentifierType() {
//TODO: propertyType(getIdentifierPropertyName())
return typeConfiguration.getBasicTypeForJavaType(Long.class);
}
@Override
public String getIdentifierPropertyName() {
//TODO: return the correct @Id property name
return "id";
}
@Override
public Type toType(String propertyName) throws QueryException {
Type type = getPropertyType(propertyName);
if (type == null) {
throw new QueryException(getEntityName()
+ " has no mapped "
+ propertyName);
}
return type;
}
@Override
public String getRootEntityName() {
for (MockEntityPersister persister : factory.getMockEntityPersisters()) {
if (this != persister && persister.isSubclassPersister(this)) {
return persister.getRootEntityName();
}
}
return entityName;
}
@Override
public Set<String> getSubclassEntityNames() {
Set<String> names = new HashSet<>();
names.add( entityName );
for (MockEntityPersister persister : factory.getMockEntityPersisters()) {
if (persister.isSubclassPersister(this)) {
names.add(persister.entityName);
}
}
return names;
}
@Override
public Declarer getSubclassPropertyDeclarer(String s) {
return Declarer.CLASS;
}
@Override
public String[] toColumns(String propertyName) {
return new String[] { "" };
}
@Override
public Serializable[] getPropertySpaces() {
return new Serializable[] {entityName};
}
@Override
public Serializable[] getQuerySpaces() {
return new Serializable[] {entityName};
}
@Override
public EntityPersister getEntityPersister() {
return this;
}
@Override
public String[] getKeyColumnNames() {
return getIdentifierColumnNames();
}
@Override
public String[] getIdentifierColumnNames() {
return ID_COLUMN;
}
@Override
public DiscriminatorMetadata getTypeDiscriminatorMetadata() {
return this;
}
@Override
public Type getResolutionType() {
return typeConfiguration.getBasicTypeForJavaType(Class.class);
}
@Override
public String getTableName() {
return entityName;
}
@Override
public String toString() {
return "MockEntityPersister[" + entityName + "]";
}
@Override
public int getVersionProperty() {
return -66;
}
@Override
public String getMappedSuperclass() {
return null;
}
@Override
public boolean consumesEntityAlias() {
return true;
}
@Override
public Type getDiscriminatorType() {
return typeConfiguration.getBasicTypeForJavaType(String.class);
}
}

View File

@ -0,0 +1,76 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.validation;
import org.hibernate.annotations.processing.GenericDialect;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.env.internal.QualifiedObjectNameFormatterStandardImpl;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
import org.hibernate.engine.jdbc.env.spi.QualifiedObjectNameFormatter;
import org.hibernate.engine.jdbc.internal.JdbcServicesInitiator;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
import java.util.Map;
/**
* @author Gavin King
*/
@SuppressWarnings("nullness")
class MockJdbcServicesInitiator extends JdbcServicesInitiator {
static final JdbcServicesInitiator INSTANCE = new MockJdbcServicesInitiator();
static final JdbcServices jdbcServices = Mocker.nullary(MockJdbcServices.class).get();
static final GenericDialect genericDialect = new GenericDialect();
public abstract static class MockJdbcServices implements JdbcServices, JdbcEnvironment {
@Override
public Dialect getDialect() {
return genericDialect;
}
@Override
public JdbcEnvironment getJdbcEnvironment() {
return this;
}
@Override
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
return new StandardSqlAstTranslatorFactory();
}
@Override
public Identifier getCurrentCatalog() {
return null;
}
@Override
public Identifier getCurrentSchema() {
return null;
}
@Override
public QualifiedObjectNameFormatter getQualifiedObjectNameFormatter() {
return new QualifiedObjectNameFormatterStandardImpl(getNameQualifierSupport());
}
@Override
public NameQualifierSupport getNameQualifierSupport() {
return genericDialect.getNameQualifierSupport();
}
}
@Override
public JdbcServices initiateService(Map configurationValues, ServiceRegistryImplementor registry) {
return jdbcServices;
}
}

View File

@ -0,0 +1,90 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.validation;
import net.bytebuddy.ByteBuddy;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import static net.bytebuddy.implementation.FixedValue.value;
import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
import static net.bytebuddy.matcher.ElementMatchers.returns;
/**
* @author Gavin King
*/
@FunctionalInterface
public interface Mocker<T> {
T make(Object... args);
Map<Class<?>, Class<?>> mocks = new HashMap<>();
static <T> Supplier<T> nullary(Class<T> clazz) {
try {
Class<? extends T> mock = load(clazz);
return () -> {
try {
return mock.newInstance();
}
catch (Exception e) {
throw new RuntimeException(e);
}
};
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
static <T> Mocker<T> variadic(Class<T> clazz) {
Constructor<?>[] constructors = load(clazz).getDeclaredConstructors();
if (constructors.length>1) {
throw new RuntimeException("more than one constructor for " + clazz);
}
Constructor<?> constructor = constructors[0];
return (args) -> {
try {
return (T) constructor.newInstance(args);
}
catch (Exception e) {
throw new RuntimeException(e);
}
};
}
@SuppressWarnings("unchecked")
static <T> Class<? extends T> load(Class<T> clazz) {
if (mocks.containsKey(clazz)) {
return (Class<? extends T>) mocks.get(clazz);
}
Class<? extends T> mock =
new ByteBuddy()
.subclass(clazz)
.method(returns(String.class).and(isAbstract()))
.intercept(value(""))
.method(returns(boolean.class).and(isAbstract()))
.intercept(value(false))
.method(returns(int.class).and(isAbstract()))
.intercept(value(0))
.method(returns(long.class).and(isAbstract()))
.intercept(value(0L))
.method(returns(int[].class).and(isAbstract()))
.intercept(value(new int[0]))
.method(returns(String[].class).and(isAbstract()))
.intercept(value(new String[0]))
.make()
.load(clazz.getClassLoader())
.getLoaded();
mocks.put(clazz,mock);
return mock;
}
}

View File

@ -0,0 +1,924 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.validation;
import jakarta.persistence.AccessType;
import org.hibernate.PropertyNotFoundException;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.type.BasicType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.CompositeType;
import org.hibernate.type.EntityType;
import org.hibernate.type.ManyToOneType;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.java.EnumJavaType;
import org.hibernate.type.descriptor.jdbc.IntegerJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.VarcharJdbcType;
import org.hibernate.type.internal.BasicTypeImpl;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.ModuleElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.NestingKind;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.beans.Introspector;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.util.Arrays.stream;
import static org.hibernate.internal.util.StringHelper.qualify;
import static org.hibernate.internal.util.StringHelper.root;
import static org.hibernate.internal.util.StringHelper.split;
import static org.hibernate.internal.util.StringHelper.unroot;
/**
* Implementation of the {@code Mock} objects based on standard
* annotation processor APIs. Note that alternative implementations
* exist in the Query Validator project.
*
* @author Gavin King
*/
@SuppressWarnings("nullness")
public abstract class ProcessorSessionFactory extends MockSessionFactory {
public static MockSessionFactory create(ProcessingEnvironment environment) {
return instance.make(environment);
}
static final Mocker<ProcessorSessionFactory> instance = Mocker.variadic(ProcessorSessionFactory.class);
private static final Mocker<Component> component = Mocker.variadic(Component.class);
private static final Mocker<ToManyAssociationPersister> toManyPersister = Mocker.variadic(ToManyAssociationPersister.class);
private static final Mocker<ElementCollectionPersister> collectionPersister = Mocker.variadic(ElementCollectionPersister.class);
private static final Mocker<EntityPersister> entityPersister = Mocker.variadic(EntityPersister.class);
private static final CharSequence jakartaPersistence = new StringBuilder("jakarta").append('.').append("persistence");
private static final CharSequence javaxPersistence = new StringBuilder("javax").append('.').append("persistence");
private final Elements elementUtil;
private final Types typeUtil;
public ProcessorSessionFactory(ProcessingEnvironment processingEnv) {
elementUtil = processingEnv.getElementUtils();
typeUtil = processingEnv.getTypeUtils();
}
@Override
MockEntityPersister createMockEntityPersister(String entityName) {
TypeElement type = findEntityClass(entityName);
return type == null ? null : entityPersister.make(entityName, type, this);
}
@Override
MockCollectionPersister createMockCollectionPersister(String role) {
String entityName = root(role); //only works because entity names don't contain dots
String propertyPath = unroot(role);
TypeElement entityClass = findEntityClass(entityName);
AccessType defaultAccessType = getDefaultAccessType(entityClass);
Element property = findPropertyByPath(entityClass, propertyPath, defaultAccessType);
CollectionType collectionType = collectionType(memberType(property), role);
if (isToManyAssociation(property)) {
return toManyPersister.make(role, collectionType,
getToManyTargetEntityName(property), this);
}
else if (isElementCollectionProperty(property)) {
Element elementType = asElement(getElementCollectionElementType(property));
return collectionPersister.make(role, collectionType,
elementType, propertyPath, defaultAccessType, this);
}
else {
return null;
}
}
@Override
Type propertyType(String typeName, String propertyPath) {
TypeElement type = findClassByQualifiedName(typeName);
AccessType accessType = getAccessType(type, AccessType.FIELD);
Element propertyByPath = findPropertyByPath(type, propertyPath, accessType);
return propertyByPath == null ? null
: propertyType(propertyByPath, typeName, propertyPath, accessType);
}
private static Element findPropertyByPath(TypeElement type,
String propertyPath,
AccessType defaultAccessType) {
return stream(split(".", propertyPath))
.reduce((Element) type,
(symbol, segment) -> dereference( defaultAccessType, symbol, segment ),
(last, current) -> current);
}
private static Element dereference(AccessType defaultAccessType, Element symbol, String segment) {
if (symbol == null) {
return null;
}
else {
Element element = asElement(symbol.asType());
return element instanceof TypeElement
? findProperty((TypeElement) element, segment, defaultAccessType)
: null;
}
}
static Type propertyType(Element member, String entityName, String path, AccessType defaultAccessType) {
TypeMirror memberType = memberType(member);
if (isEmbeddedProperty(member)) {
return component.make(asElement(memberType), entityName, path, defaultAccessType);
}
else if (isToOneAssociation(member)) {
String targetEntity = getToOneTargetEntity(member);
return new ManyToOneType(typeConfiguration, targetEntity);
}
else if (isToManyAssociation(member)) {
return collectionType(memberType, qualify(entityName, path));
}
else if (isElementCollectionProperty(member)) {
return collectionType(memberType, qualify(entityName, path));
}
else if (isEnumProperty(member)) {
return new BasicTypeImpl(new EnumJavaType(Enum.class), enumJdbcType(member));
}
else {
return typeConfiguration.getBasicTypeRegistry()
.getRegisteredType(qualifiedName(memberType));
}
}
private static JdbcType enumJdbcType(Element member) {
VariableElement mapping = (VariableElement)
getAnnotationMember(getAnnotation(member,"Enumerated"), "value");
return mapping != null && mapping.getSimpleName().contentEquals("STRING")
? VarcharJdbcType.INSTANCE
: IntegerJdbcType.INSTANCE;
}
private static Type elementCollectionElementType(TypeElement elementType,
String role, String path,
AccessType defaultAccessType) {
if (isEmbeddableType(elementType)) {
return component.make(elementType, role, path, defaultAccessType);
}
else {
return typeConfiguration.getBasicTypeRegistry()
.getRegisteredType(qualifiedName(elementType.asType()));
}
}
private static CollectionType collectionType(TypeMirror type, String role) {
return createCollectionType(role, simpleName(type));
}
public static abstract class Component implements CompositeType {
private final String[] propertyNames;
private final Type[] propertyTypes;
TypeElement type;
public Component(TypeElement type,
String entityName, String path,
AccessType defaultAccessType) {
this.type = type;
List<String> names = new ArrayList<>();
List<Type> types = new ArrayList<>();
while (type!=null) {
if (isMappedClass(type)) { //ignore unmapped intervening classes
AccessType accessType = getAccessType(type, defaultAccessType);
for (Element member: type.getEnclosedElements()) {
if (isPersistable(member, accessType)) {
String name = propertyName(member);
Type propertyType =
propertyType(member, entityName,
qualify(path, name), defaultAccessType);
if (propertyType != null) {
names.add(name);
types.add(propertyType);
}
}
}
}
type = (TypeElement) asElement(type.getSuperclass());
}
propertyNames = names.toArray(new String[0]);
propertyTypes = types.toArray(new Type[0]);
}
@Override
public int getPropertyIndex(String name) {
String[] names = getPropertyNames();
for ( int i = 0, max = names.length; i < max; i++ ) {
if ( names[i].equals( name ) ) {
return i;
}
}
throw new PropertyNotFoundException(
"Could not resolve attribute '" + name + "' of '" + getName() + "'"
);
}
@Override
public String getName() {
return type.getSimpleName().toString();
}
@Override
public boolean isComponentType() {
return true;
}
@Override
public String[] getPropertyNames() {
return propertyNames;
}
@Override
public Type[] getSubtypes() {
return propertyTypes;
}
@Override
public boolean[] getPropertyNullability() {
return new boolean[propertyNames.length];
}
@Override
public int getColumnSpan(Mapping mapping) {
return propertyNames.length;
}
}
public static abstract class EntityPersister extends MockEntityPersister {
private final TypeElement type;
private final Types typeUtil;
public EntityPersister(String entityName, TypeElement type, ProcessorSessionFactory that) {
super(entityName, getDefaultAccessType(type), that);
this.type = type;
this.typeUtil = that.typeUtil;
initSubclassPersisters();
}
@Override
boolean isSubclassPersister(MockEntityPersister entityPersister) {
EntityPersister persister = (EntityPersister) entityPersister;
return typeUtil.isSubtype( persister.type.asType(), type.asType() );
}
@Override
Type createPropertyType(String propertyPath) {
Element symbol = findPropertyByPath(type, propertyPath, defaultAccessType);
return symbol == null ? null :
propertyType(symbol, getEntityName(), propertyPath, defaultAccessType);
}
}
public abstract static class ToManyAssociationPersister extends MockCollectionPersister {
public ToManyAssociationPersister(String role, CollectionType collectionType, String targetEntityName, ProcessorSessionFactory that) {
super(role, collectionType,
new ManyToOneType(typeConfiguration, targetEntityName),
that);
}
@Override
Type getElementPropertyType(String propertyPath) {
return getElementPersister().getPropertyType(propertyPath);
}
}
public abstract static class ElementCollectionPersister extends MockCollectionPersister {
private final TypeElement elementType;
private final AccessType defaultAccessType;
public ElementCollectionPersister(String role,
CollectionType collectionType,
TypeElement elementType,
String propertyPath,
AccessType defaultAccessType,
ProcessorSessionFactory that) {
super(role, collectionType,
elementCollectionElementType(elementType, role,
propertyPath, defaultAccessType),
that);
this.elementType = elementType;
this.defaultAccessType = defaultAccessType;
}
@Override
Type getElementPropertyType(String propertyPath) {
Element symbol = findPropertyByPath(elementType, propertyPath, defaultAccessType);
return symbol == null ? null :
propertyType(symbol, getOwnerEntityName(), propertyPath, defaultAccessType);
}
}
@Override
boolean isEntityDefined(String entityName) {
return findEntityClass(entityName) != null;
}
@Override
String qualifyName(String entityName) {
TypeElement entityClass = findEntityClass(entityName);
return entityClass == null ? null : entityClass.getSimpleName().toString();
}
@Override
boolean isAttributeDefined(String entityName, String fieldName) {
TypeElement entityClass = findEntityClass(entityName);
return entityClass != null
&& findPropertyByPath(entityClass, fieldName, getDefaultAccessType(entityClass)) != null;
}
private TypeElement findEntityClass(String entityName) {
if (entityName == null) {
return null;
}
else if (entityName.indexOf('.')>0) {
return findEntityByQualifiedName(entityName);
}
else {
return findEntityByUnqualifiedName(entityName);
}
}
private TypeElement findEntityByQualifiedName(String entityName) {
TypeElement type = findClassByQualifiedName(entityName);
return type != null && isEntity(type) ? type : null;
}
//Needed only for ECJ
private final Map<String,TypeElement> entityCache = new HashMap<>();
private TypeElement findEntityByUnqualifiedName(String entityName) {
TypeElement cached = entityCache.get(entityName);
if ( cached != null ) {
return cached;
}
TypeElement symbol =
findEntityByUnqualifiedName(entityName,
elementUtil.getModuleElement(""));
if (symbol!=null) {
entityCache.put(entityName, symbol);
return symbol;
}
for (ModuleElement module: elementUtil.getAllModuleElements()) {
symbol = findEntityByUnqualifiedName(entityName, module);
if (symbol!=null) {
entityCache.put(entityName, symbol);
return symbol;
}
}
return null;
}
private static TypeElement findEntityByUnqualifiedName(String entityName, ModuleElement module) {
for (Element element: module.getEnclosedElements()) {
if (element.getKind() == ElementKind.PACKAGE) {
PackageElement pack = (PackageElement) element;
try {
for (Element member : pack.getEnclosedElements()) {
if (isMatchingEntity(member, entityName)) {
return (TypeElement) member;
}
}
}
catch (Exception e) {
}
}
}
return null;
}
private static boolean isMatchingEntity(Element symbol, String entityName) {
if (symbol.getKind() == ElementKind.CLASS) {
TypeElement type = (TypeElement) symbol;
return isEntity(type)
&& getEntityName(type).equals(entityName);
}
else {
return false;
}
}
private static Element findProperty(TypeElement type, String propertyName, AccessType defaultAccessType) {
//iterate up the superclass hierarchy
while (type!=null) {
if (isMappedClass(type)) { //ignore unmapped intervening classes
AccessType accessType = getAccessType(type, defaultAccessType);
for (Element member: type.getEnclosedElements()) {
if (isMatchingProperty(member, propertyName, accessType)) {
return member;
}
}
}
type = (TypeElement) asElement(type.getSuperclass());
}
return null;
}
private static boolean isMatchingProperty(Element symbol, String propertyName, AccessType accessType) {
return isPersistable(symbol, accessType)
&& propertyName.equals(propertyName(symbol));
}
private static boolean isGetterMethod(ExecutableElement method) {
if (!method.getParameters().isEmpty()) {
return false;
}
else {
Name methodName = method.getSimpleName();
TypeMirror returnType = method.getReturnType();
return methodName.subSequence(0,3).toString().equals("get") && returnType.getKind() != TypeKind.VOID
|| methodName.subSequence(0,2).toString().equals("is") && returnType.getKind() == TypeKind.BOOLEAN;
}
}
private static boolean hasAnnotation(TypeMirror type, String annotationName) {
return type.getKind() == TypeKind.DECLARED
&& getAnnotation(((DeclaredType) type).asElement(), annotationName)!=null;
}
private static boolean hasAnnotation(Element member, String annotationName) {
return getAnnotation(member, annotationName)!=null;
}
private static AnnotationMirror getAnnotation(Element member, String annotationName) {
for (AnnotationMirror mirror : member.getAnnotationMirrors()) {
TypeElement annotationType = (TypeElement) mirror.getAnnotationType().asElement();
if ( annotationType.getSimpleName().contentEquals(annotationName)
&& annotationType.getNestingKind() == NestingKind.TOP_LEVEL ) {
PackageElement pack = (PackageElement) annotationType.getEnclosingElement();
Name packageName = pack.getQualifiedName();
if (packageName.contentEquals(jakartaPersistence)
|| packageName.contentEquals(javaxPersistence)) {
return mirror;
}
}
}
return null;
}
private static Object getAnnotationMember(AnnotationMirror annotation, String memberName) {
if ( annotation == null ) {
return null;
}
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
annotation.getElementValues().entrySet()) {
if (entry.getKey().getSimpleName().contentEquals(memberName)) {
return entry.getValue().getValue();
}
}
return null;
}
private static boolean isMappedClass(TypeElement type) {
return hasAnnotation(type, "Entity")
|| hasAnnotation(type, "Embeddable")
|| hasAnnotation(type, "MappedSuperclass");
}
private static boolean isEntity(TypeElement member) {
return member.getKind() == ElementKind.CLASS
// && member.getAnnotation(entityAnnotation)!=null;
&& hasAnnotation(member, "Entity");
}
private static boolean isId(Element member) {
return hasAnnotation(member, "Id");
}
private static boolean isStatic(Element member) {
return member.getModifiers().contains(Modifier.STATIC);
}
private static boolean isTransient(Element member) {
return hasAnnotation(member, "Transient")
|| member.getModifiers().contains(Modifier.TRANSIENT);
}
private static boolean isEnumProperty(Element member) {
if (hasAnnotation(member, "Enumerated")) {
return true;
}
else {
TypeMirror type = member.asType();
if (type.getKind() == TypeKind.DECLARED) {
DeclaredType declaredType = (DeclaredType) type;
TypeElement typeElement = (TypeElement) declaredType.asElement();
//TODO: something better here!
return typeElement.getSuperclass().toString().startsWith("java.lang.Enum");
}
else {
return false;
}
}
}
private static boolean isEmbeddableType(TypeElement type) {
return hasAnnotation(type, "Embeddable");
}
private static boolean isEmbeddedProperty(Element member) {
if (hasAnnotation(member, "Embedded")) {
return true;
}
else {
TypeMirror type = member.asType();
return type.getKind() == TypeKind.DECLARED
&& hasAnnotation(type, "Embeddable");
}
}
private static boolean isElementCollectionProperty(Element member) {
return hasAnnotation(member, "ElementCollection");
}
private static boolean isToOneAssociation(Element member) {
return hasAnnotation(member, "ManyToOne")
|| hasAnnotation(member, "OneToOne");
}
private static boolean isToManyAssociation(Element member) {
return hasAnnotation(member, "ManyToMany")
|| hasAnnotation(member, "OneToMany");
}
private static AnnotationMirror toOneAnnotation(Element member) {
AnnotationMirror manyToOne =
getAnnotation(member, "ManyToOne");
if (manyToOne!=null) {
return manyToOne;
}
AnnotationMirror oneToOne =
getAnnotation(member, "OneToOne");
if (oneToOne!=null) {
return oneToOne;
}
return null;
}
private static AnnotationMirror toManyAnnotation(Element member) {
AnnotationMirror manyToMany =
getAnnotation(member, "ManyToMany");
if (manyToMany!=null) {
return manyToMany;
}
AnnotationMirror oneToMany =
getAnnotation(member, "OneToMany");
if (oneToMany!=null) {
return oneToMany;
}
return null;
}
private static String simpleName(TypeMirror type) {
return type.getKind() == TypeKind.DECLARED
? simpleName(asElement(type))
: type.toString();
}
private static String qualifiedName(TypeMirror type) {
return type.getKind() == TypeKind.DECLARED
? qualifiedName(asElement(type))
: type.toString();
}
private static String simpleName(Element type) {
return type.getSimpleName().toString();
}
private static String qualifiedName(Element type) {
if ( type instanceof PackageElement ) {
return ((PackageElement) type).getQualifiedName().toString();
}
else if ( type instanceof TypeElement ) {
return ((TypeElement) type).getQualifiedName().toString();
}
else {
Element enclosingElement = type.getEnclosingElement();
return enclosingElement != null
? qualifiedName(enclosingElement) + '.' + simpleName(type)
: simpleName(type);
}
}
private static AccessType getAccessType(TypeElement type, AccessType defaultAccessType) {
AnnotationMirror annotation =
getAnnotation(type, "Access");
if (annotation==null) {
return defaultAccessType;
}
else {
VariableElement member = (VariableElement)
getAnnotationMember(annotation, "value");
if (member==null) {
return defaultAccessType; //does not occur
}
switch (member.getSimpleName().toString()) {
case "PROPERTY":
return AccessType.PROPERTY;
case "FIELD":
return AccessType.FIELD;
default:
throw new IllegalStateException();
}
}
}
private static String getEntityName(TypeElement type) {
if ( type == null ) {
return null;
}
AnnotationMirror entityAnnotation =
getAnnotation(type, "Entity");
if (entityAnnotation==null) {
//not an entity!
return null;
}
else {
String name = (String)
getAnnotationMember(entityAnnotation, "name");
//entity names are unqualified class names
return name==null ? simpleName(type) : name;
}
}
private TypeMirror getCollectionElementType(Element property) {
DeclaredType declaredType = (DeclaredType) memberType(property);
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
TypeMirror elementType = typeArguments.get(typeArguments.size()-1);
return elementType==null
? elementUtil.getTypeElement("java.lang.Object").asType()
: elementType;
}
private static String getToOneTargetEntity(Element property) {
AnnotationMirror annotation = toOneAnnotation(property);
TypeMirror classType = (TypeMirror)
getAnnotationMember(annotation, "targetEntity");
TypeMirror targetType =
classType == null || classType.getKind() == TypeKind.VOID
? memberType(property)
: classType;
Element element = asElement(targetType);
return element != null && element.getKind() == ElementKind.CLASS
//entity names are unqualified class names
? getEntityName((TypeElement) element)
: null;
}
private String getToManyTargetEntityName(Element property) {
AnnotationMirror annotation = toManyAnnotation(property);
TypeMirror classType = (TypeMirror)
getAnnotationMember(annotation, "targetEntity");
TypeMirror targetType =
classType == null || classType.getKind() == TypeKind.VOID
? getCollectionElementType(property)
: classType;
Element element = asElement(targetType);
return element != null && element.getKind() == ElementKind.CLASS
//entity names are unqualified class names
? getEntityName((TypeElement) element)
: null;
}
private TypeMirror getElementCollectionElementType(Element property) {
AnnotationMirror annotation = getAnnotation(property,
"ElementCollection");
TypeMirror classType = (TypeMirror)
getAnnotationMember(annotation, "getElementCollectionClass");
return classType == null
|| classType.getKind() == TypeKind.VOID
? getCollectionElementType(property)
: classType;
}
@Override
protected String getSupertype(String entityName) {
return asElement(findEntityClass(entityName).getSuperclass())
.getSimpleName().toString();
}
@Override
protected boolean isSubtype(String entityName, String subtypeEntityName) {
return typeUtil.isSubtype( findEntityClass(entityName).asType(),
findEntityClass(subtypeEntityName).asType());
}
@Override
boolean isClassDefined(String qualifiedName) {
return findClassByQualifiedName(qualifiedName)!=null;
}
@Override
boolean isFieldDefined(String qualifiedClassName, String fieldName) {
TypeElement type = findClassByQualifiedName(qualifiedClassName);
return type != null
&& type.getEnclosedElements().stream()
.anyMatch(element -> element.getKind() == ElementKind.FIELD
&& element.getSimpleName().contentEquals(fieldName));
}
@Override
boolean isConstructorDefined(String qualifiedClassName, List<Type> argumentTypes) {
TypeElement symbol = findClassByQualifiedName(qualifiedClassName);
if (symbol==null) {
return false;
}
for (Element cons: symbol.getEnclosedElements()) {
if ( cons.getKind() == ElementKind.CONSTRUCTOR ) {
ExecutableElement constructor = (ExecutableElement) cons;
List<? extends VariableElement> parameters = constructor.getParameters();
if (parameters.size()==argumentTypes.size()) {
boolean argumentsCheckOut = true;
for (int i=0; i<argumentTypes.size(); i++) {
Type type = argumentTypes.get(i);
VariableElement param = parameters.get(i);
if (param.asType().getKind().isPrimitive()) {
Class<?> primitive;
try {
primitive = toPrimitiveClass( type.getReturnedClass() );
}
catch (Exception e) {
continue;
}
if (!toPrimitiveClass(param).equals(primitive)) {
argumentsCheckOut = false;
break;
}
}
else {
TypeElement typeClass;
if (type instanceof EntityType) {
EntityType entityType = (EntityType) type;
String entityName = entityType.getAssociatedEntityName();
typeClass = findEntityClass(entityName);
}
//TODO:
// else if (type instanceof CompositeCustomType) {
// typeClass = ((Component) ((CompositeCustomType) type).getUserType()).type;
// }
else if (type instanceof BasicType) {
String className;
//sadly there is no way to get the classname
//from a Hibernate Type without trying to load
//the class!
try {
className = type.getReturnedClass().getName();
}
catch (Exception e) {
continue;
}
typeClass = findClassByQualifiedName(className);
}
else {
//TODO: what other Hibernate Types do we
// need to consider here?
continue;
}
if (typeClass != null
&& !typeUtil.isSubtype( typeClass.asType(), param.asType() ) ) {
argumentsCheckOut = false;
break;
}
}
}
if (argumentsCheckOut) {
return true; //matching constructor found!
}
}
}
}
return false;
}
private static Class<?> toPrimitiveClass(VariableElement param) {
switch (param.asType().getKind()) {
case BOOLEAN:
return boolean.class;
case CHAR:
return char.class;
case INT:
return int.class;
case SHORT:
return short.class;
case BYTE:
return byte.class;
case LONG:
return long.class;
case FLOAT:
return float.class;
case DOUBLE:
return double.class;
default:
return Object.class;
}
}
private TypeElement findClassByQualifiedName(String path) {
return path == null ? null : elementUtil.getTypeElement(path);
}
private static AccessType getDefaultAccessType(TypeElement type) {
//iterate up the superclass hierarchy
while (type!=null) {
for (Element member: type.getEnclosedElements()) {
if (isId(member)) {
return member instanceof ExecutableElement
? AccessType.PROPERTY
: AccessType.FIELD;
}
}
type = (TypeElement) asElement(type.getSuperclass());
}
return AccessType.FIELD;
}
private static String propertyName(Element symbol) {
String name = symbol.getSimpleName().toString();
if (symbol.getKind() == ElementKind.METHOD) {
if (name.startsWith("get")) {
name = name.substring(3);
}
else if (name.startsWith("is")) {
name = name.substring(2);
}
return Introspector.decapitalize(name);
}
else {
return name;
}
}
private static boolean isPersistable(Element member, AccessType accessType) {
if (isStatic(member) || isTransient(member)) {
return false;
}
else if (member.getKind() == ElementKind.FIELD) {
return accessType == AccessType.FIELD
// || member.getAnnotation( accessAnnotation ) != null;
|| hasAnnotation(member, "Access");
}
else if (member.getKind() == ElementKind.METHOD) {
return isGetterMethod((ExecutableElement) member)
&& (accessType == AccessType.PROPERTY
// || member.getAnnotation( accessAnnotation ) != null);
|| hasAnnotation(member, "Access"));
}
else {
return false;
}
}
private static TypeMirror memberType(Element member) {
if (member instanceof ExecutableElement) {
return ((ExecutableElement) member).getReturnType();
}
else if (member instanceof VariableElement) {
return member.asType();
}
else {
throw new IllegalArgumentException("Not a member");
}
}
public static Element asElement(TypeMirror type) {
if ( type == null ) {
return null;
}
else {
switch (type.getKind()) {
case DECLARED:
return ((DeclaredType)type).asElement();
case TYPEVAR:
return ((TypeVariable)type).asElement();
default:
return null;
}
}
}
}

View File

@ -0,0 +1,219 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.validation;
import org.antlr.v4.runtime.ANTLRErrorListener;
import org.antlr.v4.runtime.BailErrorStrategy;
import org.antlr.v4.runtime.DefaultErrorStrategy;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.atn.PredictionMode;
import org.antlr.v4.runtime.misc.ParseCancellationException;
import org.hibernate.PropertyNotFoundException;
import org.hibernate.QueryException;
import org.hibernate.grammars.hql.HqlLexer;
import org.hibernate.grammars.hql.HqlParser;
import org.hibernate.query.hql.internal.HqlParseTreeBuilder;
import org.hibernate.query.hql.internal.SemanticQueryBuilder;
import org.hibernate.query.sqm.EntityTypeException;
import org.hibernate.query.sqm.PathElementException;
import org.hibernate.query.sqm.TerminalPathException;
import org.hibernate.type.descriptor.java.spi.JdbcTypeRecommendationException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static java.lang.Character.isJavaIdentifierStart;
import static java.lang.Integer.parseInt;
import static java.util.stream.Stream.concat;
/**
* The entry point for HQL validation.
*
* @author Gavin King
*/
public class Validation {
public interface Handler extends ANTLRErrorListener {
void error(int start, int end, String message);
void warn(int start, int end, String message);
int getErrorCount();
}
public static void validate(
String hql, boolean checkParams,
Set<Integer> setParameterLabels,
Set<String> setParameterNames,
Handler handler,
MockSessionFactory factory) {
validate(hql, checkParams, setParameterLabels, setParameterNames, handler, factory, 0);
}
public static void validate(
String hql, boolean checkParams,
Set<Integer> setParameterLabels,
Set<String> setParameterNames,
Handler handler,
MockSessionFactory factory,
int errorOffset) {
// handler = new Filter(handler, errorOffset);
try {
final HqlLexer hqlLexer = HqlParseTreeBuilder.INSTANCE.buildHqlLexer( hql );
final HqlParser hqlParser = HqlParseTreeBuilder.INSTANCE.buildHqlParser( hql, hqlLexer );
hqlLexer.addErrorListener( handler );
hqlParser.getInterpreter().setPredictionMode( PredictionMode.SLL );
hqlParser.removeErrorListeners();
hqlParser.addErrorListener( handler );
hqlParser.setErrorHandler( new BailErrorStrategy() );
HqlParser.StatementContext statementContext;
try {
statementContext = hqlParser.statement();
}
catch ( ParseCancellationException e) {
// reset the input token stream and parser state
hqlLexer.reset();
hqlParser.reset();
// fall back to LL(k)-based parsing
hqlParser.getInterpreter().setPredictionMode( PredictionMode.LL );
hqlParser.setErrorHandler( new DefaultErrorStrategy() );
statementContext = hqlParser.statement();
}
if (handler.getErrorCount() == 0) {
try {
new SemanticQueryBuilder<>(Object[].class, () -> false, factory)
.visitStatement( statementContext );
}
catch (JdbcTypeRecommendationException ignored) {
// just squash these for now
}
catch (QueryException | PathElementException | TerminalPathException | EntityTypeException
| PropertyNotFoundException se) { //TODO is this one really thrown by core? It should not be!
String message = se.getMessage();
if ( message != null ) {
handler.error(-errorOffset+1, -errorOffset + hql.length(), message);
}
}
}
if (checkParams) {
checkParameterBinding(hql, setParameterLabels, setParameterNames, handler, errorOffset);
}
}
catch (Exception e) {
// e.printStackTrace();
}
}
private static void checkParameterBinding(
String hql,
Set<Integer> setParameterLabels,
Set<String> setParameterNames,
Handler handler,
int errorOffset) {
try {
String unsetParams = null;
String notSet = null;
String parameters = null;
int start = -1;
int end = -1;
List<String> names = new ArrayList<>();
List<Integer> labels = new ArrayList<>();
final HqlLexer hqlLexer = HqlParseTreeBuilder.INSTANCE.buildHqlLexer( hql );
loop:
while (true) {
Token token = hqlLexer.nextToken();
int tokenType = token.getType();
switch (tokenType) {
case HqlLexer.EOF:
break loop;
case HqlLexer.QUESTION_MARK:
case HqlLexer.COLON:
Token next = hqlLexer.nextToken();
String text = next.getText();
switch (tokenType) {
case HqlLexer.COLON:
if (!text.isEmpty()
&& isJavaIdentifierStart(text.codePointAt(0))) {
names.add(text);
if (setParameterNames.contains(text)) {
continue;
}
}
else {
continue;
}
break;
case HqlLexer.QUESTION_MARK:
if (next.getType() == HqlLexer.INTEGER_LITERAL) {
int label;
try {
label = parseInt(text);
}
catch (NumberFormatException nfe) {
continue;
}
labels.add(label);
if (setParameterLabels.contains(label)) {
continue;
}
}
else {
continue;
}
break;
default:
continue;
}
parameters = unsetParams == null ? "Parameter " : "Parameters ";
notSet = unsetParams == null ? " is not set" : " are not set";
unsetParams = unsetParams == null ? "" : unsetParams + ", ";
unsetParams += token.getText() + text;
if (start == -1) {
start = token.getCharPositionInLine(); //TODO: wrong for multiline query strings!
}
end = token.getCharPositionInLine() + text.length() + 1;
break;
}
}
if (unsetParams != null) {
handler.warn(start-errorOffset+1, end-errorOffset, parameters + unsetParams + notSet);
}
setParameterNames.removeAll(names);
setParameterLabels.removeAll(labels);
int count = setParameterNames.size() + setParameterLabels.size();
if (count > 0) {
String missingParams =
concat(setParameterNames.stream().map(name -> ":" + name),
setParameterLabels.stream().map(label -> "?" + label))
.reduce((x, y) -> x + ", " + y)
.orElse(null);
String params =
count == 1 ?
"Parameter " :
"Parameters ";
String notOccur =
count == 1 ?
" does not occur in the query" :
" do not occur in the query";
handler.warn(0, 0, params + missingParams + notOccur);
}
}
finally {
setParameterNames.clear();
setParameterLabels.clear();
}
}
}

View File

@ -0,0 +1,12 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
/**
* Validation for HQL queries.
*
* @see org.hibernate.jpamodelgen.validation.Validation#validate
*/
package org.hibernate.jpamodelgen.validation;