Skip to content

Commit e785f69

Browse files
Pavel Rappojonathan-gibbonshns
committedNov 24, 2021
8276124: Provide snippet support for properties files
Co-authored-by: Jonathan Gibbons <jjg@openjdk.org> Co-authored-by: Hannes Wallnöfer <hannesw@openjdk.org> Reviewed-by: jjg
1 parent 96fe1d0 commit e785f69

File tree

4 files changed

+263
-41
lines changed

4 files changed

+263
-41
lines changed
 

‎src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SnippetTaglet.java

+50-13
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.HashMap;
3131
import java.util.Map;
3232
import java.util.Objects;
33+
import java.util.Optional;
3334
import java.util.stream.Collectors;
3435

3536
import javax.lang.model.element.Element;
@@ -63,6 +64,38 @@
6364
*/
6465
public class SnippetTaglet extends BaseTaglet {
6566

67+
public enum Language {
68+
69+
JAVA("java"),
70+
PROPERTIES("properties");
71+
72+
private static final Map<String, Language> languages;
73+
74+
static {
75+
Map<String, Language> tmp = new HashMap<>();
76+
for (var language : values()) {
77+
String id = Objects.requireNonNull(language.identifier);
78+
if (tmp.put(id, language) != null)
79+
throw new IllegalStateException(); // 1-1 correspondence
80+
}
81+
languages = Map.copyOf(tmp);
82+
}
83+
84+
Language(String id) {
85+
identifier = id;
86+
}
87+
88+
private final String identifier;
89+
90+
public static Optional<Language> of(String identifier) {
91+
if (identifier == null)
92+
return Optional.empty();
93+
return Optional.ofNullable(languages.get(identifier));
94+
}
95+
96+
public String getIdentifier() {return identifier;}
97+
}
98+
6699
public SnippetTaglet() {
67100
super(DocTree.Kind.SNIPPET, true, EnumSet.allOf(Taglet.Location.class));
68101
}
@@ -217,14 +250,27 @@ private Content generateContent(Element holder, DocTree tag, TagletWriter writer
217250
}
218251
}
219252

253+
String lang = null;
254+
AttributeTree langAttr = attributes.get("lang");
255+
if (langAttr != null) {
256+
lang = stringValueOf(langAttr);
257+
} else if (containsClass) {
258+
lang = "java";
259+
} else if (containsFile) {
260+
lang = languageFromFileName(fileObject.getName());
261+
}
262+
263+
Optional<Language> language = Language.of(lang);
264+
265+
220266
// TODO cache parsed external snippet (WeakHashMap)
221267

222268
StyledText inlineSnippet = null;
223269
StyledText externalSnippet = null;
224270

225271
try {
226272
if (inlineContent != null) {
227-
inlineSnippet = parse(writer.configuration().getDocResources(), inlineContent);
273+
inlineSnippet = parse(writer.configuration().getDocResources(), language, inlineContent);
228274
}
229275
} catch (ParseException e) {
230276
var path = writer.configuration().utils.getCommentHelper(holder)
@@ -239,7 +285,7 @@ private Content generateContent(Element holder, DocTree tag, TagletWriter writer
239285

240286
try {
241287
if (externalContent != null) {
242-
externalSnippet = parse(writer.configuration().getDocResources(), externalContent);
288+
externalSnippet = parse(writer.configuration().getDocResources(), language, externalContent);
243289
}
244290
} catch (ParseException e) {
245291
assert fileObject != null;
@@ -289,15 +335,6 @@ private Content generateContent(Element holder, DocTree tag, TagletWriter writer
289335
assert inlineSnippet != null || externalSnippet != null;
290336
StyledText text = inlineSnippet != null ? inlineSnippet : externalSnippet;
291337

292-
String lang = null;
293-
AttributeTree langAttr = attributes.get("lang");
294-
if (langAttr != null) {
295-
lang = stringValueOf(langAttr);
296-
} else if (containsClass) {
297-
lang = "java";
298-
} else if (containsFile) {
299-
lang = languageFromFileName(fileObject.getName());
300-
}
301338
AttributeTree idAttr = attributes.get("id");
302339
String id = idAttr == null
303340
? null
@@ -326,8 +363,8 @@ private static String diff(String inline, String external) {
326363
""".formatted(inline, external);
327364
}
328365

329-
private StyledText parse(Resources resources, String content) throws ParseException {
330-
Parser.Result result = new Parser(resources).parse(content);
366+
private StyledText parse(Resources resources, Optional<Language> language, String content) throws ParseException {
367+
Parser.Result result = new Parser(resources).parse(language, content);
331368
result.actions().forEach(Action::perform);
332369
return result.text();
333370
}

‎src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Parser.java

+20-28
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import java.util.regex.PatternSyntaxException;
4040

4141
import jdk.javadoc.internal.doclets.toolkit.Resources;
42+
import jdk.javadoc.internal.doclets.toolkit.taglets.SnippetTaglet;
4243

4344
/*
4445
* Semantics of a EOL comment; plus
@@ -76,10 +77,10 @@
7677
*/
7778
public final class Parser {
7879

79-
// next-line tag behaves as if it were specified on the next line
80-
81-
private String eolMarker;
82-
private Matcher markedUpLine;
80+
private static final Pattern JAVA_COMMENT = Pattern.compile(
81+
"^(?<payload>.*)//(?<markup>\\s*@\\s*\\w+.+?)$");
82+
private static final Pattern PROPERTIES_COMMENT = Pattern.compile(
83+
"^(?<payload>[ \t]*([#!].*)?)[#!](?<markup>\\s*@\\s*\\w+.+?)$");
8384

8485
private final Resources resources;
8586
private final MarkupParser markupParser;
@@ -93,32 +94,23 @@ public Parser(Resources resources) {
9394
this.markupParser = new MarkupParser(resources);
9495
}
9596

96-
public Result parse(String source) throws ParseException {
97-
return parse("//", source);
97+
public Result parse(Optional<SnippetTaglet.Language> language, String source) throws ParseException {
98+
SnippetTaglet.Language lang = language.orElse(SnippetTaglet.Language.JAVA);
99+
var p = switch (lang) {
100+
case JAVA -> JAVA_COMMENT;
101+
case PROPERTIES -> PROPERTIES_COMMENT;
102+
};
103+
return parse(p, source);
98104
}
99105

100106
/*
101107
* Newline characters in the returned text are of the \n form.
102108
*/
103-
public Result parse(String eolMarker, String source) throws ParseException {
104-
Objects.requireNonNull(eolMarker);
109+
private Result parse(Pattern commentPattern, String source) throws ParseException {
110+
Objects.requireNonNull(commentPattern);
105111
Objects.requireNonNull(source);
106-
if (!Objects.equals(eolMarker, this.eolMarker)) {
107-
if (eolMarker.length() < 1) {
108-
throw new IllegalArgumentException();
109-
}
110-
for (int i = 0; i < eolMarker.length(); i++) {
111-
switch (eolMarker.charAt(i)) {
112-
case '\f', '\n', '\r' -> throw new IllegalArgumentException();
113-
}
114-
}
115-
this.eolMarker = eolMarker;
116-
// capture the rightmost eolMarker (e.g. "//")
117-
// The below Pattern.compile should never throw PatternSyntaxException
118-
Pattern pattern = Pattern.compile("^(.*)(" + Pattern.quote(eolMarker)
119-
+ "(\\s*@\\s*\\w+.+?))$");
120-
this.markedUpLine = pattern.matcher(""); // reusable matcher
121-
}
112+
113+
Matcher markedUpLine = commentPattern.matcher(""); // reusable matcher
122114

123115
tags.clear();
124116
regions.clear();
@@ -151,17 +143,17 @@ record OffsetAndLine(int offset, String line) { }
151143
if (!markedUpLine.matches()) { // (1)
152144
line = rawLine + (addLineTerminator ? "\n" : "");
153145
} else {
154-
String maybeMarkup = markedUpLine.group(3);
146+
String maybeMarkup = rawLine.substring(markedUpLine.start("markup"));
155147
List<Tag> parsedTags;
156148
try {
157149
parsedTags = markupParser.parse(maybeMarkup);
158150
} catch (ParseException e) {
159151
// translate error position from markup to file line
160-
throw new ParseException(e::getMessage, markedUpLine.start(3) + e.getPosition());
152+
throw new ParseException(e::getMessage, markedUpLine.start("markup") + e.getPosition());
161153
}
162154
for (Tag t : parsedTags) {
163155
t.lineSourceOffset = next.offset();
164-
t.markupLineOffset = markedUpLine.start(3);
156+
t.markupLineOffset = markedUpLine.start("markup");
165157
}
166158
thisLineTags.addAll(parsedTags);
167159
for (var tagIterator = thisLineTags.iterator(); tagIterator.hasNext(); ) {
@@ -176,7 +168,7 @@ record OffsetAndLine(int offset, String line) { }
176168
// TODO: log this with NOTICE;
177169
line = rawLine + (addLineTerminator ? "\n" : "");
178170
} else { // (3)
179-
String payload = markedUpLine.group(1);
171+
String payload = rawLine.substring(0, markedUpLine.end("payload"));
180172
line = payload + (addLineTerminator ? "\n" : "");
181173
}
182174
}

‎test/langtools/jdk/javadoc/doclet/testSnippetTag/SnippetTester.java

+6
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ protected String getSnippetHtmlRepresentation(String pathToHtmlFile,
101101
return getSnippetHtmlRepresentation(pathToHtmlFile, content, Optional.empty(), Optional.empty());
102102
}
103103

104+
protected String getSnippetHtmlRepresentation(String pathToHtmlFile,
105+
String content,
106+
Optional<String> lang) {
107+
return getSnippetHtmlRepresentation(pathToHtmlFile, content, lang, Optional.empty());
108+
}
109+
104110
protected String getSnippetHtmlRepresentation(String pathToHtmlFile,
105111
String content,
106112
Optional<String> lang,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/*
2+
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8266666
27+
* @summary Implementation for snippets
28+
* @library /tools/lib ../../lib
29+
* @modules jdk.compiler/com.sun.tools.javac.api
30+
* jdk.compiler/com.sun.tools.javac.main
31+
* jdk.javadoc/jdk.javadoc.internal.tool
32+
* @build javadoc.tester.* toolbox.ToolBox toolbox.ModuleBuilder builder.ClassBuilder
33+
* @run main TestLangProperties
34+
*/
35+
36+
import java.io.IOException;
37+
import java.nio.file.Files;
38+
import java.nio.file.Path;
39+
import java.nio.file.Paths;
40+
import java.util.ArrayList;
41+
import java.util.List;
42+
import java.util.Optional;
43+
44+
public class TestLangProperties extends SnippetTester {
45+
46+
public static void main(String... args) throws Exception {
47+
new TestLangProperties().runTests(m -> new Object[]{Paths.get(m.getName())});
48+
}
49+
50+
@Test
51+
public void testPositiveOuterMarkup(Path base) throws Exception {
52+
var testCases = new ArrayList<TestSnippetMarkup.TestCase>();
53+
for (String whitespace1 : List.of("", " ", "\t"))
54+
for (String commentIndicator1 : List.of("#", "!"))
55+
for (String whitespace2 : List.of("", " ", "\t")) {
56+
String markup = whitespace1 + commentIndicator1
57+
+ whitespace2 + "@highlight :";
58+
var t = new TestSnippetMarkup.TestCase(
59+
"""
60+
%s
61+
coffee=espresso
62+
tea=black
63+
""".formatted(markup),
64+
"""
65+
66+
<span class="bold">coffee=espresso
67+
</span>tea=black
68+
""");
69+
testCases.add(t);
70+
}
71+
testPositive(base, testCases);
72+
}
73+
74+
@Test
75+
public void testPositiveInnerMarkup(Path base) throws Exception {
76+
var testCases = new ArrayList<TestSnippetMarkup.TestCase>();
77+
for (String whitespace1 : List.of("", " ", "\t"))
78+
for (String commentIndicator1 : List.of("#", "!"))
79+
for (String whitespace2 : List.of("", " ", "\t"))
80+
for (String unrelatedComment : List.of("a comment"))
81+
for (String whitespace3 : List.of("", " "))
82+
for (String commentIndicator2 : List.of("#", "!")) {
83+
String payload = whitespace1 + commentIndicator1 + whitespace2 + unrelatedComment;
84+
String markup = payload + whitespace3 + commentIndicator2 + "@highlight :";
85+
var t = new TestSnippetMarkup.TestCase(
86+
"""
87+
%s
88+
coffee=espresso
89+
tea=black
90+
""".formatted(markup),
91+
"""
92+
%s
93+
<span class="bold">coffee=espresso
94+
</span>tea=black
95+
""".formatted(payload));
96+
testCases.add(t);
97+
}
98+
testPositive(base, testCases);
99+
}
100+
101+
@Test
102+
public void testPositiveIneffectiveOuterMarkup(Path base) throws Exception {
103+
var testCases = new ArrayList<TestSnippetMarkup.TestCase>();
104+
for (String whitespace1 : List.of("", " ", "\t"))
105+
for (String commentIndicator1 : List.of("#", "!"))
106+
for (String whitespace2 : List.of("", " ", "\t")) {
107+
String ineffectiveMarkup = whitespace1
108+
+ commentIndicator1 + whitespace2
109+
+ "@highlight :";
110+
var t = new TestSnippetMarkup.TestCase(
111+
"""
112+
coffee=espresso%s
113+
tea=black
114+
""".formatted(ineffectiveMarkup),
115+
"""
116+
coffee=espresso%s
117+
tea=black
118+
""".formatted(ineffectiveMarkup));
119+
testCases.add(t);
120+
}
121+
testPositive(base, testCases);
122+
}
123+
124+
@Test
125+
public void testPositiveIneffectiveInnerMarkup(Path base) throws Exception {
126+
var testCases = new ArrayList<TestSnippetMarkup.TestCase>();
127+
for (String whitespace1 : List.of("", " ", "\t"))
128+
for (String commentIndicator1 : List.of("#", "!"))
129+
for (String whitespace2 : List.of("", " ", "\t"))
130+
for (String unrelatedComment : List.of("a comment"))
131+
for (String whitespace3 : List.of("", " "))
132+
for (String commentIndicator2 : List.of("#", "!")) {
133+
String ineffectiveMarkup = whitespace1
134+
+ commentIndicator1 + whitespace2
135+
+ unrelatedComment + whitespace3
136+
+ commentIndicator2 + "@highlight :";
137+
var t = new TestSnippetMarkup.TestCase(
138+
"""
139+
coffee=espresso%s
140+
tea=black
141+
""".formatted(ineffectiveMarkup),
142+
"""
143+
coffee=espresso%s
144+
tea=black
145+
""".formatted(ineffectiveMarkup));
146+
testCases.add(t);
147+
}
148+
testPositive(base, testCases);
149+
}
150+
151+
private void testPositive(Path base, List<TestSnippetMarkup.TestCase> testCases)
152+
throws IOException {
153+
StringBuilder methods = new StringBuilder();
154+
forEachNumbered(testCases, (i, n) -> {
155+
String r = i.region().isBlank() ? "" : "region=" + i.region();
156+
var methodDef = """
157+
158+
/**
159+
{@snippet lang="properties" %s:
160+
%s}*/
161+
public void case%s() {}
162+
""".formatted(r, i.input(), n);
163+
methods.append(methodDef);
164+
});
165+
var classDef = """
166+
public class A {
167+
%s
168+
}
169+
""".formatted(methods.toString());
170+
Path src = Files.createDirectories(base.resolve("src"));
171+
tb.writeJavaFiles(src, classDef);
172+
javadoc("-d", base.resolve("out").toString(),
173+
"-sourcepath", src.toString(),
174+
src.resolve("A.java").toString());
175+
checkExit(Exit.OK);
176+
checkNoCrashes();
177+
forEachNumbered(testCases, (t, index) -> {
178+
String html = """
179+
<span class="element-name">case%s</span>()</div>
180+
<div class="block">
181+
%s
182+
</div>""".formatted(index, getSnippetHtmlRepresentation("A.html",
183+
t.expectedOutput(), Optional.of("properties")));
184+
checkOutput("A.html", true, html);
185+
});
186+
}
187+
}

0 commit comments

Comments
 (0)
Please sign in to comment.