Proposal: Enhance ServiceLoader to understand factory methods

Justification: A lot of our services in NetBeans are final classes. Still,
we 
would like to use the META-INF/services registration to register their 
providers. Currently this does not work, we either need to unfinalize the 
classes (which restricts future evolution options) or use different ways of 
registering the providers. We propose simple and backward compatible 
extension to current ServiceLoader registration scheme. If accepted, 
NetBeans'll eliminate our non-standard registration methods, while keeping 
benefits of final classes. Moreover the same techniques will be available to 
any JavaSE project.

Risks: API change includes simple, compatible extension. New code is able to 
accept old registrations. Old code (JDK6) will generate 
ServiceConfigurationError in case of seeing new registration, however.

Test: I have found tests in test/java/util/ServiceLoader/ directory and I've 
modified them to verify also our new usecase. All of them are passing, if I 
run them by running ./basic.sh script - I am not sure if that is the right 
way of running your tests.

I am attaching my current patch. Let me know what shall I change to allow
this 
to be integrated to OpenJDK7. Thanks a lot.

Jaroslav Tulach
NetBeans Platform Architect

diff -r 14f50aee4989 src/share/classes/java/util/ServiceLoader.java
--- a/src/share/classes/java/util/ServiceLoader.java	Thu Oct 02 19:58:32 2008 -0700
+++ b/src/share/classes/java/util/ServiceLoader.java	Sat Oct 04 13:53:11 2008 +0200
@@ -29,6 +29,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.lang.reflect.Method;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Enumeration;
@@ -260,8 +261,13 @@
                 fail(service, u, lc, "Illegal provider-class name: " + ln);
             for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                 cp = ln.codePointAt(i);
-                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
+                if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) {
+                    if (cp == '(' && i == ln.length() - 2 && ln.codePointAt(i + 1) == ')') {
+                        // OK ends with "()"
+                        break;
+                    }
                     fail(service, u, lc, "Illegal provider-class name: " + ln);
+                }
             }
             if (!providers.containsKey(ln) && !names.contains(ln))
                 names.add(ln);
@@ -359,8 +365,20 @@
             String cn = nextName;
             nextName = null;
             try {
-                S p = service.cast(Class.forName(cn, true, loader)
+                S p;
+                if (cn.endsWith("()")) { // NOI18N
+                    int lastDot = cn.lastIndexOf('.');
+                    if (lastDot == -1) {
+                        fail(service, "Use pkg.ClassName.methodName() in " + cn);
+                    }
+                    String mn = cn.substring(lastDot + 1, cn.length() - 2);
+                    Class<?> clazz = Class.forName(cn.substring(0, lastDot), true, loader);
+                    Method method = clazz.getDeclaredMethod(mn);
+                    p = service.cast(method.invoke(null));
+                } else {
+                    p = service.cast(Class.forName(cn, true, loader)
                                    .newInstance());
+                }
                 providers.put(cn, p);
                 return p;
             } catch (ClassNotFoundException x) {
diff -r 14f50aee4989 test/java/util/ServiceLoader/FooProvider1.java
--- a/test/java/util/ServiceLoader/FooProvider1.java	Thu Oct 02 19:58:32 2008 -0700
+++ b/test/java/util/ServiceLoader/FooProvider1.java	Sat Oct 04 13:53:11 2008 +0200
@@ -21,4 +21,6 @@
  * have any questions.
  */
 
-public class FooProvider1 extends FooService { }
+public class FooProvider1 extends FooService { 
+    public static FooProvider1 create1() { return new FooProvider1(); }
+}
diff -r 14f50aee4989 test/java/util/ServiceLoader/FooProvider2.java
--- a/test/java/util/ServiceLoader/FooProvider2.java	Thu Oct 02 19:58:32 2008 -0700
+++ b/test/java/util/ServiceLoader/FooProvider2.java	Sat Oct 04 13:53:11 2008 +0200
@@ -21,4 +21,6 @@
  * have any questions.
  */
 
-public class FooProvider2 extends FooService { }
+public class FooProvider2 extends FooService {
+    public static FooService create2() { return new FooProvider2(); }
+}
diff -r 14f50aee4989 test/java/util/ServiceLoader/FooService.java
--- a/test/java/util/ServiceLoader/FooService.java	Thu Oct 02 19:58:32 2008 -0700
+++ b/test/java/util/ServiceLoader/FooService.java	Sat Oct 04 13:53:11 2008 +0200
@@ -21,4 +21,5 @@
  * have any questions.
  */
 
-public abstract class FooService { }
+public abstract class FooService { 
+}
diff -r 14f50aee4989 test/java/util/ServiceLoader/basic.sh
--- a/test/java/util/ServiceLoader/basic.sh	Thu Oct 02 19:58:32 2008 -0700
+++ b/test/java/util/ServiceLoader/basic.sh	Sat Oct 04 13:53:11 2008 +0200
@@ -71,6 +71,18 @@
       (cd $JARD; "$JAR" -cf ../p$n.jar *)
     done
 
+    rm -rf $JARD/*; mkdir -p $JARD/META-INF/services
+    echo "FooProvider1.create1()" \
+        >$JARD/META-INF/services/FooService
+    cp $TESTCLASSES/FooProvider1.class $JARD
+    (cd $JARD; "$JAR" -cf ../p-c1.jar *)
+
+    rm -rf $JARD/*; mkdir -p $JARD/META-INF/services
+    echo "FooProvider2.create2()" \
+        >$JARD/META-INF/services/FooService
+    cp $TESTCLASSES/FooProvider1.class $JARD
+    (cd $JARD; "$JAR" -cf ../p-c2.jar *)
+
     mv p3.jar $EXTD
 
     cp $TESTCLASSES/Load.class $TESTD
@@ -113,6 +125,9 @@
 
 go ".${SEP}p2.jar" "" FooProvider2
 
+go ".${SEP}p-c2.jar" "" FooProvider2
+go ".${SEP}p-c1.jar" "" FooProvider1
+
 go "" "-Djava.ext.dirs=$EXTD" FooProvider3
 
 go "$TESTD${SEP}p2.jar" "" FooProvider1 FooProvider2

Reply via email to