From commits-return-7865-archive-asf-public=cust-asf.ponee.io@groovy.apache.org Sat Dec 29 15:12:59 2018 Return-Path: X-Original-To: archive-asf-public@cust-asf.ponee.io Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx-eu-01.ponee.io (Postfix) with SMTP id 5386A1807B5 for ; Sat, 29 Dec 2018 15:12:56 +0100 (CET) Received: (qmail 56621 invoked by uid 500); 29 Dec 2018 14:12:55 -0000 Mailing-List: contact commits-help@groovy.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@groovy.apache.org Delivered-To: mailing list commits@groovy.apache.org Received: (qmail 56612 invoked by uid 99); 29 Dec 2018 14:12:55 -0000 Received: from ec2-52-202-80-70.compute-1.amazonaws.com (HELO gitbox.apache.org) (52.202.80.70) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 29 Dec 2018 14:12:55 +0000 Received: by gitbox.apache.org (ASF Mail Server at gitbox.apache.org, from userid 33) id 5962E821BF; Sat, 29 Dec 2018 14:12:54 +0000 (UTC) Date: Sat, 29 Dec 2018 14:13:08 +0000 To: "commits@groovy.apache.org" Subject: [groovy] 15/28: GROOVY-8935: Provide a @NullCheck AST transformation similar to Lombok's NonNull (closes #845) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit From: sunlan@apache.org In-Reply-To: <154609277349.4144.7885010621981286402@gitbox.apache.org> References: <154609277349.4144.7885010621981286402@gitbox.apache.org> X-Git-Host: gitbox.apache.org X-Git-Repo: groovy X-Git-Refname: refs/heads/refine-groovydoc X-Git-Reftype: branch X-Git-Rev: 5422c81a4d220e4da2a104859215ad231b6eef68 X-Git-NotificationType: diff X-Git-Multimail-Version: 1.5.dev Auto-Submitted: auto-generated Message-Id: <20181229141254.5962E821BF@gitbox.apache.org> This is an automated email from the ASF dual-hosted git repository. sunlan pushed a commit to branch refine-groovydoc in repository https://gitbox.apache.org/repos/asf/groovy.git commit 5422c81a4d220e4da2a104859215ad231b6eef68 Author: Paul King AuthorDate: Sat Dec 22 14:18:03 2018 +1000 GROOVY-8935: Provide a @NullCheck AST transformation similar to Lombok's NonNull (closes #845) --- src/main/groovy/groovy/transform/NullCheck.java | 83 +++++++++++++++++++ .../codehaus/groovy/ast/tools/GeneralUtils.java | 4 + .../transform/NullCheckASTTransformation.java | 92 ++++++++++++++++++++++ 3 files changed, 179 insertions(+) diff --git a/src/main/groovy/groovy/transform/NullCheck.java b/src/main/groovy/groovy/transform/NullCheck.java new file mode 100644 index 0000000..56ba109 --- /dev/null +++ b/src/main/groovy/groovy/transform/NullCheck.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package groovy.transform; + +import org.codehaus.groovy.transform.GroovyASTTransformationClass; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Class, method or constructor annotation which indicates that each parameter + * should be checked to ensure it isn't null. If placed at the class level, + * all explicit methods and constructors will be checked. + *

+ * Example usage: + *

+ * import groovy.transform.NullCheck
+ * import static groovy.test.GroovyAssert.shouldFail
+ *
+ * {@code @NullCheck)
+ * class Greeter {
+ *     private String audience
+ *
+ *     Greeter(String audience) {
+ *         this.audience = audience.toLowerCase()
+ *     }
+ *
+ *     String greeting(String salutation) {
+ *         salutation.toUpperCase() + ' ' + audience
+ *     }
+ * }
+ *
+ * assert new Greeter('World').greeting('hello') == 'HELLO world'
+ *
+ * def ex = shouldFail(IllegalArgumentException) { new Greeter(null) }
+ * assert ex.message == 'audience cannot be null'
+ *
+ * ex = shouldFail(IllegalArgumentException) { new Greeter('Universe').greeting(null) }
+ * assert ex.message == 'salutation cannot be null'
+ * 
+ * The produced code for the above example looks like this: + *
+ * class Greeter {
+ *     private String audience
+ *
+ *     Foo(String audience) {
+ *         if (audience == null) throw new IllegalArgumentException('audience cannot be null')
+ *         this.audience = audience.toLowerCase()
+ *     }
+ *
+ *     String greeting(String salutation) {
+ *         if (salutation == null) throw new IllegalArgumentException('salutation cannot be null')
+ *         salutation.toUpperCase() + ' ' + audience
+ *     }
+ * }
+ * 
+ * + * @since 3.0.0 + */ +@java.lang.annotation.Documented +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.TYPE}) +@GroovyASTTransformationClass("org.codehaus.groovy.transform.NullCheckASTTransformation") +public @interface NullCheck { +} diff --git a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java index ed985cc..d1651f6 100644 --- a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java +++ b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java @@ -608,6 +608,10 @@ public class GeneralUtils { return new BooleanExpression(new BinaryExpression(objectExpression, INSTANCEOF, classX(cNode))); } + public static BooleanExpression isNullX(Expression expr) { + return new BooleanExpression(new BinaryExpression(expr, EQ, new ConstantExpression(null))); + } + public static BooleanExpression isOneX(Expression expr) { return new BooleanExpression(new BinaryExpression(expr, EQ, new ConstantExpression(1))); } diff --git a/src/main/java/org/codehaus/groovy/transform/NullCheckASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/NullCheckASTTransformation.java new file mode 100644 index 0000000..f57a91a --- /dev/null +++ b/src/main/java/org/codehaus/groovy/transform/NullCheckASTTransformation.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.codehaus.groovy.transform; + +import groovy.transform.NullCheck; +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.AnnotatedNode; +import org.codehaus.groovy.ast.AnnotationNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.ConstructorNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.control.CompilePhase; +import org.codehaus.groovy.control.SourceUnit; + +import static org.codehaus.groovy.ast.ClassHelper.make; +import static org.codehaus.groovy.ast.tools.GeneralUtils.constX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.isNullX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.throwS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; + +/** + * Handles generation of code for the @AutoImplement annotation. + */ +@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) +public class NullCheckASTTransformation extends AbstractASTTransformation { + private static final Class MY_CLASS = NullCheck.class; + private static final ClassNode MY_TYPE = make(MY_CLASS); + private static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage(); + private static final ClassNode EXCEPTION = ClassHelper.make(IllegalArgumentException.class); + + @Override + public void visit(ASTNode[] nodes, SourceUnit source) { + init(nodes, source); + AnnotatedNode parent = (AnnotatedNode) nodes[1]; + AnnotationNode anno = (AnnotationNode) nodes[0]; + if (!MY_TYPE.equals(anno.getClassNode())) return; + + if (parent instanceof ClassNode) { + ClassNode cNode = (ClassNode) parent; + if (!checkNotInterface(cNode, MY_TYPE_NAME)) return; + for (ConstructorNode cn : cNode.getDeclaredConstructors()) { + adjustMethod(cn); + } + for (MethodNode mn : cNode.getAllDeclaredMethods()) { + adjustMethod(mn); + } + } else if (parent instanceof MethodNode) { + // handles constructor case too + adjustMethod((MethodNode) parent); + } + } + + private void adjustMethod(MethodNode mn) { + Statement origCode = mn.getCode(); + BlockStatement newCode = new BlockStatement(); + if (mn.getParameters().length == 0) return; + for (Parameter p : mn.getParameters()) { + newCode.addStatement(ifS(isNullX(varX(p)), + throwS(ctorX(EXCEPTION, constX(p.getName() + " cannot be null"))))); + } + if (origCode instanceof BlockStatement) { + for (Statement s : ((BlockStatement) origCode).getStatements()) { + newCode.addStatement(s); + } + } else { + newCode.addStatement(origCode); + } + mn.setCode(newCode); + } +}