diff --git a/jOOQ-checker/src/main/java/org/jooq/checker/AbstractChecker.java b/jOOQ-checker/src/main/java/org/jooq/checker/AbstractChecker.java new file mode 100644 index 0000000000..fa46c89c01 --- /dev/null +++ b/jOOQ-checker/src/main/java/org/jooq/checker/AbstractChecker.java @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2009-2016, Data Geekery GmbH (http://www.datageekery.com) + * All rights reserved. + * + * 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.checkerframework.framework.source.Result; +import org.checkerframework.framework.source.SourceChecker; + +/** + * Common base class for checkers. + * + * @author Lukas Eder + */ +abstract class AbstractChecker extends SourceChecker { + + void error(Object node, String message) { + getChecker().report(Result.failure(message, node), node); + } + + void print(Printer printer) { + try (PrintWriter writer = new PrintWriter(new FileWriter("error.txt"))){ + writer.println("This is probably a bug in jOOQ-checker."); + writer.println("Please report this bug here: https://github.com/jOOQ/jOOQ/issues/new"); + writer.println("---------------------------------------------------------------------"); + + printer.print(writer); + } + catch (IOException ignore) {} + } + + interface Printer { + void print(PrintWriter writer); + } +} diff --git a/jOOQ-checker/src/main/java/org/jooq/checker/PlainSQLChecker.java b/jOOQ-checker/src/main/java/org/jooq/checker/PlainSQLChecker.java new file mode 100644 index 0000000000..4ea8620c1e --- /dev/null +++ b/jOOQ-checker/src/main/java/org/jooq/checker/PlainSQLChecker.java @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2009-2016, Data Geekery GmbH (http://www.datageekery.com) + * All rights reserved. + * + * 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.sun.source.util.TreePath.getPath; +import static org.checkerframework.javacutil.TreeUtils.elementFromDeclaration; +import static org.checkerframework.javacutil.TreeUtils.elementFromUse; +import static org.checkerframework.javacutil.TreeUtils.enclosingMethod; + +import java.io.PrintWriter; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; + +import org.jooq.Allow; +import org.jooq.PlainSQL; + +import org.checkerframework.framework.source.SourceVisitor; + +import com.sun.source.tree.MethodInvocationTree; + +/** + * A checker to disallow usage of {@link PlainSQL} API, except where allowed + * explicitly. + * + * @author Lukas Eder + */ +public class PlainSQLChecker extends AbstractChecker { + + @Override + protected SourceVisitor createSourceVisitor() { + return new SourceVisitor(getChecker()) { + + @Override + public Void visitMethodInvocation(MethodInvocationTree node, Void p) { + 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) { + Element enclosing = elementFromDeclaration(enclosingMethod(getPath(root, node))); + boolean allowed = false; + + moveUpEnclosingLoop: + while (enclosing != null) { + if (enclosing.getAnnotation(Allow.PlainSQL.class) != null) { + allowed = true; + break moveUpEnclosingLoop; + } + + enclosing = enclosing.getEnclosingElement(); + } + + if (!allowed) + error(node, "Plain SQL usage not allowed at current scope. Use @Allow.PlainSQL."); + } + } + catch (final Exception e) { + print(new Printer() { + @Override + public void print(PrintWriter t) { + e.printStackTrace(t); + } + }); + } + + return super.visitMethodInvocation(node, p); + } + }; + } +} diff --git a/jOOQ-checker/src/main/java/org/jooq/checker/SQLDialectChecker.java b/jOOQ-checker/src/main/java/org/jooq/checker/SQLDialectChecker.java index 4581fbfb35..b0fd4cbbe9 100644 --- a/jOOQ-checker/src/main/java/org/jooq/checker/SQLDialectChecker.java +++ b/jOOQ-checker/src/main/java/org/jooq/checker/SQLDialectChecker.java @@ -46,8 +46,6 @@ import static org.checkerframework.javacutil.TreeUtils.elementFromDeclaration; import static org.checkerframework.javacutil.TreeUtils.elementFromUse; import static org.checkerframework.javacutil.TreeUtils.enclosingMethod; -import java.io.FileWriter; -import java.io.IOException; import java.io.PrintWriter; import java.util.EnumSet; @@ -59,8 +57,6 @@ import org.jooq.Require; import org.jooq.SQLDialect; import org.jooq.Support; -import org.checkerframework.framework.source.Result; -import org.checkerframework.framework.source.SourceChecker; import org.checkerframework.framework.source.SourceVisitor; import com.sun.source.tree.MethodInvocationTree; @@ -71,11 +67,11 @@ import com.sun.source.tree.MethodInvocationTree; * * @author Lukas Eder */ -public class SQLDialectChecker extends SourceChecker { +public class SQLDialectChecker extends AbstractChecker { @Override - protected SourceVisitor createSourceVisitor() { - return new SourceVisitor(this) { + protected SourceVisitor createSourceVisitor() { + return new SourceVisitor(getChecker()) { @Override public Void visitMethodInvocation(MethodInvocationTree node, Void p) { @@ -134,23 +130,4 @@ public class SQLDialectChecker extends SourceChecker { } }; } - - void error(Object node, String message) { - getChecker().report(Result.failure(message, node), node); - } - - void print(Printer printer) { - try (PrintWriter writer = new PrintWriter(new FileWriter("error.txt"))){ - writer.println("This is probably a bug in jOOQ-checker."); - writer.println("Please report this bug here: https://github.com/jOOQ/jOOQ/issues/new"); - writer.println("---------------------------------------------------------------------"); - - printer.print(writer); - } - catch (IOException ignore) {} - } - - 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 fd8f99e620..4ccb2f1cd0 100644 --- a/jOOQ-examples/jOOQ-checker-framework-example/pom.xml +++ b/jOOQ-examples/jOOQ-checker-framework-example/pom.xml @@ -60,6 +60,7 @@ true org.jooq.checker.SQLDialectChecker + org.jooq.checker.PlainSQLChecker -Xbootclasspath/p:1.8 diff --git a/jOOQ-examples/jOOQ-checker-framework-example/src/main/java/org/jooq/example/checker/PlainSQLCheckerTests.java b/jOOQ-examples/jOOQ-checker-framework-example/src/main/java/org/jooq/example/checker/PlainSQLCheckerTests.java new file mode 100644 index 0000000000..ce24fc6c6a --- /dev/null +++ b/jOOQ-examples/jOOQ-checker-framework-example/src/main/java/org/jooq/example/checker/PlainSQLCheckerTests.java @@ -0,0 +1,24 @@ +package org.jooq.example.checker; + +import org.jooq.Allow; +import org.jooq.impl.DSL; + +// This class allows for using PlainSQL API +@Allow.PlainSQL +class PlainSQLCheckerTests1 { + public static void compiles() { + DSL.field("plain SQL"); + } +} + +// This class doesn't allow for using PlainSQL API +class PlainSQLCheckerTests2 { + public static void doesntCompile() { + DSL.field("plain SQL"); + } + + @Allow.PlainSQL + public static void compiles() { + DSL.field("plain SQL"); + } +} \ No newline at end of file diff --git a/jOOQ-examples/jOOQ-checker-framework-example/src/main/java/org/jooq/example/checker/CheckerTests.java b/jOOQ-examples/jOOQ-checker-framework-example/src/main/java/org/jooq/example/checker/SQLDialectCheckerTests.java similarity index 96% rename from jOOQ-examples/jOOQ-checker-framework-example/src/main/java/org/jooq/example/checker/CheckerTests.java rename to jOOQ-examples/jOOQ-checker-framework-example/src/main/java/org/jooq/example/checker/SQLDialectCheckerTests.java index e9cdeae073..1a87611d22 100644 --- a/jOOQ-examples/jOOQ-checker-framework-example/src/main/java/org/jooq/example/checker/CheckerTests.java +++ b/jOOQ-examples/jOOQ-checker-framework-example/src/main/java/org/jooq/example/checker/SQLDialectCheckerTests.java @@ -10,7 +10,7 @@ import org.jooq.impl.DSL; // The class requires both H2 and MySQL // The inherited @Allow annotation from the package allows only MySQL, though. @Require({ SQLDialect.H2, SQLDialect.MYSQL }) -public class CheckerTests { +public class SQLDialectCheckerTests { // @Allow = MySQL (inherited from package) // @Require = { H2, MySQL } (inherited from class) diff --git a/jOOQ-examples/jOOQ-checker-framework-example/src/main/java/org/jooq/example/checker/ShouldntCompile.java b/jOOQ-examples/jOOQ-checker-framework-example/src/main/java/org/jooq/example/checker/ShouldntCompile.java deleted file mode 100644 index 599b3a33ac..0000000000 --- a/jOOQ-examples/jOOQ-checker-framework-example/src/main/java/org/jooq/example/checker/ShouldntCompile.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.jooq.example.checker; -import static org.jooq.example.checker.db.h2.Tables.AUTHOR; -import static org.jooq.example.checker.db.h2.Tables.BOOK; - -import java.sql.Connection; -import java.sql.DriverManager; - -import org.jooq.Require; -import org.jooq.Result; -import org.jooq.SQLDialect; -import org.jooq.impl.DSL; - -/** - * @author Lukas Eder - */ -@Require( - value = { SQLDialect.H2, SQLDialect.MYSQL } -) -public class ShouldntCompile { - - @Require({SQLDialect.H2, SQLDialect.MYSQL}) - public static void main(String[] args) throws Exception { - try (Connection c = DriverManager.getConnection("jdbc:h2:~/checker-test", "sa", "")) { - Result result = - DSL.using(c) - .select( - AUTHOR.FIRST_NAME, - AUTHOR.LAST_NAME, - BOOK.ID, - BOOK.TITLE, - DSL.array(1) - ) - .from(AUTHOR) - .join(BOOK) - .on(AUTHOR.ID.eq(BOOK.AUTHOR_ID)) - .connectBy("abc") - .orderBy(BOOK.ID.asc()) - .fetch(); - - System.out.println(result); - } - } - - public static void x() { - DSL.array(2); - } -} diff --git a/jOOQ/src/main/java/org/jooq/Allow.java b/jOOQ/src/main/java/org/jooq/Allow.java index 8f07cf070d..0137bb81ef 100644 --- a/jOOQ/src/main/java/org/jooq/Allow.java +++ b/jOOQ/src/main/java/org/jooq/Allow.java @@ -130,6 +130,9 @@ import java.lang.annotation.Target; * {@link SQLDialect#predecessor()}, and its {@link SQLDialect#family()} are * allowed. * + *

+ * Apart from the above main purpose, the {@link Allow} annotation also serves + * as a semantic namespace for other annotations, such as {@link Allow.PlainSQL} * * @author Lukas Eder * @see Require @@ -172,4 +175,20 @@ public @interface Allow { POSTGRES, SQLITE, }; + + /** + * This annotation allows {@link PlainSQL} API usage within the scope of + * where it is placed. + *

+ * Type checking for these annotations can be supplied by + * org.jooq.checker.PlainSQLChecker from the jOOQ-checker + * module. + * + * @author Lukas Eder + */ + @Target({ METHOD, CONSTRUCTOR, TYPE, PACKAGE }) + @Retention(RUNTIME) + @Documented + @Inherited + @interface PlainSQL {} }