changeset 4327:619ef41f7663

Update checking of service provider and return type
author jlahoda
date Thu, 29 Sep 2016 19:16:22 +0100
parents 8684b932d0cf
children 56179bb0bcc3
files src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java test/tools/javac/diags/examples/ServiceImplProviderReturnMustBeSubtypeOfServiceIntf/ServiceImplProviderReturnMustBeSubtypeOfServiceIntf.java test/tools/javac/diags/examples/ServiceImplProviderReturnMustBeSubtypeOfServiceIntf/modulesourcepath/m/impl/Impl.java test/tools/javac/diags/examples/ServiceImplProviderReturnMustBeSubtypeOfServiceIntf/modulesourcepath/m/module-info.java test/tools/javac/modules/ProvidesTest.java
diffstat 7 files changed, 212 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java	Thu Sep 22 15:24:33 2016 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java	Thu Sep 29 19:16:22 2016 +0100
@@ -101,6 +101,7 @@
 import static com.sun.tools.javac.code.Flags.PUBLIC;
 import static com.sun.tools.javac.code.Flags.UNATTRIBUTED;
 import static com.sun.tools.javac.code.Kinds.Kind.MDL;
+import static com.sun.tools.javac.code.Kinds.Kind.MTH;
 import static com.sun.tools.javac.code.TypeTag.CLASS;
 
 import com.sun.tools.javac.tree.JCTree.JCDirective;
@@ -836,6 +837,16 @@
             return null;
         }
 
+        MethodSymbol factoryMethod(ClassSymbol tsym) {
+            for (Symbol sym : tsym.members().getSymbolsByName(names.provider, sym -> sym.kind == MTH)) {
+                MethodSymbol mSym = (MethodSymbol)sym;
+                if (mSym.isStatic() && (mSym.flags() & Flags.PUBLIC) != 0 && mSym.params().isEmpty()) {
+                    return mSym;
+                }
+            }
+            return null;
+        }
+
         Map<Directive.ProvidesDirective, JCProvides> directiveToTreeMap = new HashMap<>();
 
         @Override
@@ -844,21 +855,29 @@
             Type it = attr.attribType(tree.implName, env, syms.objectType);
             ClassSymbol service = (ClassSymbol) st.tsym;
             ClassSymbol impl = (ClassSymbol) it.tsym;
-            if (!types.isSubtype(it, st)) {
-                log.error(tree.implName.pos(), Errors.ServiceImplementationMustBeSubtypeOfServiceInterface);
-            }
-            if ((impl.flags() & ABSTRACT) != 0) {
-                log.error(tree.implName.pos(), Errors.ServiceImplementationIsAbstract(impl));
-            } else if (impl.isInner()) {
-                log.error(tree.implName.pos(), Errors.ServiceImplementationIsInner(impl));
-            } else if (service.isInner()) {
-                log.error(tree.serviceName.pos(), Errors.ServiceDefinitionIsInner(service));
+            //find provider factory:
+            MethodSymbol factory = factoryMethod(impl);
+            if (factory != null) {
+                Type returnType = factory.type.getReturnType();
+                if (!types.isSubtype(returnType, st)) {
+                    log.error(tree.implName.pos(), Errors.ServiceImplementationProviderReturnMustBeSubtypeOfServiceInterface);
+                }
             } else {
-                MethodSymbol constr = noArgsConstructor(impl);
-                if (constr == null) {
-                    log.error(tree.implName.pos(), Errors.ServiceImplementationDoesntHaveANoArgsConstructor(impl));
-                } else if ((constr.flags() & PUBLIC) == 0) {
-                    log.error(tree.implName.pos(), Errors.ServiceImplementationNoArgsConstructorNotPublic(impl));
+                if (!types.isSubtype(it, st)) {
+                    log.error(tree.implName.pos(), Errors.ServiceImplementationMustBeSubtypeOfServiceInterface);
+                } else if ((impl.flags() & ABSTRACT) != 0) {
+                    log.error(tree.implName.pos(), Errors.ServiceImplementationIsAbstract(impl));
+                } else if (impl.isInner()) {
+                    log.error(tree.implName.pos(), Errors.ServiceImplementationIsInner(impl));
+                } else if (service.isInner()) {
+                    log.error(tree.serviceName.pos(), Errors.ServiceDefinitionIsInner(service));
+                } else {
+                    MethodSymbol constr = noArgsConstructor(impl);
+                    if (constr == null) {
+                        log.error(tree.implName.pos(), Errors.ServiceImplementationDoesntHaveANoArgsConstructor(impl));
+                    } else if ((constr.flags() & PUBLIC) == 0) {
+                        log.error(tree.implName.pos(), Errors.ServiceImplementationNoArgsConstructorNotPublic(impl));
+                    }
                 }
             }
             if (st.hasTag(CLASS) && it.hasTag(CLASS)) {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties	Thu Sep 22 15:24:33 2016 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties	Thu Sep 29 19:16:22 2016 +0100
@@ -2748,7 +2748,11 @@
     the service implementation is an abstract class: {0}
 
 compiler.err.service.implementation.must.be.subtype.of.service.interface=\
-    the service implementation type must be a subtype of the service interface type
+    the service implementation type must be a subtype of the service interface type, or \
+    have a public static no-args method named "provider" returning the service implementation
+
+compiler.err.service.implementation.provider.return.must.be.subtype.of.service.interface=\
+    the "provider" method return type must be a subtype of the service interface type
 
 # 0: symbol
 compiler.err.service.implementation.is.inner=\
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java	Thu Sep 22 15:24:33 2016 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java	Thu Sep 29 19:16:22 2016 +0100
@@ -103,6 +103,7 @@
     public final Name length;
     public final Name next;
     public final Name ordinal;
+    public final Name provider;
     public final Name serialVersionUID;
     public final Name toString;
     public final Name value;
@@ -262,6 +263,7 @@
         length = fromString("length");
         next = fromString("next");
         ordinal = fromString("ordinal");
+        provider = fromString("provider");
         serialVersionUID = fromString("serialVersionUID");
         toString = fromString("toString");
         value = fromString("value");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/diags/examples/ServiceImplProviderReturnMustBeSubtypeOfServiceIntf/ServiceImplProviderReturnMustBeSubtypeOfServiceIntf.java	Thu Sep 29 19:16:22 2016 +0100
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+// key: compiler.err.service.implementation.provider.return.must.be.subtype.of.service.interface
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/diags/examples/ServiceImplProviderReturnMustBeSubtypeOfServiceIntf/modulesourcepath/m/impl/Impl.java	Thu Sep 29 19:16:22 2016 +0100
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package impl;
+
+public class Impl {
+    public static Impl provider() {
+        return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/diags/examples/ServiceImplProviderReturnMustBeSubtypeOfServiceIntf/modulesourcepath/m/module-info.java	Thu Sep 29 19:16:22 2016 +0100
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+module m {
+    provides java.lang.String with impl.Impl;
+}
--- a/test/tools/javac/modules/ProvidesTest.java	Thu Sep 22 15:24:33 2016 -0700
+++ b/test/tools/javac/modules/ProvidesTest.java	Thu Sep 29 19:16:22 2016 +0100
@@ -39,7 +39,6 @@
 
 import toolbox.JavacTask;
 import toolbox.Task;
-import toolbox.ToolBox;
 
 public class ProvidesTest extends ModuleTestBase {
     public static void main(String... args) throws Exception {
@@ -435,4 +434,96 @@
             throw new Exception("Expected output not found");
         }
     }
+
+    @Test
+    public void testFactory(Path base) throws Exception {
+        Path src = base.resolve("src");
+        tb.writeJavaFiles(src,
+                "module m { exports p1; provides p1.C1 with p2.C2; }",
+                "package p1; public interface C1 { }",
+                "package p2; public class C2 { public static p1.C1 provider() { return null; } }");
+
+        new JavacTask(tb)
+                .options("-XDrawDiagnostics")
+                .outdir(Files.createDirectories(base.resolve("classes")))
+                .files(findJavaFiles(src))
+                .run()
+                .writeAll()
+                .getOutput(Task.OutputKind.DIRECT);
+
+        List<String> output;
+        List<String> expected;
+
+        tb.writeJavaFiles(src,
+                "package p2; public class C2 { public p1.C1 provider() { return null; } }");
+
+        output = new JavacTask(tb)
+                .options("-XDrawDiagnostics")
+                .outdir(Files.createDirectories(base.resolve("classes")))
+                .files(findJavaFiles(src))
+                .run(Task.Expect.FAIL)
+                .writeAll()
+                .getOutputLines(Task.OutputKind.DIRECT);
+
+        expected = Arrays.asList("module-info.java:1:46: compiler.err.service.implementation.must.be.subtype.of.service.interface",
+                                 "1 error");
+
+        if (!expected.equals(output)) {
+            throw new Exception("Expected output not found. Output: " + output);
+        }
+
+        tb.writeJavaFiles(src,
+                "package p2; public class C2 { static p1.C1 provider() { return null; } }");
+
+        output = new JavacTask(tb)
+                .options("-XDrawDiagnostics")
+                .outdir(Files.createDirectories(base.resolve("classes")))
+                .files(findJavaFiles(src))
+                .run(Task.Expect.FAIL)
+                .writeAll()
+                .getOutputLines(Task.OutputKind.DIRECT);
+
+        expected = Arrays.asList("module-info.java:1:46: compiler.err.service.implementation.must.be.subtype.of.service.interface",
+                                 "1 error");
+
+        if (!expected.equals(output)) {
+            throw new Exception("Expected output not found. Output: " + output);
+        }
+
+        tb.writeJavaFiles(src,
+                "package p2; public class C2 { public static Object provider() { return null; } }");
+
+        output = new JavacTask(tb)
+                .options("-XDrawDiagnostics")
+                .outdir(Files.createDirectories(base.resolve("classes")))
+                .files(findJavaFiles(src))
+                .run(Task.Expect.FAIL)
+                .writeAll()
+                .getOutputLines(Task.OutputKind.DIRECT);
+
+        expected = Arrays.asList("module-info.java:1:46: compiler.err.service.implementation.provider.return.must.be.subtype.of.service.interface",
+                                 "1 error");
+
+        if (!expected.equals(output)) {
+            throw new Exception("Expected output not found. Output: " + output);
+        }
+
+        tb.writeJavaFiles(src,
+                "package p2; public class C2 { public static p1.C1 provider = new p1.C1() {}; }");
+
+        output = new JavacTask(tb)
+                .options("-XDrawDiagnostics")
+                .outdir(Files.createDirectories(base.resolve("classes")))
+                .files(findJavaFiles(src))
+                .run(Task.Expect.FAIL)
+                .writeAll()
+                .getOutputLines(Task.OutputKind.DIRECT);
+
+        expected = Arrays.asList("module-info.java:1:46: compiler.err.service.implementation.must.be.subtype.of.service.interface",
+                                 "1 error");
+
+        if (!expected.equals(output)) {
+            throw new Exception("Expected output not found. Output: " + output);
+        }
+    }
 }