Skip to content

Commit cb6c45e

Browse files
committed
7904082: The UI for API Diff could be improved
Reviewed-by: cstein
1 parent 80d071c commit cb6c45e

File tree

9 files changed

+761
-97
lines changed

9 files changed

+761
-97
lines changed

src/share/classes/jdk/codetools/apidiff/Options.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ public String toString() {
171171
Boolean compareDocComments;
172172
Boolean compareApiDescriptions;
173173
Boolean compareApiDescriptionsAsText;
174+
Boolean showUnchanged;
174175
String jdkDocs;
175176

176177
// output options
@@ -181,7 +182,6 @@ public String toString() {
181182
Path mainStylesheet;
182183
List<Path> extraStylesheets;
183184
List<Path> resourceFiles;
184-
boolean showEqual;
185185

186186
/**
187187
* The position of additional text to be included in the report.
@@ -336,6 +336,16 @@ void process(String opt, String arg, Options options) throws BadOption {
336336
}
337337
},
338338

339+
/**
340+
* {@code --show-unchanged} <var>boolean-value</var>
341+
*/
342+
SHOW_UNCHANGED("--show-unchanged", "opt.arg.boolean") {
343+
@Override
344+
void process(String opt, String arg, Options options) throws BadOption {
345+
options.showUnchanged = asBoolean(arg);
346+
}
347+
},
348+
339349
/**
340350
* {@code --enable-preview}.
341351
*
@@ -909,6 +919,15 @@ public boolean compareApiDescriptionsAsText() {
909919
return compareApiDescriptionsAsText;
910920
}
911921

922+
/**
923+
* Returns whether unchanged API elements should be unconditionally shown.
924+
*
925+
* @return {@code true} if unchanged API elements should be unconditionally shown
926+
*/
927+
public boolean showUnchanged() {
928+
return showUnchanged;
929+
}
930+
912931
/**
913932
* Returns whether documentation comments should be compared.
914933
*
@@ -981,10 +1000,6 @@ public List<Path> getResourceFiles() {
9811000
return (resourceFiles == null) ? Collections.emptyList() : resourceFiles;
9821001
}
9831002

984-
public boolean showEqual() {
985-
return showEqual;
986-
}
987-
9881003
/**
9891004
* Returns the value of a "hidden" option, set by {@code -XD<name>} or
9901005
* {@code -XD<name>=<value>}.
@@ -1082,6 +1097,10 @@ void validate() {
10821097
compareDocComments = !compareApiDescriptions;
10831098
}
10841099

1100+
if (showUnchanged == null) {
1101+
showUnchanged = false;
1102+
}
1103+
10851104
if (resourceFiles != null) {
10861105
for (var resFile : resourceFiles) {
10871106
if (resFile.isAbsolute() && !Files.exists(resFile)) {

src/share/classes/jdk/codetools/apidiff/report/html/ModulePageReporter.java

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@
3030
import java.util.List;
3131
import java.util.Map;
3232
import java.util.Objects;
33+
import java.util.Optional;
3334
import java.util.Set;
3435
import java.util.TreeMap;
3536
import java.util.TreeSet;
3637
import java.util.function.BiFunction;
3738
import java.util.function.Function;
3839
import java.util.function.Predicate;
3940
import java.util.stream.Collectors;
41+
import java.util.stream.Stream;
4042
import javax.lang.model.element.Element;
4143
import javax.lang.model.element.ModuleElement;
4244
import javax.lang.model.element.ModuleElement.Directive;
@@ -163,7 +165,7 @@ protected List<Content> buildEnclosedElements() {
163165
private <T> void addDirectives(List<Content> contents,
164166
String headingKey,
165167
Predicate<RelativePosition<?>> filter,
166-
BiFunction<RelativePosition<?>, APIMap<T>, Content> f) {
168+
BiFunction<RelativePosition<?>, APIMap<T>, ContentAndResultKind> f) {
167169
Map<RelativePosition<?>, APIMap<T>> dMaps = new TreeMap<>(RelativePosition.elementKeyIndexComparator);
168170
// apiMaps will only contain maps for directives which should be compared and displayed;
169171
// i.e. they have already been filtered according to accessKind.allDirectiveDetails
@@ -179,27 +181,44 @@ private <T> void addDirectives(List<Content> contents,
179181
}
180182
}
181183

182-
if (!dMaps.isEmpty()) {
184+
List<ContentAndResultKind> converted =
185+
dMaps.entrySet()
186+
.stream()
187+
.map(e -> f.apply(e.getKey(), e.getValue()))
188+
.toList();
189+
190+
if (!converted.isEmpty()) {
191+
boolean allUnchanged = converted.stream()
192+
.allMatch(c -> c.resultKind() == ResultKind.SAME);
183193
HtmlTree section = HtmlTree.SECTION().setClass("enclosed");
184194
section.add(HtmlTree.H2(Text.of(msgs.getString(headingKey))));
185195
HtmlTree ul = HtmlTree.UL();
186-
dMaps.forEach((rp, apiMap) -> ul.add(HtmlTree.LI(f.apply(rp, apiMap))));
196+
for (ContentAndResultKind c : converted) {
197+
HtmlTree item = HtmlTree.LI(c.content());
198+
if (!allUnchanged && c.resultKind() == ResultKind.SAME) {
199+
item.setClass("unchanged");
200+
}
201+
ul.add(item);
202+
}
187203
section.add(ul);
204+
if (allUnchanged) {
205+
section = HtmlTree.DIV(section).setClass("unchanged");
206+
}
188207
contents.add(section);
189208
}
190209
}
191210

192-
private Content buildExports(RelativePosition<?> rPos, APIMap<ExportsDirective> apiMap) {
211+
private ContentAndResultKind buildExports(RelativePosition<?> rPos, APIMap<ExportsDirective> apiMap) {
193212
return buildExportsOpensProvides(rPos, apiMap, Keywords.EXPORTS, Keywords.TO,
194213
ExportsDirective::getPackage, ExportsDirective::getTargetModules);
195214
}
196215

197-
private Content buildOpens(RelativePosition<?> rPos, APIMap<OpensDirective> apiMap) {
216+
private ContentAndResultKind buildOpens(RelativePosition<?> rPos, APIMap<OpensDirective> apiMap) {
198217
return buildExportsOpensProvides(rPos, apiMap, Keywords.OPENS, Keywords.TO,
199218
OpensDirective::getPackage, OpensDirective::getTargetModules);
200219
}
201220

202-
private Content buildProvides(RelativePosition<?> rPos, APIMap<ProvidesDirective> apiMap) {
221+
private ContentAndResultKind buildProvides(RelativePosition<?> rPos, APIMap<ProvidesDirective> apiMap) {
203222
// ProvidesDirective is unusual in that part of it (i.e. the implementations)
204223
// is not part of the public API, and should only be displayed if allDirectiveDetails
205224
// is true.
@@ -208,13 +227,13 @@ private Content buildProvides(RelativePosition<?> rPos, APIMap<ProvidesDirective
208227
pd -> allDirectiveDetails ? pd.getImplementations() : Collections.emptyList());
209228
}
210229

211-
private Content buildUses(RelativePosition<?> rPos, APIMap<UsesDirective> apiMap) {
230+
private ContentAndResultKind buildUses(RelativePosition<?> rPos, APIMap<UsesDirective> apiMap) {
212231
return buildExportsOpensProvides(rPos, apiMap, Keywords.USES, Content.empty,
213232
UsesDirective::getService, d -> Collections.emptyList());
214233
}
215234

216235
private <T extends Directive, U extends Element, V extends Element>
217-
Content buildExportsOpensProvides(RelativePosition<?> rPos, APIMap<T> apiMap,
236+
ContentAndResultKind buildExportsOpensProvides(RelativePosition<?> rPos, APIMap<T> apiMap,
218237
Content directiveKeyword, Content sublistKeyword,
219238
Function<T, U> getPrimaryElement,
220239
Function<T, List<V>> getSecondaryElements) {
@@ -261,13 +280,14 @@ Content buildExportsOpensProvides(RelativePosition<?> rPos, APIMap<T> apiMap,
261280
}
262281
}
263282

264-
265283
// TODO: for now, this is stylistically similar to buildEnclosedElement,
266284
// but arguably a better way would be to move code for the check or cross into
267285
// the enclosing loop that builds the list.
268-
return HtmlTree.SPAN(getResultGlyph(rPos), Text.SPACE)
269-
.add(HtmlTree.SPAN(contents).setClass("signature"));
270286

287+
ResultKind result = getResultKind(rPos);
288+
289+
return new ContentAndResultKind(HtmlTree.SPAN(result.getContent(), Text.SPACE)
290+
.add(HtmlTree.SPAN(contents).setClass("signature")), result);
271291
}
272292

273293
private Content getName(ElementKey ek) {
@@ -288,7 +308,7 @@ private CharSequence getQualifiedName(ElementKey ek) {
288308
}
289309
}
290310

291-
private Content buildRequires(RelativePosition<?> rPos, APIMap<RequiresDirective> apiMap) {
311+
private ContentAndResultKind buildRequires(RelativePosition<?> rPos, APIMap<RequiresDirective> apiMap) {
292312
List<Content> contents = new ArrayList<>();
293313

294314
contents.add(Keywords.REQUIRES);
@@ -337,11 +357,12 @@ private Content buildRequires(RelativePosition<?> rPos, APIMap<RequiresDirective
337357
// TODO: would be nice to link to the module page if it can be determined to be available.
338358
contents.add(Text.of(archetype.getDependency().getQualifiedName()));
339359

340-
341360
// TODO: for now, this is stylistically similar to buildEnclosedElement,
342361
// but arguably a better way would be to move code for the check or cross into
343362
// the enclosing loop that builds the list.
344-
return HtmlTree.SPAN(getResultGlyph(rPos), Text.SPACE)
345-
.add(HtmlTree.SPAN(contents).setClass("signature"));
363+
ResultKind result = getResultKind(rPos);
364+
365+
return new ContentAndResultKind(HtmlTree.SPAN(result.getContent(), Text.SPACE)
366+
.add(HtmlTree.SPAN(contents).setClass("signature")), result);
346367
}
347368
}

0 commit comments

Comments
 (0)