8295653: Add a graph of the sealed class hierarchy for marked classes
Co-authored-by: Per Minborg <pminborg@openjdk.org> Reviewed-by: erikj, jjg
This commit is contained in:
parent
59a13b1856
commit
b7442d12e2
126
make/Docs.gmk
126
make/Docs.gmk
@ -1,4 +1,4 @@
|
||||
# Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
#
|
||||
# This code is free software; you can redistribute it and/or modify it
|
||||
@ -80,6 +80,7 @@ JAVADOC_TAGS := \
|
||||
-taglet build.tools.taglet.JSpec\$$JLS \
|
||||
-taglet build.tools.taglet.JSpec\$$JVMS \
|
||||
-taglet build.tools.taglet.ModuleGraph \
|
||||
-taglet build.tools.taglet.SealedGraph \
|
||||
-taglet build.tools.taglet.ToolGuide \
|
||||
-tag since \
|
||||
-tag serialData \
|
||||
@ -187,25 +188,55 @@ JAVASE_LONG_NAME := Java<sup>®</sup> Platform, Standard Edition
|
||||
# Functions
|
||||
|
||||
# Helper function for creating a svg file from a dot file generated by the
|
||||
# GenGraphs tool.
|
||||
# GenGraphs tool for a module.
|
||||
# param 1: SetupJavadocGeneration namespace ($1)
|
||||
# param 2: module name
|
||||
#
|
||||
define setup_gengraph_dot_to_svg
|
||||
$1_$2_DOT_SRC := $$($1_GENGRAPHS_DIR)/$2.dot
|
||||
define setup_module_graph_dot_to_svg
|
||||
$1_$2_DOT_SRC := $$($1_MODULE_GRAPHS_DIR)/$2.dot
|
||||
$1_$2_SVG_TARGET := $$($1_TARGET_DIR)/$2/module-graph.svg
|
||||
|
||||
# For each module needing a graph, create a svg file from the dot file
|
||||
# generated by the GenGraphs tool and store it in the target dir.
|
||||
$$(eval $$(call SetupExecute, gengraphs_svg_$1_$2, \
|
||||
$$(eval $$(call SetupExecute, module_graphs_svg_$1_$2, \
|
||||
INFO := Running dot for module graphs for $2, \
|
||||
DEPS := $$(gengraphs_$1_TARGET), \
|
||||
DEPS := $$(module_graphs_dot_$1_TARGET), \
|
||||
OUTPUT_FILE := $$($1_$2_SVG_TARGET), \
|
||||
SUPPORT_DIR := $$($1_GENGRAPHS_DIR), \
|
||||
SUPPORT_DIR := $$($1_MODULE_GRAPHS_DIR), \
|
||||
COMMAND := $$(DOT) -Tsvg -o $$($1_$2_SVG_TARGET) $$($1_$2_DOT_SRC), \
|
||||
))
|
||||
|
||||
$1_MODULEGRAPH_TARGETS += $$($1_$2_SVG_TARGET)
|
||||
$1_GRAPHS_TARGETS += $$($1_$2_SVG_TARGET)
|
||||
endef
|
||||
|
||||
# Helper function for creating a svg file for a class for which the SealedGraph
|
||||
# taglet has generated a dot file. The dot file has a special name which
|
||||
# encodes the module and class the graph belongs to.
|
||||
#
|
||||
# param 1: SetupJavadocGeneration namespace ($1)
|
||||
# param 2: dot file name
|
||||
#
|
||||
define setup_sealed_graph_dot_to_svg
|
||||
$1_$2_DOT_SRC := $$($1_SEALED_GRAPHS_DIR)/$2.dot
|
||||
$1_$2_TARGET_CLASS := $$(word 2, $$(subst _, , $2))
|
||||
$1_$2_SLASHED_NAME := $$(subst .,/, $$($1_$2_TARGET_CLASS))
|
||||
$1_$2_TARGET_MODULE := $$(word 1, $$(subst _, , $2))
|
||||
$1_$2_TARGET_PATH := $$($1_TARGET_DIR)/$$($1_$2_TARGET_MODULE)/$$(dir $$($1_$2_SLASHED_NAME))
|
||||
$1_$2_TARGET_NAME := $$(notdir $$($1_$2_SLASHED_NAME))
|
||||
$1_$2_SVG_TARGET := $$($1_$2_TARGET_PATH)/$$($1_$2_TARGET_NAME)-sealed-graph.svg
|
||||
$$(call MakeDir, $$($1_$2_TARGET_PATH))
|
||||
|
||||
# For each class needing a graph, create a svg file from the dot file
|
||||
# generated by the SealedGraph taglet and store it in the target dir.
|
||||
$$(eval $$(call SetupExecute, sealed_graphs_svg_$1_$2, \
|
||||
INFO := Running dot for sealed graphs for $$($1_$2_TARGET_MODULE)/$$($1_$2_TARGET_CLASS), \
|
||||
DEPS := $$($1_$2_DOT_SRC), \
|
||||
OUTPUT_FILE := $$($1_$2_SVG_TARGET), \
|
||||
SUPPORT_DIR := $$($1_SEALED_GRAPHS_DIR), \
|
||||
COMMAND := $$(DOT) -Tsvg -o $$($1_$2_SVG_TARGET) $$($1_$2_DOT_SRC), \
|
||||
))
|
||||
|
||||
$1_GRAPHS_TARGETS += $$($1_$2_SVG_TARGET)
|
||||
endef
|
||||
|
||||
# Helper function to create the overview.html file to use with the -overview
|
||||
@ -253,7 +284,7 @@ endef
|
||||
#
|
||||
# Parameter 1 is the name of the rule. This name is used as variable prefix.
|
||||
# Targets generated are returned as $1_JAVADOC_TARGETS and
|
||||
# $1_MODULEGRAPH_TARGETS. Note that the index.html file will work as a "touch
|
||||
# $1_GRAPHS_TARGETS. Note that the index.html file will work as a "touch
|
||||
# file" for all the magnitude of files that are generated by javadoc.
|
||||
#
|
||||
# Remaining parameters are named arguments. These include:
|
||||
@ -276,9 +307,12 @@ define SetupApiDocsGenerationBody
|
||||
-Djspec.version=$$(VERSION_SPECIFICATION)
|
||||
|
||||
ifeq ($$(ENABLE_FULL_DOCS), true)
|
||||
# Tell the ModuleGraph taglet to generate html links to soon-to-be-created
|
||||
# svg files with module graphs.
|
||||
$1_JAVA_ARGS += -DenableModuleGraph=true
|
||||
$1_SEALED_GRAPHS_DIR := $$(SUPPORT_OUTPUTDIR)/docs/$1-sealed-graphs
|
||||
|
||||
# Tell the ModuleGraph and SealedGraph taglets to generate html links to
|
||||
# soon-to-be-created svg files with module/sealed graphs.
|
||||
$1_JAVA_ARGS += -DenableModuleGraph=true -DsealedDotOutputDir=$$($1_SEALED_GRAPHS_DIR)
|
||||
$$(call MakeDir, $$($1_SEALED_GRAPHS_DIR))
|
||||
endif
|
||||
|
||||
# Start with basic options and tags
|
||||
@ -384,30 +418,46 @@ define SetupApiDocsGenerationBody
|
||||
|
||||
# First we run the GenGraph tool. It will query the module structure of the
|
||||
# running JVM and output .dot files for all existing modules.
|
||||
GENGRAPHS_PROPS := \
|
||||
MODULE_GRAPHS_PROPS := \
|
||||
$$(TOPDIR)/make/jdk/src/classes/build/tools/jigsaw/javadoc-graphs.properties
|
||||
|
||||
$1_GENGRAPHS_DIR := $$(SUPPORT_OUTPUTDIR)/docs/$1-gengraphs
|
||||
$1_MODULE_GRAPHS_DIR := $$(SUPPORT_OUTPUTDIR)/docs/$1-module-graphs
|
||||
|
||||
$$(eval $$(call SetupExecute, gengraphs_$1, \
|
||||
INFO := Running gengraphs for $1 documentation, \
|
||||
DEPS := $$(BUILD_JIGSAW_TOOLS) $$(GENGRAPHS_PROPS), \
|
||||
OUTPUT_DIR := $$($1_GENGRAPHS_DIR), \
|
||||
COMMAND := $$(TOOL_GENGRAPHS) --spec --output $$($1_GENGRAPHS_DIR) \
|
||||
--dot-attributes $$(GENGRAPHS_PROPS), \
|
||||
$$(eval $$(call SetupExecute, module_graphs_dot_$1, \
|
||||
INFO := Generating module graphs for $1 documentation, \
|
||||
DEPS := $$(BUILD_JIGSAW_TOOLS) $$(MODULE_GRAPHS_PROPS), \
|
||||
OUTPUT_DIR := $$($1_MODULE_GRAPHS_DIR), \
|
||||
COMMAND := $$(TOOL_GENGRAPHS) --spec --output $$($1_MODULE_GRAPHS_DIR) \
|
||||
--dot-attributes $$(MODULE_GRAPHS_PROPS), \
|
||||
))
|
||||
|
||||
# For each module needing a graph, create a svg file from the dot file
|
||||
# generated by the GenGraphs tool and store it in the target dir.
|
||||
# They will depend on gengraphs_$1_TARGET, and will be added to $1.
|
||||
# They will depend on module_graphs_dot_$1_TARGET, and will be added to
|
||||
# $1_GRAPHS_TARGETS.
|
||||
$$(foreach m, $$($1_MODULES_NEEDING_GRAPH), \
|
||||
$$(eval $$(call setup_gengraph_dot_to_svg,$1,$$m)) \
|
||||
$$(eval $$(call setup_module_graph_dot_to_svg,$1,$$m)) \
|
||||
)
|
||||
|
||||
# We have asked SealedGraph to generate dot files and links to svg files.
|
||||
# Now we must produce the svg files from the dot files.
|
||||
|
||||
# Get a list of classes for which SealedGraph has generated dot files
|
||||
$1_SEALED_CLASSES := $$(patsubst %.dot,%,$$(patsubst \
|
||||
$$($1_SEALED_GRAPHS_DIR)/%,%, \
|
||||
$$(wildcard $$($1_SEALED_GRAPHS_DIR)/*.dot)))
|
||||
|
||||
# For each class needing a graph, create a svg file from the dot file
|
||||
# generated by the SealedGraph taglet and store it in the target dir.
|
||||
# They will will be added to $1_GRAPHS_TARGETS.
|
||||
$$(foreach c, $$($1_SEALED_CLASSES), \
|
||||
$$(eval $$(call setup_sealed_graph_dot_to_svg,$1,$$c)) \
|
||||
)
|
||||
endif
|
||||
endef
|
||||
|
||||
################################################################################
|
||||
# Setup generation of the JDK API documentation (javadoc + modulegraph)
|
||||
# Setup generation of the JDK API documentation (javadoc + graphs)
|
||||
|
||||
# Define the groups of the JDK API documentation
|
||||
JavaSE_GROUP_NAME := Java SE
|
||||
@ -456,10 +506,10 @@ $(eval $(call SetupApiDocsGeneration, JDK_API, \
|
||||
))
|
||||
|
||||
# Targets generated are returned in JDK_API_JAVADOC_TARGETS and
|
||||
# JDK_API_MODULEGRAPH_TARGETS.
|
||||
# JDK_API_GRAPHS_TARGETS.
|
||||
|
||||
################################################################################
|
||||
# Setup generation of the Java SE API documentation (javadoc + modulegraph)
|
||||
# Setup generation of the Java SE API documentation (javadoc + graphs)
|
||||
|
||||
# The Java SE module scope is just java.se and its transitive indirect
|
||||
# exports.
|
||||
@ -473,10 +523,10 @@ $(eval $(call SetupApiDocsGeneration, JAVASE_API, \
|
||||
))
|
||||
|
||||
# Targets generated are returned in JAVASE_API_JAVADOC_TARGETS and
|
||||
# JAVASE_API_MODULEGRAPH_TARGETS.
|
||||
# JAVASE_API_GRAPHS_TARGETS.
|
||||
|
||||
################################################################################
|
||||
# Setup generation of the reference Java SE API documentation (javadoc + modulegraph)
|
||||
# Setup generation of the reference Java SE API documentation (javadoc + graphs)
|
||||
|
||||
# The reference javadoc is just the same as javase, but using the BootJDK javadoc
|
||||
# and a stable set of javadoc options. Typically it is used for generating
|
||||
@ -494,7 +544,7 @@ $(eval $(call SetupApiDocsGeneration, REFERENCE_API, \
|
||||
))
|
||||
|
||||
# Targets generated are returned in REFERENCE_API_JAVADOC_TARGETS and
|
||||
# REFERENCE_API_MODULEGRAPH_TARGETS.
|
||||
# REFERENCE_API_GRAPHS_TARGETS.
|
||||
|
||||
################################################################################
|
||||
|
||||
@ -711,7 +761,7 @@ JAVADOC_ZIP_FILE := $(OUTPUTDIR)/bundles/$(JAVADOC_ZIP_NAME)
|
||||
$(eval $(call SetupZipArchive, BUILD_JAVADOC_ZIP, \
|
||||
SRC := $(DOCS_OUTPUTDIR), \
|
||||
ZIP := $(JAVADOC_ZIP_FILE), \
|
||||
EXTRA_DEPS := $(JDK_API_JAVADOC_TARGETS) $(JDK_API_MODULEGRAPH_TARGETS) \
|
||||
EXTRA_DEPS := $(JDK_API_JAVADOC_TARGETS) $(JDK_API_GRAPHS_TARGETS) \
|
||||
$(JDK_SPECS_TARGETS), \
|
||||
))
|
||||
|
||||
@ -739,15 +789,15 @@ SPECS_ZIP_TARGETS += $(BUILD_SPECS_ZIP)
|
||||
|
||||
docs-jdk-api-javadoc: $(JDK_API_JAVADOC_TARGETS) $(JDK_API_CUSTOM_TARGETS)
|
||||
|
||||
docs-jdk-api-modulegraph: $(JDK_API_MODULEGRAPH_TARGETS)
|
||||
docs-jdk-api-graphs: $(JDK_API_GRAPHS_TARGETS)
|
||||
|
||||
docs-javase-api-javadoc: $(JAVASE_API_JAVADOC_TARGETS) $(JAVASE_API_CUSTOM_TARGETS)
|
||||
|
||||
docs-javase-api-modulegraph: $(JAVASE_API_MODULEGRAPH_TARGETS)
|
||||
docs-javase-api-graphs: $(JAVASE_API_GRAPHS_TARGETS)
|
||||
|
||||
docs-reference-api-javadoc: $(REFERENCE_API_JAVADOC_TARGETS) $(REFERENCE_API_CUSTOM_TARGETS)
|
||||
|
||||
docs-reference-api-modulegraph: $(REFERENCE_API_MODULEGRAPH_TARGETS)
|
||||
docs-reference-api-graphs: $(REFERENCE_API_GRAPHS_TARGETS)
|
||||
|
||||
docs-jdk-specs: $(JDK_SPECS_TARGETS)
|
||||
|
||||
@ -757,12 +807,12 @@ docs-zip: $(ZIP_TARGETS)
|
||||
|
||||
docs-specs-zip: $(SPECS_ZIP_TARGETS)
|
||||
|
||||
all: docs-jdk-api-javadoc docs-jdk-api-modulegraph docs-javase-api-javadoc \
|
||||
docs-javase-api-modulegraph docs-reference-api-javadoc \
|
||||
docs-reference-api-modulegraph docs-jdk-specs docs-jdk-index docs-zip \
|
||||
all: docs-jdk-api-javadoc docs-jdk-api-graphs docs-javase-api-javadoc \
|
||||
docs-javase-api-graphs docs-reference-api-javadoc \
|
||||
docs-reference-api-graphs docs-jdk-specs docs-jdk-index docs-zip \
|
||||
docs-specs-zip
|
||||
|
||||
.PHONY: default all docs-jdk-api-javadoc docs-jdk-api-modulegraph \
|
||||
docs-javase-api-javadoc docs-javase-api-modulegraph \
|
||||
docs-reference-api-javadoc docs-reference-api-modulegraph docs-jdk-specs \
|
||||
.PHONY: default all docs-jdk-api-javadoc docs-jdk-api-graphs \
|
||||
docs-javase-api-javadoc docs-javase-api-graphs \
|
||||
docs-reference-api-javadoc docs-reference-api-graphs docs-jdk-specs \
|
||||
docs-jdk-index docs-zip docs-specs-zip
|
||||
|
@ -466,15 +466,15 @@ ALL_TARGETS += bootcycle-images
|
||||
# Docs targets
|
||||
|
||||
# If building full docs, to complete docs-*-api we need both the javadoc and
|
||||
# modulegraph targets.
|
||||
# graphs targets.
|
||||
$(eval $(call SetupTarget, docs-jdk-api-javadoc, \
|
||||
MAKEFILE := Docs, \
|
||||
TARGET := docs-jdk-api-javadoc, \
|
||||
))
|
||||
|
||||
$(eval $(call SetupTarget, docs-jdk-api-modulegraph, \
|
||||
$(eval $(call SetupTarget, docs-jdk-api-graphs, \
|
||||
MAKEFILE := Docs, \
|
||||
TARGET := docs-jdk-api-modulegraph, \
|
||||
TARGET := docs-jdk-api-graphs, \
|
||||
DEPS := buildtools-modules runnable-buildjdk, \
|
||||
))
|
||||
|
||||
@ -483,9 +483,9 @@ $(eval $(call SetupTarget, docs-javase-api-javadoc, \
|
||||
TARGET := docs-javase-api-javadoc, \
|
||||
))
|
||||
|
||||
$(eval $(call SetupTarget, docs-javase-api-modulegraph, \
|
||||
$(eval $(call SetupTarget, docs-javase-api-graphs, \
|
||||
MAKEFILE := Docs, \
|
||||
TARGET := docs-javase-api-modulegraph, \
|
||||
TARGET := docs-javase-api-graphs, \
|
||||
DEPS := buildtools-modules runnable-buildjdk, \
|
||||
))
|
||||
|
||||
@ -494,9 +494,9 @@ $(eval $(call SetupTarget, docs-reference-api-javadoc, \
|
||||
TARGET := docs-reference-api-javadoc, \
|
||||
))
|
||||
|
||||
$(eval $(call SetupTarget, docs-reference-api-modulegraph, \
|
||||
$(eval $(call SetupTarget, docs-reference-api-graphs, \
|
||||
MAKEFILE := Docs, \
|
||||
TARGET := docs-reference-api-modulegraph, \
|
||||
TARGET := docs-reference-api-graphs, \
|
||||
DEPS := buildtools-modules runnable-buildjdk, \
|
||||
))
|
||||
|
||||
@ -1107,9 +1107,14 @@ docs-reference-api: docs-reference-api-javadoc
|
||||
# If we're building full docs, we must also generate the module graphs to
|
||||
# get non-broken api documentation.
|
||||
ifeq ($(ENABLE_FULL_DOCS), true)
|
||||
docs-jdk-api: docs-jdk-api-modulegraph
|
||||
docs-javase-api: docs-javase-api-modulegraph
|
||||
docs-reference-api: docs-reference-api-modulegraph
|
||||
docs-jdk-api: docs-jdk-api-graphs
|
||||
docs-javase-api: docs-javase-api-graphs
|
||||
docs-reference-api: docs-reference-api-graphs
|
||||
|
||||
# We must generate javadoc first so we know what graphs are needed
|
||||
docs-jdk-api-graphs: docs-jdk-api-javadoc
|
||||
docs-javase-api-graphs: docs-javase-api-javadoc
|
||||
docs-reference-api-graphs: docs-reference-api-javadoc
|
||||
endif
|
||||
|
||||
docs-jdk: docs-jdk-api docs-jdk-specs docs-jdk-index
|
||||
|
260
make/jdk/src/classes/build/tools/taglet/SealedGraph.java
Normal file
260
make/jdk/src/classes/build/tools/taglet/SealedGraph.java
Normal file
@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package build.tools.taglet;
|
||||
|
||||
import com.sun.source.doctree.DocTree;
|
||||
import jdk.javadoc.doclet.Doclet;
|
||||
import jdk.javadoc.doclet.DocletEnvironment;
|
||||
import jdk.javadoc.doclet.Taglet;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.lang.System.lineSeparator;
|
||||
import static java.nio.file.StandardOpenOption.*;
|
||||
import static jdk.javadoc.doclet.Taglet.Location.TYPE;
|
||||
|
||||
/**
|
||||
* A block tag to optionally insert a reference to a sealed class hierarchy graph,
|
||||
* and generate the corresponding dot file.
|
||||
*/
|
||||
public final class SealedGraph implements Taglet {
|
||||
private static final String sealedDotOutputDir =
|
||||
System.getProperty("sealedDotOutputDir");
|
||||
|
||||
private DocletEnvironment docletEnvironment;
|
||||
|
||||
/**
|
||||
* Returns the set of locations in which a taglet may be used.
|
||||
*/
|
||||
@Override
|
||||
public Set<Location> getAllowedLocations() {
|
||||
return EnumSet.of(TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInlineTag() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "sealedGraph";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(DocletEnvironment env, Doclet doclet) {
|
||||
docletEnvironment = env;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(List<? extends DocTree> tags, Element element) {
|
||||
if (sealedDotOutputDir == null || sealedDotOutputDir.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
if (docletEnvironment == null || !(element instanceof TypeElement typeElement)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
ModuleElement module = docletEnvironment.getElementUtils().getModuleOf(element);
|
||||
Path dotFile = Path.of(sealedDotOutputDir,
|
||||
module.getQualifiedName() + "_" + typeElement.getQualifiedName() + ".dot");
|
||||
|
||||
Set<String> exports = module.getDirectives().stream()
|
||||
.filter(ModuleElement.ExportsDirective.class::isInstance)
|
||||
.map(ModuleElement.ExportsDirective.class::cast)
|
||||
// Only include packages that are globally exported (i.e. no "to" exports)
|
||||
.filter(ed -> ed.getTargetModules() == null)
|
||||
.map(ModuleElement.ExportsDirective::getPackage)
|
||||
.map(PackageElement::getQualifiedName)
|
||||
.map(Objects::toString)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
|
||||
String dotContent = Renderer.graph(typeElement, exports);
|
||||
|
||||
try {
|
||||
Files.writeString(dotFile, dotContent, WRITE, CREATE, TRUNCATE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
String simpleTypeName = element.getSimpleName().toString();
|
||||
String imageFile = simpleTypeName + "-sealed-graph.svg";
|
||||
int thumbnailHeight = 100; // also appears in the stylesheet
|
||||
String hoverImage = "<span>"
|
||||
+ getImage(simpleTypeName, imageFile, -1, true)
|
||||
+ "</span>";
|
||||
|
||||
return "<dt>Sealed Class Hierarchy Graph:</dt>"
|
||||
+ "<dd>"
|
||||
+ "<a class=\"sealed-graph\" href=\"" + imageFile + "\">"
|
||||
+ getImage(simpleTypeName, imageFile, thumbnailHeight, false)
|
||||
+ hoverImage
|
||||
+ "</a>"
|
||||
+ "</dd>";
|
||||
}
|
||||
|
||||
private static final String VERTICAL_ALIGN = "vertical-align:top";
|
||||
private static final String BORDER = "border: solid lightgray 1px;";
|
||||
|
||||
private String getImage(String typeName, String file, int height, boolean useBorder) {
|
||||
return String.format("<img style=\"%s\" alt=\"Sealed class hierarchy graph for %s\" src=\"%s\"%s>",
|
||||
useBorder ? BORDER + " " + VERTICAL_ALIGN : VERTICAL_ALIGN,
|
||||
typeName,
|
||||
file,
|
||||
(height <= 0 ? "" : " height=\"" + height + "\""));
|
||||
}
|
||||
|
||||
private static final class Renderer {
|
||||
|
||||
private Renderer() {
|
||||
}
|
||||
|
||||
// Generates a graph in DOT format
|
||||
static String graph(TypeElement rootClass, Set<String> exports) {
|
||||
final State state = new State(rootClass);
|
||||
traverse(state, rootClass, exports);
|
||||
return state.render();
|
||||
}
|
||||
|
||||
static void traverse(State state, TypeElement node, Set<String> exports) {
|
||||
state.addNode(node);
|
||||
for (TypeElement subNode : permittedSubclasses(node, exports)) {
|
||||
if (isInPublicApi(node, exports) && isInPublicApi(subNode, exports)) {
|
||||
state.addEdge(node, subNode);
|
||||
}
|
||||
traverse(state, subNode, exports);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class State {
|
||||
|
||||
private static final String LABEL = "label";
|
||||
private static final String TOOLTIP = "tooltip";
|
||||
private static final String STYLE = "style";
|
||||
|
||||
private final StringBuilder builder;
|
||||
|
||||
private final Map<String, Map<String, String>> nodeStyleMap;
|
||||
|
||||
public State(TypeElement rootNode) {
|
||||
nodeStyleMap = new LinkedHashMap<>();
|
||||
builder = new StringBuilder()
|
||||
.append("digraph G {")
|
||||
.append(lineSeparator())
|
||||
.append(" nodesep=0.500000;")
|
||||
.append(lineSeparator())
|
||||
.append(" ranksep=0.600000;")
|
||||
.append(lineSeparator())
|
||||
.append(" pencolor=transparent;")
|
||||
.append(lineSeparator())
|
||||
.append(" node [shape=plaintext, fontcolor=\"#e76f00\", fontname=\"DejaVuSans\", fontsize=12, margin=\".2,.2\"];")
|
||||
.append(lineSeparator())
|
||||
.append(" edge [penwidth=2, color=\"#999999\", arrowhead=open, arrowsize=1];")
|
||||
.append(lineSeparator())
|
||||
.append(" rankdir=\"BT\";")
|
||||
.append(lineSeparator());
|
||||
}
|
||||
|
||||
public void addNode(TypeElement node) {
|
||||
var styles = nodeStyleMap.computeIfAbsent(id(node), n -> new LinkedHashMap<>());
|
||||
styles.put(LABEL, node.getSimpleName().toString());
|
||||
styles.put(TOOLTIP, node.getQualifiedName().toString());
|
||||
if (!(node.getModifiers().contains(Modifier.SEALED) || node.getModifiers().contains(Modifier.FINAL))) {
|
||||
// This indicates that the hierarchy is not closed
|
||||
styles.put(STYLE, "dashed");
|
||||
}
|
||||
}
|
||||
|
||||
public void addEdge(TypeElement node, TypeElement subNode) {
|
||||
builder.append(" ")
|
||||
.append(quotedId(subNode))
|
||||
.append(" -> ")
|
||||
.append(quotedId(node))
|
||||
.append(";")
|
||||
.append(lineSeparator());
|
||||
}
|
||||
|
||||
public String render() {
|
||||
nodeStyleMap.forEach((nodeName, styles) -> {
|
||||
builder.append(" ")
|
||||
.append('"').append(nodeName).append("\" ")
|
||||
.append(styles.entrySet().stream()
|
||||
.map(e -> e.getKey() + "=\"" + e.getValue() + "\"")
|
||||
.collect(Collectors.joining(" ", "[", "]")))
|
||||
.append(System.lineSeparator());
|
||||
});
|
||||
builder.append("}");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String id(TypeElement node) {
|
||||
return node.getQualifiedName().toString();
|
||||
}
|
||||
|
||||
private String quotedId(TypeElement node) {
|
||||
return "\"" + id(node) + "\"";
|
||||
}
|
||||
|
||||
private String simpleName(String name) {
|
||||
int lastDot = name.lastIndexOf('.');
|
||||
return lastDot < 0
|
||||
? name
|
||||
: name.substring(lastDot);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static List<TypeElement> permittedSubclasses(TypeElement node, Set<String> exports) {
|
||||
return node.getPermittedSubclasses().stream()
|
||||
.filter(DeclaredType.class::isInstance)
|
||||
.map(DeclaredType.class::cast)
|
||||
.map(DeclaredType::asElement)
|
||||
.filter(TypeElement.class::isInstance)
|
||||
.map(TypeElement.class::cast)
|
||||
.filter(te -> isInPublicApi(te, exports))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static boolean isInPublicApi(TypeElement typeElement, Set<String> exports) {
|
||||
return (exports.contains(packageName(typeElement.getQualifiedName().toString())) ||
|
||||
exports.contains(packageName(typeElement.getSuperclass().toString()))) &&
|
||||
typeElement.getModifiers().contains(Modifier.PUBLIC);
|
||||
}
|
||||
|
||||
private static String packageName(String name) {
|
||||
int lastDot = name.lastIndexOf('.');
|
||||
return lastDot < 0
|
||||
? ""
|
||||
: name.substring(0, lastDot);
|
||||
}
|
||||
}
|
||||
}
|
@ -835,11 +835,11 @@ button.page-search-header {
|
||||
span#page-search-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.module-graph span {
|
||||
.module-graph span, .sealed-graph span {
|
||||
display:none;
|
||||
position:absolute;
|
||||
}
|
||||
.module-graph:hover span {
|
||||
.module-graph:hover span, .sealed-graph:hover span {
|
||||
display:block;
|
||||
margin: -100px 0 0 100px;
|
||||
z-index: 1;
|
||||
|
@ -142,6 +142,7 @@ public class CheckStylesheetClasses {
|
||||
|
||||
// very JDK specific
|
||||
styleSheetNames.remove("module-graph");
|
||||
styleSheetNames.remove("sealed-graph");
|
||||
|
||||
boolean ok = check(htmlStyleNames, "HtmlStyle", styleSheetNames, "stylesheet")
|
||||
& check(styleSheetNames, "stylesheet", htmlStyleNames, "HtmlStyle");
|
||||
|
Loading…
Reference in New Issue
Block a user