Hi,

Excellent work and congratulations on finally bringing modules to Java!

I tried to run a medium-sized stand-alone server application with Jigsaw 9.0 EA build and it seems to work fine after adding a single -XaddExports option. This app uses the following 3rd party libraries/components:

Hibernate 4.3
Jasper Reports 4.5
Jetty 6.1.5
JRules 6.7
Spring 4.2
Coherence 3.7
...and lots of other small libraries

All used libraries behave nicely. The -XaddExports:java.rmi/sun.rmi.server=ALL-UNNAMED was needed because of Oracle Coherence!

Building the app with Maven doesn't work though. Maven compiler module doesn't seem to be able to use the javac compiler API correctly. Maven folks would have to look at it.

Spotted an error in "The State of the Module System" document at: http://openjdk.java.net/projects/jigsaw/spec/sotms/ ... In the "Implied readability" section, it writes:

The java.sql.Driver interface, in particular, declares the public method

public Logger getParentLogger();

where Logger is a type declared in the exported java.util.logging package of the java.logging module.

Suppose that code in the com.foo.app module invokes this method in order to acquire a logger and log a message:

String url = ...;
Properties props = ...;
Driver d = DriverManager.getDriver(url);
Connection c = d.connect(url, props);
d.getParentLogger().info("Connection acquired");

If the com.foo.app module is declared as above then this will not work: *The getParentLogger method is defined in the Logger class, which is in the java.logging module, which is not read by the com.foo.app module. That class is therefore inaccessible to code in the com.foo.app module and so the invocation of the getParentLogger method will fail, at both compile time and run time.*


The correct text should be, I think: "The getParentLogger method defined in Driver class has a signature with a return type of Logger class, which is in the java.logging module, which is not read by the com.foo.app module. That class is therefore inaccessible to code in the com.foo.app module and so the invocation of the .info method will fail, at both compile time and run time (in case the modules were successfully compiled together using legacy compiler mode and then their artifacts split into modules)".

The fact is that getParentLogger method can be invoked even though it's return type is in an unreadable module if the return type is not used in any way in such invocation. For example:

d.getParentLogger().toString();

(the above assumes that .toString() is declared in Object and not overridden in Logger). If it is overriden, it can be invoked nevertheless with the following trick:

((Object) d.getParentLogger()).toString().

The important thing is that Logger as a type must not be referenced in bytecodes if the bytecodes are in a module that doesn't read java.logging module.

Am I correct? I have experimented with the EA and this is my experience. Is the prototype working correctly in this respect?

Now just a thought on implied readability. While it is a convenient feature, it can be dangerous if not used correctly. I think a module should declare it's own dependencies (requires) for all types it references explicitly. In above example, it invokes method Loger.info() and therefore uses types from module java.logging explicitly.

A case where implied readability would be abused could be described in the following example:

- java.sql module uses java.logging internally and also exposes a method Driver.getParentLogger() which returns a Logger which is declared in java.logging module. Therefore java.sql requires *public* java.logging to imply the readability of java.logging for modules that require only java.sql and not java.logging explicitly. - com.foo.internal module requires java.sql and only uses the part of it's API that doesn't need types from java.logging - com.foo.internal also uses Logger internally, but fails to declare this. It nevertheless works since java.logging is implicitly readable to it through declaration in java.sql - java.sql module decides to change it's internal use of logging API and instead of java.logging starts using org.apache.log4j. It now requires *public* org.apache.log4j instead of java.logging - an incompatible change, yes, but should not affect com.foo.internal that only uses the part of it's API that doesn't use types from java.logging - com.foo.internal now fails because it still uses java.logging, but implicit readability of it is not provided by java.sql any more. If com.foo.internal declared explicitly a dependency on java.logging, such configuration would still work.

So while convenient, implied readability should be used with care.

Regards, Peter

P.S.

Here's a perl script one can find useful for parsing exception traces (from logs for example) and turning them into -XaddExports options that allow access to non-public APIs:

#!/bin/perl
my %exports = ();
while (<STDIN>) {
  chomp;
if (/cannot access \S+ (\S+) \(in module: (.*?)\), (\S+) is not exported to (.*)/) {
    my $target_module = ($4 eq "Unnamed Module") ? "ALL-UNNAMED" : $4;
    my $source_module = $2;
    my $source_pkg = $3;
$exports{"-XaddExports:$source_module/$source_pkg=$target_module"}++;
  }
}
print join(" ", keys %exports) . "\n";



Reply via email to