Project Jigsaw: Scopes
For module dependencies, the common case is code in one module
that has static references to types in some other module. The other
module must be present at compile-time and runtime. We say
requires M; in a module declaration.
In less common cases, the need for the other module to be
present at compile-time and/or runtime may be relaxed. The author
of a module expresses this by adding scopes to module
dependencies. A scope indicates a phase at which a type must be
visible and accessible if the program is to ultimately run
requires M; is shorthand for a
dependency which must be present at both compile-time and runtime,
i.e. in the compilation and execution scopes:
requires M for
Service dependencies do not have scopes. A service dependency is a request for resolution in all phases to bind a service provider class to a service interface. The availability of service provider modules may vary across phases and still the program may ultimately run successfully. A module's service dependency may even be flagged as optional to allow the module to resolve with no service provider modules available - code is assumed to handle such a scenario at runtime. The fact that service provider classes are never known, in any phase, to a service consumer module, is markedly different than how ordinary classes referenced by a module are known to it, but not necessarily in every phase (e.g. reflection may be used to avoid a compile-time dependency).
(It is plausible to imagine resolution requiring that a minimum number of service provider classes be bindable in some phases but not in others. However, this is orthogonal to resolution requiring that referenced types be visible at some phases and not others.)
There are two special cases for code that has static references:
1) The static reference is an annotation whose declaration
retention policy of
SOURCE. This is a rare case in
the Java language of a source artifact being compiled to nothing in
the class file. There is no need for the module containing the
annotation's declaration to be present after compilation. A similar
situation is source code that makes a static reference to a member
which is a constant variable; the member's value will be inlined in
the referring code, so the module containing the member is not
needed after compilation.
For (1), we say
requires M for compilation; in a
2) The code which makes static references to types is guarded dynamically by code which reflectively checks the existence of those types. At runtime, the program will typically continue to execute with reduced functionality if the statically-referenced types are not available. At compile-time, however, the compiler has no leeway: the statically-referenced types must be visible.
For (2), we say
requires M for compilation,
reflection; in a module declaration.
The uncommon case is code that uses types in some other module but makes no static references to them. There are two sub-cases:
a) The code needs the other module to be visible at runtime, i.e. the other module must be installed so that its types may be used reflectively at runtime. This sub-case is likely to be very uncommon - code that uses types in some other module solely through reflection is surely flexible enough to execute even if those types are invisible. After all, if visibility of those types was essential, then the code could have simply made static references to them (possibly with a dynamic guard as in (2) above).
For (a), we say
requires M for execution; in a
b) The code does not need the other module to be visible at runtime, i.e. the other module may not be installed and the program will continue to execute without using that module's types. The other module is effectively always "optional", whether at compile-time, runtime, or any other time. This sub-case is likely to be more common than (a).
For (b), we say
requires M for reflection; in a
Table of scenarios
The policies above are summarized in the following table, where
M is a module and
T is a type in module
|Static reference to
(Dynamic guard + static references)
|No static reference to
(Dynamic guard + no static references)
The table is perhaps clearer if
optional execution, and if
M; is written out "in full":
|Static reference to
|No static reference to
The second row is the equivalent of the first row without the
compilation scope, since the second row assumes no
static references, hence no compile-time dependencies.
In the above discussion, there are scopes for compilation, execution, and optional execution. What about optional compilation? This scope is plausible in the context of annotation processors, which run at compile-time and which may be able to continue running even if some of their referenced types are unavailable.
That said, the proper way to view an annotation processor is as
a standalone module which has a) a main class that implements
javax.annotation.processing.Processor and b) its own
dependencies, some of which may be optional. The compile-time of a
module undergoing annotation processing is simultaneously the
run-time of the module doing the annotation processing. Therefore,
there is no need for a module dependency which is optional at
compilation, as such a dependency is instead optional at execution
for some other module (the "annotation processor").