mirror of
https://github.com/hibernate/hibernate-orm
synced 2025-02-23 11:47:40 +00:00
HHH-16887 integrate full HQL typechecking into Metamodel Generator!
This commit is contained in:
parent
445f2cbdd8
commit
f61e00c642
@ -23,6 +23,7 @@ dependencies {
|
||||
implementation jakartaLibs.jaxb
|
||||
implementation libs.antlrRuntime
|
||||
implementation project( ':hibernate-core' )
|
||||
implementation libs.byteBuddy
|
||||
|
||||
xjc jakartaLibs.xjc
|
||||
xjc jakartaLibs.jaxb
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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[] {""};
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
Loading…
x
Reference in New Issue
Block a user