diff --git a/jOOQ-checker/src/main/java/org/jooq/checker/AbstractMatcher.java b/jOOQ-checker/src/main/java/org/jooq/checker/AbstractMatcher.java new file mode 100644 index 0000000000..daa8e752df --- /dev/null +++ b/jOOQ-checker/src/main/java/org/jooq/checker/AbstractMatcher.java @@ -0,0 +1,79 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.checker; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; + +import org.jooq.checker.Tools.Printer; + +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; +import com.google.errorprone.matchers.Description; +import com.sun.source.tree.Tree; + +/** + * Common base class for matchers. + * + * @author Lukas Eder + */ +abstract class AbstractMatcher extends BugChecker implements MethodInvocationTreeMatcher { + + /** + * Generated UID + */ + private static final long serialVersionUID = -3955673252940475227L; + + Description error(Tree node, String message) { + return buildDescription(node).setMessage(message).build(); + } + + static Description print(Printer printer) { + try (PrintWriter writer = new PrintWriter(new FileWriter("error.txt"))){ + writer.println("This is probably a bug in jOOQ-checker."); + writer.println("If you think this is a bug in jOOQ, please report it here: https://github.com/jOOQ/jOOQ/issues/new"); + writer.println("---------------------------------------------------------------------"); + + printer.print(writer); + } + catch (IOException ignore) {} + + return null; + } +} diff --git a/jOOQ-checker/src/main/java/org/jooq/checker/PlainSQLMatcher.java b/jOOQ-checker/src/main/java/org/jooq/checker/PlainSQLMatcher.java new file mode 100644 index 0000000000..cc9e3053a1 --- /dev/null +++ b/jOOQ-checker/src/main/java/org/jooq/checker/PlainSQLMatcher.java @@ -0,0 +1,81 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.checker; + +import static com.google.errorprone.BugPattern.LinkType.NONE; +import static com.google.errorprone.BugPattern.SeverityLevel.ERROR; + +import org.jooq.PlainSQL; + +import com.google.auto.service.AutoService; +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.matchers.Description; +import com.sun.source.tree.MethodInvocationTree; + +/** + * A checker to disallow usage of {@link PlainSQL} API, except where allowed + * explicitly. + * + * @author Lukas Eder + */ +@AutoService(BugChecker.class) +@BugPattern( + name = "PlainSQLMatcher", + summary = "jOOQ Plain SQL usage where this is not allowed", + severity = ERROR, + linkType = NONE +) +public class PlainSQLMatcher extends AbstractMatcher { + + /** + * Generated UID + */ + private static final long serialVersionUID = -690695693050288771L; + + @Override + public Description matchMethodInvocation(MethodInvocationTree node, VisitorState state) { + return Tools.checkPlainSQL( + node, + () -> Tools.enclosing(state.getPath()), + message -> error(node, message), + printer -> print(printer) + ); + } +} diff --git a/jOOQ-checker/src/main/java/org/jooq/checker/SQLDialectMatcher.java b/jOOQ-checker/src/main/java/org/jooq/checker/SQLDialectMatcher.java new file mode 100644 index 0000000000..2b89f1ffa4 --- /dev/null +++ b/jOOQ-checker/src/main/java/org/jooq/checker/SQLDialectMatcher.java @@ -0,0 +1,81 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.checker; + +import static com.google.errorprone.BugPattern.LinkType.NONE; +import static com.google.errorprone.BugPattern.SeverityLevel.ERROR; + +import org.jooq.PlainSQL; + +import com.google.auto.service.AutoService; +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.matchers.Description; +import com.sun.source.tree.MethodInvocationTree; + +/** + * A checker to disallow usage of {@link PlainSQL} API, except where allowed + * explicitly. + * + * @author Lukas Eder + */ +@AutoService(BugChecker.class) +@BugPattern( + name = "SQLDialectMatcher", + summary = "jOOQ API was used which is not supported by your SQLDialect", + severity = ERROR, + linkType = NONE +) +public class SQLDialectMatcher extends AbstractMatcher { + + /** + * Generated UID + */ + private static final long serialVersionUID = -690695693050288771L; + + @Override + public Description matchMethodInvocation(MethodInvocationTree node, VisitorState state) { + return Tools.checkSQLDialect( + node, + () -> Tools.enclosing(state.getPath()), + message -> error(node, message), + printer -> print(printer) + ); + } +} diff --git a/jOOQ-checker/src/main/java/org/jooq/checker/Tools.java b/jOOQ-checker/src/main/java/org/jooq/checker/Tools.java new file mode 100644 index 0000000000..a050c02920 --- /dev/null +++ b/jOOQ-checker/src/main/java/org/jooq/checker/Tools.java @@ -0,0 +1,215 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.checker; + +import static java.util.Arrays.asList; +import static org.checkerframework.javacutil.TreeUtils.elementFromDeclaration; +import static org.checkerframework.javacutil.TreeUtils.elementFromUse; +import static org.checkerframework.javacutil.TreeUtils.enclosingClass; +import static org.checkerframework.javacutil.TreeUtils.enclosingMethod; + +import java.io.PrintWriter; +import java.util.EnumSet; +import java.util.function.Function; +import java.util.function.Supplier; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; + +import org.jooq.Allow; +import org.jooq.PlainSQL; +import org.jooq.Require; +import org.jooq.SQLDialect; +import org.jooq.Support; + +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.util.TreePath; + +/** + * The actual implementation of the checkers / matchers. + * + * @author Lukas Eder + */ +final class Tools { + static final T checkSQLDialect( + MethodInvocationTree node, + Supplier enclosingSupplier, + Function error, + Function print + ) { + try { + ExecutableElement elementFromUse = elementFromUse(node); + Support support = elementFromUse.getAnnotation(Support.class); + + // In the absence of a @Support annotation, all jOOQ API method calls will type check. + if (support != null) { + Element enclosing = enclosingSupplier.get(); + + // [#7929] "Empty" @Support annotations expand to all SQLDialects + EnumSet supported = EnumSet.copyOf( + support.value().length > 0 + ? asList(support.value()) + : asList(SQLDialect.values()) + ); + + EnumSet allowed = EnumSet.noneOf(SQLDialect.class); + EnumSet required = EnumSet.noneOf(SQLDialect.class); + + boolean evaluateRequire = true; + while (enclosing != null) { + Allow allow = enclosing.getAnnotation(Allow.class); + + if (allow != null) + allowed.addAll(asList(allow.value())); + + if (evaluateRequire) { + Require require = enclosing.getAnnotation(Require.class); + + if (require != null) { + evaluateRequire = false; + + required.clear(); + required.addAll(asList(require.value())); + } + } + + enclosing = enclosing.getEnclosingElement(); + } + + if (allowed.isEmpty()) + return error.apply("No jOOQ API usage is allowed at current scope. Use @Allow."); + + boolean allowedFail = true; + allowedLoop: + for (SQLDialect a : allowed) { + for (SQLDialect s : supported) { + if (a.supports(s)) { + allowedFail = false; + break allowedLoop; + } + } + } + + if (allowedFail) + return error.apply("The allowed dialects in scope " + allowed + " do not include any of the supported dialects: " + supported); + + boolean requiredFail = false; + requiredLoop: + for (SQLDialect r : required) { + for (SQLDialect s : supported) + if (r.supports(s)) + continue requiredLoop; + + requiredFail = true; + break requiredLoop; + } + + if (requiredFail) + return error.apply("Not all of the required dialects " + required + " from the current scope are supported " + supported); + } + } + catch (final Exception e) { + return print.apply(new Printer() { + @Override + public void print(PrintWriter t) { + e.printStackTrace(t); + } + }); + } + + return null; + } + + static final T checkPlainSQL( + MethodInvocationTree node, + Supplier enclosingSupplier, + Function error, + Function print + ) { + try { + ExecutableElement elementFromUse = elementFromUse(node); + PlainSQL plainSQL = elementFromUse.getAnnotation(PlainSQL.class); + + // In the absence of a @PlainSQL annotation, + // all jOOQ API method calls will type check. + if (plainSQL != null) { + boolean allowed = false; + Element enclosing = enclosingSupplier.get(); + + moveUpEnclosingLoop: + while (enclosing != null) { + if (enclosing.getAnnotation(Allow.PlainSQL.class) != null) { + allowed = true; + break moveUpEnclosingLoop; + } + + enclosing = enclosing.getEnclosingElement(); + } + + if (!allowed) + return error.apply("Plain SQL usage not allowed at current scope. Use @Allow.PlainSQL."); + } + } + catch (final Exception e) { + return print.apply(new Printer() { + @Override + public void print(PrintWriter t) { + e.printStackTrace(t); + } + }); + } + + return null; + } + + static Element enclosing(TreePath path) { + MethodTree enclosingMethod = enclosingMethod(path); + + if (enclosingMethod != null) + return elementFromDeclaration(enclosingMethod); + + ClassTree enclosingClass = enclosingClass(path); + return elementFromDeclaration(enclosingClass); + } + + interface Printer { + void print(PrintWriter writer); + } +} diff --git a/jOOQ-examples/jOOQ-checker-framework-example/pom.xml b/jOOQ-examples/jOOQ-checker-framework-example/pom.xml index 73ca923e04..c1a0943674 100644 --- a/jOOQ-examples/jOOQ-checker-framework-example/pom.xml +++ b/jOOQ-examples/jOOQ-checker-framework-example/pom.xml @@ -19,7 +19,6 @@ UTF-8 3.12.0-SNAPSHOT - 1.8 @@ -60,16 +59,13 @@ maven-compiler-plugin 3.8.0 - ${java.version} - ${java.version} + 9 + 9 true org.jooq.checker.SQLDialectChecker - - -Xbootclasspath/p:1.8 - @@ -88,8 +84,8 @@ javac-with-errorprone true - ${java.version} - ${java.version} + 9 + 9 org.jooq @@ -98,7 +94,6 @@ - -Xbootclasspath/p:1.8 -XDcompilePolicy=simple -Xplugin:ErrorProne