Skip to content

Commit

Permalink
8328006: refactor large anonymous inner class in HtmlDocletWriter
Browse files Browse the repository at this point in the history
Reviewed-by: hannesw
  • Loading branch information
jonathan-gibbons committed Mar 15, 2024
1 parent 044f4ed commit 65a84c2
Showing 1 changed file with 189 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1292,196 +1292,218 @@ public ContentBuilder add(CharSequence text) {
}
}

var docTreeVisitor = new SimpleDocTreeVisitor<Boolean, Content>() {
var docTreeVisitor = new InlineVisitor(element, tag, isLastNode, context, ch, trees);

private boolean inAnAtag() {
return (tag instanceof StartElementTree st) && equalsIgnoreCase(st.getName(), "a");
}

@Override
public Boolean visitAttribute(AttributeTree node, Content content) {
if (!content.isEmpty()) {
content.add(" ");
}
content.add(node.getName());
if (node.getValueKind() == ValueKind.EMPTY) {
return false;
}
content.add("=");
String quote = switch (node.getValueKind()) {
case DOUBLE -> "\"";
case SINGLE -> "'";
default -> "";
};
content.add(quote);

/* In the following code for an attribute value:
* 1. {@docRoot} followed by text beginning "/.." is replaced by the value
* of the docrootParent option, followed by the remainder of the text
* 2. in the value of an "href" attribute in a <a> tag, an initial text
* value will have a relative link redirected.
* Note that, realistically, it only makes sense to ever use {@docRoot}
* at the beginning of a URL in an attribute value, but this is not
* required or enforced.
*/
boolean isHRef = inAnAtag() && equalsIgnoreCase(node.getName(), "href");
boolean first = true;
DocRootTree pendingDocRoot = null;
for (DocTree dt : node.getValue()) {
if (pendingDocRoot != null) {
if (dt instanceof TextTree tt) {
String text = tt.getBody();
if (text.startsWith("/..") && !options.docrootParent().isEmpty()) {
content.add(options.docrootParent());
content.add(textCleanup(text.substring(3), isLastNode));
pendingDocRoot = null;
continue;
}
}
pendingDocRoot.accept(this, content);
pendingDocRoot = null;
}

if (dt instanceof TextTree tt) {
String text = tt.getBody();
if (first && isHRef) {
text = redirectRelativeLinks(element, tt);
}
content.add(textCleanup(text, isLastNode));
} else if (dt instanceof DocRootTree drt) {
// defer until we see what, if anything, follows this node
pendingDocRoot = drt;
} else {
dt.accept(this, content);
}
first = false;
}
if (pendingDocRoot != null) {
pendingDocRoot.accept(this, content);
}

content.add(quote);
return false;
}
boolean allDone = docTreeVisitor.visit(tag, result);
commentRemoved = false;

@Override
public Boolean visitComment(CommentTree node, Content content) {
content.add(RawHtml.comment(node.getBody()));
return false;
}
if (allDone)
break;
}
// Close any open inline tags
while (!openTags.isEmpty()) {
result.add(RawHtml.endElement(openTags.removeLast()));
}
return result;
}

@Override
public Boolean visitEndElement(EndElementTree node, Content content) {
content.add(RawHtml.endElement(node.getName()));
return false;
}
private class InlineVisitor extends SimpleDocTreeVisitor<Boolean, Content> {
private final Element element;
private final DocTree tag;
private final boolean isLastNode;
private final TagletWriter.Context context;
private final CommentHelper ch;
private final List<? extends DocTree> trees;

InlineVisitor(Element element,
DocTree tag,
boolean isLastNode,
TagletWriter.Context context,
CommentHelper ch,
List<? extends DocTree> trees) {

this.element = element;
this.tag = tag;
this.isLastNode = isLastNode;
this.context = context;
this.ch = ch;
this.trees = trees;
}

@Override
public Boolean visitEntity(EntityTree node, Content content) {
content.add(Entity.of(node.getName()));
return false;
}
private boolean inAnAtag() {
return (tag instanceof StartElementTree st) && equalsIgnoreCase(st.getName(), "a");
}

@Override
public Boolean visitErroneous(ErroneousTree node, Content content) {
DocTreePath dtp = ch.getDocTreePath(node);
if (dtp != null) {
String body = node.getBody();
Matcher m = Pattern.compile("(?i)\\{@([a-z]+).*").matcher(body);
String tagName = m.matches() ? m.group(1) : null;
if (tagName == null) {
if (!configuration.isDocLintSyntaxGroupEnabled()) {
messages.warning(dtp, "doclet.tag.invalid_input", body);
}
content.add(invalidTagOutput(resources.getText("doclet.tag.invalid_input", body),
Optional.empty()));
} else {
messages.warning(dtp, "doclet.tag.invalid_usage", body);
content.add(invalidTagOutput(resources.getText("doclet.tag.invalid", tagName),
Optional.of(Text.of(body))));
@Override
public Boolean visitAttribute(AttributeTree node, Content content) {
if (!content.isEmpty()) {
content.add(" ");
}
content.add(node.getName());
if (node.getValueKind() == ValueKind.EMPTY) {
return false;
}
content.add("=");
String quote = switch (node.getValueKind()) {
case DOUBLE -> "\"";
case SINGLE -> "'";
default -> "";
};
content.add(quote);

/* In the following code for an attribute value:
* 1. {@docRoot} followed by text beginning "/.." is replaced by the value
* of the docrootParent option, followed by the remainder of the text
* 2. in the value of an "href" attribute in a <a> tag, an initial text
* value will have a relative link redirected.
* Note that, realistically, it only makes sense to ever use {@docRoot}
* at the beginning of a URL in an attribute value, but this is not
* required or enforced.
*/
boolean isHRef = inAnAtag() && equalsIgnoreCase(node.getName(), "href");
boolean first = true;
DocRootTree pendingDocRoot = null;
for (DocTree dt : node.getValue()) {
if (pendingDocRoot != null) {
if (dt instanceof TextTree tt) {
String text = tt.getBody();
if (text.startsWith("/..") && !options.docrootParent().isEmpty()) {
content.add(options.docrootParent());
content.add(textCleanup(text.substring(3), isLastNode));
pendingDocRoot = null;
continue;
}
}
return false;
pendingDocRoot.accept(this, content);
pendingDocRoot = null;
}

@Override
public Boolean visitEscape(EscapeTree node, Content content) {
result.add(node.getBody());
return false;
if (dt instanceof TextTree tt) {
String text = tt.getBody();
if (first && isHRef) {
text = redirectRelativeLinks(element, tt);
}
content.add(textCleanup(text, isLastNode));
} else if (dt instanceof DocRootTree drt) {
// defer until we see what, if anything, follows this node
pendingDocRoot = drt;
} else {
dt.accept(this, content);
}
first = false;
}
if (pendingDocRoot != null) {
pendingDocRoot.accept(this, content);
}

@Override
public Boolean visitInheritDoc(InheritDocTree node, Content content) {
Content output = getInlineTagOutput(element, node, context);
content.add(output);
// if we obtained the first sentence successfully, nothing more to do
return (context.isFirstSentence && !output.isEmpty());
}
content.add(quote);
return false;
}

@Override
public Boolean visitStartElement(StartElementTree node, Content content) {
Content attrs = new ContentBuilder();
if (node.getName().toString().matches("(?i)h[1-6]")
&& isIndexable()) {
createSectionIdAndIndex(node, trees, attrs, element, context);
}
for (DocTree dt : node.getAttributes()) {
dt.accept(this, attrs);
}
content.add(RawHtml.startElement(node.getName(), attrs, node.isSelfClosing()));
return false;
}
@Override
public Boolean visitComment(CommentTree node, Content content) {
content.add(RawHtml.comment(node.getBody()));
return false;
}

private CharSequence textCleanup(String text, boolean isLast) {
return textCleanup(text, isLast, false);
}
@Override
public Boolean visitEndElement(EndElementTree node, Content content) {
content.add(RawHtml.endElement(node.getName()));
return false;
}

private CharSequence textCleanup(String text, boolean isLast, boolean stripLeading) {
boolean stripTrailing = context.isFirstSentence && isLast;
if (stripLeading && stripTrailing) {
text = text.strip();
} else if (stripLeading) {
text = text.stripLeading();
} else if (stripTrailing) {
text = text.stripTrailing();
@Override
public Boolean visitEntity(EntityTree node, Content content) {
content.add(Entity.of(node.getName()));
return false;
}

@Override
public Boolean visitErroneous(ErroneousTree node, Content content) {
DocTreePath dtp = ch.getDocTreePath(node);
if (dtp != null) {
String body = node.getBody();
Matcher m = Pattern.compile("(?i)\\{@([a-z]+).*").matcher(body);
String tagName = m.matches() ? m.group(1) : null;
if (tagName == null) {
if (!configuration.isDocLintSyntaxGroupEnabled()) {
messages.warning(dtp, "doclet.tag.invalid_input", body);
}
text = utils.replaceTabs(text);
return Text.normalizeNewlines(text);
content.add(invalidTagOutput(resources.getText("doclet.tag.invalid_input", body),
Optional.empty()));
} else {
messages.warning(dtp, "doclet.tag.invalid_usage", body);
content.add(invalidTagOutput(resources.getText("doclet.tag.invalid", tagName),
Optional.of(Text.of(body))));
}
}
return false;
}

@Override
public Boolean visitText(TextTree node, Content content) {
String text = node.getBody();
result.add(text.startsWith("<![CDATA[")
? RawHtml.cdata(text)
: Text.of(textCleanup(text, isLastNode, commentRemoved)));
return false;
}
@Override
public Boolean visitEscape(EscapeTree node, Content content) {
content.add(node.getBody());
return false;
}

@Override
protected Boolean defaultAction(DocTree node, Content content) {
if (node instanceof InlineTagTree itt) {
var output = getInlineTagOutput(element, itt, context);
if (output != null) {
content.add(output);
}
}
return false;
}
@Override
public Boolean visitInheritDoc(InheritDocTree node, Content content) {
Content output = getInlineTagOutput(element, node, context);
content.add(output);
// if we obtained the first sentence successfully, nothing more to do
return (context.isFirstSentence && !output.isEmpty());
}

};
@Override
public Boolean visitStartElement(StartElementTree node, Content content) {
Content attrs = new ContentBuilder();
if (node.getName().toString().matches("(?i)h[1-6]")
&& isIndexable()) {
createSectionIdAndIndex(node, trees, attrs, element, context);
}
for (DocTree dt : node.getAttributes()) {
dt.accept(this, attrs);
}
content.add(RawHtml.startElement(node.getName(), attrs, node.isSelfClosing()));
return false;
}

boolean allDone = docTreeVisitor.visit(tag, result);
commentRemoved = false;
private CharSequence textCleanup(String text, boolean isLast) {
return textCleanup(text, isLast, false);
}

if (allDone)
break;
private CharSequence textCleanup(String text, boolean isLast, boolean stripLeading) {
boolean stripTrailing = context.isFirstSentence && isLast;
if (stripLeading && stripTrailing) {
text = text.strip();
} else if (stripLeading) {
text = text.stripLeading();
} else if (stripTrailing) {
text = text.stripTrailing();
}
text = utils.replaceTabs(text);
return Text.normalizeNewlines(text);
}
// Close any open inline tags
while (!openTags.isEmpty()) {
result.add(RawHtml.endElement(openTags.removeLast()));

@Override
public Boolean visitText(TextTree node, Content content) {
String text = node.getBody();
content.add(text.startsWith("<![CDATA[")
? RawHtml.cdata(text)
: Text.of(textCleanup(text, isLastNode, commentRemoved)));
return false;
}

@Override
protected Boolean defaultAction(DocTree node, Content content) {
if (node instanceof InlineTagTree itt) {
var output = getInlineTagOutput(element, itt, context);
if (output != null) {
content.add(output);
}
}
return false;
}
return result;
}

private boolean equalsIgnoreCase(Name name, String s) {
Expand Down

1 comment on commit 65a84c2

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.