2

Let say there is module x that exports something. Then there is module a:

module a {
 exports some.package.from.a
 requires transitive x;
}

Then there are 100 modules b that require a :

module b1 {
  exports some.package.from.b1
  requires a;
}
...
module b100 {
  exports some.package.from.b100
  requires a;
}

Then there are 100 modules c that require respective bs

module c1 {
  requires b1;
}

...
module c100 {
  requires b100;
}

Assume c1 ... c100 do not need to know about packages from a. Those are only used by b1 ... b100 internally and thus not transitively required.

However the API exported by b1 ... b100 uses classes in packages exported by x.

Modules c1 ... c100 can NOT see those. Trying to compile them, results in

Compilation failure:
(package x.y.z is declared in module x, but module c1 does not read it)

Why is requires transitive limited to the modules that directly require the module containing it? How can the issue described above be solved without editing 100 module-info files?


UPDATE: There are at least 3 ways to "fix" this:

  • add requires transitive a to b1 ... b100
  • add requires transitive x to b1 ... b100
  • add requires x to c1 ... c100

While all would work in this particular case, it is not clear which one should be used and what the side effects of each of them are. Moreover this is only 3 level deep hierarchy - with more levels, it gets even more complex.

If requires transitive was to be transitive for all dependents (as the word transitive implies) this would be solved automatically. If there was no transitive there would have been only one way to approach the issue. With transitive limited to the direct dependents there are "options" which needs to considered, but it seams not enough information is provided for one to make proper choice.

Milen Dyankov
  • 2,972
  • 14
  • 25
  • 2
    Sounds like you are looking to [increase the module readability](http://openjdk.java.net/jeps/261#Increasing-readability)? – Naman Apr 28 '18 at 03:25
  • Perhaps! I just want to understand where the design decision behind this limitation comes from. I couldn't find any explanation. I know that according to section 7.7.1 of the The Java Language Specification, the "proper" way is to add `requires transitive a` to all of the `b` modules. Yet this looks counterintuitive. Moreover it is not at all necessary for any other reason but making `x` transitive on more than one level. – Milen Dyankov Apr 28 '18 at 13:41

2 Answers2

1

If a module (your b's) use another module's (your x) types independently of an interspersed requires transitive (b's on a's), they should require them directly and not rely on implied readability.

In your case, b's should absolutely requires transitive x.

Nicolai Parlog
  • 47,972
  • 24
  • 125
  • 255
  • How do you know this is the way it should be? Perhaps you are right, but it's not clear to me how did you arrive at that conclusion? And that is basically my question - WHY it has to be done in a particular way. Given all possibilities this approach creates, how does one know which one to use (see updated question)? – Milen Dyankov Apr 30 '18 at 14:38
  • To explain the confusion, if I read the first paragraph of your answer without the text in brackets, I would come to the conclusion that the right approach is to add `requires x` to `c1` ... `c100` – Milen Dyankov Apr 30 '18 at 14:45
  • 2
    @MilenDyankov I think this line from the question is significant: "However the API exported by b1 ... b100 uses classes in packages exported by x." Any consumer of an API of `bK` will need to say `requires bK`. From your statement, that API will also use types from `x`, so the module declaration for `bK` should also say `requires transitive x`. – Stuart Marks Apr 30 '18 at 21:14
  • @StuartMarks Indeed every `cK` in the example say `requires bK`. However how do one decides between (1) `bK` say `requires transitive x` _which is what you suggest_ (2) `bK` say `requires transitive a` _which is what one finds in section 7.7.1 of the The Java Language Specification_ (3) `cK` say `requires x` _which is also an option_ ? – Milen Dyankov Apr 30 '18 at 21:30
  • 1
    @MilenDyankov The JLS 7.7.1 example is different from yours; no need to follow it blindly. Module bK certainly uses types from x, so it must at least say `requires x`. The API of bK, used by its clients cK, also uses types from x. Module cK thus needs `requires bK`. It would work, though, poorly, if it were necessary for every cK **also** to declare `requires x`. Since requiring bK implies that x is also required, bK can state this by declaring `requires transitive x` so that x is "passed through" to bK's clients. – Stuart Marks May 01 '18 at 15:38
  • @StuartMarks if I take your sentence _"Module bK certainly uses types from x, so it must at least say `requires x`"_ and replace in it `bK` with `cK` it still holds true. Both use `x` through some other module(s) so what's the difference? Do you see the confusion now? It is not about what would work, all 3 approaches "work". It is about the ambiguity the design introduces and lack of understanding of the intended usage. I would appreciate an answer which explains "in such and such cases one should do that because of this reason and not use that because of such and such reason". – Milen Dyankov May 03 '18 at 09:15
  • 1
    @MilenDyankov Suppose module b declares `requires x` and not `requires transitive x`, and as stated previously, b's API uses types from x. Module c needs to use b, so it declares `requires b`. This fails, because b's API cannot be used by c, because x's types aren't readable from c. This is an error in b's declaration; b's clients can't use its API without x, therefore b should declare `requires transitive x`. It's possible to *work around* this error by having c declare `requires x` but that doesn't fix the fundamental error. – Stuart Marks May 03 '18 at 15:37
  • 1
    @MilenDyankov So that's why b declaring `requires transitive x` (approach 2) is preferable to c declaring `requires x` (approach 3). Now consider b declaring `requires transitive a` (approach 1). In the question you stated *Assume c does not need to know about packages from a. Those are only used by b internally and thus not transitively required.* Having b declare `requires transitive a` would mean that a is readable to clients of b (such as c) which contradicts your statement. Therefore, approach 1 is incorrect. – Stuart Marks May 03 '18 at 15:44
  • @StuartMarks I am still not 100% convinced because you assume `b` knows that types come from `x` while from `b`'s perspective they come transitively from `a`. I know one can figure out that it's `x` but that's not obvious in complex dependency graphs. Yet, that is fair explanation, so if you care to add you comments as an answer I'll accept it. I'd also appreciate some explanation of your statement that having cK `requires x` would work "poorly". – Milen Dyankov May 14 '18 at 13:57
0

Ok, 4 years later but I'll have a crack at some of those questions.

I know one can figure out that it's x but that's not obvious in complex dependency graphs.

I think the key here is that to a large extent we only ever need to look 1 level deep in a dependency graph. That still might be quite a lot to look at but I believe we would not look to solve this outside of the C's or B's.

That is, we have the compilation error with the C's. Either we add the requires X to C's OR change the B's somehow (with 2 options to look at).

That is, we are relatively unlikely to modify any module deeper in the dependency graph because other modules have somewhat proven they don't need X. Modifying modules that currently don't need to "transitively export" X would not seem correct. [Edit: given we generally desire to minimise what is transitively exported]

Therefore, our solution should be found at a max depth of 1 in the dependency graph - In the C's or B's.

StuartMarks I am still not 100% convinced because you assume b knows that types come from x while from b's perspective they come transitively from a.

I think it's more that B's public API explicitly does not include X. B's use of X was deemed for internal purposes only (perhaps incorrectly).

It's when we are compiling C that we see that does not match up with B's module-info / what B makes readable. It's at this point we have an issue with B's exported API (what B makes readable) and we have the options:

  • Q1: Was B API supposed to export A (and X via transitive) ?
  • Q2: Was B API supposed to export X only (and not include A) ?
  • Q3: Was B API correct and we need all the C's to require X ?

If the answer was Q1 - Yes, then we change B's to requires transitive a;

If the answer was Q2 - Yes, then we change B's by adding requires transitive x;

If the answer was Q3 - Yes, then we change C's by adding requires x;

Rob Bygrave
  • 3,861
  • 28
  • 28