5959 * Bundle B: Export-Package: b.api
6060 * Import-Package: d.api, f.api(optional)
6161 * Require-Bundle: C, E(optional), G(reexport)
62+ * b.api.MyObject extends c.api.MyObject implements d.api.Processor
63+ * b.internal.MyObject uses e.api + f.api (optional, internal only)
6264 * Bundle C: Export-Package: c.api (leaf)
65+ * c.api.Configurable interface, c.api.MyObject implements it
6366 * Bundle D: Export-Package: d.api (leaf)
64- * Bundle E: Export-Package: e.api (leaf)
65- * Bundle F: Export-Package: f.api (leaf)
67+ * d.api.Processor interface, d.api.MyObject with getData()
68+ * Bundle E: Export-Package: e.api (leaf, e.api.MyObject with activate())
69+ * Bundle F: Export-Package: f.api (leaf, f.api.MyObject with isAvailable())
6670 * Bundle G: Export-Package: g.api; Import-Package: h.api(optional)
67- * Bundle H: Export-Package: h.api (leaf)
71+ * g.api.MyObject.describe() uses h.api internally (optional)
72+ * Bundle H: Export-Package: h.api (leaf, h.api.MyObject with getIdentifier())
6873 * </pre>
6974 *
75+ * <b>Type hierarchy chain exercised by A:</b>
76+ * <pre>
77+ * c.api.Configurable (interface)
78+ * ↑ implements
79+ * c.api.MyObject (class with getValue(), getConfig())
80+ * ↑ extends
81+ * b.api.MyObject (class) ← also implements d.api.Processor
82+ * </pre>
83+ * When A uses b.api.MyObject and calls inherited methods, the compiler
84+ * needs c.api.MyObject, c.api.Configurable, d.api.Processor, and
85+ * d.api.MyObject on the classpath — modelling the real-world scenario from
86+ * <a href="https://github.com/eclipse-pde/eclipse.pde/issues/2195">issue
87+ * #2195</a> where IWidgetValueProperty's type hierarchy required the
88+ * databinding bundle on the classpath.
89+ *
7090 * Each bundle also has an internal package (e.g. {@code b.internal}) that is
7191 * <b>not</b> listed in Export-Package.
7292 * <p>
@@ -452,9 +472,12 @@ public void testExpectedCompilationMarkers_forbiddenIsError() throws Exception {
452472 * because transitive dependencies are on the classpath (PR #2218). Before
453473 * PR #2218, transitive types caused "The type X cannot be resolved. It is
454474 * indirectly referenced from required type Y" errors.</li>
455- * <li><b>No markers on accessible types:</b> b.api.MyObject (line 29) and
456- * g.api.MyObject (line 34) produce zero markers because they match
457- * {@code K_ACCESSIBLE} access rules for exported packages (§3.6.5).</li>
475+ * <li><b>No markers on accessible types:</b> b.api.MyObject (line 28) and
476+ * g.api.MyObject (line 64) produce zero markers because they match
477+ * {@code K_ACCESSIBLE} access rules for exported packages (§3.6.5).
478+ * Additionally, inherited method calls (e.g. {@code service.configure()},
479+ * {@code service.process()}) accessed through b.api.MyObject produce no
480+ * markers even though they involve transitive types (c.api, d.api).</li>
458481 * <li><b>Forbidden reference markers on all non-accessible types:</b> Every
459482 * type from non-exported packages of direct deps (b.internal, g.internal)
460483 * and from transitive-only bundles (C, D, E, F, H) produces exactly 2
@@ -481,10 +504,12 @@ private void assertExpectedCompilationMarkers(IProject project, int expectedSeve
481504 // ("The type 'MyObject' is not API") and one for the constructor
482505 // call ("The constructor 'MyObject()' is not API").
483506 //
484- // Lines 29 (b.api.MyObject) and 34 (g.api.MyObject) are
485- // K_ACCESSIBLE and must have NO markers. The total count assertion
486- // below implicitly validates this — any extra markers would
487- // increase the count.
507+ // Lines 28 (b.api.MyObject) and 64 (g.api.MyObject) are
508+ // K_ACCESSIBLE and must have NO markers. Lines 32-56 contain
509+ // method calls through b.api.MyObject (inherited methods from
510+ // c.api/d.api) and g.api.MyObject — also no markers. The total
511+ // count assertion below implicitly validates this — any extra
512+ // markers would increase the count.
488513 record ForbiddenRef (int line , String qualifiedType , String project ) {
489514 }
490515 List <ForbiddenRef > expected = List .of (
@@ -493,7 +518,7 @@ record ForbiddenRef(int line, String qualifiedType, String project) {
493518 // B/G entries. Non-exported packages are not returned by
494519 // StateHelper.getVisiblePackages() — they have no explicit
495520 // rule, so the catch-all EXCLUDE_ALL fires (§3.6.5).
496- new ForbiddenRef (31 , "b.internal.MyObject" , "B" ), new ForbiddenRef (36 , "g.internal.MyObject" , "G" ),
521+ new ForbiddenRef (62 , "b.internal.MyObject" , "B" ), new ForbiddenRef (70 , "g.internal.MyObject" , "G" ),
497522 // Transitive forbidden: ALL packages forbidden via the
498523 // single **/* K_NON_ACCESSIBLE rule added by
499524 // addTransitiveDependenciesWithForbiddenAccess().
@@ -502,26 +527,27 @@ record ForbiddenRef(int line, String qualifiedType, String project) {
502527 //
503528 // C: Required by B (Require-Bundle: C,
504529 // visibility:=private §3.13.1)
505- new ForbiddenRef (45 , "c.api.MyObject" , "C" ), new ForbiddenRef (46 , "c.internal.MyObject" , "C" ),
530+ new ForbiddenRef (77 , "c.api.MyObject" , "C" ), new ForbiddenRef (78 , "c.internal.MyObject" , "C" ),
506531 // D: Package imported by B (Import-Package: d.api §3.6.4
507532 // — never re-exports)
508- new ForbiddenRef (49 , "d.api.MyObject" , "D" ), new ForbiddenRef (50 , "d.internal.MyObject" , "D" ),
533+ new ForbiddenRef (81 , "d.api.MyObject" , "D" ), new ForbiddenRef (82 , "d.internal.MyObject" , "D" ),
509534 // E: Optionally required by B (Require-Bundle: E;optional
510535 // §3.7.5 + §3.13.1)
511- new ForbiddenRef (53 , "e.api.MyObject" , "E" ), new ForbiddenRef (54 , "e.internal.MyObject" , "E" ),
536+ new ForbiddenRef (85 , "e.api.MyObject" , "E" ), new ForbiddenRef (86 , "e.internal.MyObject" , "E" ),
512537 // F: Optionally imported by B (Import-Package:
513538 // f.api;optional §3.7.5 + §3.6.4)
514- new ForbiddenRef (57 , "f.api.MyObject" , "F" ), new ForbiddenRef (58 , "f.internal.MyObject" , "F" ),
539+ new ForbiddenRef (89 , "f.api.MyObject" , "F" ), new ForbiddenRef (90 , "f.internal.MyObject" , "F" ),
515540 // H: Optionally imported by G (Import-Package:
516541 // h.api;optional §3.7.5)
517- new ForbiddenRef (61 , "h.api.MyObject" , "H" ), new ForbiddenRef (62 , "h.internal.MyObject" , "H" ));
542+ new ForbiddenRef (93 , "h.api.MyObject" , "H" ), new ForbiddenRef (94 , "h.internal.MyObject" , "H" ));
518543
519544 IMarker [] allMarkers = project .findMarkers (IJavaModelMarker .JAVA_MODEL_PROBLEM_MARKER , true ,
520545 IResource .DEPTH_INFINITE );
521546
522547 // Each forbidden reference produces exactly 2 markers (type +
523548 // constructor). The total count also implicitly asserts that NO
524- // markers exist for K_ACCESSIBLE references on lines 29 and 34.
549+ // markers exist for K_ACCESSIBLE references (line 28 b.api,
550+ // line 64 g.api) or inherited method calls (lines 32-56).
525551 assertThat (allMarkers ).as (
526552 "Project %s must have exactly %d JDT markers: " + "%d forbidden references × 2 (type + "
527553 + "constructor). Zero 'cannot be resolved' " + "errors, zero markers on accessible types." ,
@@ -627,8 +653,9 @@ public void testDiscouragedAccessRulesForXInternal() throws Exception {
627653 * <p>
628654 * Expected markers on Ad/src/a/api/AClass.java:
629655 * <ul>
630- * <li>Lines 30, 33, 36: no markers (K_ACCESSIBLE API)</li>
631- * <li>Line 40: exactly 2 markers — discouraged type + constructor for
656+ * <li>Lines 26-36: no markers (K_ACCESSIBLE API and inherited method
657+ * calls through b.api.MyObject, g.api.MyObject, x.api.MyObject)</li>
658+ * <li>Line 44: exactly 2 markers — discouraged type + constructor for
632659 * x.internal.MyObject</li>
633660 * </ul>
634661 */
@@ -638,14 +665,14 @@ public void testDiscouragedMarkersOnProjectAd() throws Exception {
638665 IResource .DEPTH_INFINITE );
639666
640667 // Ad only has 1 discouraged reference (x.internal.MyObject on line
641- // 38 ) producing 2 markers (type + constructor). Zero markers for
642- // exported API (b.api, g.api, x.api).
668+ // 44 ) producing 2 markers (type + constructor). Zero markers for
669+ // exported API (b.api, g.api, x.api) and inherited method calls .
643670 assertThat (allMarkers ).as ("Project Ad must have exactly 2 markers: " + "discouraged type + constructor for "
644671 + "x.internal.MyObject. Zero errors, zero " + "markers on accessible types." ).hasSize (2 );
645672
646673 for (IMarker m : allMarkers ) {
647674 assertThat (m .getAttribute (IMarker .LINE_NUMBER , -1 ))
648- .as ("Discouraged marker must be on line 40 " + "(x.internal.MyObject)" ).isEqualTo (40 );
675+ .as ("Discouraged marker must be on line 44 " + "(x.internal.MyObject)" ).isEqualTo (44 );
649676
650677 assertThat (m .getAttribute (IMarker .SEVERITY , -1 ))
651678 .as ("Discouraged access must be WARNING " + "(discouragedReference=warning in "
@@ -680,9 +707,10 @@ public void testDiscouragedMarkersOnProjectAd() throws Exception {
680707
681708 /**
682709 * All dependency bundles (B-H) must build without any compilation problems
683- * — no errors AND no warnings. Each bundle's own dependencies are properly
684- * declared in its manifest, and their source code does not reference any
685- * forbidden types.
710+ * — no errors AND no warnings. Each bundle actively uses its declared
711+ * dependencies (e.g. B extends c.api.MyObject, implements d.api.Processor,
712+ * uses e.api/f.api internally; G uses h.api internally) but only through
713+ * properly declared imports, so no access rule violations occur.
686714 */
687715 @ Test
688716 public void testDependencyBundlesBuildClean () throws Exception {
0 commit comments