changeset 17649:7a414cba0dbf

Initial support for exports dynamic default
author alanb
date Thu, 25 Aug 2016 16:22:39 +0100
parents ee2d9ac5e51f
children 93e8da13581c
files src/java.base/share/classes/java/lang/module/ModuleDescriptor.java src/java.base/share/classes/java/lang/module/ModuleInfo.java src/java.base/share/classes/java/lang/reflect/Module.java src/java.base/share/classes/jdk/internal/loader/BuiltinClassLoader.java src/java.base/share/classes/jdk/internal/loader/Loader.java src/java.base/share/classes/jdk/internal/module/ClassFileAttributes.java src/java.base/share/classes/sun/launcher/LauncherHelper.java src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModuleDescriptorPlugin.java test/java/lang/module/ConfigurationTest.java test/java/lang/module/ModuleDescriptorTest.java test/java/lang/reflect/Layer/BasicLayerTest.java
diffstat 11 files changed, 257 insertions(+), 70 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java	Thu Aug 25 09:28:43 2016 +0100
+++ b/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java	Thu Aug 25 16:22:39 2016 +0100
@@ -288,16 +288,29 @@
          */
         private Exports(Set<Modifier> ms, String source, Set<String> targets) {
             if (ms.isEmpty()) {
-                this.mods = Collections.emptySet();
+                ms = Collections.emptySet();
             } else {
-                this.mods = Collections.unmodifiableSet(EnumSet.copyOf(ms));
+                ms = Collections.unmodifiableSet(EnumSet.copyOf(ms));
             }
-            this.source = requirePackageName(source);
+
+            if (source != null) {
+                requirePackageName(source);
+            } else {
+                // exports dynamic default [private] to <targets>
+                if (!ms.contains(Modifier.DYNAMIC)) {
+                    throw new IllegalArgumentException("DYNAMIC modifier missing");
+                }
+            }
+
+            targets = Collections.unmodifiableSet(new HashSet<>(targets));
             if (targets.isEmpty()) {
                 throw new IllegalArgumentException("Empty target set");
             }
             targets.stream().forEach(Checks::requireModuleName);
-            this.targets = Collections.unmodifiableSet(new HashSet<>(targets));
+
+            this.mods = ms;
+            this.source = source;
+            this.targets = targets;
         }
 
         private Exports(Set<Modifier> ms,
@@ -314,11 +327,22 @@
          */
         private Exports(Set<Modifier> ms, String source) {
             if (ms.isEmpty()) {
-                this.mods = Collections.emptySet();
+                ms = Collections.emptySet();
             } else {
-                this.mods = Collections.unmodifiableSet(EnumSet.copyOf(ms));
+                ms = Collections.unmodifiableSet(EnumSet.copyOf(ms));
             }
-            this.source = requirePackageName(source);
+
+            if (source != null) {
+                requirePackageName(source);
+            } else {
+                // exports dynamic default [private] to <targets>
+                if (!ms.contains(Modifier.DYNAMIC)) {
+                    throw new IllegalArgumentException("DYNAMIC modifier missing");
+                }
+            }
+
+            this.mods = ms;
+            this.source = source;
             this.targets = Collections.emptySet();
         }
 
@@ -347,7 +371,7 @@
         }
 
         /**
-         * Returns the package name.
+         * Returns the package name or {@code null} if this is a default export.
          *
          * @return The package name
          */
@@ -440,12 +464,14 @@
         private final Set<String> providers;
 
         private Provides(String service, Set<String> providers) {
+            providers = Collections.unmodifiableSet(new LinkedHashSet<>(providers));
+            if (providers.isEmpty()) {
+                throw new IllegalArgumentException("Empty providers set");
+            }
+            providers.forEach(Checks::requireServiceProviderName);
+
             this.service = requireServiceTypeName(service);
-            if (providers.isEmpty())
-                throw new IllegalArgumentException("Empty providers set");
-            providers.forEach(Checks::requireServiceProviderName);
-            this.providers =
-                Collections.unmodifiableSet(new LinkedHashSet<>(providers));
+            this.providers = providers;
         }
 
         private Provides(String service, Set<String> providers, boolean unused) {
@@ -1103,20 +1129,25 @@
     }
 
     /**
-     * Returns the names of the packages defined in, but not exported by, this
-     * module.
+     * Returns the names of the packages defined in, but not explicitly exported
+     * by, this module.
+     *
+     * @apiNote Packages that are not explicitly exported may still be exported
+     * at run time when the module declares a default export.
      *
      * @return A possibly-empty unmodifiable set of the concealed packages
      */
     public Set<String> conceals() {
         Set<String> conceals = new HashSet<>(packages);
-        exports.stream().map(Exports::source).forEach(conceals::remove);
+        exports.stream().filter(e -> e.source() != null)
+                .map(Exports::source)
+                .forEach(conceals::remove);
         return emptyOrUnmodifiableSet(conceals);
     }
 
     /**
      * Returns the names of all the packages defined in this module, whether
-     * exported or concealed.
+     * explicitly exported or concealed.
      *
      * @return A possibly-empty unmodifiable set of the all packages
      */
@@ -1158,6 +1189,8 @@
         final Set<String> uses = new HashSet<>();
         final Map<String, Exports> unqualifiedExports = new HashMap<>();
         final Map<String, Exports> qualifiedExports = new HashMap<>();
+        Exports defaultUnqualified;
+        Exports defaultQualified;
         final Map<String, Provides> provides = new HashMap<>();
         final Set<String> conceals = new HashSet<>();
         Version version;
@@ -1331,8 +1364,12 @@
          *
          * <p> A specific package may be exported both unconditionally and to
          * a set of target modules for cases where the qualified export is a
-         * strict superset of the unconditional export. Specifically, for a
-         * package {@code p}, the following combinations are allowed: </p>
+         * <em>strict superset</em> of the unconditional export. A strict
+         * superset is where the qualified exports has {@code private} when
+         * the unconditional export doesn't, or where the unconditional
+         * export has {@code dynamic} and the qualified export doesn't. More
+         * specifically, for a package {@code p}, the following combinations
+         * are allowed: </p>
          * <table border=1 cellpadding=5 summary="allowed">
          *     <tr>
          *         <th>Unconditional export</th>
@@ -1340,23 +1377,27 @@
          *     </tr>
          *     <tr>
          *         <td>{@code exports p}</td>
-         *         <td>{@code exports private p to <target-modules>}
+         *         <td>{@code exports private p to <target-modules>}</td>
+         *     </tr>
+         *     <tr>
+         *         <td>{@code exports private p}</td>
+         *         <td>N/A</td>
          *     </tr>
          *     <tr>
          *         <td>{@code exports dynamic p}</td>
-         *         <td>{@code exports p to <target-modules>}
+         *         <td>{@code exports p to <target-modules>}</td>
          *     </tr>
          *     <tr>
          *         <td>{@code exports dynamic p}</td>
-         *         <td>{@code exports private p to <target-modules>}
+         *         <td>{@code exports private p to <target-modules>}</td>
          *     </tr>
          *     <tr>
          *         <td>{@code exports dynamic p}</td>
-         *         <td>{@code exports dynamic private p to <target-modules>}
+         *         <td>{@code exports dynamic private p to <target-modules>}</td>
          *     </tr>
          *     <tr>
          *         <td>{@code exports dynamic private p}</td>
-         *         <td>{@code exports private p to <target-modules>}
+         *         <td>{@code exports private p to <target-modules>}</td>
          *     </tr>
          * </table>
          * <p> An export may otherwise not be added when the package has
@@ -1481,28 +1522,6 @@
         }
 
         /**
-         * Adds an export to a target module.
-         *
-         * @param  pn
-         *         The package name
-         * @param  target
-         *         The target module name
-         *
-         * @return This builder
-         *
-         * @throws IllegalArgumentException
-         *         If the package name or target module is {@code null} or is
-         *         not a legal Java identifier
-         * @throws IllegalStateException
-         *         If the package is already declared as a concealed package
-         *         or the export conflicts with a declared exported package
-         *         (see {@link #exports(Exports) exports(Exports)}
-         */
-        public Builder exports(String pn, String target) {
-            return exports(pn, Collections.singleton(target));
-        }
-
-        /**
          * Adds an export.
          *
          * @param  pn
@@ -1522,6 +1541,76 @@
             return exports(Collections.emptySet(), pn);
         }
 
+        /**
+         * Adds a <em>default</em> export.
+         *
+         * @param  ms
+         *         The set of modifiers
+         *
+         * @return This builder
+         *
+         * @throws IllegalArgumentException
+         *         If set of modifiers does not contain {@code DYNAMIC}
+         * @throws IllegalStateException
+         *         If the default unqualified export has already been declared
+         *         or the export exports with the declared qualified export
+         */
+        public Builder exportsDefault(Set<Exports.Modifier> ms) {
+            if (defaultUnqualified != null) {
+                throw new IllegalStateException("default unqualified export"
+                        + " already declared");
+            }
+            if (defaultQualified != null) {
+                Set<Exports.Modifier> mods = defaultQualified.modifiers();
+                boolean isPrivate = mods.contains(Exports.Modifier.PRIVATE);
+                if (!isPrivate) {
+                    throw new IllegalStateException("default unqualified exports"
+                            + " conflicts with default qualified exports");
+                }
+            }
+
+            Exports e = new Exports(ms, null);
+            defaultUnqualified = e;
+            return this;
+        }
+
+        /**
+         * Adds a <em>default</em> export to a set of target modules.
+         *
+         * @param  ms
+         *         The set of modifiers
+         * @param  targets
+         *         The set of target modules names
+         *
+         * @return This builder
+         *
+         * @throws IllegalArgumentException
+         *         If set of modifiers does not contain {@code DYNAMIC}
+         * @throws IllegalStateException
+         *         If the default qualified export has already been declared
+         *         or the export exports with the declared unqualified export
+         */
+        public Builder exportsDefault(Set<Exports.Modifier> ms, Set<String> targets) {
+            if (defaultQualified != null) {
+                throw new IllegalStateException("default qualified export"
+                                                + " already declared");
+            }
+            if (defaultUnqualified != null) {
+                Set<Exports.Modifier> mods = defaultUnqualified.modifiers();
+                boolean isPrivate = mods.contains(Exports.Modifier.PRIVATE);
+                if (isPrivate) {
+                    throw new IllegalStateException("default qualified exports"
+                            + " conflicts with default unqualified exports");
+                }
+            }
+
+            // no check to ensure targets is not empty
+            Exports e = new Exports(ms, null, targets);
+            defaultQualified = e;
+            return this;
+        }
+
+
         // Used by ModuleInfo, after a packageFinder is invoked
         /* package */ Set<String> exportedPackages() {
             Set<String> exported = new HashSet<>();
@@ -1799,7 +1888,12 @@
             Set<Exports> exports = new HashSet<>();
             exports.addAll(unqualifiedExports.values());
             exports.addAll(qualifiedExports.values());
-
+            if (defaultUnqualified != null) {
+                exports.add(defaultUnqualified);
+            }
+            if (defaultQualified != null) {
+                exports.add(defaultQualified);
+            }
             Set<Requires> requires = new HashSet<>(this.requires.values());
 
             return new ModuleDescriptor(name,
--- a/src/java.base/share/classes/java/lang/module/ModuleInfo.java	Thu Aug 25 09:28:43 2016 +0100
+++ b/src/java.base/share/classes/java/lang/module/ModuleInfo.java	Thu Aug 25 16:22:39 2016 +0100
@@ -341,9 +341,17 @@
                         int exports_to_index = in.readUnsignedShort();
                         targets.add(cpool.getUtf8(exports_to_index));
                     }
-                    if (pkg != null) builder.exports(mods, pkg, targets);
+                    if (pkg == null) {
+                        builder.exportsDefault(mods, targets);
+                    } else {
+                        builder.exports(mods, pkg, targets);
+                    }
                 } else {
-                    if (pkg != null) builder.exports(mods, pkg);
+                    if (pkg == null) {
+                        builder.exportsDefault(mods);
+                    } else {
+                        builder.exports(mods, pkg);
+                    }
                 }
             }
         }
--- a/src/java.base/share/classes/java/lang/reflect/Module.java	Thu Aug 25 09:28:43 2016 +0100
+++ b/src/java.base/share/classes/java/lang/reflect/Module.java	Thu Aug 25 16:22:39 2016 +0100
@@ -1089,8 +1089,19 @@
             Map<String, Boolean> unqualifiedExports = new HashMap<>();
             Map<String, Map<Module, Boolean>> qualifiedExports = new HashMap<>();
 
+            Exports defaultUnqualified = null;
+            Exports defaultQualified = null;
+
             for (Exports export : descriptor.exports()) {
                 String source = export.source();
+                if (source == null) {
+                    if (export.targets().isEmpty()) {
+                        defaultUnqualified = export;
+                    } else {
+                        defaultQualified = export;
+                    }
+                    continue;
+                }
                 String sourceInternalForm = source.replace('.', '/');
 
                 boolean b = export.modifiers().contains(Exports.Modifier.PRIVATE);
@@ -1120,6 +1131,44 @@
                 }
             }
 
+            // exports dynamic [private] default
+            if (defaultUnqualified != null) {
+                Exports e = defaultUnqualified;
+                boolean b = e.modifiers().contains(Exports.Modifier.PRIVATE);
+                Boolean value = Boolean.valueOf(b);
+
+                for (String source : descriptor.packages()) {
+                    if (!unqualifiedExports.containsKey(source)) {
+                        String sourceInternalForm = source.replace('.', '/');
+                        addExportsToAll0(m, sourceInternalForm);
+                        unqualifiedExports.put(source, value);
+                    }
+                }
+            }
+
+            // exports dynamic [private] default to <targets>
+            if (defaultQualified != null) {
+                Exports e = defaultQualified;
+                boolean b = e.modifiers().contains(Exports.Modifier.PRIVATE);
+                Boolean value = Boolean.valueOf(b);
+
+                for (String source : descriptor.packages()) {
+                    if (!qualifiedExports.containsKey(source)) {
+                        String sourceInternalForm = source.replace('.', '/');
+                        Map<Module, Boolean> targets = new HashMap<>();
+                        for (String target : defaultQualified.targets()) {
+                            // only export to modules that are in this configuration
+                            Module m2 = modules.get(target);
+                            if (m2 != null) {
+                                addExports0(m, sourceInternalForm, m2);
+                                targets.put(m2, value);
+                            }
+                        }
+                        qualifiedExports.put(source, targets);
+                    }
+                }
+            }
+
             if (!unqualifiedExports.isEmpty())
                 m.unqualifiedExports = unqualifiedExports;
             if (!qualifiedExports.isEmpty())
--- a/src/java.base/share/classes/jdk/internal/loader/BuiltinClassLoader.java	Thu Aug 25 09:28:43 2016 +0100
+++ b/src/java.base/share/classes/jdk/internal/loader/BuiltinClassLoader.java	Thu Aug 25 16:22:39 2016 +0100
@@ -884,7 +884,8 @@
      */
     private boolean isExported(ModuleReference mref, String pn) {
         for (ModuleDescriptor.Exports e : mref.descriptor().exports()) {
-            if (!e.isQualified() && e.source().equals(pn)) {
+            String source = e.source();
+            if (!e.isQualified() && (source == null || source.equals(pn))) {
                 return true;
             }
         }
--- a/src/java.base/share/classes/jdk/internal/loader/Loader.java	Thu Aug 25 09:28:43 2016 +0100
+++ b/src/java.base/share/classes/jdk/internal/loader/Loader.java	Thu Aug 25 16:22:39 2016 +0100
@@ -635,7 +635,8 @@
      */
     private boolean isExported(ModuleReference mref, String pn) {
         for (ModuleDescriptor.Exports e : mref.descriptor().exports()) {
-            if (!e.isQualified() && e.source().equals(pn)) {
+            String source = e.source();
+            if (!e.isQualified() && (source == null || source.equals(pn))) {
                 return true;
             }
         }
--- a/src/java.base/share/classes/jdk/internal/module/ClassFileAttributes.java	Thu Aug 25 09:28:43 2016 +0100
+++ b/src/java.base/share/classes/jdk/internal/module/ClassFileAttributes.java	Thu Aug 25 16:22:39 2016 +0100
@@ -145,9 +145,17 @@
                             off += 2;
                             targets.add(t);
                         }
-                        if (pkg != null) builder.exports(mods, pkg, targets);
+                        if (pkg == null) {
+                            builder.exportsDefault(mods, targets);
+                        } else {
+                            builder.exports(mods, pkg, targets);
+                        }
                     } else {
-                        if (pkg != null) builder.exports(mods, pkg);
+                        if (pkg == null) {
+                            builder.exportsDefault(mods);
+                        } else {
+                            builder.exports(mods, pkg);
+                        }
                     }
                 }
             }
@@ -218,8 +226,12 @@
             } else {
                 attr.putShort(descriptor.exports().size());
                 for (Exports e : descriptor.exports()) {
-                    String pkg = e.source().replace('.', '/');
-                    attr.putShort(cw.newUTF8(pkg));
+                    if (e.source() == null) {
+                        attr.putShort(0);
+                    } else {
+                        String pkg = e.source().replace('.', '/');
+                        attr.putShort(cw.newUTF8(pkg));
+                    }
 
                     int flags = 0;
                     if (e.modifiers().contains(Exports.Modifier.PRIVATE))
--- a/src/java.base/share/classes/sun/launcher/LauncherHelper.java	Thu Aug 25 09:28:43 2016 +0100
+++ b/src/java.base/share/classes/sun/launcher/LauncherHelper.java	Thu Aug 25 16:22:39 2016 +0100
@@ -991,9 +991,21 @@
                     ostream.format("  uses %s%n", s);
                 }
 
-                // sorted exports
+                // exports
+                Exports defaultUnqualified = null;
+                Exports defaultQualified = null;
                 Set<Exports> exports = new TreeSet<>(Comparator.comparing(Exports::source));
-                exports.addAll(md.exports());
+                for (Exports e : md.exports()) {
+                    if (e.source() == null) {
+                        if (e.targets().isEmpty()) {
+                            defaultUnqualified = e;
+                        } else {
+                            defaultQualified = e;
+                        }
+                    } else {
+                        exports.add(e);
+                    }
+                }
                 for (Exports e : exports) {
                     String modsAndSource = Stream.concat(toStringStream(e.modifiers()),
                                                          Stream.of(e.source()))
@@ -1005,6 +1017,15 @@
                         ostream.println();
                     }
                 }
+                if (defaultUnqualified != null) {
+                    String mods = toString(defaultUnqualified.modifiers());
+                    ostream.format("  exports %s default%n", mods);
+                }
+                if (defaultQualified != null) {
+                    String mods = toString(defaultQualified.modifiers());
+                    ostream.format("  exports %s default", mods);
+                    formatCommaList(ostream, " to", defaultQualified.targets());
+                }
 
                 // concealed packages
                 new TreeSet<>(md.conceals())
@@ -1019,6 +1040,10 @@
         }
     }
 
+    static <T> String toString(Set<T> s) {
+        return toStringStream(s).collect(Collectors.joining(" "));
+    }
+
     static <T> Stream<String> toStringStream(Set<T> s) {
         return s.stream().map(e -> e.toString().toLowerCase());
     }
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModuleDescriptorPlugin.java	Thu Aug 25 09:28:43 2016 +0100
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModuleDescriptorPlugin.java	Thu Aug 25 16:22:39 2016 +0100
@@ -185,9 +185,11 @@
             Checks.requireModuleName(req.name());
         }
         for (Exports e : md.exports()) {
-            Checks.requirePackageName(e.source());
+            String source = e.source();
+            if (source != null)
+                Checks.requirePackageName(e.source());
             if (e.isQualified())
-               e.targets().forEach(Checks::requireModuleName);
+                e.targets().forEach(Checks::requireModuleName);
         }
         for (Map.Entry<String, Provides> e : md.provides().entrySet()) {
             String service = e.getKey();
--- a/test/java/lang/module/ConfigurationTest.java	Thu Aug 25 09:28:43 2016 +0100
+++ b/test/java/lang/module/ConfigurationTest.java	Thu Aug 25 16:22:39 2016 +0100
@@ -1304,7 +1304,7 @@
 
         ModuleDescriptor descriptor3
             =  new ModuleDescriptor.Builder("m3")
-                .exports("p", "m1")
+                .exports("p", Set.of("m1"))
                 .build();
 
         ModuleFinder finder
--- a/test/java/lang/module/ModuleDescriptorTest.java	Thu Aug 25 09:28:43 2016 +0100
+++ b/test/java/lang/module/ModuleDescriptorTest.java	Thu Aug 25 16:22:39 2016 +0100
@@ -252,7 +252,7 @@
 
     private Exports exports(String pn, String target) {
         return new Builder("foo")
-            .exports(pn, target)
+            .exports(pn, Set.of(target))
             .build()
             .exports()
             .iterator()
@@ -366,7 +366,7 @@
 
     @Test(expectedExceptions = IllegalStateException.class)
     public void testExportsToTargetWithConcealedPackage() {
-        new Builder("foo").conceals("p").exports("p", "bar");
+        new Builder("foo").conceals("p").exports("p", Set.of("bar"));
     }
 
     @Test(expectedExceptions = IllegalArgumentException.class )
@@ -385,11 +385,6 @@
         new Builder("foo").exports((Exports)null);
     }
 
-    @Test(expectedExceptions = IllegalArgumentException.class )
-    public void testExportsWithNullTarget() {
-        new Builder("foo").exports("p", (String) null);
-    }
-
     @Test(expectedExceptions = NullPointerException.class )
     public void testExportsWithNullTargets() {
         new Builder("foo").exports("p", (Set<String>) null);
@@ -428,7 +423,7 @@
 
     public void testExportsToString() {
         String s = new Builder("foo")
-            .exports("p1", "bar")
+            .exports("p1", Set.of("bar"))
             .build()
             .exports()
             .iterator()
--- a/test/java/lang/reflect/Layer/BasicLayerTest.java	Thu Aug 25 09:28:43 2016 +0100
+++ b/test/java/lang/reflect/Layer/BasicLayerTest.java	Thu Aug 25 16:22:39 2016 +0100
@@ -381,7 +381,7 @@
                 .build();
         ModuleDescriptor descriptor2
             =  new ModuleDescriptor.Builder("m2")
-                .exports("p", "m1")
+                .exports("p", Set.of("m1"))
                 .build();
 
         // m3 reads m4, m4 exports p to m3
@@ -391,7 +391,7 @@
                 .build();
         ModuleDescriptor descriptor4
             =  new ModuleDescriptor.Builder("m4")
-                .exports("p", "m3")
+                .exports("p", Set.of("m3"))
                 .build();
 
         ModuleFinder finder