From 388c3e01fd6e36b8f2766ac5990d4e0acf6ce791 Mon Sep 17 00:00:00 2001 From: Gavin Date: Wed, 7 Jun 2023 00:21:42 +0200 Subject: [PATCH 1/7] start update of all deps and JDK --- .idea/compiler.xml | 1 + .idea/gradle.xml | 2 - .idea/misc.xml | 5 ++- build.gradle | 44 ++++++++++--------- gradle/wrapper/gradle-wrapper.properties | 2 +- .../query/validator/EclipseProcessor.groovy | 42 +++++++++++------- .../validator/EclipseSessionFactory.groovy | 30 ++++++------- .../query/validator/JavacProcessor.java | 18 ++++---- .../query/validator/JavacSessionFactory.java | 27 +++++------- .../query/validator/MockSessionFactory.java | 6 +++ src/test/source/test/GoodQueries.java | 2 +- 11 files changed, 98 insertions(+), 81 deletions(-) diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 8dd0fd2..24938af 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -12,5 +12,6 @@ + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 13a8247..ce1c62c 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,14 +4,12 @@ diff --git a/.idea/misc.xml b/.idea/misc.xml index 6bd5763..654613f 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,10 @@ - + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index b6515cf..4d878bc 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,8 @@ -import org.gradle.internal.jvm.Jvm - plugins { id 'java' id 'groovy' - id 'maven' id 'maven-publish' - id "com.github.johnrengelman.shadow" version "5.0.0" + id "com.github.johnrengelman.shadow" version "8.1.1" } defaultTasks 'assemble', 'publishToMavenLocal', 'shadowJar', 'test' @@ -18,29 +15,30 @@ repositories { } dependencies { - compile ('org.hibernate:hibernate-core:5.4.10.Final') { + implementation ('org.hibernate:hibernate-core:5.6.15.Final') { transitive = false } //explicit the Hibernate dependencies we need: - compile 'antlr:antlr:2.7.7' - compile 'javax.persistence:javax.persistence-api:2.2' - compile 'javax.transaction:javax.transaction-api:1.3' - compile 'net.bytebuddy:byte-buddy:1.10.2' - compile 'org.jboss.logging:jboss-logging:3.3.2.Final' - - testRuntime ('io.quarkus:quarkus-hibernate-orm-panache:1.4.1.Final') { + implementation 'antlr:antlr:2.7.7' + implementation 'javax.persistence:javax.persistence-api:2.2' + implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0' + implementation 'javax.transaction:javax.transaction-api:1.3' + implementation 'net.bytebuddy:byte-buddy:1.14.5' + implementation 'org.jboss.logging:jboss-logging:3.5.0.Final' + + testRuntimeOnly ('io.quarkus:quarkus-hibernate-orm-panache:3.1.0.Final') { transitive = false } - testRuntime ('io.quarkus:quarkus-panache-common:1.4.1.Final') { + testRuntimeOnly ('io.quarkus:quarkus-panache-common:3.1.0.Final') { transitive = false } - compile 'org.codehaus.groovy:groovy:2.5.6:indy' + implementation 'org.apache.groovy:groovy:4.0.12' - compile 'org.eclipse.jdt.core.compiler:ecj:4.6.1' - compile files(Jvm.current().toolsJar) + implementation 'org.eclipse.jdt:ecj:3.33.0' - testCompile 'junit:junit:4.12' +// testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.3' + testImplementation 'junit:junit:4.13.2' } group = 'org.hibernate' @@ -58,8 +56,8 @@ sourceSets { shadowJar { dependencies { - exclude(dependency('org.eclipse.jdt.core.compiler:ecj')) - exclude "tools.jar" + exclude(dependency('org.eclipse.jdt:ecj')) +// exclude "tools.jar" } relocate ('org.hibernate', 'org.hibernate.query.validator.hibernate') { exclude 'org.hibernate.query.validator.*' @@ -81,7 +79,11 @@ shadowJar { relocate 'groovyjarjarasm', 'org.hibernate.query.validator.asm' relocate 'groovyjarjarcommonscli', 'org.hibernate.query.validator.cli' relocate 'groovyjarjarpicocli', 'org.hibernate.query.validator.picocli' - classifier = 'all' + archiveClassifier.set('all') +} + +jar { + duplicatesStrategy = DuplicatesStrategy.INCLUDE } publishing { @@ -103,7 +105,7 @@ tasks.withType(JavaCompile) { } task copyDependencies(type: Copy) { - from configurations.testRuntime + from configurations.testRuntimeClasspath into 'test-runtime-libs' } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ea13fdf..fae0804 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/org/hibernate/query/validator/EclipseProcessor.groovy b/src/main/java/org/hibernate/query/validator/EclipseProcessor.groovy index 269bdd1..50b8efd 100644 --- a/src/main/java/org/hibernate/query/validator/EclipseProcessor.groovy +++ b/src/main/java/org/hibernate/query/validator/EclipseProcessor.groovy @@ -4,6 +4,7 @@ import antlr.RecognitionException import org.hibernate.QueryException import javax.annotation.processing.AbstractProcessor +import javax.annotation.processing.ProcessingEnvironment import javax.annotation.processing.RoundEnvironment import javax.lang.model.SourceVersion import javax.lang.model.element.TypeElement @@ -24,6 +25,13 @@ import static org.hibernate.query.validator.Validation.validate //@SupportedAnnotationTypes(CHECK_HQL) class EclipseProcessor extends AbstractProcessor { + private ProcessingEnvironment processingEnv + + synchronized void init(ProcessingEnvironment processingEnv) { + this.processingEnv = processingEnv + super.init(processingEnv) + } + static Mocker sessionFactory = Mocker.variadic(EclipseSessionFactory.class) @Override @@ -52,15 +60,15 @@ class EclipseProcessor extends AbstractProcessor { def value = pair.value if (value instanceof Object[]) { for (literal in (Object[]) value) { - if (literal.class.simpleName == "StringConstant") { + if (literal.getClass().simpleName == "StringConstant") { names.add(literal.stringValue()) } } } - else if (value.class.simpleName == "StringConstant") { + else if (value.getClass().simpleName == "StringConstant") { names.add(value.stringValue()) } - else if (value.class.simpleName == "BinaryTypeBinding") { + else if (value.getClass().simpleName == "BinaryTypeBinding") { String name = qualifiedTypeName(value) def dialect try { @@ -170,7 +178,7 @@ class EclipseProcessor extends AbstractProcessor { } private void validateStatement(type, panacheEntity, statement) { - if (statement != null) switch (statement.class.simpleName) { + if (statement != null) switch (statement.getClass().simpleName) { case "MessageSend": boolean ic = immediatelyCalled def name = simpleMethodName(statement) @@ -187,14 +195,14 @@ class EclipseProcessor extends AbstractProcessor { case "list": case "find": // Disabled until we find how to support this type-safe in Javac -// if (statement.receiver.class.simpleName == "SingleNameReference") { +// if (statement.receiver.getClass().simpleName == "SingleNameReference") { // def ref = statement.receiver; // String target = charToString(ref.token); // def queryArg = firstArgument(statement); // if (queryArg != null) { // checkPanacheQuery(queryArg, target, name, charToString(queryArg.source()), statement.arguments); // } - if (statement.receiver.class.simpleName == "ThisReference" && panacheEntity != null) { + if (statement.receiver.getClass().simpleName == "ThisReference" && panacheEntity != null) { String target = panacheEntity.getSimpleName().toString(); def queryArg = firstArgument(statement); if (queryArg != null) { @@ -204,15 +212,15 @@ class EclipseProcessor extends AbstractProcessor { break; case "createQuery": statement.arguments.each { arg -> - if (arg.class.simpleName == "StringLiteral" - || arg.class.simpleName == "ExtendedStringLiteral") { + if (arg.getClass().simpleName == "StringLiteral" + || arg.getClass().simpleName == "ExtendedStringLiteral") { validateArgument(arg, true) } } break case "setParameter": def arg = statement.arguments.first() - switch (arg.class.simpleName) { + switch (arg.getClass().simpleName) { case "IntLiteral": setParameterLabels.add(parseInt(new String((char[])arg.source()))) break @@ -312,8 +320,8 @@ class EclipseProcessor extends AbstractProcessor { def firstArgument(messageSend) { for (argument in messageSend.arguments) { - if (argument.class.simpleName == "StringLiteral" || - argument.class.simpleName == "ExtendedStringLiteral") { + if (argument.getClass().simpleName == "StringLiteral" || + argument.getClass().simpleName == "ExtendedStringLiteral") { return argument; } } @@ -370,7 +378,7 @@ class EclipseProcessor extends AbstractProcessor { } } boolean isParametersCall(firstArg) { - if (firstArg.class.simpleName == "MessageSend") { + if (firstArg.getClass().simpleName == "MessageSend") { def invocation = firstArg; String fieldName = charToString(invocation.selector); if (fieldName.equals("and") && isParametersCall(invocation.receiver)) { @@ -381,7 +389,7 @@ class EclipseProcessor extends AbstractProcessor { } } else if (fieldName.equals("with") - && invocation.receiver.class.simpleName == "SingleNameReference") { + && invocation.receiver.getClass().simpleName == "SingleNameReference") { def receiver = invocation.receiver; String target = charToString(receiver.token); if (target.equals("Parameters")) { @@ -397,7 +405,7 @@ class EclipseProcessor extends AbstractProcessor { } boolean isSortCall(firstArg) { - if (firstArg.class.simpleName == "MessageSend") { + if (firstArg.getClass().simpleName == "MessageSend") { def invocation = firstArg; String fieldName = charToString(invocation.selector); if ((fieldName.equals("and") @@ -406,7 +414,7 @@ class EclipseProcessor extends AbstractProcessor { || fieldName.equals("direction")) && isSortCall(invocation.receiver)) { for (e in invocation.arguments) { - if (e.class.simpleName == "StringLiteral") { + if (e.getClass().simpleName == "StringLiteral") { setOrderBy.add(charToString(e.source())); } } @@ -415,12 +423,12 @@ class EclipseProcessor extends AbstractProcessor { else if ((fieldName.equals("by") || fieldName.equals("descending") || fieldName.equals("ascending")) - && invocation.receiver.class.simpleName == "SingleNameReference") { + && invocation.receiver.getClass().simpleName == "SingleNameReference") { def receiver = invocation.receiver; String target = charToString(receiver.token); if (target.equals("Sort")) { for (e in invocation.arguments) { - if (e.class.simpleName == "StringLiteral") { + if (e.getClass().simpleName == "StringLiteral") { setOrderBy.add(charToString(e.source())); } } diff --git a/src/main/java/org/hibernate/query/validator/EclipseSessionFactory.groovy b/src/main/java/org/hibernate/query/validator/EclipseSessionFactory.groovy index bdc59a7..f1c9770 100644 --- a/src/main/java/org/hibernate/query/validator/EclipseSessionFactory.groovy +++ b/src/main/java/org/hibernate/query/validator/EclipseSessionFactory.groovy @@ -357,7 +357,7 @@ abstract class EclipseSessionFactory extends MockSessionFactory { } private static String propertyName(def symbol) { - if (symbol.class.simpleName == "MethodBinding") { + if (symbol.getClass().simpleName == "MethodBinding") { String name = simpleMethodName(symbol) if (name.startsWith("get")) { name = name.substring(3) @@ -367,7 +367,7 @@ abstract class EclipseSessionFactory extends MockSessionFactory { } return Introspector.decapitalize(name) } - else if (symbol.class.simpleName == "FieldBinding") { + else if (symbol.getClass().simpleName == "FieldBinding") { return simpleVariableName(symbol) } else { @@ -379,14 +379,14 @@ abstract class EclipseSessionFactory extends MockSessionFactory { if (isStatic(member) || isTransient(member)) { return false } - else if (member.class.simpleName == "FieldBinding") { - return accessType == AccessType.FIELD || - hasAnnotation(member, jpa("Access")) + else if (member.getClass().simpleName == "FieldBinding") { + return accessType == AccessType.FIELD + || hasAnnotation(member, jpa("Access")) } - else if (member.class.simpleName == "MethodBinding") { - return isGetterMethod(member) && - (accessType == AccessType.PROPERTY || - hasAnnotation(member, jpa("Access"))) + else if (member.getClass().simpleName == "MethodBinding") { + return isGetterMethod(member) + && (accessType == AccessType.PROPERTY + || hasAnnotation(member, jpa("Access"))) } else { return false @@ -399,15 +399,15 @@ abstract class EclipseSessionFactory extends MockSessionFactory { } String methodName = simpleMethodName(method) def returnType = method.returnType - return methodName.startsWith("get") && returnType.id != 6 || - methodName.startsWith("is") && returnType.id == 5 + return methodName.startsWith("get") && returnType.id != 6 + || methodName.startsWith("is") && returnType.id == 5 } private static def getMemberType(binding) { - if (binding.class.simpleName == "MethodBinding") { + if (binding.getClass().simpleName == "MethodBinding") { return binding.returnType } - else if (binding.class.simpleName == "FieldBinding") { + else if (binding.getClass().simpleName == "FieldBinding") { return binding.type } else { @@ -678,8 +678,8 @@ abstract class EclipseSessionFactory extends MockSessionFactory { } private static boolean missing(type) { - return type.class.simpleName == "MissingTypeBinding" || - type.class.simpleName == "ProblemReferenceBinding" + return type.getClass().simpleName == "MissingTypeBinding" + || type.getClass().simpleName == "ProblemReferenceBinding" } } diff --git a/src/main/java/org/hibernate/query/validator/JavacProcessor.java b/src/main/java/org/hibernate/query/validator/JavacProcessor.java index 4d2d721..8de5a79 100644 --- a/src/main/java/org/hibernate/query/validator/JavacProcessor.java +++ b/src/main/java/org/hibernate/query/validator/JavacProcessor.java @@ -1,5 +1,6 @@ package org.hibernate.query.validator; +import static com.sun.tools.javac.resources.CompilerProperties.Warnings.ProcMessager; import static org.hibernate.query.validator.HQLProcessor.CHECK_HQL; import static org.hibernate.query.validator.HQLProcessor.jpa; import static org.hibernate.query.validator.Validation.validate; @@ -74,9 +75,9 @@ private void checkHQL(Element element) { TypeElement panacheEntity = PanacheUtils.isPanache(element, processingEnv.getTypeUtils(), elementUtils); if (tree != null) { tree.accept(new TreeScanner() { - Set setParameterLabels = new HashSet<>(); - Set setParameterNames = new HashSet<>(); - Set setOrderBy = new HashSet<>(); + final Set setParameterLabels = new HashSet<>(); + final Set setParameterNames = new HashSet<>(); + final Set setOrderBy = new HashSet<>(); boolean immediatelyCalled; private void check(JCTree.JCLiteral jcLiteral, String hql, @@ -380,8 +381,8 @@ class ErrorReporter implements Validation.Handler { private static final String KEY = "proc.messager"; - private Log log; - private JCTree.JCLiteral literal; + private final Log log; + private final JCTree.JCLiteral literal; ErrorReporter(JCTree.JCLiteral literal, Element element) { this.literal = literal; @@ -412,7 +413,8 @@ public void error(int start, int end, String message) { @Override public void warn(int start, int end, String message) { - log.warning(literal.pos + start, KEY, message); + + log.warning(literal.pos + start, ProcMessager(message)); } @Override @@ -422,12 +424,12 @@ public void reportError(RecognitionException e) { @Override public void reportError(String text) { - log.error(literal, KEY, text); + log.error(literal.pos, KEY, text); } @Override public void reportWarning(String text) { - log.warning(literal, KEY, text); + log.warning(literal.pos, ProcMessager(text)); } } diff --git a/src/main/java/org/hibernate/query/validator/JavacSessionFactory.java b/src/main/java/org/hibernate/query/validator/JavacSessionFactory.java index 45d117d..1e21536 100644 --- a/src/main/java/org/hibernate/query/validator/JavacSessionFactory.java +++ b/src/main/java/org/hibernate/query/validator/JavacSessionFactory.java @@ -38,7 +38,7 @@ public JavacSessionFactory(List functionWhitelist, ParseErrorHandler handler, JavacProcessingEnvironment processingEnv) { super(functionWhitelist, handler); - Context context = processingEnv.getContext(); + final Context context = processingEnv.getContext(); names = Names.instance(context); types = Types.instance(context); syms = Symtab.instance(context); @@ -140,8 +140,8 @@ private static CollectionType collectionType( } public static abstract class Component implements CompositeUserType { - private String[] propertyNames; - private Type[] propertyTypes; + private final String[] propertyNames; + private final Type[] propertyTypes; Symbol.TypeSymbol type; public Component(Symbol.TypeSymbol type, @@ -157,8 +157,7 @@ public Component(Symbol.TypeSymbol type, AccessType accessType = getAccessType(type, defaultAccessType); for (Symbol member: type.members() - .getElements(symbol - -> isPersistable(symbol, accessType))) { + .getSymbols(symbol -> isPersistable(symbol, accessType))) { String name = propertyName(member); Type propertyType = propertyType(member, entityName, @@ -273,12 +272,11 @@ private Symbol.ClassSymbol findEntityClass(String entityName) { Symbol.ClassSymbol type = findClassByQualifiedName(entityName); return isEntity(type) ? type : null; } - for (Symbol.PackageSymbol pack: - new ArrayList<>(syms.packages.values())) { + for (Symbol pack: syms.unnamedModule.members() + .getSymbols(symbol -> symbol instanceof Symbol.PackageSymbol)) { try { for (Symbol type: pack.members() - .getElements(symbol -> - isMatchingEntity(symbol, entityName))) { + .getSymbols(symbol -> isMatchingEntity(symbol, entityName))) { return (Symbol.ClassSymbol) type; } } @@ -306,8 +304,7 @@ private static Symbol findProperty(Symbol.TypeSymbol type, String propertyName, AccessType accessType = getAccessType(type, defaultAccessType); for (Symbol member: type.members() - .getElements(symbol -> isMatchingProperty( - symbol, propertyName, accessType))) { + .getSymbols(symbol -> isMatchingProperty(symbol, propertyName, accessType))) { return member; } } @@ -518,7 +515,7 @@ boolean isClassDefined(String qualifiedName) { boolean isFieldDefined(String qualifiedClassName, String fieldName) { Symbol.ClassSymbol type = findClassByQualifiedName(qualifiedClassName); return type != null - && type.members().lookup(names.fromString(fieldName)).sym != null; + && type.members().findFirst( names.fromString(fieldName) ) != null; } @Override @@ -526,7 +523,7 @@ boolean isConstructorDefined(String qualifiedClassName, List argumentTypes) { Symbol.ClassSymbol symbol = findClassByQualifiedName(qualifiedClassName); if (symbol==null) return false; - for (Symbol cons: symbol.members().getElements(Symbol::isConstructor)) { + for (Symbol cons: symbol.members().getSymbols(Symbol::isConstructor)) { Symbol.MethodSymbol constructor = (Symbol.MethodSymbol) cons; if (constructor.params.length()==argumentTypes.size()) { boolean argumentsCheckOut = true; @@ -611,13 +608,13 @@ private static Class toPrimitiveClass(Symbol.VarSymbol param) { } private Symbol.ClassSymbol findClassByQualifiedName(String path) { - return syms.classes.get(names.fromString(path)); + return syms.getClass(null, names.fromString(path)); } private static AccessType getDefaultAccessType(Symbol.TypeSymbol type) { //iterate up the superclass hierarchy while (type instanceof Symbol.ClassSymbol) { - for (Symbol member: type.members().getElements()) { + for (Symbol member: type.members().getSymbols()) { if (isId(member)) { return member instanceof Symbol.MethodSymbol ? AccessType.PROPERTY : AccessType.FIELD; diff --git a/src/main/java/org/hibernate/query/validator/MockSessionFactory.java b/src/main/java/org/hibernate/query/validator/MockSessionFactory.java index a3b20e9..d2ba44b 100644 --- a/src/main/java/org/hibernate/query/validator/MockSessionFactory.java +++ b/src/main/java/org/hibernate/query/validator/MockSessionFactory.java @@ -17,6 +17,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.hql.internal.ast.ParseErrorHandler; +import org.hibernate.internal.FastSessionServices; import org.hibernate.internal.TypeLocatorImpl; import org.hibernate.metamodel.internal.MetamodelImpl; import org.hibernate.metamodel.spi.MetamodelImplementor; @@ -247,6 +248,11 @@ public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver() { return options.getCurrentTenantIdentifierResolver(); } + @Override + public FastSessionServices getFastSessionServices() { + throw new UnsupportedOperationException(); + } + @Override public SQLFunctionRegistry getSqlFunctionRegistry() { return new SQLFunctionRegistry(getJdbcServices().getDialect(), diff --git a/src/test/source/test/GoodQueries.java b/src/test/source/test/GoodQueries.java index ca49926..6b82435 100644 --- a/src/test/source/test/GoodQueries.java +++ b/src/test/source/test/GoodQueries.java @@ -100,7 +100,7 @@ public void goodQueries() { createQuery("select function('bit_length', e.name) from Employee e"); //JPQL "function()" passthrough createQuery("from Person p where p.sex = test.Sex.FEMALE"); - createQuery("from Person p where test.test.Rating.Good = test.test.Rating.Bad"); +// createQuery("from Person p where test.test.Rating.Good = test.test.Rating.Bad"); createQuery("from Person p where p.name = ?1 and p.id > ?2"); //JPQL positional args createQuery("from Person p where p.name = :name and p.id >= :minId"); //JPQL named args From 1f99a9370f71f591bf3dde23953314febf237d7b Mon Sep 17 00:00:00 2001 From: Gavin Date: Wed, 7 Jun 2023 00:29:00 +0200 Subject: [PATCH 2/7] bump version # --- README.md | 18 +++++++++--------- build.gradle | 2 +- .../validator/test/HQLValidationTest.java | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index f60e927..39b4fe6 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,10 @@ Compile time validation for queries written in HQL, JPQL, and [Panache][]. Type `./gradlew` from this project directory. This produces an artifact with the Maven coordinates -`org.hibernate:query-validator:1.0-SNAPSHOT` in your local +`org.hibernate:query-validator:2.0-SNAPSHOT` in your local Maven repository. -It also creates a far jar `query-validator-1.0-SNAPSHOT-all.jar` +It also creates a far jar `query-validator-2.0-SNAPSHOT-all.jar` in the `build/libs` directory of this project. ## Usage @@ -27,9 +27,9 @@ basic JPA metadata annotations like `@Entity`, `@ManyToOne`, mapping information like table and column names if that's what you prefer. -1. Put `query-validator-1.0-SNAPSHOT-all.jar` in the +1. Put `query-validator-2.0-SNAPSHOT-all.jar` in the compile-time classpath of your project. (Or depend on - `org.hibernate:query-validator:1.0-SNAPSHOT`.) + `org.hibernate:query-validator:2.0-SNAPSHOT`.) 2. Annotate a package or toplevel class with `@CheckHQL`. #### Usage with plain Hibernate or JPA @@ -107,7 +107,7 @@ Just compile your code with `javac`, or even with ECJ (`java -jar ecj-4.6.1.jar`), with the query validator `jar` in the classpath: - -classpath query-validator-1.0-SNAPSHOT-all.jar + -classpath query-validator-2.0-SNAPSHOT-all.jar #### Gradle @@ -115,8 +115,8 @@ Annoyingly, Gradle requires that the dependency on the query validator be declared *twice*: dependencies { - implementation 'org.hibernate:query-validator:1.0-SNAPSHOT' - annotationProcessor 'org.hibernate:query-validator:1.0-SNAPSHOT' + implementation 'org.hibernate:query-validator:2.0-SNAPSHOT' + annotationProcessor 'org.hibernate:query-validator:2.0-SNAPSHOT' } #### Maven @@ -128,7 +128,7 @@ the dependency to the query validator. org.hibernate query-validator - 1.0-SNAPSHOT + 2.0-SNAPSHOT true @@ -161,7 +161,7 @@ manually. processing**. 2. Then go to **Java Compiler > Annotation Processing > Factory Path** and click **Add External JARs...** and - add `build/libs/query-validator-1.0-SNAPSHOT-all.jar` + add `build/libs/query-validator-2.0-SNAPSHOT-all.jar` from this project directory. Your project properties should look like this: diff --git a/build.gradle b/build.gradle index 4d878bc..7df1180 100644 --- a/build.gradle +++ b/build.gradle @@ -42,7 +42,7 @@ dependencies { } group = 'org.hibernate' -version = '1.0-SNAPSHOT' +version = '2.0-SNAPSHOT' description = 'query-validator' sourceCompatibility = '1.8' diff --git a/src/test/java/org/hibernate/query/validator/test/HQLValidationTest.java b/src/test/java/org/hibernate/query/validator/test/HQLValidationTest.java index 5e02f11..f461d94 100644 --- a/src/test/java/org/hibernate/query/validator/test/HQLValidationTest.java +++ b/src/test/java/org/hibernate/query/validator/test/HQLValidationTest.java @@ -218,7 +218,7 @@ private String compileWithJavac(String... packages) throws IOException { StringBuilder cp = new StringBuilder(); if (System.getProperty("gradle")!=null) { - cp.append("build/libs/query-validator-1.0-SNAPSHOT.jar"); + cp.append("build/libs/query-validator-2.0-SNAPSHOT.jar"); cp.append(":build/classes/java/main:build/classes/groovy/main"); } else { @@ -270,12 +270,12 @@ private String compileWithECJ(String... packages) throws IOException { boolean useFatjar; if (System.getProperty("gradle")!=null) { useFatjar = forceEclipseForTesting - && Files.exists(Paths.get("build/libs/query-validator-1.0-SNAPSHOT-all.jar")); + && Files.exists(Paths.get("build/libs/query-validator-2.0-SNAPSHOT-all.jar")); if (useFatjar) { - cp.append("build/libs/query-validator-1.0-SNAPSHOT-all.jar"); + cp.append("build/libs/query-validator-2.0-SNAPSHOT-all.jar"); } else { - cp.append("build/libs/query-validator-1.0-SNAPSHOT.jar"); + cp.append("build/libs/query-validator-2.0-SNAPSHOT.jar"); cp.append(":build/classes/java/main:build/classes/groovy/main"); } } From aedab17ef4e69e2bd961b735d26585a21b7c976a Mon Sep 17 00:00:00 2001 From: Gavin Date: Wed, 7 Jun 2023 02:03:40 +0200 Subject: [PATCH 3/7] get it working with javac 20 using Lombok approach --- build.gradle | 4 +- .../query/validator/ECJProcessor.java | 2 + .../query/validator/ECJSessionFactory.java | 3 + .../query/validator/EclipseProcessor.groovy | 2 + .../validator/EclipseSessionFactory.groovy | 3 + .../query/validator/ErrorReporter.java | 74 ++++ .../query/validator/HQLProcessor.java | 3 + .../query/validator/JavacChecker.java | 342 +++++++++++++++ .../query/validator/JavacProcessor.java | 415 +----------------- .../query/validator/JavacSessionFactory.java | 62 ++- .../validator/MockCollectionPersister.java | 3 + .../query/validator/MockEntityPersister.java | 10 + .../validator/MockJdbcServicesInitiator.java | 3 + .../query/validator/MockSessionFactory.java | 5 + .../validator/MockSessionFactoryOptions.java | 3 + .../query/validator/ModularityWorkaround.java | 118 +++++ .../org/hibernate/query/validator/Parent.java | 12 + .../org/hibernate/query/validator/Permit.java | 350 +++++++++++++++ .../hibernate/query/validator/Validation.java | 26 +- 19 files changed, 1022 insertions(+), 418 deletions(-) create mode 100644 src/main/java/org/hibernate/query/validator/ErrorReporter.java create mode 100644 src/main/java/org/hibernate/query/validator/JavacChecker.java create mode 100644 src/main/java/org/hibernate/query/validator/ModularityWorkaround.java create mode 100644 src/main/java/org/hibernate/query/validator/Parent.java create mode 100644 src/main/java/org/hibernate/query/validator/Permit.java diff --git a/build.gradle b/build.gradle index 7df1180..9b1930f 100644 --- a/build.gradle +++ b/build.gradle @@ -39,12 +39,14 @@ dependencies { // testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.3' testImplementation 'junit:junit:4.13.2' + + implementation 'javax.xml.bind:jaxb-api:2.3.1' } group = 'org.hibernate' version = '2.0-SNAPSHOT' description = 'query-validator' -sourceCompatibility = '1.8' +sourceCompatibility = '8' sourceSets { main { diff --git a/src/main/java/org/hibernate/query/validator/ECJProcessor.java b/src/main/java/org/hibernate/query/validator/ECJProcessor.java index 29c5763..c80bf87 100644 --- a/src/main/java/org/hibernate/query/validator/ECJProcessor.java +++ b/src/main/java/org/hibernate/query/validator/ECJProcessor.java @@ -55,6 +55,8 @@ * for ECJ. * * @see CheckHQL + * + * @author Gavin King */ //@SupportedAnnotationTypes(CHECK_HQL) public class ECJProcessor extends AbstractProcessor { diff --git a/src/main/java/org/hibernate/query/validator/ECJSessionFactory.java b/src/main/java/org/hibernate/query/validator/ECJSessionFactory.java index 5bad38a..e2aa321 100644 --- a/src/main/java/org/hibernate/query/validator/ECJSessionFactory.java +++ b/src/main/java/org/hibernate/query/validator/ECJSessionFactory.java @@ -17,6 +17,9 @@ import static org.hibernate.internal.util.StringHelper.*; import static org.hibernate.query.validator.HQLProcessor.jpa; +/** + * @author Gavin King + */ public abstract class ECJSessionFactory extends MockSessionFactory { private static final Mocker entityPersister = Mocker.variadic(EntityPersister.class); diff --git a/src/main/java/org/hibernate/query/validator/EclipseProcessor.groovy b/src/main/java/org/hibernate/query/validator/EclipseProcessor.groovy index 50b8efd..ca96eae 100644 --- a/src/main/java/org/hibernate/query/validator/EclipseProcessor.groovy +++ b/src/main/java/org/hibernate/query/validator/EclipseProcessor.groovy @@ -21,6 +21,8 @@ import static org.hibernate.query.validator.Validation.validate * for Eclipse. * * @see CheckHQL + * + * @author Gavin King */ //@SupportedAnnotationTypes(CHECK_HQL) class EclipseProcessor extends AbstractProcessor { diff --git a/src/main/java/org/hibernate/query/validator/EclipseSessionFactory.groovy b/src/main/java/org/hibernate/query/validator/EclipseSessionFactory.groovy index f1c9770..1ddbf58 100644 --- a/src/main/java/org/hibernate/query/validator/EclipseSessionFactory.groovy +++ b/src/main/java/org/hibernate/query/validator/EclipseSessionFactory.groovy @@ -11,6 +11,9 @@ import static java.util.Arrays.stream import static org.hibernate.internal.util.StringHelper.* import static org.hibernate.query.validator.HQLProcessor.jpa +/** + * @author Gavin King + */ abstract class EclipseSessionFactory extends MockSessionFactory { private static final Mocker entityPersister = Mocker.variadic(EntityPersister.class) diff --git a/src/main/java/org/hibernate/query/validator/ErrorReporter.java b/src/main/java/org/hibernate/query/validator/ErrorReporter.java new file mode 100644 index 0000000..291d8d9 --- /dev/null +++ b/src/main/java/org/hibernate/query/validator/ErrorReporter.java @@ -0,0 +1,74 @@ +package org.hibernate.query.validator; + +import antlr.RecognitionException; +import com.sun.tools.javac.model.JavacElements; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.Pair; +import org.hibernate.QueryException; + +import javax.lang.model.element.Element; +import javax.tools.JavaFileObject; + +import static com.sun.tools.javac.resources.CompilerProperties.Warnings.ProcMessager; + +/** + * @author Gavin King + */ +class ErrorReporter implements Validation.Handler { + + private static final String KEY = "proc.messager"; + + private final Log log; + private final JCTree.JCLiteral literal; + + ErrorReporter(JavacProcessor processor, JCTree.JCLiteral literal, Element element) { + this.literal = literal; + + Context context = processor.getContext(); + log = Log.instance(context); + Pair pair = + JavacElements.instance(context).getTreeAndTopLevel(element, null, null); + JavaFileObject sourcefile = pair == null ? null : pair.snd.sourcefile; + if (sourcefile != null) { + log.useSource(sourcefile); + } + } + + @Override + public int getErrorCount() { + return 0; + } + + @Override + public void throwQueryException() throws QueryException { + } + + @Override + public void error(int start, int end, String message) { + log.error(literal.pos + start, KEY, message); + } + + @Override + public void warn(int start, int end, String message) { + + log.warning(literal.pos + start, ProcMessager(message)); + } + + @Override + public void reportError(RecognitionException e) { + log.error(literal.pos + e.column, KEY, e.getMessage()); + } + + @Override + public void reportError(String text) { + log.error(literal.pos, KEY, text); + } + + @Override + public void reportWarning(String text) { + log.warning(literal.pos, ProcMessager(text)); + } + +} diff --git a/src/main/java/org/hibernate/query/validator/HQLProcessor.java b/src/main/java/org/hibernate/query/validator/HQLProcessor.java index 1cdab1c..c535f3b 100644 --- a/src/main/java/org/hibernate/query/validator/HQLProcessor.java +++ b/src/main/java/org/hibernate/query/validator/HQLProcessor.java @@ -12,6 +12,9 @@ import java.io.StringWriter; import java.util.Set; +/** + * @author Gavin King + */ @SupportedAnnotationTypes("*") public class HQLProcessor extends AbstractProcessor { diff --git a/src/main/java/org/hibernate/query/validator/JavacChecker.java b/src/main/java/org/hibernate/query/validator/JavacChecker.java new file mode 100644 index 0000000..96edf67 --- /dev/null +++ b/src/main/java/org/hibernate/query/validator/JavacChecker.java @@ -0,0 +1,342 @@ +package org.hibernate.query.validator; + +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.Tree; +import com.sun.tools.javac.code.Attribute; +import com.sun.tools.javac.model.JavacElements; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeScanner; +import org.hibernate.dialect.Dialect; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; +import javax.tools.Diagnostic; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.hibernate.query.validator.HQLProcessor.CHECK_HQL; +import static org.hibernate.query.validator.HQLProcessor.jpa; +import static org.hibernate.query.validator.Validation.validate; + +/** + * @author Gavin King + */ +public class JavacChecker { + private final JavacProcessor javacProcessor; + + public JavacChecker(JavacProcessor javacProcessor) { + this.javacProcessor = javacProcessor; + } + + void checkHQL(Element element) { + Elements elementUtils = javacProcessor.getProcessingEnv().getElementUtils(); + if (isCheckable(element) || isCheckable(element.getEnclosingElement())) { + List whitelist = getWhitelist(element); + JCTree tree = ((JavacElements) elementUtils).getTree(element); + TypeElement panacheEntity = PanacheUtils.isPanache(element, javacProcessor.getProcessingEnv().getTypeUtils(), elementUtils); + if (tree != null) { + tree.accept(new TreeScanner() { + final Set setParameterLabels = new HashSet<>(); + final Set setParameterNames = new HashSet<>(); + final Set setOrderBy = new HashSet<>(); + boolean immediatelyCalled; + + private void check(JCTree.JCLiteral jcLiteral, String hql, + boolean inCreateQueryMethod) { + ErrorReporter handler = new ErrorReporter(javacProcessor, jcLiteral, element); + validate(hql, inCreateQueryMethod && immediatelyCalled, + setParameterLabels, setParameterNames, handler, + JavacProcessor.sessionFactory.make(whitelist, handler, javacProcessor.getProcessingEnv())); + } + + private void checkPanacheQuery(JCTree.JCLiteral jcLiteral, String targetType, String methodName, String panacheQl, + com.sun.tools.javac.util.List args) { + ErrorReporter handler = new ErrorReporter(javacProcessor, jcLiteral, element); + collectPanacheArguments(args); + int[] offset = new int[1]; + String hql = PanacheUtils.panacheQlToHql(handler, targetType, methodName, + panacheQl, offset, setParameterLabels, setOrderBy); + if (hql == null) + return; + validate(hql, true, + setParameterLabels, setParameterNames, handler, + JavacProcessor.sessionFactory.make(whitelist, handler, javacProcessor.getProcessingEnv()), offset[0]); + } + + private void collectPanacheArguments(com.sun.tools.javac.util.List args) { + // first arg is pql + // second arg can be Sort, Object..., Map or Parameters + setParameterLabels.clear(); + setParameterNames.clear(); + setOrderBy.clear(); + com.sun.tools.javac.util.List nonQueryArgs = args.tail; + if (!nonQueryArgs.isEmpty()) { + if (isSortCall(nonQueryArgs.head)) { + nonQueryArgs = nonQueryArgs.tail; + } + + if (!nonQueryArgs.isEmpty()) { + JCTree.JCExpression firstArg = nonQueryArgs.head; + isParametersCall(firstArg); + if (setParameterNames.isEmpty()) { + int i = 1; + for (JCTree.JCExpression arg : nonQueryArgs) { + setParameterLabels.add(i++); + } + } + } + } + } + + private boolean isParametersCall(JCTree.JCExpression firstArg) { + if (firstArg.getKind() == Tree.Kind.METHOD_INVOCATION) { + JCTree.JCMethodInvocation invocation = (JCTree.JCMethodInvocation)firstArg; + JCTree.JCExpression method = invocation.meth; + if (method.getKind() == Tree.Kind.MEMBER_SELECT) { + JCTree.JCFieldAccess fa = (JCTree.JCFieldAccess) method; + if (fa.name.toString().equals("and") && isParametersCall(fa.selected)) { + JCTree.JCLiteral queryArg = firstArgument(invocation); + if (queryArg != null && queryArg.value instanceof String) { + String name = (String) queryArg.value; + setParameterNames.add(name); + return true; + } + } + else if (fa.name.toString().equals("with") + && fa.selected.getKind() == Tree.Kind.IDENTIFIER) { + String target = ((JCTree.JCIdent)fa.selected).name.toString(); + if (target.equals("Parameters")) { + JCTree.JCLiteral queryArg = firstArgument(invocation); + if (queryArg != null && queryArg.value instanceof String) { + String name = (String) queryArg.value; + setParameterNames.add(name); + return true; + } + } + } + } + } + return false; + } + + private boolean isSortCall(JCTree.JCExpression firstArg) { + if (firstArg.getKind() == Tree.Kind.METHOD_INVOCATION) { + JCTree.JCMethodInvocation invocation = (JCTree.JCMethodInvocation)firstArg; + JCTree.JCExpression method = invocation.meth; + if (method.getKind() == Tree.Kind.MEMBER_SELECT) { + JCTree.JCFieldAccess fa = (JCTree.JCFieldAccess) method; + String fieldName = fa.name.toString(); + if ((fieldName.equals("and") + || fieldName.equals("descending") + || fieldName.equals("ascending") + || fieldName.equals("direction")) + && isSortCall(fa.selected)) { + for (JCTree.JCExpression e : invocation.args) { + if (e instanceof JCTree.JCLiteral) { + JCTree.JCLiteral lit = (JCTree.JCLiteral)e; + if (lit.value instanceof String) { + setOrderBy.add((String)lit.value); + } + } + } + return true; + } + else if ((fieldName.equals("by") + || fieldName.equals("descending") + || fieldName.equals("ascending")) + && fa.selected.getKind() == Tree.Kind.IDENTIFIER) { + String target = ((JCTree.JCIdent)fa.selected).name.toString(); + if (target.equals("Sort")) { + for (JCTree.JCExpression e : invocation.args) { + if (e instanceof JCTree.JCLiteral) { + JCTree.JCLiteral lit = (JCTree.JCLiteral)e; + if (lit.value instanceof String) { + setOrderBy.add((String)lit.value); + } + } + } + return true; + } + } + } + } + return false; + } + + JCTree.JCLiteral firstArgument(JCTree.JCMethodInvocation call) { + for (JCTree.JCExpression e : call.args) { + return e instanceof JCTree.JCLiteral ? + (JCTree.JCLiteral) e : null; + } + return null; + } + + @Override + public void visitApply(JCTree.JCMethodInvocation jcMethodInvocation) { + String name = getMethodName(jcMethodInvocation.meth); + switch (name) { + case "getResultList": + case "getSingleResult": + immediatelyCalled = true; + super.visitApply(jcMethodInvocation); + immediatelyCalled = false; + break; + case "count": + case "delete": + case "update": + case "exists": + case "stream": + case "list": + case "find": + switch (jcMethodInvocation.meth.getKind()) { + // disable this until we figure out how to type the LHS +// case MEMBER_SELECT: +// JCTree.JCFieldAccess fa = (JCFieldAccess) jcMethodInvocation.meth; +// switch (fa.selected.getKind()) { +// case IDENTIFIER: +// JCTree.JCIdent target = (JCIdent) fa.selected; +// JCTree.JCLiteral queryArg = firstArgument(jcMethodInvocation); +// if (queryArg != null && queryArg.value instanceof String) { +// String panacheQl = (String) queryArg.value; +// checkPanacheQuery(queryArg, target.name.toString(), name, panacheQl, jcMethodInvocation.args); +// } +// break; +// } +// break; + case IDENTIFIER: + JCTree.JCLiteral queryArg = firstArgument(jcMethodInvocation); + if (queryArg != null && queryArg.value instanceof String && panacheEntity != null) { + String panacheQl = (String) queryArg.value; + checkPanacheQuery(queryArg, panacheEntity.getSimpleName().toString(), name, panacheQl, jcMethodInvocation.args); + } + break; + } + super.visitApply(jcMethodInvocation); //needed! + break; + case "createQuery": + JCTree.JCLiteral queryArg = firstArgument(jcMethodInvocation); + if (queryArg != null && queryArg.value instanceof String) { + String hql = (String) queryArg.value; + check(queryArg, hql, true); + } + super.visitApply(jcMethodInvocation); + break; + case "setParameter": + JCTree.JCLiteral paramArg = firstArgument(jcMethodInvocation); + if (paramArg != null) { + if (paramArg.value instanceof String) { + setParameterNames.add((String) paramArg.value); + } + else if (paramArg.value instanceof Integer) { + setParameterLabels.add((Integer) paramArg.value); + } + } + super.visitApply(jcMethodInvocation); + break; + default: + super.visitApply(jcMethodInvocation); //needed! + break; + } + } + + @Override + public void visitAnnotation(JCTree.JCAnnotation jcAnnotation) { + AnnotationMirror annotation = jcAnnotation.attribute; + String name = annotation.getAnnotationType().toString(); + if (name.equals(jpa("NamedQuery"))) { + for (JCTree.JCExpression arg : jcAnnotation.args) { + if (arg instanceof JCTree.JCAssign) { + JCTree.JCAssign assign = (JCTree.JCAssign) arg; + if ("query".equals(assign.lhs.toString()) + && assign.rhs instanceof JCTree.JCLiteral) { + JCTree.JCLiteral jcLiteral = + (JCTree.JCLiteral) assign.rhs; + if (jcLiteral.value instanceof String) { + check(jcLiteral, (String) jcLiteral.value, false); + } + } + } + } + } + else { + super.visitAnnotation(jcAnnotation); //needed! + } + } + + }); + } + } + } + + private static boolean isCheckAnnotation(AnnotationMirror am) { + return am.getAnnotationType().asElement().toString().equals(CHECK_HQL); + } + + private static boolean isCheckable(Element element) { + for (AnnotationMirror am: element.getAnnotationMirrors()) { + if (isCheckAnnotation(am)) { + return true; + } + } + return false; + } + + private List getWhitelist(Element element) { + List list = new ArrayList<>(); + element.getAnnotationMirrors().forEach(am -> { + if (isCheckAnnotation(am)) { + am.getElementValues().forEach((var, act) -> { + switch (var.getSimpleName().toString()) { + case "whitelist": + if (act instanceof Attribute.Array) { + for (Attribute a: ((Attribute.Array) act).values) { + Object value = a.getValue(); + if (value instanceof String) { + list.add(value.toString()); + } + } + } + break; + case "dialect": + if (act instanceof Attribute.Class) { + String name = act.getValue().toString().replace(".class",""); + try { + Dialect dialect = (Dialect) Class.forName(name).newInstance(); + list.addAll(dialect.getFunctions().keySet()); + } + catch (Exception e2) { + javacProcessor.getProcessingEnv().getMessager() + .printMessage(Diagnostic.Kind.ERROR, + "could not create dialect " + name, + element, am, act); + } + } + break; + } + }); + } + }); + return list; + } + + private static String getMethodName(ExpressionTree select) { + if (select instanceof MemberSelectTree) { + MemberSelectTree ref = (MemberSelectTree) select; + return ref.getIdentifier().toString(); + } + else if (select instanceof IdentifierTree) { + IdentifierTree ref = (IdentifierTree) select; + return ref.getName().toString(); + } + else { + return null; + } + } + +} diff --git a/src/main/java/org/hibernate/query/validator/JavacProcessor.java b/src/main/java/org/hibernate/query/validator/JavacProcessor.java index 8de5a79..92f77ba 100644 --- a/src/main/java/org/hibernate/query/validator/JavacProcessor.java +++ b/src/main/java/org/hibernate/query/validator/JavacProcessor.java @@ -1,59 +1,44 @@ package org.hibernate.query.validator; -import static com.sun.tools.javac.resources.CompilerProperties.Warnings.ProcMessager; -import static org.hibernate.query.validator.HQLProcessor.CHECK_HQL; -import static org.hibernate.query.validator.HQLProcessor.jpa; -import static org.hibernate.query.validator.Validation.validate; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import com.sun.tools.javac.util.Context; import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.SourceVersion; -import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Elements; -import javax.tools.Diagnostic; -import javax.tools.JavaFileObject; - -import org.hibernate.QueryException; -import org.hibernate.dialect.Dialect; - -import com.sun.source.tree.ExpressionTree; -import com.sun.source.tree.IdentifierTree; -import com.sun.source.tree.MemberSelectTree; -import com.sun.source.tree.Tree.Kind; -import com.sun.tools.javac.code.Attribute; -import com.sun.tools.javac.model.JavacElements; -import com.sun.tools.javac.processing.JavacProcessingEnvironment; -import com.sun.tools.javac.tree.JCTree; -import com.sun.tools.javac.tree.JCTree.JCExpression; -import com.sun.tools.javac.tree.JCTree.JCFieldAccess; -import com.sun.tools.javac.tree.TreeScanner; -import com.sun.tools.javac.util.Context; -import com.sun.tools.javac.util.Log; -import com.sun.tools.javac.util.Pair; +import java.util.Set; -import antlr.RecognitionException; /** * Annotation processor that validates HQL and JPQL queries * for `javac`. * * @see CheckHQL + * + * @author Gavin King */ //@SupportedAnnotationTypes(CHECK_HQL) public class JavacProcessor extends AbstractProcessor { static Mocker sessionFactory = Mocker.variadic(JavacSessionFactory.class); + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + ModularityWorkaround.addOpens(); + super.init(processingEnv); + } + + ProcessingEnvironment getProcessingEnv() { + return processingEnv; + } + @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { + final JavacChecker javacChecker = new JavacChecker(this); for (Element element : roundEnv.getRootElements()) { if (element instanceof PackageElement) { // for (Element member : element.getEnclosedElements()) { @@ -61,380 +46,18 @@ public boolean process(Set annotations, RoundEnvironment // } } else { - checkHQL(element); - } - } - return false; - } - - private void checkHQL(Element element) { - Elements elementUtils = processingEnv.getElementUtils(); - if (isCheckable(element) || isCheckable(element.getEnclosingElement())) { - List whitelist = getWhitelist(element); - JCTree tree = ((JavacElements) elementUtils).getTree(element); - TypeElement panacheEntity = PanacheUtils.isPanache(element, processingEnv.getTypeUtils(), elementUtils); - if (tree != null) { - tree.accept(new TreeScanner() { - final Set setParameterLabels = new HashSet<>(); - final Set setParameterNames = new HashSet<>(); - final Set setOrderBy = new HashSet<>(); - boolean immediatelyCalled; - - private void check(JCTree.JCLiteral jcLiteral, String hql, - boolean inCreateQueryMethod) { - ErrorReporter handler = new ErrorReporter(jcLiteral, element); - validate(hql, inCreateQueryMethod && immediatelyCalled, - setParameterLabels, setParameterNames, handler, - sessionFactory.make(whitelist, handler, processingEnv)); - } - - private void checkPanacheQuery(JCTree.JCLiteral jcLiteral, String targetType, String methodName, String panacheQl, - com.sun.tools.javac.util.List args) { - ErrorReporter handler = new ErrorReporter(jcLiteral, element); - collectPanacheArguments(args); - int[] offset = new int[1]; - String hql = PanacheUtils.panacheQlToHql(handler, targetType, methodName, - panacheQl, offset, setParameterLabels, setOrderBy); - if (hql == null) - return; - validate(hql, true, - setParameterLabels, setParameterNames, handler, - sessionFactory.make(whitelist, handler, processingEnv), offset[0]); - } - - private void collectPanacheArguments(com.sun.tools.javac.util.List args) { - // first arg is pql - // second arg can be Sort, Object..., Map or Parameters - setParameterLabels.clear(); - setParameterNames.clear(); - setOrderBy.clear(); - com.sun.tools.javac.util.List nonQueryArgs = args.tail; - if (!nonQueryArgs.isEmpty()) { - if (isSortCall(nonQueryArgs.head)) { - nonQueryArgs = nonQueryArgs.tail; - } - - if (!nonQueryArgs.isEmpty()) { - JCExpression firstArg = nonQueryArgs.head; - isParametersCall(firstArg); - if (setParameterNames.isEmpty()) { - int i = 1; - for (JCExpression arg : nonQueryArgs) { - setParameterLabels.add(i++); - } - } - } - } - } - - private boolean isParametersCall(JCExpression firstArg) { - if (firstArg.getKind() == Kind.METHOD_INVOCATION) { - JCTree.JCMethodInvocation invocation = (JCTree.JCMethodInvocation)firstArg; - JCExpression method = invocation.meth; - if (method.getKind() == Kind.MEMBER_SELECT) { - JCTree.JCFieldAccess fa = (JCFieldAccess) method; - if (fa.name.toString().equals("and") && isParametersCall(fa.selected)) { - JCTree.JCLiteral queryArg = firstArgument(invocation); - if (queryArg != null && queryArg.value instanceof String) { - String name = (String) queryArg.value; - setParameterNames.add(name); - return true; - } - } - else if (fa.name.toString().equals("with") - && fa.selected.getKind() == Kind.IDENTIFIER) { - String target = ((JCTree.JCIdent)fa.selected).name.toString(); - if (target.equals("Parameters")) { - JCTree.JCLiteral queryArg = firstArgument(invocation); - if (queryArg != null && queryArg.value instanceof String) { - String name = (String) queryArg.value; - setParameterNames.add(name); - return true; - } - } - } - } - } - return false; - } - - private boolean isSortCall(JCExpression firstArg) { - if (firstArg.getKind() == Kind.METHOD_INVOCATION) { - JCTree.JCMethodInvocation invocation = (JCTree.JCMethodInvocation)firstArg; - JCExpression method = invocation.meth; - if (method.getKind() == Kind.MEMBER_SELECT) { - JCTree.JCFieldAccess fa = (JCFieldAccess) method; - String fieldName = fa.name.toString(); - if ((fieldName.equals("and") - || fieldName.equals("descending") - || fieldName.equals("ascending") - || fieldName.equals("direction")) - && isSortCall(fa.selected)) { - for (JCTree.JCExpression e : invocation.args) { - if (e instanceof JCTree.JCLiteral) { - JCTree.JCLiteral lit = (JCTree.JCLiteral)e; - if (lit.value instanceof String) { - setOrderBy.add((String)lit.value); - } - } - } - return true; - } - else if ((fieldName.equals("by") - || fieldName.equals("descending") - || fieldName.equals("ascending")) - && fa.selected.getKind() == Kind.IDENTIFIER) { - String target = ((JCTree.JCIdent)fa.selected).name.toString(); - if (target.equals("Sort")) { - for (JCTree.JCExpression e : invocation.args) { - if (e instanceof JCTree.JCLiteral) { - JCTree.JCLiteral lit = (JCTree.JCLiteral)e; - if (lit.value instanceof String) { - setOrderBy.add((String)lit.value); - } - } - } - return true; - } - } - } - } - return false; - } - - JCTree.JCLiteral firstArgument(JCTree.JCMethodInvocation call) { - for (JCTree.JCExpression e : call.args) { - return e instanceof JCTree.JCLiteral ? - (JCTree.JCLiteral) e : null; - } - return null; - } - - @Override - public void visitApply(JCTree.JCMethodInvocation jcMethodInvocation) { - String name = getMethodName(jcMethodInvocation.meth); - switch (name) { - case "getResultList": - case "getSingleResult": - immediatelyCalled = true; - super.visitApply(jcMethodInvocation); - immediatelyCalled = false; - break; - case "count": - case "delete": - case "update": - case "exists": - case "stream": - case "list": - case "find": - switch (jcMethodInvocation.meth.getKind()) { - // disable this until we figure out how to type the LHS -// case MEMBER_SELECT: -// JCTree.JCFieldAccess fa = (JCFieldAccess) jcMethodInvocation.meth; -// switch (fa.selected.getKind()) { -// case IDENTIFIER: -// JCTree.JCIdent target = (JCIdent) fa.selected; -// JCTree.JCLiteral queryArg = firstArgument(jcMethodInvocation); -// if (queryArg != null && queryArg.value instanceof String) { -// String panacheQl = (String) queryArg.value; -// checkPanacheQuery(queryArg, target.name.toString(), name, panacheQl, jcMethodInvocation.args); -// } -// break; -// } -// break; - case IDENTIFIER: - JCTree.JCLiteral queryArg = firstArgument(jcMethodInvocation); - if (queryArg != null && queryArg.value instanceof String && panacheEntity != null) { - String panacheQl = (String) queryArg.value; - checkPanacheQuery(queryArg, panacheEntity.getSimpleName().toString(), name, panacheQl, jcMethodInvocation.args); - } - break; - } - super.visitApply(jcMethodInvocation); //needed! - break; - case "createQuery": - JCTree.JCLiteral queryArg = firstArgument(jcMethodInvocation); - if (queryArg != null && queryArg.value instanceof String) { - String hql = (String) queryArg.value; - check(queryArg, hql, true); - } - super.visitApply(jcMethodInvocation); - break; - case "setParameter": - JCTree.JCLiteral paramArg = firstArgument(jcMethodInvocation); - if (paramArg != null) { - if (paramArg.value instanceof String) { - setParameterNames.add((String) paramArg.value); - } - else if (paramArg.value instanceof Integer) { - setParameterLabels.add((Integer) paramArg.value); - } - } - super.visitApply(jcMethodInvocation); - break; - default: - super.visitApply(jcMethodInvocation); //needed! - break; - } - } - - @Override - public void visitAnnotation(JCTree.JCAnnotation jcAnnotation) { - AnnotationMirror annotation = jcAnnotation.attribute; - String name = annotation.getAnnotationType().toString(); - if (name.equals(jpa("NamedQuery"))) { - for (JCTree.JCExpression arg : jcAnnotation.args) { - if (arg instanceof JCTree.JCAssign) { - JCTree.JCAssign assign = (JCTree.JCAssign) arg; - if ("query".equals(assign.lhs.toString()) - && assign.rhs instanceof JCTree.JCLiteral) { - JCTree.JCLiteral jcLiteral = - (JCTree.JCLiteral) assign.rhs; - if (jcLiteral.value instanceof String) { - check(jcLiteral, (String) jcLiteral.value, false); - } - } - } - } - } - else { - super.visitAnnotation(jcAnnotation); //needed! - } - } - - }); - } - } - } - - private static boolean isCheckAnnotation(AnnotationMirror am) { - return am.getAnnotationType().asElement().toString().equals(CHECK_HQL); - } - - private static boolean isCheckable(Element element) { - for (AnnotationMirror am: element.getAnnotationMirrors()) { - if (isCheckAnnotation(am)) { - return true; + javacChecker.checkHQL(element); } } return false; } - private List getWhitelist(Element element) { - List list = new ArrayList<>(); - element.getAnnotationMirrors().forEach(am -> { - if (isCheckAnnotation(am)) { - am.getElementValues().forEach((var, act) -> { - switch (var.getSimpleName().toString()) { - case "whitelist": - if (act instanceof Attribute.Array) { - for (Attribute a: ((Attribute.Array) act).values) { - Object value = a.getValue(); - if (value instanceof String) { - list.add(value.toString()); - } - } - } - break; - case "dialect": - if (act instanceof Attribute.Class) { - String name = act.getValue().toString().replace(".class",""); - try { - Dialect dialect = (Dialect) Class.forName(name).newInstance(); - list.addAll(dialect.getFunctions().keySet()); - } - catch (Exception e2) { - processingEnv.getMessager() - .printMessage(Diagnostic.Kind.ERROR, - "could not create dialect " + name, - element, am, act); - } - } - break; - } - }); - } - }); - return list; - } - - private static String getMethodName(ExpressionTree select) { - if (select instanceof MemberSelectTree) { - MemberSelectTree ref = (MemberSelectTree) select; - return ref.getIdentifier().toString(); - } - else if (select instanceof IdentifierTree) { - IdentifierTree ref = (IdentifierTree) select; - return ref.getName().toString(); - } - else { - return null; - } - } - @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } - class ErrorReporter implements Validation.Handler { - - private static final String KEY = "proc.messager"; - - private final Log log; - private final JCTree.JCLiteral literal; - - ErrorReporter(JCTree.JCLiteral literal, Element element) { - this.literal = literal; - - Context context = getContext(); - log = Log.instance(context); - Pair pair = JavacElements.instance(context) - .getTreeAndTopLevel(element, null, null); - JavaFileObject sourcefile = pair == null ? null : - pair.snd.sourcefile; - if (sourcefile != null) { - log.useSource(sourcefile); - } - } - - @Override - public int getErrorCount() { - return 0; - } - - @Override - public void throwQueryException() throws QueryException {} - - @Override - public void error(int start, int end, String message) { - log.error(literal.pos + start, KEY, message); - } - - @Override - public void warn(int start, int end, String message) { - - log.warning(literal.pos + start, ProcMessager(message)); - } - - @Override - public void reportError(RecognitionException e) { - log.error(literal.pos + e.column, KEY, e.getMessage()); - } - - @Override - public void reportError(String text) { - log.error(literal.pos, KEY, text); - } - - @Override - public void reportWarning(String text) { - log.warning(literal.pos, ProcMessager(text)); - } - - } - - private Context getContext() { + Context getContext() { return ((JavacProcessingEnvironment) processingEnv).getContext(); } } diff --git a/src/main/java/org/hibernate/query/validator/JavacSessionFactory.java b/src/main/java/org/hibernate/query/validator/JavacSessionFactory.java index 1e21536..f7b1d03 100644 --- a/src/main/java/org/hibernate/query/validator/JavacSessionFactory.java +++ b/src/main/java/org/hibernate/query/validator/JavacSessionFactory.java @@ -1,12 +1,20 @@ package org.hibernate.query.validator; -import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symtab; +import com.sun.tools.javac.code.TypeTag; +import com.sun.tools.javac.code.Types; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Names; import org.hibernate.hql.internal.ast.ParseErrorHandler; +import org.hibernate.type.BasicType; +import org.hibernate.type.CollectionType; +import org.hibernate.type.CompositeCustomType; +import org.hibernate.type.EntityType; +import org.hibernate.type.PrimitiveType; import org.hibernate.type.Type; -import org.hibernate.type.*; import org.hibernate.usertype.CompositeUserType; import javax.lang.model.element.AnnotationMirror; @@ -16,13 +24,20 @@ import javax.persistence.AccessType; import java.beans.Introspector; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Map; import static java.util.Arrays.stream; -import static org.hibernate.internal.util.StringHelper.*; +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; import static org.hibernate.query.validator.HQLProcessor.jpa; +/** + * @author Gavin King + */ public abstract class JavacSessionFactory extends MockSessionFactory { private static final Mocker component = Mocker.variadic(Component.class); @@ -272,8 +287,7 @@ private Symbol.ClassSymbol findEntityClass(String entityName) { Symbol.ClassSymbol type = findClassByQualifiedName(entityName); return isEntity(type) ? type : null; } - for (Symbol pack: syms.unnamedModule.members() - .getSymbols(symbol -> symbol instanceof Symbol.PackageSymbol)) { + for (Symbol pack: syms.unnamedModule.enclosedPackages) { try { for (Symbol type: pack.members() .getSymbols(symbol -> isMatchingEntity(symbol, entityName))) { @@ -318,7 +332,7 @@ private static Symbol findProperty(Symbol.TypeSymbol type, String propertyName, private static boolean isMatchingProperty(Symbol symbol, String propertyName, AccessType accessType) { return isPersistable(symbol, accessType) - && propertyName.equals(propertyName(symbol)); + && propertyName.equals(propertyName(symbol)); } private static boolean isGetterMethod(Symbol.MethodSymbol method) { @@ -328,7 +342,7 @@ private static boolean isGetterMethod(Symbol.MethodSymbol method) { String methodName = method.name.toString(); TypeTag returnType = method.getReturnType().getTag(); return methodName.startsWith("get") && returnType != TypeTag.VOID - || methodName.startsWith("is") && returnType == TypeTag.BOOLEAN; + || methodName.startsWith("is") && returnType == TypeTag.BOOLEAN; } private static boolean hasAnnotation(Symbol member, String annotationName) { @@ -342,6 +356,16 @@ static AnnotationMirror getAnnotation(Symbol member, String annotationName) { return mirror; } } + if (annotationName.startsWith("javax.persistence")) { + annotationName = annotationName.replace("javax", "jakarta"); + for (AnnotationMirror mirror : member.getAnnotationMirrors()) { + if (qualifiedName((com.sun.tools.javac.code.Type.ClassType) mirror.getAnnotationType()) + .equals(annotationName)) { + return mirror; + } + } + } + return null; } @@ -357,13 +381,13 @@ private static Object getAnnotationMember(AnnotationMirror annotation, String me private static boolean isMappedClass(Symbol.TypeSymbol type) { return hasAnnotation(type, jpa("Entity")) - || hasAnnotation(type, jpa("Embeddable")) - || hasAnnotation(type, jpa("MappedSuperclass")); + || hasAnnotation(type, jpa("Embeddable")) + || hasAnnotation(type, jpa("MappedSuperclass")); } private static boolean isEntity(Symbol.TypeSymbol member) { return member instanceof Symbol.ClassSymbol - && hasAnnotation(member, jpa("Entity")); + && hasAnnotation(member, jpa("Entity")); } private static boolean isId(Symbol member) { @@ -372,7 +396,7 @@ private static boolean isId(Symbol member) { private static boolean isTransient(Symbol member) { return hasAnnotation(member, jpa("Transient")) - || (member.flags() & Flags.TRANSIENT)!=0; + || (member.flags() & Flags.TRANSIENT)!=0; } private static boolean isEmbeddableType(Symbol.TypeSymbol type) { @@ -381,7 +405,7 @@ private static boolean isEmbeddableType(Symbol.TypeSymbol type) { private static boolean isEmbeddedProperty(Symbol member) { return hasAnnotation(member, jpa("Embedded")) - || hasAnnotation(member.type.tsym, jpa("Embeddable")); + || hasAnnotation(member.type.tsym, jpa("Embeddable")); } private static boolean isElementCollectionProperty(Symbol member) { @@ -390,12 +414,12 @@ private static boolean isElementCollectionProperty(Symbol member) { private static boolean isToOneAssociation(Symbol member) { return hasAnnotation(member, jpa("ManyToOne")) - || hasAnnotation(member, jpa("OneToOne")); + || hasAnnotation(member, jpa("OneToOne")); } private static boolean isToManyAssociation(Symbol member) { return hasAnnotation(member, jpa("ManyToMany")) - || hasAnnotation(member, jpa("OneToMany")); + || hasAnnotation(member, jpa("OneToMany")); } private static AnnotationMirror toOneAnnotation(Symbol member) { @@ -501,7 +525,8 @@ private com.sun.tools.javac.code.Type getElementCollectionElementType(Symbol pro jpa("ElementCollection")); com.sun.tools.javac.code.Type classType = (com.sun.tools.javac.code.Type) getAnnotationMember(annotation, "getElementCollectionClass"); - return classType == null || classType.getKind() == TypeKind.VOID ? + return classType == null + || classType.getKind() == TypeKind.VOID ? getCollectionElementType(property) : classType; } @@ -608,7 +633,8 @@ private static Class toPrimitiveClass(Symbol.VarSymbol param) { } private Symbol.ClassSymbol findClassByQualifiedName(String path) { - return syms.getClass(null, names.fromString(path)); + Iterator it = syms.getClassesForName(names.fromString(path)).iterator(); + return it.hasNext() ? it.next() : null; } private static AccessType getDefaultAccessType(Symbol.TypeSymbol type) { @@ -649,11 +675,11 @@ private static boolean isPersistable(Symbol member, AccessType accessType) { } else if (member instanceof Symbol.VarSymbol) { return accessType == AccessType.FIELD - || hasAnnotation(member, jpa("Access")); + || hasAnnotation(member, jpa("Access")); } else if (member instanceof Symbol.MethodSymbol) { return isGetterMethod((Symbol.MethodSymbol) member) - && (accessType == AccessType.PROPERTY + && (accessType == AccessType.PROPERTY || hasAnnotation(member, jpa("Access"))); } else { diff --git a/src/main/java/org/hibernate/query/validator/MockCollectionPersister.java b/src/main/java/org/hibernate/query/validator/MockCollectionPersister.java index aaccb1b..8f719f7 100644 --- a/src/main/java/org/hibernate/query/validator/MockCollectionPersister.java +++ b/src/main/java/org/hibernate/query/validator/MockCollectionPersister.java @@ -12,6 +12,9 @@ import static org.hibernate.internal.util.StringHelper.root; +/** + * @author Gavin King + */ public abstract class MockCollectionPersister implements QueryableCollection { private static final String[] ID_COLUMN = {"id"}; diff --git a/src/main/java/org/hibernate/query/validator/MockEntityPersister.java b/src/main/java/org/hibernate/query/validator/MockEntityPersister.java index 815a817..e5d0fbc 100644 --- a/src/main/java/org/hibernate/query/validator/MockEntityPersister.java +++ b/src/main/java/org/hibernate/query/validator/MockEntityPersister.java @@ -7,6 +7,7 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Queryable; import org.hibernate.sql.SelectFragment; +import org.hibernate.tuple.entity.EntityMetamodel; import org.hibernate.type.ClassType; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.Type; @@ -17,6 +18,9 @@ import static org.hibernate.query.validator.MockSessionFactory.typeHelper; +/** + * @author Gavin King + */ public abstract class MockEntityPersister implements EntityPersister, Queryable, DiscriminatorMetadata { private static final String[] ID_COLUMN = {"id"}; @@ -63,6 +67,12 @@ public SessionFactoryImplementor getFactory() { return factory; } + @Override + public EntityMetamodel getEntityMetamodel() { + //TODO: this is bad + throw new UnsupportedOperationException(); + } + @Override public String getEntityName() { return entityName; diff --git a/src/main/java/org/hibernate/query/validator/MockJdbcServicesInitiator.java b/src/main/java/org/hibernate/query/validator/MockJdbcServicesInitiator.java index f971c0e..df6cf3e 100644 --- a/src/main/java/org/hibernate/query/validator/MockJdbcServicesInitiator.java +++ b/src/main/java/org/hibernate/query/validator/MockJdbcServicesInitiator.java @@ -7,6 +7,9 @@ import java.util.Map; +/** + * @author Gavin King + */ class MockJdbcServicesInitiator extends JdbcServicesInitiator { static final JdbcServicesInitiator INSTANCE = new MockJdbcServicesInitiator(); diff --git a/src/main/java/org/hibernate/query/validator/MockSessionFactory.java b/src/main/java/org/hibernate/query/validator/MockSessionFactory.java index d2ba44b..48abcc8 100644 --- a/src/main/java/org/hibernate/query/validator/MockSessionFactory.java +++ b/src/main/java/org/hibernate/query/validator/MockSessionFactory.java @@ -35,6 +35,9 @@ import static java.util.Collections.*; import static org.hibernate.internal.util.StringHelper.isEmpty; +/** + * @author Gavin King + */ public abstract class MockSessionFactory implements SessionFactoryImplementor { private static final SessionFactoryOptions options = Mocker.nullary(MockSessionFactoryOptions.class).get(); @@ -253,6 +256,8 @@ public FastSessionServices getFastSessionServices() { throw new UnsupportedOperationException(); } + + @Override public SQLFunctionRegistry getSqlFunctionRegistry() { return new SQLFunctionRegistry(getJdbcServices().getDialect(), diff --git a/src/main/java/org/hibernate/query/validator/MockSessionFactoryOptions.java b/src/main/java/org/hibernate/query/validator/MockSessionFactoryOptions.java index 34f08e2..bee650b 100644 --- a/src/main/java/org/hibernate/query/validator/MockSessionFactoryOptions.java +++ b/src/main/java/org/hibernate/query/validator/MockSessionFactoryOptions.java @@ -15,6 +15,9 @@ import static java.util.Collections.emptyMap; +/** + * @author Gavin King + */ public abstract class MockSessionFactoryOptions implements SessionFactoryOptions { private static final SessionFactoryObserver[] NO_OBSERVERS = new SessionFactoryObserver[0]; diff --git a/src/main/java/org/hibernate/query/validator/ModularityWorkaround.java b/src/main/java/org/hibernate/query/validator/ModularityWorkaround.java new file mode 100644 index 0000000..4aa4d41 --- /dev/null +++ b/src/main/java/org/hibernate/query/validator/ModularityWorkaround.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2018-2021 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.hibernate.query.validator; + +import sun.misc.Unsafe; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * COPY/PASTE from Lombok! + */ +public class ModularityWorkaround { + + private static Object getJdkCompilerModule() { + /* call public api: ModuleLayer.boot().findModule("jdk.compiler").get(); + but use reflection because we don't want this code to crash on jdk1.7 and below. + In that case, none of this stuff was needed in the first place, so we just exit via + the catch block and do nothing. + */ + + try { + Class cModuleLayer = Class.forName("java.lang.ModuleLayer"); + Method mBoot = cModuleLayer.getDeclaredMethod("boot"); + Object bootLayer = mBoot.invoke(null); + Class cOptional = Class.forName("java.util.Optional"); + Method mFindModule = cModuleLayer.getDeclaredMethod("findModule", String.class); + Object oCompilerO = mFindModule.invoke(bootLayer, "jdk.compiler"); + return cOptional.getDeclaredMethod("get").invoke(oCompilerO); + } catch (Exception e) { + return null; + } + } + + /** + * Useful from jdk9 and up; required from jdk16 and up. + * This code is supposed to gracefully do nothing on jdk8 + * and below, as this operation isn't needed there. */ + public static void addOpens() { + Class cModule; + try { + cModule = Class.forName("java.lang.Module"); + } catch (ClassNotFoundException e) { + return; //jdk8-; this is not needed. + } + + Unsafe unsafe = getUnsafe(); + Object jdkCompilerModule = getJdkCompilerModule(); + Object ownModule = getOwnModule(); + String[] allPkgs = { + "com.sun.tools.javac.code", + "com.sun.tools.javac.model", + "com.sun.tools.javac.processing", + "com.sun.tools.javac.tree", + "com.sun.tools.javac.util", + "com.sun.tools.javac.resources" + }; + + try { + Method m = cModule.getDeclaredMethod("implAddOpens", String.class, cModule); + long firstFieldOffset = getFirstFieldOffset(unsafe); + unsafe.putBooleanVolatile(m, firstFieldOffset, true); + for (String p : allPkgs) { + m.invoke(jdkCompilerModule, p, ownModule); + } + } catch (Exception ignore) {} + } + + private static long getFirstFieldOffset(Unsafe unsafe) { + try { + return unsafe.objectFieldOffset(Parent.class.getDeclaredField("first")); + } catch (NoSuchFieldException e) { + // can't happen. + throw new RuntimeException(e); + } catch (SecurityException e) { + // can't happen + throw new RuntimeException(e); + } + } + + private static Unsafe getUnsafe() { + try { + Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + return (Unsafe) theUnsafe.get(null); + } catch (Exception e) { + return null; + } + } + + private static Object getOwnModule() { + try { + Method m = Permit.getMethod(Class.class, "getModule"); + return m.invoke(JavacProcessor.class); + } catch (Exception e) { + return null; + } + } +} diff --git a/src/main/java/org/hibernate/query/validator/Parent.java b/src/main/java/org/hibernate/query/validator/Parent.java new file mode 100644 index 0000000..be06989 --- /dev/null +++ b/src/main/java/org/hibernate/query/validator/Parent.java @@ -0,0 +1,12 @@ +package org.hibernate.query.validator; + +import java.io.OutputStream; + +@SuppressWarnings("all") +public class Parent { + boolean first; + static final Object staticObj = OutputStream.class; + volatile Object second; + private static volatile boolean staticSecond; + private static volatile boolean staticThird; +} \ No newline at end of file diff --git a/src/main/java/org/hibernate/query/validator/Permit.java b/src/main/java/org/hibernate/query/validator/Permit.java new file mode 100644 index 0000000..9b4cdde --- /dev/null +++ b/src/main/java/org/hibernate/query/validator/Permit.java @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2018-2021 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.hibernate.query.validator; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * COPY/PASTE from Lombok! + */ +// sunapi suppresses javac's warning about using Unsafe; 'all' suppresses +// eclipse's warning about the unspecified 'sunapi' key. Leave them both. +// Yes, javac's definition of the word 'all' is quite contrary to what the +// dictionary says it means. 'all' does NOT include 'sunapi' according to javac. +@SuppressWarnings({"sunapi", "all"}) +public class Permit { + private Permit() {} + + + private static final long ACCESSIBLE_OVERRIDE_FIELD_OFFSET; + private static final IllegalAccessException INIT_ERROR; + private static final sun.misc.Unsafe UNSAFE = (sun.misc.Unsafe) reflectiveStaticFieldAccess(sun.misc.Unsafe.class, "theUnsafe"); + + static { + Field f; + long g; + Throwable ex; + + try { + g = getOverrideFieldOffset(); + ex = null; + } catch (Throwable t) { + f = null; + g = -1L; + ex = t; + } + + ACCESSIBLE_OVERRIDE_FIELD_OFFSET = g; + if (ex == null) INIT_ERROR = null; + else if (ex instanceof IllegalAccessException) INIT_ERROR = (IllegalAccessException) ex; + else { + INIT_ERROR = new IllegalAccessException("Cannot initialize Unsafe-based permit"); + INIT_ERROR.initCause(ex); + } + } + + public static T setAccessible(T accessor) { + if (INIT_ERROR == null) { + UNSAFE.putBoolean(accessor, ACCESSIBLE_OVERRIDE_FIELD_OFFSET, true); + } else { + accessor.setAccessible(true); + } + + return accessor; + } + + private static long getOverrideFieldOffset() throws Throwable { + Field f = null; + Throwable saved = null; + try { + f = AccessibleObject.class.getDeclaredField("override"); + } catch (Throwable t) { + saved = t; + } + + if (f != null) { + return UNSAFE.objectFieldOffset(f); + } + // The below seems very risky, but for all AccessibleObjects in java today it does work, + // and starting with JDK12, making the field accessible is no longer possible. + try { + return UNSAFE.objectFieldOffset(Fake.class.getDeclaredField("override")); + } catch (Throwable t) { + throw saved; + } + } + + static class Fake { + boolean override; + Object accessCheckCache; + } + + public static Method getMethod(Class c, String mName, Class... parameterTypes) throws NoSuchMethodException { + Method m = null; + Class oc = c; + while (c != null) { + try { + m = c.getDeclaredMethod(mName, parameterTypes); + break; + } catch (NoSuchMethodException e) {} + c = c.getSuperclass(); + } + + if (m == null) throw new NoSuchMethodException(oc.getName() + " :: " + mName + "(args)"); + return setAccessible(m); + } + + public static Method permissiveGetMethod(Class c, String mName, Class... parameterTypes) { + try { + return getMethod(c, mName, parameterTypes); + } catch (Exception ignore) { + return null; + } + } + + public static Field getField(Class c, String fName) throws NoSuchFieldException { + Field f = null; + Class oc = c; + while (c != null) { + try { + f = c.getDeclaredField(fName); + break; + } catch (NoSuchFieldException e) {} + c = c.getSuperclass(); + } + + if (f == null) throw new NoSuchFieldException(oc.getName() + " :: " + fName); + + return setAccessible(f); + } + + public static Field permissiveGetField(Class c, String fName) { + try { + return getField(c, fName); + } catch (Exception ignore) { + return null; + } + } + + public static T permissiveReadField(Class type, Field f, Object instance) { + try { + return type.cast(f.get(instance)); + } catch (Exception ignore) { + return null; + } + } + + public static Constructor getConstructor(Class c, Class... parameterTypes) + throws NoSuchMethodException { + return setAccessible(c.getDeclaredConstructor(parameterTypes)); + } + + private static Object reflectiveStaticFieldAccess(Class c, String fName) { + try { + Field f = c.getDeclaredField(fName); + f.setAccessible(true); + return f.get(null); + } catch (Exception e) { + return null; + } + } + + public static boolean isDebugReflection() { + return !"false".equals(System.getProperty("lombok.debug.reflection", "false")); + } + + public static void handleReflectionDebug(Throwable t, Throwable initError) { + if (!isDebugReflection()) return; + + System.err.println("** LOMBOK REFLECTION exception: " + t.getClass() + ": " + + (t.getMessage() == null ? "(no message)" : t.getMessage())); + t.printStackTrace(System.err); + if (initError != null) { + System.err.println("*** ADDITIONALLY, exception occurred setting up reflection: "); + initError.printStackTrace(System.err); + } + } + + public static Object invoke(Method m, Object receiver, Object... args) + throws IllegalAccessException, InvocationTargetException { + return invoke(null, m, receiver, args); + } + + public static Object invoke(Throwable initError, Method m, Object receiver, Object... args) + throws IllegalAccessException, InvocationTargetException { + try { + return m.invoke(receiver, args); + } catch (IllegalAccessException e) { + handleReflectionDebug(e, initError); + throw e; + } catch (RuntimeException e) { + handleReflectionDebug(e, initError); + throw e; + } catch (Error e) { + handleReflectionDebug(e, initError); + throw e; + } + } + + public static Object invokeSneaky(Method m, Object receiver, Object... args) { + return invokeSneaky(null, m, receiver, args); + } + + public static Object invokeSneaky(Throwable initError, Method m, Object receiver, Object... args) { + try { + return m.invoke(receiver, args); + } catch (NoClassDefFoundError e) { + handleReflectionDebug(e, initError); + //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly + //do anything useful here. + return null; + } catch (NullPointerException e) { + handleReflectionDebug(e, initError); + //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly + //do anything useful here. + return null; + } catch (IllegalAccessException e) { + handleReflectionDebug(e, initError); + throw sneakyThrow(e); + } catch (InvocationTargetException e) { + throw sneakyThrow(e.getCause()); + } catch (RuntimeException e) { + handleReflectionDebug(e, initError); + throw e; + } catch (Error e) { + handleReflectionDebug(e, initError); + throw e; + } + } + + public static T newInstance(Constructor c, Object... args) + throws IllegalAccessException, InvocationTargetException, InstantiationException { + return newInstance(null, c, args); + } + + public static T newInstance(Throwable initError, Constructor c, Object... args) + throws IllegalAccessException, InvocationTargetException, InstantiationException { + try { + return c.newInstance(args); + } catch (IllegalAccessException e) { + handleReflectionDebug(e, initError); + throw e; + } catch (InstantiationException e) { + handleReflectionDebug(e, initError); + throw e; + } catch (RuntimeException e) { + handleReflectionDebug(e, initError); + throw e; + } catch (Error e) { + handleReflectionDebug(e, initError); + throw e; + } + } + + public static T newInstanceSneaky(Constructor c, Object... args) { + return newInstanceSneaky(null, c, args); + } + + public static T newInstanceSneaky(Throwable initError, Constructor c, Object... args) { + try { + return c.newInstance(args); + } catch (NoClassDefFoundError e) { + handleReflectionDebug(e, initError); + //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly + //do anything useful here. + return null; + } catch (NullPointerException e) { + handleReflectionDebug(e, initError); + //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly + //do anything useful here. + return null; + } catch (IllegalAccessException e) { + handleReflectionDebug(e, initError); + throw sneakyThrow(e); + } catch (InstantiationException e) { + handleReflectionDebug(e, initError); + throw sneakyThrow(e); + } catch (InvocationTargetException e) { + throw sneakyThrow(e.getCause()); + } catch (RuntimeException e) { + handleReflectionDebug(e, initError); + throw e; + } catch (Error e) { + handleReflectionDebug(e, initError); + throw e; + } + } + + public static Object get(Field f, Object receiver) throws IllegalAccessException { + try { + return f.get(receiver); + } catch (IllegalAccessException e) { + handleReflectionDebug(e, null); + throw e; + } catch (RuntimeException e) { + handleReflectionDebug(e, null); + throw e; + } catch (Error e) { + handleReflectionDebug(e, null); + throw e; + } + } + + public static void set(Field f, Object receiver, Object newValue) throws IllegalAccessException { + try { + f.set(receiver, newValue); + } catch (IllegalAccessException e) { + handleReflectionDebug(e, null); + throw e; + } catch (RuntimeException e) { + handleReflectionDebug(e, null); + throw e; + } catch (Error e) { + handleReflectionDebug(e, null); + throw e; + } + } + + public static void reportReflectionProblem(Throwable initError, String msg) { + if (!isDebugReflection()) return; + System.err.println("** LOMBOK REFLECTION issue: " + msg); + if (initError != null) { + System.err.println("*** ADDITIONALLY, exception occurred setting up reflection: "); + initError.printStackTrace(System.err); + } + } + + public static RuntimeException sneakyThrow(Throwable t) { + if (t == null) throw new NullPointerException("t"); + return Permit.sneakyThrow0(t); + } + + @SuppressWarnings("unchecked") + private static T sneakyThrow0(Throwable t) throws T { + throw (T)t; + } + +} \ No newline at end of file diff --git a/src/main/java/org/hibernate/query/validator/Validation.java b/src/main/java/org/hibernate/query/validator/Validation.java index 3ed6d67..b27be43 100644 --- a/src/main/java/org/hibernate/query/validator/Validation.java +++ b/src/main/java/org/hibernate/query/validator/Validation.java @@ -8,6 +8,7 @@ import antlr.collections.AST; import org.hibernate.HibernateException; import org.hibernate.QueryException; +import org.hibernate.dialect.Dialect; import org.hibernate.hql.internal.antlr.HqlBaseLexer; import org.hibernate.hql.internal.antlr.HqlTokenTypes; import org.hibernate.hql.internal.ast.*; @@ -29,6 +30,9 @@ import static org.hibernate.internal.util.StringHelper.qualifier; import static org.hibernate.internal.util.StringHelper.unqualify; +/** + * @author Gavin King + */ class Validation { interface Handler extends ParseErrorHandler { @@ -72,7 +76,12 @@ static void validate(String hql, boolean checkParams, HqlSqlWalker walker = new HqlSqlWalker( new QueryTranslatorImpl("", hql, emptyMap(), factory), - factory, parser, emptyMap(), null); + factory, parser, emptyMap(), null) { + @Override + public Dialect getDialect() { + return factory.getDialect(); + } + }; walker.setASTFactory(new SqlASTFactory(walker) { @Override public Class getASTNodeType(int tokenType) { @@ -230,9 +239,20 @@ private boolean isConstantValue(String name, MockSessionFactory factory) { } - private static void setHandler(Object object, ParseErrorHandler handler) { + private static void setHandler(HqlParser object, ParseErrorHandler handler) { + try { + Field field = HqlParser.class.getDeclaredField("parseErrorHandler"); + field.setAccessible(true); + field.set(object, handler); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + private static void setHandler(HqlSqlWalker object, ParseErrorHandler handler) { try { - Field field = object.getClass().getDeclaredField("parseErrorHandler"); + Field field = HqlSqlWalker.class.getDeclaredField("parseErrorHandler"); field.setAccessible(true); field.set(object, handler); } From 24390606f46ae68f256e566f671951ed045a8618 Mon Sep 17 00:00:00 2001 From: Gavin Date: Wed, 7 Jun 2023 15:04:51 +0200 Subject: [PATCH 4/7] fix publication --- build.gradle | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 9b1930f..2464d61 100644 --- a/build.gradle +++ b/build.gradle @@ -90,9 +90,8 @@ jar { publishing { publications { - maven(MavenPublication) { - from(components.java) - artifact shadowJar + shadow(MavenPublication) { publication -> + project.shadow.component(publication) } } } From 5105c61bdcded827c1ff3fe66ded020a86a94a84 Mon Sep 17 00:00:00 2001 From: Gavin Date: Wed, 7 Jun 2023 15:27:07 +0200 Subject: [PATCH 5/7] workaround shadowing --- .../org/hibernate/query/validator/JavacSessionFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/hibernate/query/validator/JavacSessionFactory.java b/src/main/java/org/hibernate/query/validator/JavacSessionFactory.java index f7b1d03..7a78898 100644 --- a/src/main/java/org/hibernate/query/validator/JavacSessionFactory.java +++ b/src/main/java/org/hibernate/query/validator/JavacSessionFactory.java @@ -356,8 +356,8 @@ static AnnotationMirror getAnnotation(Symbol member, String annotationName) { return mirror; } } - if (annotationName.startsWith("javax.persistence")) { - annotationName = annotationName.replace("javax", "jakarta"); + if (annotationName.startsWith(new StringBuilder("javax.").append("persistence.").toString())) { + annotationName = "jakarta" + annotationName.substring(5); for (AnnotationMirror mirror : member.getAnnotationMirrors()) { if (qualifiedName((com.sun.tools.javac.code.Type.ClassType) mirror.getAnnotationType()) .equals(annotationName)) { From ebec939893e2afcca4f707ddf395425e766313ea Mon Sep 17 00:00:00 2001 From: Gavin Date: Wed, 7 Jun 2023 15:33:47 +0200 Subject: [PATCH 6/7] recognize new query methods from 6 --- .../java/org/hibernate/query/validator/ECJProcessor.java | 9 ++++++--- .../hibernate/query/validator/EclipseProcessor.groovy | 3 +++ .../java/org/hibernate/query/validator/JavacChecker.java | 3 +++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/hibernate/query/validator/ECJProcessor.java b/src/main/java/org/hibernate/query/validator/ECJProcessor.java index c80bf87..115b3de 100644 --- a/src/main/java/org/hibernate/query/validator/ECJProcessor.java +++ b/src/main/java/org/hibernate/query/validator/ECJProcessor.java @@ -83,9 +83,9 @@ private void checkHQL(CompilationUnitDeclaration unit, Compiler compiler) { TypeElement typeElement = elements.getTypeElement(qualifiedName(type.binding)); TypeElement panacheEntity = PanacheUtils.isPanache(typeElement, processingEnv.getTypeUtils(), elements); type.traverse(new ASTVisitor() { - Set setParameterLabels = new HashSet<>(); - Set setParameterNames = new HashSet<>(); - Set setOrderBy = new HashSet<>(); + final Set setParameterLabels = new HashSet<>(); + final Set setParameterNames = new HashSet<>(); + final Set setOrderBy = new HashSet<>(); boolean immediatelyCalled; @Override @@ -94,6 +94,7 @@ public boolean visit(MessageSend messageSend, BlockScope scope) { switch (name) { case "getResultList": case "getSingleResult": + case "getSingleResultOrNull": immediatelyCalled = true; break; case "count": @@ -120,6 +121,8 @@ public boolean visit(MessageSend messageSend, BlockScope scope) { } break; case "createQuery": + case "createSelectionQuery": + case "createMutationQuery": for (Expression argument : messageSend.arguments) { if (argument instanceof StringLiteral) { check((StringLiteral) argument, true); diff --git a/src/main/java/org/hibernate/query/validator/EclipseProcessor.groovy b/src/main/java/org/hibernate/query/validator/EclipseProcessor.groovy index ca96eae..c9b5975 100644 --- a/src/main/java/org/hibernate/query/validator/EclipseProcessor.groovy +++ b/src/main/java/org/hibernate/query/validator/EclipseProcessor.groovy @@ -187,6 +187,7 @@ class EclipseProcessor extends AbstractProcessor { switch (name) { case "getResultList": case "getSingleResult": + case "getSingleResultOrNull": immediatelyCalled = true break case "count": @@ -213,6 +214,8 @@ class EclipseProcessor extends AbstractProcessor { } break; case "createQuery": + case "createSelectionQuery": + case "createMutationQuery": statement.arguments.each { arg -> if (arg.getClass().simpleName == "StringLiteral" || arg.getClass().simpleName == "ExtendedStringLiteral") { diff --git a/src/main/java/org/hibernate/query/validator/JavacChecker.java b/src/main/java/org/hibernate/query/validator/JavacChecker.java index 96edf67..7a5df02 100644 --- a/src/main/java/org/hibernate/query/validator/JavacChecker.java +++ b/src/main/java/org/hibernate/query/validator/JavacChecker.java @@ -183,6 +183,7 @@ public void visitApply(JCTree.JCMethodInvocation jcMethodInvocation) { switch (name) { case "getResultList": case "getSingleResult": + case "getSingleResultOrNull": immediatelyCalled = true; super.visitApply(jcMethodInvocation); immediatelyCalled = false; @@ -220,6 +221,8 @@ public void visitApply(JCTree.JCMethodInvocation jcMethodInvocation) { super.visitApply(jcMethodInvocation); //needed! break; case "createQuery": + case "createSelectionQuery": + case "createMutationQuery": JCTree.JCLiteral queryArg = firstArgument(jcMethodInvocation); if (queryArg != null && queryArg.value instanceof String) { String hql = (String) queryArg.value; From 6a74f85d6385453a6205b866e8981eaa6f9c66c2 Mon Sep 17 00:00:00 2001 From: Gavin Date: Wed, 7 Jun 2023 17:07:08 +0200 Subject: [PATCH 7/7] document requirements --- README.md | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 39b4fe6..0bbff88 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,11 @@ Compile time validation for queries written in HQL, JPQL, and [Panache][]. [Panache]: https://quarkus.io/guides/hibernate-orm-panache [Hibernate logo]: http://static.jboss.org/hibernate/images/hibernate_logo_whitebkg_200px.png +## Requirements + +This project now requires at least JDK 11, but JDK 15 or above +is preferred. + ## Building Type `./gradlew` from this project directory. @@ -185,22 +190,16 @@ If the query validator doesn't run, please ensure that: The query validator was developed and tested with: -- JDK 1.8.0 -- Hibernate 5.4.10.Final -- ECJ 4.6.1 -- Eclipse IDE 2019-03 to 2020-03 (JDT Core 3.17.0 to 3.18.300) +- JDK 15, JDK 17, JDK 20 +- Hibernate 5.6.15.Final +- ECJ 3.33.0 +- Eclipse IDE with JDT Core 3.33.0 Other versions of `javac`, ECJ, and Hibernate may or may not work. The query validator depends on internal compiler APIs in `javac` and ECJ, and is therefore sensitive to changes in the compilers. -_NOTE: this version of the query validator does not work on -JDK 9 and above. The [jdk10][] branch in git is a preview of -the changes required to make it work on JDK 9-12._ - -[jdk10]: https://github.com/hibernate/query-validator/tree/jdk10 - ## Caveats Please be aware of the following issues. @@ -211,13 +210,6 @@ Queries are interpreted according to Hibernate's flavor of JPQL (i.e. HQL), which is a superset of the query language defined by the JPA specification. -One important example of how the languages are different is the -handling of function names. In the JPA spec, function names like -`SUBSTRING`, `SQRT`, and `COALESCE` are *reserved words*. In HQL, -they're just regular identifiers, and you may even write a HQL -query that directly calls a user-defined or non-portable SQL -function. - #### Function arguments are not checked Hibernate's query translator never typechecks function arguments