Skip to content

Commit

Permalink
Derive FQCN of a {@link using ImportTree
Browse files Browse the repository at this point in the history
  • Loading branch information
vy committed Mar 11, 2024
1 parent e06237c commit 4780647
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.sun.source.doctree.EntityTree;
import com.sun.source.doctree.LinkTree;
import com.sun.source.doctree.LiteralTree;
import com.sun.source.doctree.ReferenceTree;
import com.sun.source.doctree.StartElementTree;
import com.sun.source.doctree.TextTree;
import com.sun.source.util.SimpleDocTreeVisitor;
Expand All @@ -45,6 +46,7 @@
import org.asciidoctor.ast.Section;
import org.asciidoctor.ast.StructuralNode;
import org.asciidoctor.ast.Table;
import org.jspecify.annotations.Nullable;

abstract class AbstractAsciiDocTreeVisitor extends SimpleDocTreeVisitor<Void, AsciiDocData> {

Expand Down Expand Up @@ -117,12 +119,8 @@ public Void visitStartElement(final StartElementTree node, final AsciiDocData da
data.getCurrentParagraph().setContext(BlockImpl.LISTING_CONTEXT);
break;
case "code":
data.newTextSpan();
break;
case "em":
case "i":
data.newTextSpan();
break;
case "strong":
case "b":
data.newTextSpan();
Expand Down Expand Up @@ -225,7 +223,7 @@ public Void visitEndElement(final EndElementTree node, final AsciiDocData data)

@Override
public Void visitLink(final LinkTree node, final AsciiDocData data) {
final String referenceSignature = node.getReference().getSignature();
final String referenceSignature = getReferenceSignature(node.getReference(), data);
final String referenceLabel = linkLabelToAsciiDoc(node);
data.appendAdjustingSpace(" apiref:")
.append(referenceSignature)
Expand All @@ -235,6 +233,12 @@ public Void visitLink(final LinkTree node, final AsciiDocData data) {
return super.visitLink(node, data);
}

private static String getReferenceSignature(final ReferenceTree referenceTree, final AsciiDocData data) {
final String referenceSignature = referenceTree.getSignature();
@Nullable final String qualifiedClassName = data.imports.get(referenceSignature);
return qualifiedClassName != null ? qualifiedClassName : referenceSignature;
}

private static String linkLabelToAsciiDoc(final LinkTree node) {
int[] labelTokenIndex = {0};
return node.getLabel().stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.sun.source.doctree.ParamTree;
import com.sun.source.util.DocTrees;
import javax.lang.model.element.Element;
import org.jspecify.annotations.Nullable;

/**
* Converts a {@link DocCommentTree} into AsciiDoc text.
Expand All @@ -38,19 +39,19 @@ final class AsciiDocConverter {
this.paramTreeVisitor = new ParamTreeVisitor();
}

public String toAsciiDoc(final Element element) {
@Nullable
public String toAsciiDoc(final Element element, final ElementImports imports) {
final DocCommentTree tree = docTrees.getDocCommentTree(element);
return tree != null ? toAsciiDoc(tree) : null;
}

public String toAsciiDoc(final DocCommentTree tree) {
final AsciiDocData data = new AsciiDocData();
if (tree == null) {
return null;
}
final AsciiDocData data = new AsciiDocData(imports);
tree.accept(docCommentTreeVisitor, data);
return data.getDocument().convert();
}

public String toAsciiDoc(final ParamTree tree) {
final AsciiDocData data = new AsciiDocData();
public String toAsciiDoc(final ParamTree tree, final ElementImports imports) {
final AsciiDocData data = new AsciiDocData(imports);
tree.accept(paramTreeVisitor, data);
return data.getDocument().convert();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,21 @@ final class AsciiDocData {
private static final char SPACE_CHAR = ' ';
private static final char CODE_CHAR = '`';

final ElementImports imports;
private final Document document;
private int currentSectionLevel;
private StructuralNode currentNode;
// A stack of nested text blocks. Each can have a different style.
private final Deque<Block> paragraphs = new ArrayDeque<>();
private final Deque<StringBuilder> lines = new ArrayDeque<>();

public AsciiDocData() {
document = new DocumentImpl();
currentSectionLevel = 1;
currentNode = document;
paragraphs.push(new BlockImpl(currentNode));
lines.push(new StringBuilder());
public AsciiDocData(final ElementImports imports) {
this.imports = imports;
this.document = new DocumentImpl();
this.currentSectionLevel = 1;
this.currentNode = document;
this.paragraphs.push(new BlockImpl(currentNode));
this.lines.push(new StringBuilder());
}

public void newLine() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.sun.source.doctree.ParamTree;
import com.sun.source.util.DocTrees;
import com.sun.source.util.SimpleDocTreeVisitor;
import com.sun.source.util.Trees;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.Files;
Expand Down Expand Up @@ -139,6 +140,8 @@ public class DescriptorGenerator extends AbstractProcessor {

private Types types;

private ElementImportsFactory importsFactory;

private AsciiDocConverter converter;

private Annotations annotations;
Expand All @@ -158,6 +161,8 @@ public synchronized void init(final ProcessingEnvironment processingEnv) {
elements = processingEnv.getElementUtils();
messager = processingEnv.getMessager();
types = processingEnv.getTypeUtils();
final Trees trees = Trees.instance(processingEnv);
importsFactory = ElementImports.factory(trees);
converter = new AsciiDocConverter(docTrees);
annotations = new Annotations(elements, types);
collectionType = getDeclaredType(processingEnv, "java.util.Collection");
Expand Down Expand Up @@ -286,7 +291,7 @@ private void populateType(final QualifiedNameable element, final Type docgenType
// Class name
docgenType.setClassName(element.getQualifiedName().toString());
// Description
docgenType.setDescription(createDescription(element, null));
docgenType.setDescription(createDescription(element, null, null));
}

private void populateScalarType(final TypeElement element, final ScalarType scalarType) {
Expand All @@ -298,15 +303,15 @@ private void populateScalarType(final TypeElement element, final ScalarType scal
&& types.isSameType(member.asType(), element.asType())) {
final VariableElement field = (VariableElement) member;
final ScalarValue value = new ScalarValue();
value.setDescription(createDescription(field, null));
value.setDescription(createDescription(field, null, null));
value.setName(field.getSimpleName().toString());
scalarType.addValue(value);
}
}
}
}

private Map<String, String> getParameterDescriptions(final Element element) {
private Map<String, String> getParameterDescriptions(final Element element, final ElementImports imports) {
final Map<String, String> descriptions = new HashMap<>();
final DocCommentTree docCommentTree = docTrees.getDocCommentTree(element);
if (docCommentTree != null) {
Expand All @@ -323,7 +328,7 @@ public Void visitDocComment(final DocCommentTree node, final Map<String, String>
@Override
public Void visitParam(final ParamTree paramTree, final Map<String, String> descriptions) {
final String name = paramTree.getName().getName().toString();
descriptions.put(name, defaultString(converter.toAsciiDoc(paramTree)));
descriptions.put(name, defaultString(converter.toAsciiDoc(paramTree, imports)));
return null;
}
},
Expand All @@ -333,20 +338,21 @@ public Void visitParam(final ParamTree paramTree, final Map<String, String> desc
}

private void populatePlugin(final TypeElement element, final PluginType pluginType) {
final ElementImports imports = importsFactory.ofElement(element);
populateType(element, pluginType);
// Supertypes
registerSupertypes(element).forEach(pluginType::addSupertype);
// Plugin factory
for (final Element member : element.getEnclosedElements()) {
if (annotations.hasFactoryAnnotation(member) && member instanceof ExecutableElement) {
final ExecutableElement executable = (ExecutableElement) member;
final Map<String, String> descriptions = getParameterDescriptions(executable);
final Map<String, String> descriptions = getParameterDescriptions(executable, imports);
final List<? extends VariableElement> parameters = executable.getParameters();
if (parameters.isEmpty()) {
// We have a builder
final TypeElement returnType = getReturnType(executable);
if (returnType != null) {
populateConfigurationProperties(getAllMembers(returnType), descriptions, pluginType);
populateConfigurationProperties(imports, getAllMembers(returnType), descriptions, pluginType);
} else {
messager.printMessage(
Diagnostic.Kind.WARNING,
Expand All @@ -355,13 +361,14 @@ private void populatePlugin(final TypeElement element, final PluginType pluginTy
}
} else {
// Old style factory method
populateConfigurationProperties(parameters, descriptions, pluginType);
populateConfigurationProperties(imports, parameters, descriptions, pluginType);
}
}
}
}

private void populateConfigurationProperties(
final ElementImports imports,
final Iterable<? extends Element> members,
final Map<? super String, String> descriptions,
final PluginType pluginType) {
Expand All @@ -372,7 +379,7 @@ private void populateConfigurationProperties(
// Gather documentation, which can be on any member.
for (final Element member : members) {
final String name = getAttributeOrPropertyName(member);
final String asciiDoc = converter.toAsciiDoc(member);
final String asciiDoc = converter.toAsciiDoc(member, imports);
descriptions.compute(name, (key, value) -> Stream.of(value, asciiDoc)
.filter(StringUtils::isNotEmpty)
.collect(Collectors.joining("\n")));
Expand All @@ -384,12 +391,13 @@ private void populateConfigurationProperties(
if (annotations.isAttributeAnnotation(annotation)) {
pluginAttributes.add(createPluginAttribute(
member,
imports,
description,
annotations
.getAttributeSpecifiedName(annotation)
.orElseGet(() -> getAttributeOrPropertyName(member))));
} else {
pluginElements.add(createPluginElement(member, description));
pluginElements.add(createPluginElement(member, imports, description));
}
}
}
Expand All @@ -398,8 +406,12 @@ private void populateConfigurationProperties(
}

@Nullable
private Description createDescription(final Element element, final @Nullable String fallbackDescriptionText) {
@Nullable String descriptionText = converter.toAsciiDoc(element);
private Description createDescription(
final Element element, @Nullable ElementImports imports, final @Nullable String fallbackDescriptionText) {
if (imports == null) {
imports = importsFactory.ofElement(element);
}
@Nullable String descriptionText = converter.toAsciiDoc(element, imports);
if (StringUtils.isBlank(descriptionText)) {
if (StringUtils.isBlank(fallbackDescriptionText)) {
return null;
Expand All @@ -414,7 +426,7 @@ private Description createDescription(final Element element, final @Nullable Str
}

private PluginAttribute createPluginAttribute(
final Element element, final String description, final String specifiedName) {
final Element element, final ElementImports imports, final String description, final String specifiedName) {
final PluginAttribute attribute = new PluginAttribute();
// Name
attribute.setName(specifiedName.isEmpty() ? getAttributeOrPropertyName(element) : specifiedName);
Expand All @@ -427,7 +439,7 @@ private PluginAttribute createPluginAttribute(
}
attribute.setType(className);
// Description
attribute.setDescription(createDescription(element, description));
attribute.setDescription(createDescription(element, imports, description));
// Required
attribute.setRequired(annotations.hasRequiredConstraint(element));
// Default value
Expand All @@ -440,7 +452,8 @@ private PluginAttribute createPluginAttribute(
return attribute;
}

private PluginElement createPluginElement(final Element element, final String description) {
private PluginElement createPluginElement(
final Element element, final ElementImports imports, final String description) {
final PluginElement pluginElement = new PluginElement();
// Type and multiplicity
final TypeMirror elementType = getMemberType(element);
Expand All @@ -453,7 +466,7 @@ private PluginElement createPluginElement(final Element element, final String de
// Required
pluginElement.setRequired(annotations.hasRequiredConstraint(element));
// Description
pluginElement.setDescription(createDescription(element, description));
pluginElement.setDescription(createDescription(element, imports, description));
return pluginElement;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.logging.log4j.docgen.processor;

import com.sun.source.tree.ImportTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreeScanner;
import com.sun.source.util.Trees;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import org.jspecify.annotations.Nullable;

/**
* A map associating simple class name keys with qualified class names that is collected from {@link com.sun.source.tree.ImportTree} of an {@link javax.lang.model.element.Element}.
*/
final class ElementImports extends TreeMap<String, String> {

private static final ElementImports EMPTY = new ElementImports();

private static final long serialVersionUID = 0L;

private ElementImports() {}

private ElementImports(Map<String, String> imports) {
super(imports);
}

static ElementImportsFactory factory(final Trees trees) {
return element -> {
final ImportCollectingTreeScanner scanner = new ImportCollectingTreeScanner();
@Nullable final TreePath treePath = trees.getPath(element);
if (treePath == null) {
return EMPTY;
}
scanner.scan(treePath.getCompilationUnit(), null);
return new ElementImports(scanner.imports);
};
}

private static final class ImportCollectingTreeScanner extends TreeScanner<Object, Trees> {

private final Map<String, String> imports = new HashMap<>();

private ImportCollectingTreeScanner() {}

@Override
public Object visitImport(final ImportTree importTree, final Trees trees) {
final Tree qualifiedIdentifier = importTree.getQualifiedIdentifier();
final String qualifiedClassName = qualifiedIdentifier.toString();
final String simpleClassName = qualifiedClassName.substring(qualifiedClassName.lastIndexOf('.') + 1);
imports.put(simpleClassName, qualifiedClassName);
return super.visitImport(importTree, trees);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.logging.log4j.docgen.processor;

import javax.lang.model.element.Element;

@FunctionalInterface
interface ElementImportsFactory {

ElementImports ofElement(Element element);
}

0 comments on commit 4780647

Please sign in to comment.