3232
3333import javax .lang .model .element .*;
3434import javax .lang .model .type .DeclaredType ;
35+ import javax .lang .model .util .Elements ;
3536import java .io .IOException ;
37+ import java .net .URI ;
3638import java .nio .file .Files ;
3739import java .nio .file .Path ;
3840import java .util .*;
@@ -78,16 +80,27 @@ public void init(DocletEnvironment env, Doclet doclet) {
7880
7981 @ Override
8082 public String toString (List <? extends DocTree > tags , Element element ) {
83+ throw new UnsupportedOperationException ();
84+ }
85+
86+ // @Override - requires JDK-8373922 in build JDK
87+ public String toString (List <? extends DocTree > tags , Element element , URI docRoot ) {
8188 if (sealedDotOutputDir == null || sealedDotOutputDir .isEmpty ()) {
8289 return "" ;
8390 }
8491 if (docletEnvironment == null || !(element instanceof TypeElement typeElement )) {
8592 return "" ;
8693 }
8794
88- ModuleElement module = docletEnvironment .getElementUtils ().getModuleOf (element );
95+ Elements util = docletEnvironment .getElementUtils ();
96+ ModuleElement module = util .getModuleOf (element );
97+
98+ // '.' in .DOT file name is converted to '/' in .SVG path, so we use '-' as separator for nested classes.
99+ // module_package.subpackage.Outer-Inner.dot => module/package/subpackage/Outer-Inner-sealed-graph.svg
89100 Path dotFile = Path .of (sealedDotOutputDir ,
90- module .getQualifiedName () + "_" + typeElement .getQualifiedName () + ".dot" );
101+ module .getQualifiedName () + "_"
102+ + util .getPackageOf (element ).getQualifiedName () + "."
103+ + packagelessCanonicalName (typeElement ).replace ("." , "-" ) + ".dot" );
91104
92105 Set <String > exports = module .getDirectives ().stream ()
93106 .filter (ModuleElement .ExportsDirective .class ::isInstance )
@@ -99,16 +112,16 @@ public String toString(List<? extends DocTree> tags, Element element) {
99112 .map (Objects ::toString )
100113 .collect (Collectors .toUnmodifiableSet ());
101114
102- String dotContent = new Renderer ().graph (typeElement , exports );
115+ String dotContent = new Renderer ().graph (typeElement , exports , docRoot );
103116
104117 try {
105118 Files .writeString (dotFile , dotContent , WRITE , CREATE , TRUNCATE_EXISTING );
106119 } catch (IOException e ) {
107120 throw new RuntimeException (e );
108121 }
109122
110- String simpleTypeName = packagelessCanonicalName (typeElement ). replace ( '.' , '/' ) ;
111- String imageFile = simpleTypeName + "-sealed-graph.svg" ;
123+ String simpleTypeName = packagelessCanonicalName (typeElement );
124+ String imageFile = simpleTypeName . replace ( "." , "-" ) + "-sealed-graph.svg" ;
112125 int thumbnailHeight = 100 ; // also appears in the stylesheet
113126 String hoverImage = "<span>"
114127 + getImage (simpleTypeName , imageFile , -1 , true )
@@ -137,12 +150,12 @@ private String getImage(String typeName, String file, int height, boolean useBor
137150 private final class Renderer {
138151
139152 // Generates a graph in DOT format
140- String graph (TypeElement rootClass , Set <String > exports ) {
153+ String graph (TypeElement rootClass , Set <String > exports , URI pathToRoot ) {
141154 if (!isInPublicApi (rootClass , exports )) {
142155 // Alternatively we can return "" for the graph since there is no single root to render
143156 throw new IllegalArgumentException ("Root not in public API: " + rootClass .getQualifiedName ());
144157 }
145- final State state = new State (rootClass );
158+ final State state = new State (pathToRoot );
146159 traverse (state , rootClass , exports );
147160 return state .render ();
148161 }
@@ -168,7 +181,7 @@ private final class State {
168181 private static final String TOOLTIP = "tooltip" ;
169182 private static final String LINK = "href" ;
170183
171- private final TypeElement rootNode ;
184+ private final URI pathToRoot ;
172185
173186 private final StringBuilder builder ;
174187
@@ -193,8 +206,8 @@ public String valueString() {
193206 }
194207 }
195208
196- public State (TypeElement rootNode ) {
197- this .rootNode = rootNode ;
209+ public State (URI pathToRoot ) {
210+ this .pathToRoot = pathToRoot ;
198211 nodeStyleMap = new LinkedHashMap <>();
199212 builder = new StringBuilder ()
200213 .append ("digraph G {" )
@@ -217,24 +230,15 @@ public void addNode(TypeElement node) {
217230 var styles = nodeStyleMap .computeIfAbsent (id (node ), n -> new LinkedHashMap <>());
218231 styles .put (LABEL , new StyleItem .PlainString (node .getSimpleName ().toString ()));
219232 styles .put (TOOLTIP , new StyleItem .PlainString (node .getQualifiedName ().toString ()));
220- styles .put (LINK , new StyleItem .PlainString (relativeLink (node )));
233+ styles .put (LINK , new StyleItem .PlainString (pathToRoot . resolve ( relativeLink (node )). toString ( )));
221234 }
222235
223- // A permitted class must be in the same package or in the same module.
224- // This implies the module is always the same.
225236 private String relativeLink (TypeElement node ) {
226237 var util = SealedGraph .this .docletEnvironment .getElementUtils ();
227- var nodePackage = util .getPackageOf (node );
228- // Note: SVG files for nested types use the simple names of containing types as parent directories.
229- // We therefore need to convert all dots in the qualified name to "../" below.
230- var backNavigator = rootNode .getQualifiedName ().toString ().chars ()
231- .filter (c -> c == '.' )
232- .mapToObj (c -> "../" )
233- .collect (joining ());
234- var forwardNavigator = nodePackage .getQualifiedName ().toString ()
235- .replace ("." , "/" );
236-
237- return backNavigator + forwardNavigator + "/" + packagelessCanonicalName (node ) + ".html" ;
238+ var path = util .getModuleOf (node ).getQualifiedName ().toString () + "/"
239+ + util .getPackageOf (node ).getQualifiedName ().toString ().replace ("." , "/" );
240+
241+ return path + "/" + packagelessCanonicalName (node ) + ".html" ;
238242 }
239243
240244 public void addEdge (TypeElement node , TypeElement subNode ) {
@@ -286,14 +290,6 @@ private String id(TypeElement node) {
286290 private String quotedId (TypeElement node ) {
287291 return "\" " + id (node ) + "\" " ;
288292 }
289-
290- private String simpleName (String name ) {
291- int lastDot = name .lastIndexOf ('.' );
292- return lastDot < 0
293- ? name
294- : name .substring (lastDot );
295- }
296-
297293 }
298294
299295 private static List <TypeElement > permittedSubclasses (TypeElement node , Set <String > exports ) {
0 commit comments