From 06ca02ac972a84f5addc3447b59668f0fde12555 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 31 Jan 2024 23:18:27 +0100 Subject: [PATCH 1/6] Add Javadoc to Asciidoc converter We add a converter between Javadoc and Asciidoc that converts: * from a [`DocCommentTree`](https://docs.oracle.com/en/java/javase/17/docs/api/jdk.compiler/com/sun/source/doctree/DocCommentTree.html), provided by the `javac` compiler or `javadoc` tool, * to an AsciiDoctorJ [`Document`](https://javadoc.io/static/org.asciidoctor/asciidoctorj/3.0.0-alpha.2/org/asciidoctor/ast/Document.html). We provide a primitive implementation of the AsciiDoctorJ API that converts the AST back into a text document. --- log4j-docgen/pom.xml | 31 +++ .../AbstractAsciidocTreeVisitor.java | 233 ++++++++++++++++++ .../docgen/processor/AsciidocConverter.java | 86 +++++++ .../log4j/docgen/processor/AsciidocData.java | 149 +++++++++++ .../docgen/processor/internal/BlockImpl.java | 73 ++++++ .../docgen/processor/internal/CellImpl.java | 113 +++++++++ .../processor/internal/ContentNodeImpl.java | 190 ++++++++++++++ .../processor/internal/DocumentImpl.java | 123 +++++++++ .../docgen/processor/internal/ListImpl.java | 96 ++++++++ .../processor/internal/ListItemImpl.java | 74 ++++++ .../docgen/processor/internal/RowImpl.java | 32 +++ .../processor/internal/SectionImpl.java | 108 ++++++++ .../internal/StructuralNodeImpl.java | 158 ++++++++++++ .../docgen/processor/internal/TableImpl.java | 120 +++++++++ .../src/test/it/example/JavadocExample.java | 100 ++++++++ .../processor/AsciidocConverterTest.java | 132 ++++++++++ .../expected/processor/JavadocExample.adoc | 105 ++++++++ log4j-tools-parent/pom.xml | 16 ++ 18 files changed, 1939 insertions(+) create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocConverter.java create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/BlockImpl.java create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/CellImpl.java create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ContentNodeImpl.java create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/DocumentImpl.java create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListImpl.java create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListItemImpl.java create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/RowImpl.java create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/SectionImpl.java create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/StructuralNodeImpl.java create mode 100644 log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/TableImpl.java create mode 100644 log4j-docgen/src/test/it/example/JavadocExample.java create mode 100644 log4j-docgen/src/test/java/org/apache/logging/log4j/docgen/processor/AsciidocConverterTest.java create mode 100644 log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc diff --git a/log4j-docgen/pom.xml b/log4j-docgen/pom.xml index d8d735f4..a3b5e606 100644 --- a/log4j-docgen/pom.xml +++ b/log4j-docgen/pom.xml @@ -27,7 +27,11 @@ log4j-docgen + 17 false + + + 3.0.0-alpha.2 @@ -38,11 +42,38 @@ provided + + org.asciidoctor + asciidoctorj-api + ${asciidoctorj-api.version} + + + + org.apache.commons + commons-lang3 + + org.freemarker freemarker + + org.apache.logging.log4j + log4j-api + + + + org.apache.logging.log4j + log4j-plugins + + + + org.apache.logging.log4j + log4j-core + test + + org.junit.jupiter junit-jupiter-api diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java new file mode 100644 index 00000000..00b4fbe8 --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java @@ -0,0 +1,233 @@ +/* + * 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 static org.apache.commons.lang3.StringUtils.substringBefore; + +import com.sun.source.doctree.DocTree; +import com.sun.source.doctree.EndElementTree; +import com.sun.source.doctree.LinkTree; +import com.sun.source.doctree.LiteralTree; +import com.sun.source.doctree.StartElementTree; +import com.sun.source.doctree.TextTree; +import com.sun.source.util.SimpleDocTreeVisitor; +import java.util.ArrayList; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.docgen.processor.internal.BlockImpl; +import org.apache.logging.log4j.docgen.processor.internal.CellImpl; +import org.apache.logging.log4j.docgen.processor.internal.ListImpl; +import org.apache.logging.log4j.docgen.processor.internal.ListItemImpl; +import org.apache.logging.log4j.docgen.processor.internal.RowImpl; +import org.apache.logging.log4j.docgen.processor.internal.TableImpl; +import org.asciidoctor.ast.Block; +import org.asciidoctor.ast.Cell; +import org.asciidoctor.ast.Document; +import org.asciidoctor.ast.List; +import org.asciidoctor.ast.ListItem; +import org.asciidoctor.ast.Row; +import org.asciidoctor.ast.Section; +import org.asciidoctor.ast.StructuralNode; +import org.asciidoctor.ast.Table; + +class AbstractAsciidocTreeVisitor extends SimpleDocTreeVisitor { + + private static void appendSentences(final String text, final AsciidocData data) { + final String body = StringUtils.normalizeSpace(text); + final String[] sentences = body.split("(?<=\\w{2}[.!?])", -1); + // Full sentences + for (int i = 0; i < sentences.length - 1; i++) { + data.appendWords(sentences[i].strip()); + data.newLine(); + } + // Partial sentence + data.appendWords(sentences[sentences.length - 1].strip()); + } + + @Override + public Void visitStartElement(final StartElementTree node, final AsciidocData data) { + final String elementName = node.getName().toString(); + switch (elementName) { + case "p": + data.newParagraph(); + break; + case "ol": + // Nested list without a first paragraph + if (data.getCurrentNode() instanceof ListItem) { + data.newParagraph(); + } + data.pushChildNode(ListImpl::new).setContext(ListImpl.ORDERED_LIST_CONTEXT); + break; + case "ul": + // Nested list without a first paragraph + if (data.getCurrentNode() instanceof ListItem) { + data.newParagraph(); + } + data.pushChildNode(ListImpl::new).setContext(ListImpl.UNORDERED_LIST_CONTEXT); + break; + case "li": + if (!(data.getCurrentNode() instanceof List)) { + throw new IllegalArgumentException("A
  • tag must be a child of a
      or
        tag."); + } + data.pushChildNode(ListItemImpl::new); + break; + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + // Flush the current paragraph + data.newParagraph(); + StructuralNode currentNode; + // Remove other types of nodes from stack + while ((currentNode = data.getCurrentNode()) != null + && !(currentNode instanceof Section || currentNode instanceof Document)) { + data.popNode(); + } + break; + case "table": + data.pushChildNode(TableImpl::new); + break; + case "tr": + break; + case "th": + data.pushChildNode(CellImpl::new).setContext(CellImpl.HEADER_CONTEXT); + break; + case "td": + data.pushChildNode(CellImpl::new); + break; + case "pre": + data.newParagraph(); + final Block currentParagraph = data.getCurrentParagraph(); + currentParagraph.setContext(BlockImpl.LISTING_CONTEXT); + currentParagraph.setStyle(BlockImpl.SOURCE_STYLE); + break; + default: + } + return super.visitStartElement(node, data); + } + + @Override + public Void visitEndElement(final EndElementTree node, final AsciidocData data) { + final String elementName = node.getName().toString(); + switch (elementName) { + case "p": + // Ignore closing tags. + break; + case "ol": + case "ul": + case "li": + case "table": + case "th": + case "td": + data.popNode(); + break; + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + // Only flush the current line + if (!data.getCurrentLine().isEmpty()) { + data.newLine(); + } + // The current paragraph contains the title + // We retrieve the text and empty the paragraph + final Block currentParagraph = data.getCurrentParagraph(); + final String title = StringUtils.normalizeSpace(currentParagraph.convert()); + currentParagraph.setLines(new ArrayList<>()); + + // There should be no

        tags + final int newLevel = "h1".equals(elementName) ? 2 : elementName.charAt(1) - '0'; + data.setCurrentSectionLevel(newLevel); + data.getCurrentNode().setTitle(title); + break; + case "pre": + data.newParagraph(); + break; + case "tr": + // We group the new cells into a row + final Table table = (Table) data.getCurrentNode(); + final java.util.List cells = table.getBlocks(); + // First index of the row + int idx = 0; + for (final Row row : table.getHeader()) { + idx += row.getCells().size(); + } + for (final Row row : table.getBody()) { + idx += row.getCells().size(); + } + final Row row = new RowImpl(); + String context = CellImpl.BODY_CONTEXT; + for (int i = idx; i < table.getBlocks().size(); i++) { + final StructuralNode cell = cells.get(i); + context = cell.getContext(); + if (cell instanceof Cell) { + row.getCells().add((Cell) cell); + } + } + if (CellImpl.HEADER_CONTEXT.equals(context)) { + table.getHeader().add(row); + } else { + table.getBody().add(row); + } + break; + default: + } + return super.visitEndElement(node, data); + } + + @Override + public Void visitLink(final LinkTree node, final AsciidocData data) { + final String className = substringBefore(node.getReference().getSignature(), '#'); + final String simpleName = StringUtils.substringAfterLast(className, '.'); + if (!data.getCurrentLine().isEmpty()) { + data.append(" "); + } + data.append("xref:") + .append(className) + .append(".adoc[") + .append(simpleName) + .append("]"); + return super.visitLink(node, data); + } + + @Override + public Void visitLiteral(final LiteralTree node, final AsciidocData data) { + if (node.getKind() == DocTree.Kind.CODE) { + if (!data.getCurrentLine().isEmpty()) { + data.append(" "); + } + data.append("`").append(node.getBody().getBody()).append("`"); + } else { + node.getBody().accept(this, data); + } + return super.visitLiteral(node, data); + } + + @Override + public Void visitText(final TextTree node, final AsciidocData data) { + final Block currentParagraph = data.getCurrentParagraph(); + if (BlockImpl.PARAGRAPH_CONTEXT.equals(currentParagraph.getContext())) { + appendSentences(node.getBody(), data); + } else { + data.append(node.getBody()); + } + return super.visitText(node, data); + } +} diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocConverter.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocConverter.java new file mode 100644 index 00000000..19fe0e1a --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocConverter.java @@ -0,0 +1,86 @@ +/* + * 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.doctree.DocCommentTree; +import com.sun.source.doctree.DocTree; +import com.sun.source.doctree.DocTreeVisitor; +import com.sun.source.doctree.ParamTree; +import com.sun.source.util.DocTrees; +import javax.lang.model.element.Element; + +/** + * Converts a {@link DocCommentTree} into AsciiDoc text. + */ +class AsciidocConverter { + + private static final DocTreeVisitor DOC_COMMENT_TREE_VISITOR = new DocCommentTreeVisitor(); + private static final DocTreeVisitor PARAM_TREE_VISITOR = new ParamTreeVisitor(); + + private final DocTrees docTrees; + + AsciidocConverter(final DocTrees docTrees) { + this.docTrees = docTrees; + } + + public String toAsciiDoc(final Element element) { + final DocCommentTree tree = docTrees.getDocCommentTree(element); + return tree != null ? toAsciiDoc(tree) : null; + } + + public String toAsciiDoc(final DocCommentTree tree) { + final AsciidocData data = new AsciidocData(); + tree.accept(DOC_COMMENT_TREE_VISITOR, data); + return data.getDocument().convert(); + } + + public String toAsciiDoc(final ParamTree tree) { + final AsciidocData data = new AsciidocData(); + tree.accept(PARAM_TREE_VISITOR, data); + return data.getDocument().convert(); + } + + private static class DocCommentTreeVisitor extends AbstractAsciidocTreeVisitor { + @Override + public Void visitDocComment(final DocCommentTree node, final AsciidocData data) { + // Summary block wrapped in a new paragraph. + for (final DocTree docTree : node.getFirstSentence()) { + docTree.accept(this, data); + } + data.newParagraph(); + // Body + for (final DocTree docTree : node.getBody()) { + docTree.accept(this, data); + } + // Flushes the last paragraph + data.newParagraph(); + return super.visitDocComment(node, data); + } + } + + private static class ParamTreeVisitor extends AbstractAsciidocTreeVisitor { + @Override + public Void visitParam(final ParamTree node, final AsciidocData data) { + for (final DocTree docTree : node.getDescription()) { + docTree.accept(this, data); + } + // Flushes the last paragraph + data.newParagraph(); + return super.visitParam(node, data); + } + } +} diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java new file mode 100644 index 00000000..a35e8293 --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java @@ -0,0 +1,149 @@ +/* + * 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 java.util.EmptyStackException; +import java.util.function.Function; +import org.apache.logging.log4j.docgen.processor.internal.BlockImpl; +import org.apache.logging.log4j.docgen.processor.internal.DocumentImpl; +import org.apache.logging.log4j.docgen.processor.internal.SectionImpl; +import org.asciidoctor.ast.Block; +import org.asciidoctor.ast.Document; +import org.asciidoctor.ast.StructuralNode; + +class AsciidocData { + private final Document document; + private int currentSectionLevel; + private StructuralNode currentNode; + // not attached to the current node + private Block currentParagraph; + private final StringBuilder currentLine; + + public AsciidocData() { + document = new DocumentImpl(); + currentSectionLevel = 1; + currentNode = document; + currentParagraph = new BlockImpl(currentNode); + currentLine = new StringBuilder(); + } + + public void newLine() { + // Remove trailing space + final String line = currentLine.toString().stripTrailing(); + // Ignore leading empty lines + if (!currentParagraph.getLines().isEmpty() || !line.isEmpty()) { + currentParagraph.getLines().add(line); + } + currentLine.setLength(0); + } + + public AsciidocData append(final String text) { + final String[] lines = text.split("\r?\n", -1); + for (int i = 0; i < lines.length; i++) { + currentLine.append(lines[i]); + if (i != lines.length - 1) { + newLine(); + } + } + return this; + } + + public void appendWords(final String words) { + if (words.isBlank()) { + return; + } + // Separate text from previous words + if (!currentLine.isEmpty() && Character.isAlphabetic(words.codePointAt(0))) { + currentLine.append(" "); + } + currentLine.append(words); + } + + public void newParagraph() { + newLine(); + final java.util.List lines = currentParagraph.getLines(); + // Remove trailing empty lines + for (int i = lines.size() - 1; i >= 0; i--) { + if (lines.get(i).isEmpty()) { + lines.remove(i); + } + } + if (!currentParagraph.getLines().isEmpty()) { + currentNode.append(currentParagraph); + currentParagraph = new BlockImpl(currentNode); + } + } + + public StructuralNode getCurrentNode() { + return currentNode; + } + + public Block getCurrentParagraph() { + return currentParagraph; + } + + public StringBuilder getCurrentLine() { + return currentLine; + } + + public Document getDocument() { + return document; + } + + public void setCurrentSectionLevel(final int sectionLevel) { + while (sectionLevel < currentSectionLevel) { + popNode(); + currentSectionLevel--; + } + while (sectionLevel > currentSectionLevel) { + pushChildNode(SectionImpl::new); + currentSectionLevel++; + } + } + + /** + * Creates and appends a new child to the current node. + * + * @param supplier a function to create a new node that takes its parent node a parameter. + */ + public StructuralNode pushChildNode(final Function supplier) { + // Flushes the current paragraph + newParagraph(); + + final StructuralNode child = supplier.apply(currentNode); + // Creates a new current paragraph + currentParagraph = new BlockImpl(child); + + currentNode.append(child); + return currentNode = child; + } + + public void popNode() { + final StructuralNode currentNode = this.currentNode; + // Flushes the current paragraph + newParagraph(); + + final StructuralNode parent = (StructuralNode) currentNode.getParent(); + if (parent == null) { + throw new EmptyStackException(); + } + // Creates a new current paragraph + currentParagraph = new BlockImpl(parent); + + this.currentNode = parent; + } +} diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/BlockImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/BlockImpl.java new file mode 100644 index 00000000..a2dbfd8d --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/BlockImpl.java @@ -0,0 +1,73 @@ +/* + * 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.internal; + +import java.util.ArrayList; +import java.util.List; +import org.asciidoctor.ast.Block; +import org.asciidoctor.ast.ContentNode; + +public class BlockImpl extends StructuralNodeImpl implements Block { + + public static final String PARAGRAPH_CONTEXT = "paragraph"; + public static final String LISTING_CONTEXT = "listing"; + public static final String SOURCE_STYLE = "source"; + + private List lines = new ArrayList<>(); + + public BlockImpl(final ContentNode parent) { + super(parent); + setContext(PARAGRAPH_CONTEXT); + } + + @Override + public void formatTo(final StringBuilder buffer) { + if (getStyle() != null) { + buffer.append('[').append(getStyle()).append("]\n"); + } + if (LISTING_CONTEXT.equals(getContext())) { + buffer.append("----\n"); + } + lines.forEach(line -> buffer.append(line).append('\n')); + if (LISTING_CONTEXT.equals(getContext())) { + buffer.append("----\n"); + } + } + + @Override + public List getLines() { + return lines; + } + + @Override + public void setLines(final List lines) { + this.lines = lines; + } + + // + // All methods below this line are not implemented. + // + @Override + public String getSource() { + throw new UnsupportedOperationException(); + } + + @Override + public void setSource(final String source) { + throw new UnsupportedOperationException(); + } +} diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/CellImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/CellImpl.java new file mode 100644 index 00000000..a28c9a90 --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/CellImpl.java @@ -0,0 +1,113 @@ +/* + * 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.internal; + +import org.apache.logging.log4j.util.StringBuilderFormattable; +import org.asciidoctor.ast.Cell; +import org.asciidoctor.ast.Column; +import org.asciidoctor.ast.ContentNode; +import org.asciidoctor.ast.Document; +import org.asciidoctor.ast.Table; + +public class CellImpl extends StructuralNodeImpl implements Cell { + + public static final String HEADER_CONTEXT = "header"; + public static final String BODY_CONTEXT = "body"; + + public CellImpl(final ContentNode parent) { + super(parent); + setContext(BODY_CONTEXT); + } + + @Override + public void formatTo(final StringBuilder buffer) { + if (getBlocks().size() > 1) { + buffer.append('a'); + } + buffer.append("| "); + getBlocks().forEach(node -> { + if (node instanceof final StringBuilderFormattable formattable) { + formattable.formatTo(buffer); + } else { + buffer.append(node.convert()); + } + }); + } + + // + // All methods below this line are not implemented. + // + @Override + public Column getColumn() { + throw new UnsupportedOperationException(); + } + + @Override + public int getColspan() { + throw new UnsupportedOperationException(); + } + + @Override + public int getRowspan() { + throw new UnsupportedOperationException(); + } + + @Override + public String getText() { + throw new UnsupportedOperationException(); + } + + @Override + public String getSource() { + throw new UnsupportedOperationException(); + } + + @Override + public void setSource(final String source) { + throw new UnsupportedOperationException(); + } + + @Override + public Table.HorizontalAlignment getHorizontalAlignment() { + throw new UnsupportedOperationException(); + } + + @Override + public void setHorizontalAlignment(final Table.HorizontalAlignment halign) { + throw new UnsupportedOperationException(); + } + + @Override + public Table.VerticalAlignment getVerticalAlignment() { + throw new UnsupportedOperationException(); + } + + @Override + public void setVerticalAlignment(final Table.VerticalAlignment valign) { + throw new UnsupportedOperationException(); + } + + @Override + public Document getInnerDocument() { + throw new UnsupportedOperationException(); + } + + @Override + public void setInnerDocument(final Document document) { + throw new UnsupportedOperationException(); + } +} diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ContentNodeImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ContentNodeImpl.java new file mode 100644 index 00000000..63e2b915 --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ContentNodeImpl.java @@ -0,0 +1,190 @@ +/* + * 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.internal; + +import java.util.List; +import java.util.Map; +import org.asciidoctor.ast.ContentNode; +import org.asciidoctor.ast.Document; + +public abstract class ContentNodeImpl implements ContentNode { + + private final ContentNode parent; + private String context; + + protected ContentNodeImpl(final ContentNode parent) { + this.parent = parent; + } + + @Override + public ContentNode getParent() { + return parent; + } + + @Override + public String getContext() { + return context; + } + + @Override + public void setContext(final String context) { + this.context = context; + } + + @Override + public Document getDocument() { + return parent != null ? parent.getDocument() : null; + } + + // + // All methods below this line are not implemented. + // + @Override + public String getId() { + throw new UnsupportedOperationException(); + } + + @Override + public void setId(final String id) { + throw new UnsupportedOperationException(); + } + + @Override + public String getNodeName() { + throw new UnsupportedOperationException(); + } + + @Override + public Map getAttributes() { + throw new UnsupportedOperationException(); + } + + @Override + public Object getAttribute(final Object name, final Object defaultValue, final boolean inherit) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getAttribute(final Object name, final Object defaultValue) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getAttribute(final Object name) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasAttribute(final Object name) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasAttribute(final Object name, final boolean inherited) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isAttribute(final Object name, final Object expected) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isAttribute(final Object name, final Object expected, final boolean inherit) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean setAttribute(final Object name, final Object value, final boolean overwrite) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isOption(final Object name) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isRole() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasRole(final String role) { + throw new UnsupportedOperationException(); + } + + @Override + public String getRole() { + throw new UnsupportedOperationException(); + } + + @Override + public List getRoles() { + throw new UnsupportedOperationException(); + } + + @Override + public void addRole(final String role) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeRole(final String role) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isReftext() { + throw new UnsupportedOperationException(); + } + + @Override + public String getReftext() { + throw new UnsupportedOperationException(); + } + + @Override + public String iconUri(final String name) { + throw new UnsupportedOperationException(); + } + + @Override + public String mediaUri(final String target) { + throw new UnsupportedOperationException(); + } + + @Override + public String imageUri(final String targetImage) { + throw new UnsupportedOperationException(); + } + + @Override + public String imageUri(final String targetImage, final String assetDirKey) { + throw new UnsupportedOperationException(); + } + + @Override + public String readAsset(final String path, final Map opts) { + throw new UnsupportedOperationException(); + } + + @Override + public String normalizeWebPath(final String path, final String start, final boolean preserveUriTarget) { + throw new UnsupportedOperationException(); + } +} diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/DocumentImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/DocumentImpl.java new file mode 100644 index 00000000..53b0a251 --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/DocumentImpl.java @@ -0,0 +1,123 @@ +/* + * 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.internal; + +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.util.StringBuilderFormattable; +import org.asciidoctor.ast.Author; +import org.asciidoctor.ast.Catalog; +import org.asciidoctor.ast.Document; +import org.asciidoctor.ast.RevisionInfo; +import org.asciidoctor.ast.StructuralNode; +import org.asciidoctor.ast.Title; + +public class DocumentImpl extends StructuralNodeImpl implements Document { + + public DocumentImpl() { + super(null); + } + + @Override + public void formatTo(final StringBuilder buffer) { + if (!StringUtils.isBlank(getTitle())) { + buffer.append("= ").append(getTitle()).append("\n\n"); + } + boolean first = true; + for (final StructuralNode node : getBlocks()) { + if (!first) { + buffer.append('\n'); + } else { + first = false; + } + if (node instanceof final StringBuilderFormattable formattable) { + formattable.formatTo(buffer); + } else { + buffer.append(node.convert()); + } + } + } + + @Override + public String getDoctitle() { + return getTitle(); + } + + // + // All methods below this line are not implemented. + // + @Override + public Title getStructuredDoctitle() { + throw new UnsupportedOperationException(); + } + + @Override + public List getAuthors() { + throw new UnsupportedOperationException(); + } + + @Override + public String getSource() { + throw new UnsupportedOperationException(); + } + + @Override + public List getSourceLines() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isBasebackend(final String backend) { + throw new UnsupportedOperationException(); + } + + @Override + public Map getOptions() { + throw new UnsupportedOperationException(); + } + + @Override + public int getAndIncrementCounter(final String name) { + throw new UnsupportedOperationException(); + } + + @Override + public int getAndIncrementCounter(final String name, final int initialValue) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isSourcemap() { + throw new UnsupportedOperationException(); + } + + @Override + public void setSourcemap(final boolean state) { + throw new UnsupportedOperationException(); + } + + @Override + public Catalog getCatalog() { + throw new UnsupportedOperationException(); + } + + @Override + public RevisionInfo getRevisionInfo() { + throw new UnsupportedOperationException(); + } +} diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListImpl.java new file mode 100644 index 00000000..d802825f --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListImpl.java @@ -0,0 +1,96 @@ +/* + * 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.internal; + +import java.util.Objects; +import org.apache.logging.log4j.util.StringBuilderFormattable; +import org.asciidoctor.ast.ContentNode; +import org.asciidoctor.ast.List; +import org.asciidoctor.ast.ListItem; +import org.asciidoctor.ast.StructuralNode; + +public class ListImpl extends StructuralNodeImpl implements List { + + public static final String ORDERED_LIST_CONTEXT = "olist"; + private static final char ORDERED_LIST_MARKER = '.'; + public static final String UNORDERED_LIST_CONTEXT = "ulist"; + private static final char UNORDERED_LIST_MARKER = '*'; + + public ListImpl(final ContentNode parent) { + super(parent); + setContext(ORDERED_LIST_CONTEXT); + } + + @Override + public void formatTo(final StringBuilder buffer) { + final String prefix = computeItemPrefix(); + getBlocks().forEach(node -> { + buffer.append(prefix); + if (node instanceof final StringBuilderFormattable formattable) { + formattable.formatTo(buffer); + } else { + buffer.append(node.convert()); + } + }); + } + + /** + * Computes the appropriate prefix for the list. + */ + private String computeItemPrefix() { + final StringBuilder sb = new StringBuilder(); + ContentNode currentNode = this; + while (currentNode instanceof List) { + // If the type of list changes stop. + if (!Objects.equals(getContext(), currentNode.getContext())) { + break; + } + sb.append( + ORDERED_LIST_CONTEXT.equals(currentNode.getContext()) + ? ORDERED_LIST_MARKER + : UNORDERED_LIST_MARKER); + currentNode = currentNode.getParent(); + if (!(currentNode instanceof ListItem)) { + break; + } + currentNode = currentNode.getParent(); + } + return sb.reverse().append(' ').toString(); + } + + @Override + public java.util.List getItems() { + return getBlocks(); + } + + @Override + public boolean hasItems() { + return !getBlocks().isEmpty(); + } + + @Override + public void setContext(final String context) { + switch (context) { + case ORDERED_LIST_CONTEXT: + case UNORDERED_LIST_CONTEXT: + break; + default: + throw new RuntimeException("Unknown list context " + context); + } + super.setContext(context); + } +} diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListItemImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListItemImpl.java new file mode 100644 index 00000000..29880411 --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListItemImpl.java @@ -0,0 +1,74 @@ +/* + * 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.internal; + +import org.apache.logging.log4j.util.StringBuilderFormattable; +import org.asciidoctor.ast.ContentNode; +import org.asciidoctor.ast.ListItem; +import org.asciidoctor.ast.StructuralNode; + +public class ListItemImpl extends StructuralNodeImpl implements ListItem { + + public ListItemImpl(final ContentNode parent) { + super(parent); + } + + @Override + public void formatTo(final StringBuilder buffer) { + boolean first = true; + for (final StructuralNode node : getBlocks()) { + if (!first) { + buffer.append("+\n"); + } else { + first = false; + } + if (node instanceof final StringBuilderFormattable formattable) { + formattable.formatTo(buffer); + } else { + buffer.append(node.convert()); + } + } + } + + // + // All methods below this line are not implemented. + // + @Override + public String getMarker() { + throw new UnsupportedOperationException(); + } + + @Override + public String getText() { + throw new UnsupportedOperationException(); + } + + @Override + public String getSource() { + throw new UnsupportedOperationException(); + } + + @Override + public void setSource(final String source) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasText() { + throw new UnsupportedOperationException(); + } +} diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/RowImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/RowImpl.java new file mode 100644 index 00000000..6dff8982 --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/RowImpl.java @@ -0,0 +1,32 @@ +/* + * 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.internal; + +import java.util.ArrayList; +import java.util.List; +import org.asciidoctor.ast.Cell; +import org.asciidoctor.ast.Row; + +public class RowImpl implements Row { + + private final List cells = new ArrayList<>(); + + @Override + public List getCells() { + return cells; + } +} diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/SectionImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/SectionImpl.java new file mode 100644 index 00000000..6824f15b --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/SectionImpl.java @@ -0,0 +1,108 @@ +/* + * 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.internal; + +import java.util.ArrayList; +import java.util.List; +import org.apache.logging.log4j.util.StringBuilderFormattable; +import org.asciidoctor.ast.ContentNode; +import org.asciidoctor.ast.Document; +import org.asciidoctor.ast.Section; +import org.asciidoctor.ast.StructuralNode; + +public class SectionImpl extends StructuralNodeImpl implements Section { + + private final List content = new ArrayList<>(); + + public SectionImpl(final ContentNode parent) { + super(parent); + } + + @Override + public void formatTo(final StringBuilder buffer) { + final String title = getTitle(); + if (title != null) { + buffer.append("=".repeat(computeSectionLevel(this))) + .append(' ') + .append(title) + .append("\n\n"); + } + boolean first = true; + for (final StructuralNode node : getBlocks()) { + if (!first) { + buffer.append('\n'); + } else { + first = false; + } + if (node instanceof final StringBuilderFormattable formattable) { + formattable.formatTo(buffer); + } else { + buffer.append(node.convert()); + } + } + } + + private static int computeSectionLevel(final StructuralNode section) { + int level = 0; + StructuralNode currentNode = section; + while (currentNode != null) { + if (currentNode instanceof Section || currentNode instanceof Document) { + level++; + } + currentNode = (StructuralNode) currentNode.getParent(); + } + return level; + } + + @Override + public String getSectionName() { + return getTitle(); + } + + // + // All methods below this line are not implemented. + // + @Override + public int getIndex() { + throw new UnsupportedOperationException(); + } + + @Override + public String getNumeral() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isSpecial() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isNumbered() { + throw new UnsupportedOperationException(); + } + + @Override + public String getSectnum() { + throw new UnsupportedOperationException(); + } + + @Override + public String getSectnum(final String delimiter) { + throw new UnsupportedOperationException(); + } +} diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/StructuralNodeImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/StructuralNodeImpl.java new file mode 100644 index 00000000..402ae54d --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/StructuralNodeImpl.java @@ -0,0 +1,158 @@ +/* + * 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.internal; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.apache.logging.log4j.util.StringBuilderFormattable; +import org.asciidoctor.ast.ContentNode; +import org.asciidoctor.ast.Cursor; +import org.asciidoctor.ast.StructuralNode; + +public abstract class StructuralNodeImpl extends ContentNodeImpl implements StructuralNode, StringBuilderFormattable { + + private int level; + private String title; + private String style; + + private final List blocks = new ArrayList<>(); + + public StructuralNodeImpl(final ContentNode parent) { + super(parent); + } + + @Override + public String getTitle() { + return title; + } + + @Override + public void setTitle(final String title) { + this.title = title; + } + + @Override + public String getStyle() { + return style; + } + + @Override + public void setStyle(final String style) { + this.style = style; + } + + @Override + public List getBlocks() { + return blocks; + } + + @Override + public void append(final StructuralNode structuralNode) { + blocks.add(structuralNode); + } + + @Override + public String convert() { + final StringBuilder sb = new StringBuilder(); + formatTo(sb); + return sb.toString(); + } + + @Override + public int getLevel() { + return level; + } + + @Override + public void setLevel(final int level) { + this.level = level; + } + + // + // All methods below this line are not implemented. + // + @Override + public List findBy(final Map map) { + throw new UnsupportedOperationException(); + } + + @Override + public String getCaption() { + throw new UnsupportedOperationException(); + } + + @Override + public void setCaption(final String caption) { + throw new UnsupportedOperationException(); + } + + @Override + public String getContentModel() { + throw new UnsupportedOperationException(); + } + + @Override + public Cursor getSourceLocation() { + throw new UnsupportedOperationException(); + } + + @Override + public List getSubstitutions() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isSubstitutionEnabled(final String substitutionEnabled) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeSubstitution(final String substitution) { + throw new UnsupportedOperationException(); + } + + @Override + public void addSubstitution(final String substitution) { + throw new UnsupportedOperationException(); + } + + @Override + public void prependSubstitution(final String substitution) { + throw new UnsupportedOperationException(); + } + + @Override + public void setSubstitutions(final String... substitutions) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getContent() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isInline() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isBlock() { + throw new UnsupportedOperationException(); + } +} diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/TableImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/TableImpl.java new file mode 100644 index 00000000..c57b1795 --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/TableImpl.java @@ -0,0 +1,120 @@ +/* + * 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.internal; + +import java.util.ArrayList; +import java.util.List; +import org.apache.logging.log4j.util.StringBuilderFormattable; +import org.asciidoctor.ast.Column; +import org.asciidoctor.ast.ContentNode; +import org.asciidoctor.ast.Row; +import org.asciidoctor.ast.Table; + +public class TableImpl extends StructuralNodeImpl implements Table { + + private final List header = new ArrayList<>(); + private final List body = new ArrayList<>(); + + public TableImpl(final ContentNode parent) { + super(parent); + } + + @Override + public void formatTo(final StringBuilder buffer) { + final int colCount = header.isEmpty() + ? body.isEmpty() ? 1 : body.get(0).getCells().size() + : header.get(0).getCells().size(); + + buffer.append("[cols=\""); + formatColSpecifier(colCount, buffer); + if (!header.isEmpty()) { + buffer.append("\",options=\"headers"); + } + buffer.append("\"]\n").append("|===\n"); + getHeader().forEach(row -> formatRow(row, buffer)); + getBody().forEach(row -> formatRow(row, buffer)); + buffer.append("\n|===\n"); + } + + private static void formatColSpecifier(final int colCount, final StringBuilder buffer) { + if (colCount > 0) { + buffer.append("1"); + } + for (int i = 1; i < colCount; i++) { + buffer.append(",1"); + } + } + + private void formatRow(final Row row, final StringBuilder buffer) { + buffer.append('\n'); + row.getCells().forEach(cell -> { + if (cell instanceof final StringBuilderFormattable formattable) { + formattable.formatTo(buffer); + } else { + buffer.append(cell.convert()); + } + }); + } + + @Override + public List getHeader() { + return header; + } + + @Override + public List getBody() { + return body; + } + + // + // All methods below this line are not implemented. + // + @Override + public List getFooter() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasHeaderOption() { + throw new UnsupportedOperationException(); + } + + @Override + public List getColumns() { + throw new UnsupportedOperationException(); + } + + @Override + public String getFrame() { + throw new UnsupportedOperationException(); + } + + @Override + public void setFrame(final String frame) { + throw new UnsupportedOperationException(); + } + + @Override + public String getGrid() { + throw new UnsupportedOperationException(); + } + + @Override + public void setGrid(final String grid) { + throw new UnsupportedOperationException(); + } +} diff --git a/log4j-docgen/src/test/it/example/JavadocExample.java b/log4j-docgen/src/test/it/example/JavadocExample.java new file mode 100644 index 00000000..5ddf2c4c --- /dev/null +++ b/log4j-docgen/src/test/it/example/JavadocExample.java @@ -0,0 +1,100 @@ +/* + * 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 example; + +/** + * Example of JavaDoc to AsciiDoc conversion + *

        + * We run the {@code javadoc} tool on this class to test conversion of JavaDoc comments to AsciiDoc. This + * paragraph has two sentences. + *

        + *

        + * Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum blandit dictum sem, ornare posuere lorem + * convallis sit amet. Sed dui augue, faucibus ut nisi id, mollis euismod nibh. Donec lobortis luctus viverra. In + * orci ante, pretium et fringilla at, sagittis nec justo. Cras finibus lorem vel volutpat interdum. Sed laoreet + * libero eros, ac cursus nibh dapibus vitae. Integer ante lorem, rhoncus at tortor vel, congue accumsan lorem. + * In hac habitasse platea dictumst. Nunc luctus ornare justo. Etiam ut metus id tortor dignissim semper. Nam + * turpis dui, aliquet nec enim et, faucibus accumsan dui. + *

        + *

        + * Aenean tincidunt elit id posuere mattis. Fusce bibendum sapien sed risus ultricies, non molestie erat volutpat. + * Donec nisi felis, egestas eu lobortis id, vulputate nec felis. In at dui euismod, blandit nulla et, accumsan + * elit. Proin id venenatis dui. Suspendisse sit amet est ut neque tincidunt venenatis. Donec bibendum quis velit + * fermentum porttitor. Maecenas faucibus, eros sit amet maximus malesuada, turpis neque bibendum justo, eu + * vehicula justo metus a ipsum. In at ullamcorper ipsum. Quisque in vehicula erat. Proin vitae suscipit dui, + * rutrum hendrerit augue. Curabitur finibus feugiat elit. + *

        + *
          + *
        1. Item with a nested ordered list. + *
            + *
          1. First nested item.
          2. + *
          3. Second nested item.
          4. + *
          + *
        2. + *
        3. Item with a nested unordered list. + *
            + *
          • Unordered list item.
          • + *
          + *
        4. + *
        5. + *

          + * Item with complex content + *

          + *

          + * Mauris suscipit velit nec ligula mattis, nec varius augue accumsan. Curabitur a dolor dui. Quisque + * congue facilisis est nec dignissim. Pellentesque egestas eleifend faucibus. Fusce imperdiet ligula a + * lectus fringilla varius. Sed malesuada porta vulputate. Sed vulputate purus nec nibh interdum + * convallis. Cras faucibus, dolor tempus lacinia vehicula, elit risus luctus libero, sed molestie nisi + * lorem sit amet enim. Integer vitae enim sagittis, malesuada lorem at, interdum tellus. Suspendisse + * potenti. Vestibulum ac nisi sit amet ex dictum suscipit. Nulla varius augue a velit tincidunt feugiat. + * Proin fringilla id leo ut dignissim. Vivamus eu tellus eget orci suscipit viverra. Donec sodales et + * arcu vel mollis. + *

          + *

          + * Praesent gravida auctor lectus quis interdum. Etiam semper mauris quis neque bibendum molestie. + * Maecenas a lacus nec risus pellentesque accumsan. Suspendisse dictum dui eleifend nibh facilisis, non + * consequat neque elementum. Donec scelerisque ultricies ipsum, pretium elementum ex pellentesque + * malesuada. Mauris egestas massa vitae sapien lobortis convallis. Donec feugiat, purus commodo + * consequat vehicula, dolor urna aliquam arcu, id rutrum quam tortor quis libero. Sed varius justo eget + * congue lacinia. + *

          + *
        6. + *
        + *
          + *
        • Item of an unordered list.
        • + *
        + *

        First section

        + * + * + * + * + * + * + * + * + * + * + * + * + * + *
        KeyValue
        A1
        B2
        + *

        Subsection

        + *
        + *     private static final Logger logger = LogManager.getLogger();
        + * 
        + */ +public class JavadocExample {} diff --git a/log4j-docgen/src/test/java/org/apache/logging/log4j/docgen/processor/AsciidocConverterTest.java b/log4j-docgen/src/test/java/org/apache/logging/log4j/docgen/processor/AsciidocConverterTest.java new file mode 100644 index 00000000..7ca4f0c8 --- /dev/null +++ b/log4j-docgen/src/test/java/org/apache/logging/log4j/docgen/processor/AsciidocConverterTest.java @@ -0,0 +1,132 @@ +/* + * 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 static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticCollector; +import javax.tools.DocumentationTool; +import javax.tools.DocumentationTool.DocumentationTask; +import javax.tools.FileObject; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; +import jdk.javadoc.doclet.Doclet; +import jdk.javadoc.doclet.DocletEnvironment; +import jdk.javadoc.doclet.Reporter; +import org.junit.jupiter.api.Test; + +public class AsciidocConverterTest { + + private static final Path LICENSE_PATH; + + static { + try { + LICENSE_PATH = Paths.get(AsciidocConverterTest.class + .getResource("/templates/license.ftl") + .toURI()); + } catch (final URISyntaxException e) { + throw new RuntimeException(e); + } + } + + @Test + void convertToAsciidoc() throws Exception { + final DocumentationTool tool = ToolProvider.getSystemDocumentationTool(); + final DiagnosticCollector ds = new DiagnosticCollector<>(); + final StandardJavaFileManager fileManager = tool.getStandardFileManager(null, Locale.ROOT, UTF_8); + + final Path basePath = Paths.get(System.getProperty("basedir", ".")); + final Path sourcePath = basePath.resolve("src/test/it/example/JavadocExample.java"); + final Iterable sources = fileManager.getJavaFileObjectsFromPaths(List.of(sourcePath)); + + final Path destPath = basePath.resolve("target/test-site"); + Files.createDirectories(destPath); + fileManager.setLocationFromPaths(StandardLocation.CLASS_OUTPUT, Set.of(destPath)); + + final DocumentationTask task = tool.getTask(null, fileManager, ds, TestDoclet.class, null, sources); + task.call(); + + final List warnings = ds.getDiagnostics().stream() + .filter(d -> d.getKind() != Diagnostic.Kind.NOTE) + .map(d -> d.getMessage(Locale.ROOT)) + .toList(); + assertThat(warnings).isEmpty(); + final Path expectedPath = Paths.get(AsciidocConverterTest.class + .getResource("/expected/processor/JavadocExample.adoc") + .toURI()); + assertThat(destPath.resolve("processor/JavadocExample.adoc")).hasSameTextualContentAs(expectedPath, UTF_8); + } + + public static class TestDoclet implements Doclet { + + @Override + public void init(final Locale locale, final Reporter reporter) {} + + @Override + public String getName() { + return "test"; + } + + @Override + public Set getSupportedOptions() { + return Set.of(); + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.RELEASE_17; + } + + @Override + public boolean run(final DocletEnvironment environment) { + final AsciidocConverter converter = new AsciidocConverter(environment.getDocTrees()); + final JavaFileManager fileManager = environment.getJavaFileManager(); + try { + for (final Element element : environment.getIncludedElements()) { + if ("JavadocExample".equals(element.getSimpleName().toString())) { + final FileObject output = fileManager.getFileForOutput( + StandardLocation.CLASS_OUTPUT, "processor", "JavadocExample.adoc", null); + final String asciiDoc = converter.toAsciiDoc(element); + try (final OutputStream os = output.openOutputStream()) { + Files.copy(LICENSE_PATH, os); + os.write(asciiDoc.getBytes(UTF_8)); + } + } + } + } catch (final IOException e) { + throw new RuntimeException(e); + } + return true; + } + } +} diff --git a/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc b/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc new file mode 100644 index 00000000..1afe6570 --- /dev/null +++ b/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc @@ -0,0 +1,105 @@ +//// +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 + + https://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. +//// +Example of JavaDoc to AsciiDoc conversion + +We run the `javadoc` tool on this class to test conversion of JavaDoc comments to AsciiDoc. +This paragraph has two sentences. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. +Vestibulum blandit dictum sem, ornare posuere lorem convallis sit amet. +Sed dui augue, faucibus ut nisi id, mollis euismod nibh. +Donec lobortis luctus viverra. +In orci ante, pretium et fringilla at, sagittis nec justo. +Cras finibus lorem vel volutpat interdum. +Sed laoreet libero eros, ac cursus nibh dapibus vitae. +Integer ante lorem, rhoncus at tortor vel, congue accumsan lorem. +In hac habitasse platea dictumst. +Nunc luctus ornare justo. +Etiam ut metus id tortor dignissim semper. +Nam turpis dui, aliquet nec enim et, faucibus accumsan dui. + +Aenean tincidunt elit id posuere mattis. +Fusce bibendum sapien sed risus ultricies, non molestie erat volutpat. +Donec nisi felis, egestas eu lobortis id, vulputate nec felis. +In at dui euismod, blandit nulla et, accumsan elit. +Proin id venenatis dui. +Suspendisse sit amet est ut neque tincidunt venenatis. +Donec bibendum quis velit fermentum porttitor. +Maecenas faucibus, eros sit amet maximus malesuada, turpis neque bibendum justo, eu vehicula justo metus a ipsum. +In at ullamcorper ipsum. +Quisque in vehicula erat. +Proin vitae suscipit dui, rutrum hendrerit augue. +Curabitur finibus feugiat elit. + +. Item with a nested ordered list. ++ +.. First nested item. +.. Second nested item. +. Item with a nested unordered list. ++ +* Unordered list item. +. Item with complex content ++ +Mauris suscipit velit nec ligula mattis, nec varius augue accumsan. +Curabitur a dolor dui. +Quisque congue facilisis est nec dignissim. +Pellentesque egestas eleifend faucibus. +Fusce imperdiet ligula a lectus fringilla varius. +Sed malesuada porta vulputate. +Sed vulputate purus nec nibh interdum convallis. +Cras faucibus, dolor tempus lacinia vehicula, elit risus luctus libero, sed molestie nisi lorem sit amet enim. +Integer vitae enim sagittis, malesuada lorem at, interdum tellus. +Suspendisse potenti. +Vestibulum ac nisi sit amet ex dictum suscipit. +Nulla varius augue a velit tincidunt feugiat. +Proin fringilla id leo ut dignissim. +Vivamus eu tellus eget orci suscipit viverra. +Donec sodales et arcu vel mollis. ++ +Praesent gravida auctor lectus quis interdum. +Etiam semper mauris quis neque bibendum molestie. +Maecenas a lacus nec risus pellentesque accumsan. +Suspendisse dictum dui eleifend nibh facilisis, non consequat neque elementum. +Donec scelerisque ultricies ipsum, pretium elementum ex pellentesque malesuada. +Mauris egestas massa vitae sapien lobortis convallis. +Donec feugiat, purus commodo consequat vehicula, dolor urna aliquam arcu, id rutrum quam tortor quis libero. +Sed varius justo eget congue lacinia. + +* Item of an unordered list. + +== First section + +[cols="1,1",options="headers"] +|=== + +| Key +| Value + +| A +| 1 + +| B +| 2 + +|=== + +=== Subsection + +[source] +---- + private static final Logger logger = LogManager.getLogger(); +---- diff --git a/log4j-tools-parent/pom.xml b/log4j-tools-parent/pom.xml index c9e0cdea..dff618b3 100644 --- a/log4j-tools-parent/pom.xml +++ b/log4j-tools-parent/pom.xml @@ -34,9 +34,11 @@ 3.25.3 2.15.1 + 3.14.0 2.3.32 1.0.5 5.10.2 + 3.0.0-beta1 2.1.2 2.9.1 @@ -51,6 +53,14 @@ + + org.apache.logging.log4j + log4j-bom + ${log4j-bom.version} + pom + import + + org.junit junit-bom @@ -71,6 +81,12 @@ ${commons-io.version} + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + org.freemarker freemarker From 6e01fa9b81769fe596eb5fdb7b0871ac9adc590a Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Tue, 6 Feb 2024 12:14:51 +0100 Subject: [PATCH 2/6] Protect implementation classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Finalize and otherwise protect implementation classes. Co-authored-by: Volkan Yazıcı --- .../logging/log4j/docgen/processor/AsciidocConverter.java | 6 +++--- .../apache/logging/log4j/docgen/processor/AsciidocData.java | 2 +- .../log4j/docgen/processor/internal/ContentNodeImpl.java | 2 +- .../log4j/docgen/processor/internal/DocumentImpl.java | 2 +- .../logging/log4j/docgen/processor/internal/ListImpl.java | 2 +- .../log4j/docgen/processor/internal/ListItemImpl.java | 2 +- .../logging/log4j/docgen/processor/internal/RowImpl.java | 2 +- .../log4j/docgen/processor/internal/SectionImpl.java | 2 +- .../logging/log4j/docgen/processor/internal/TableImpl.java | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocConverter.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocConverter.java index 19fe0e1a..93224c2b 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocConverter.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocConverter.java @@ -26,7 +26,7 @@ /** * Converts a {@link DocCommentTree} into AsciiDoc text. */ -class AsciidocConverter { +final class AsciidocConverter { private static final DocTreeVisitor DOC_COMMENT_TREE_VISITOR = new DocCommentTreeVisitor(); private static final DocTreeVisitor PARAM_TREE_VISITOR = new ParamTreeVisitor(); @@ -54,7 +54,7 @@ public String toAsciiDoc(final ParamTree tree) { return data.getDocument().convert(); } - private static class DocCommentTreeVisitor extends AbstractAsciidocTreeVisitor { + private static final class DocCommentTreeVisitor extends AbstractAsciidocTreeVisitor { @Override public Void visitDocComment(final DocCommentTree node, final AsciidocData data) { // Summary block wrapped in a new paragraph. @@ -72,7 +72,7 @@ public Void visitDocComment(final DocCommentTree node, final AsciidocData data) } } - private static class ParamTreeVisitor extends AbstractAsciidocTreeVisitor { + private static final class ParamTreeVisitor extends AbstractAsciidocTreeVisitor { @Override public Void visitParam(final ParamTree node, final AsciidocData data) { for (final DocTree docTree : node.getDescription()) { diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java index a35e8293..09cbe7a6 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java @@ -25,7 +25,7 @@ import org.asciidoctor.ast.Document; import org.asciidoctor.ast.StructuralNode; -class AsciidocData { +final class AsciidocData { private final Document document; private int currentSectionLevel; private StructuralNode currentNode; diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ContentNodeImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ContentNodeImpl.java index 63e2b915..519e25c2 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ContentNodeImpl.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ContentNodeImpl.java @@ -21,7 +21,7 @@ import org.asciidoctor.ast.ContentNode; import org.asciidoctor.ast.Document; -public abstract class ContentNodeImpl implements ContentNode { +abstract class ContentNodeImpl implements ContentNode { private final ContentNode parent; private String context; diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/DocumentImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/DocumentImpl.java index 53b0a251..7573c8ac 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/DocumentImpl.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/DocumentImpl.java @@ -27,7 +27,7 @@ import org.asciidoctor.ast.StructuralNode; import org.asciidoctor.ast.Title; -public class DocumentImpl extends StructuralNodeImpl implements Document { +public final class DocumentImpl extends StructuralNodeImpl implements Document { public DocumentImpl() { super(null); diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListImpl.java index d802825f..64a3ebaf 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListImpl.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListImpl.java @@ -23,7 +23,7 @@ import org.asciidoctor.ast.ListItem; import org.asciidoctor.ast.StructuralNode; -public class ListImpl extends StructuralNodeImpl implements List { +public final class ListImpl extends StructuralNodeImpl implements List { public static final String ORDERED_LIST_CONTEXT = "olist"; private static final char ORDERED_LIST_MARKER = '.'; diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListItemImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListItemImpl.java index 29880411..0508fd62 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListItemImpl.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListItemImpl.java @@ -21,7 +21,7 @@ import org.asciidoctor.ast.ListItem; import org.asciidoctor.ast.StructuralNode; -public class ListItemImpl extends StructuralNodeImpl implements ListItem { +public final class ListItemImpl extends StructuralNodeImpl implements ListItem { public ListItemImpl(final ContentNode parent) { super(parent); diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/RowImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/RowImpl.java index 6dff8982..c9dd3085 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/RowImpl.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/RowImpl.java @@ -21,7 +21,7 @@ import org.asciidoctor.ast.Cell; import org.asciidoctor.ast.Row; -public class RowImpl implements Row { +public final class RowImpl implements Row { private final List cells = new ArrayList<>(); diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/SectionImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/SectionImpl.java index 6824f15b..4a61c0c4 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/SectionImpl.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/SectionImpl.java @@ -24,7 +24,7 @@ import org.asciidoctor.ast.Section; import org.asciidoctor.ast.StructuralNode; -public class SectionImpl extends StructuralNodeImpl implements Section { +public final class SectionImpl extends StructuralNodeImpl implements Section { private final List content = new ArrayList<>(); diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/TableImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/TableImpl.java index c57b1795..e0016db4 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/TableImpl.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/TableImpl.java @@ -24,7 +24,7 @@ import org.asciidoctor.ast.Row; import org.asciidoctor.ast.Table; -public class TableImpl extends StructuralNodeImpl implements Table { +public final class TableImpl extends StructuralNodeImpl implements Table { private final List header = new ArrayList<>(); private final List body = new ArrayList<>(); From aa56053f53359d642417a815fddb5d94b06b1fcd Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Thu, 8 Feb 2024 09:36:17 +0100 Subject: [PATCH 3/6] Add support for emphasis and code tags --- .../AbstractAsciidocTreeVisitor.java | 65 ++++++++++--- .../log4j/docgen/processor/AsciidocData.java | 92 +++++++++++++------ .../docgen/processor/internal/BlockImpl.java | 1 - .../src/test/it/example/JavadocExample.java | 7 ++ .../expected/processor/JavadocExample.adoc | 5 + 5 files changed, 127 insertions(+), 43 deletions(-) diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java index 00b4fbe8..609cca7d 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java @@ -43,18 +43,25 @@ import org.asciidoctor.ast.StructuralNode; import org.asciidoctor.ast.Table; -class AbstractAsciidocTreeVisitor extends SimpleDocTreeVisitor { +abstract class AbstractAsciidocTreeVisitor extends SimpleDocTreeVisitor { + + private static final String SOURCE_STYLE = "source"; + // These are not supported by AsciiDoctor and are only used internally + private static final String CODE_STYLE = "code"; + private static final String CODE_DELIM = "`"; + private static final String EMPHASIS_STYLE = "em"; + private static final String EMPHASIS_DELIM = "_"; + private static final String STRONG_EMPHASIS_STYLE = "strong"; + private static final String STRONG_EMPHASIS_DELIM = "*"; private static void appendSentences(final String text, final AsciidocData data) { - final String body = StringUtils.normalizeSpace(text); - final String[] sentences = body.split("(?<=\\w{2}[.!?])", -1); + final String[] sentences = text.split("(?<=\\w{2}[.!?])", -1); // Full sentences for (int i = 0; i < sentences.length - 1; i++) { - data.appendWords(sentences[i].strip()); - data.newLine(); + data.appendAdjustingSpace(sentences[i]).newLine(); } // Partial sentence - data.appendWords(sentences[sentences.length - 1].strip()); + data.appendAdjustingSpace(sentences[sentences.length - 1]); } @Override @@ -112,9 +119,19 @@ public Void visitStartElement(final StartElementTree node, final AsciidocData da break; case "pre": data.newParagraph(); - final Block currentParagraph = data.getCurrentParagraph(); - currentParagraph.setContext(BlockImpl.LISTING_CONTEXT); - currentParagraph.setStyle(BlockImpl.SOURCE_STYLE); + data.getCurrentParagraph().setContext(BlockImpl.LISTING_CONTEXT); + data.getCurrentParagraph().setStyle(SOURCE_STYLE); + break; + case "code": + data.newTextSpan(CODE_STYLE); + break; + case "em": + case "i": + data.newTextSpan(EMPHASIS_STYLE); + break; + case "strong": + case "b": + data.newTextSpan(STRONG_EMPHASIS_STYLE); break; default: } @@ -187,6 +204,17 @@ public Void visitEndElement(final EndElementTree node, final AsciidocData data) table.getBody().add(row); } break; + case "code": + appendSpan(data, CODE_DELIM); + break; + case "em": + case "i": + appendSpan(data, EMPHASIS_DELIM); + break; + case "strong": + case "b": + appendSpan(data, STRONG_EMPHASIS_DELIM); + break; default: } return super.visitEndElement(node, data); @@ -210,16 +238,27 @@ public Void visitLink(final LinkTree node, final AsciidocData data) { @Override public Void visitLiteral(final LiteralTree node, final AsciidocData data) { if (node.getKind() == DocTree.Kind.CODE) { - if (!data.getCurrentLine().isEmpty()) { - data.append(" "); - } - data.append("`").append(node.getBody().getBody()).append("`"); + data.newTextSpan(CODE_STYLE); + node.getBody().accept(this, data); + appendSpan(data, "`"); } else { node.getBody().accept(this, data); } return super.visitLiteral(node, data); } + private void appendSpan(final AsciidocData data, final String delimiter) { + final String body = data.popTextSpan(); + data.append(delimiter); + final boolean needsEscaping = body.contains(delimiter); + if (needsEscaping) { + data.append("++").append(body).append("++"); + } else { + data.append(body); + } + data.append(delimiter); + } + @Override public Void visitText(final TextTree node, final AsciidocData data) { final Block currentParagraph = data.getCurrentParagraph(); diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java index 09cbe7a6..c9c7628f 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java @@ -16,8 +16,11 @@ */ package org.apache.logging.log4j.docgen.processor; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.EmptyStackException; import java.util.function.Function; +import java.util.regex.Pattern; import org.apache.logging.log4j.docgen.processor.internal.BlockImpl; import org.apache.logging.log4j.docgen.processor.internal.DocumentImpl; import org.apache.logging.log4j.docgen.processor.internal.SectionImpl; @@ -26,35 +29,41 @@ import org.asciidoctor.ast.StructuralNode; final class AsciidocData { + private static final Pattern WHITESPACE_SEQUENCE = Pattern.compile("\\s+"); + private static final String SPACE = " "; + private static final char SPACE_CHAR = ' '; + private static final char CODE_CHAR = '`'; + private static final String NEW_LINE = "\n"; + private final Document document; private int currentSectionLevel; private StructuralNode currentNode; - // not attached to the current node - private Block currentParagraph; - private final StringBuilder currentLine; + // A stack of nested text blocks. Each can have a different style. + private final Deque paragraphs = new ArrayDeque<>(); + private final Deque lines = new ArrayDeque<>(); public AsciidocData() { document = new DocumentImpl(); currentSectionLevel = 1; currentNode = document; - currentParagraph = new BlockImpl(currentNode); - currentLine = new StringBuilder(); + paragraphs.push(new BlockImpl(currentNode)); + lines.push(new StringBuilder()); } public void newLine() { // Remove trailing space - final String line = currentLine.toString().stripTrailing(); + final String line = getCurrentLine().toString().stripTrailing(); // Ignore leading empty lines - if (!currentParagraph.getLines().isEmpty() || !line.isEmpty()) { - currentParagraph.getLines().add(line); + if (!getCurrentParagraph().getLines().isEmpty() || !line.isEmpty()) { + getCurrentParagraph().getLines().add(line); } - currentLine.setLength(0); + getCurrentLine().setLength(0); } public AsciidocData append(final String text) { final String[] lines = text.split("\r?\n", -1); for (int i = 0; i < lines.length; i++) { - currentLine.append(lines[i]); + getCurrentLine().append(lines[i]); if (i != lines.length - 1) { newLine(); } @@ -62,19 +71,48 @@ public AsciidocData append(final String text) { return this; } - public void appendWords(final String words) { - if (words.isBlank()) { - return; + public AsciidocData appendAdjustingSpace(final CharSequence text) { + final String normalized = WHITESPACE_SEQUENCE.matcher(text).replaceAll(SPACE); + if (!normalized.isEmpty()) { + final StringBuilder currentLine = getCurrentLine(); + // Last char of current line or space + final char lineLastChar = currentLine.isEmpty() ? SPACE_CHAR : currentLine.charAt(currentLine.length() - 1); + // First char of test + final char textFirstChar = normalized.charAt(0); + if (lineLastChar == SPACE_CHAR && textFirstChar == SPACE_CHAR) { + // Merge spaces + currentLine.append(normalized, 1, normalized.length()); + } else if (lineLastChar == CODE_CHAR && Character.isAlphabetic(textFirstChar)) { + currentLine.append(SPACE_CHAR).append(normalized); + } else { + currentLine.append(normalized); + } } - // Separate text from previous words - if (!currentLine.isEmpty() && Character.isAlphabetic(words.codePointAt(0))) { - currentLine.append(" "); + return this; + } + + public void newTextSpan(final String style) { + paragraphs.push(new BlockImpl(paragraphs.peek())); + lines.push(new StringBuilder()); + } + + public String popTextSpan() { + // Flush the paragraph + final StringBuilder line = lines.peek(); + if (line != null && !line.isEmpty()) { + newLine(); } - currentLine.append(words); + lines.pop(); + return String.join(SPACE, paragraphs.pop().getLines()); } public void newParagraph() { + newParagraph(currentNode); + } + + private void newParagraph(final StructuralNode parent) { newLine(); + final Block currentParagraph = paragraphs.pop(); final java.util.List lines = currentParagraph.getLines(); // Remove trailing empty lines for (int i = lines.size() - 1; i >= 0; i--) { @@ -84,8 +122,8 @@ public void newParagraph() { } if (!currentParagraph.getLines().isEmpty()) { currentNode.append(currentParagraph); - currentParagraph = new BlockImpl(currentNode); } + paragraphs.push(new BlockImpl(parent)); } public StructuralNode getCurrentNode() { @@ -93,11 +131,11 @@ public StructuralNode getCurrentNode() { } public Block getCurrentParagraph() { - return currentParagraph; + return paragraphs.peek(); } public StringBuilder getCurrentLine() { - return currentLine; + return lines.peek(); } public Document getDocument() { @@ -121,12 +159,10 @@ public void setCurrentSectionLevel(final int sectionLevel) { * @param supplier a function to create a new node that takes its parent node a parameter. */ public StructuralNode pushChildNode(final Function supplier) { - // Flushes the current paragraph - newParagraph(); - final StructuralNode child = supplier.apply(currentNode); - // Creates a new current paragraph - currentParagraph = new BlockImpl(child); + + // Flushes and reparents the current paragraph + newParagraph(child); currentNode.append(child); return currentNode = child; @@ -134,15 +170,13 @@ public StructuralNode pushChildNode(final Function lines = new ArrayList<>(); diff --git a/log4j-docgen/src/test/it/example/JavadocExample.java b/log4j-docgen/src/test/it/example/JavadocExample.java index 5ddf2c4c..bc76bfd0 100644 --- a/log4j-docgen/src/test/it/example/JavadocExample.java +++ b/log4j-docgen/src/test/it/example/JavadocExample.java @@ -23,6 +23,13 @@ * paragraph has two sentences. *

        *

        + * A sentence with foo, foo`, foobar. Another sentence with {@code foo}, + * {@code foo`}, {@code foo}bar. + *

        + *

        + * We can use strong emphasis too, or we can use bold and italic. + *

        + *

        * Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum blandit dictum sem, ornare posuere lorem * convallis sit amet. Sed dui augue, faucibus ut nisi id, mollis euismod nibh. Donec lobortis luctus viverra. In * orci ante, pretium et fringilla at, sagittis nec justo. Cras finibus lorem vel volutpat interdum. Sed laoreet diff --git a/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc b/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc index 1afe6570..9986f396 100644 --- a/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc +++ b/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc @@ -19,6 +19,11 @@ Example of JavaDoc to AsciiDoc conversion We run the `javadoc` tool on this class to test conversion of JavaDoc comments to AsciiDoc. This paragraph has two sentences. +A sentence with `foo`, `++foo`++`, `foo` bar. +Another sentence with `foo`, `++foo`++`, `foo` bar. + +We can use *strong* _emphasis_ too, or we can use *bold* and _italic_. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum blandit dictum sem, ornare posuere lorem convallis sit amet. Sed dui augue, faucibus ut nisi id, mollis euismod nibh. From 51994c80f3c10e11da98868ca25e1e41248f1da3 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Thu, 8 Feb 2024 11:47:50 +0100 Subject: [PATCH 4/6] Add support for simple code language detection and formatting If we encounter a `

        ` element with content containing XML elements,
        we classify it as XML, otherwise Java.
        
        The maximal common indentation is stripped from the content of a `
        `
        tag.
        ---
         .../AbstractAsciidocTreeVisitor.java          | 81 ++++++++++++++-----
         .../docgen/processor/AsciidocConverter.java   | 19 +++--
         .../log4j/docgen/processor/AsciidocData.java  | 27 +++++--
         .../src/test/it/example/JavadocExample.java   | 23 +++++-
         .../expected/processor/JavadocExample.adoc    | 32 +++++++-
         5 files changed, 144 insertions(+), 38 deletions(-)
        
        diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java
        index 609cca7d..1901579d 100644
        --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java
        +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java
        @@ -20,12 +20,15 @@
         
         import com.sun.source.doctree.DocTree;
         import com.sun.source.doctree.EndElementTree;
        +import com.sun.source.doctree.EntityTree;
         import com.sun.source.doctree.LinkTree;
         import com.sun.source.doctree.LiteralTree;
         import com.sun.source.doctree.StartElementTree;
         import com.sun.source.doctree.TextTree;
        +import com.sun.source.util.DocTrees;
         import com.sun.source.util.SimpleDocTreeVisitor;
         import java.util.ArrayList;
        +import java.util.regex.Pattern;
         import org.apache.commons.lang3.StringUtils;
         import org.apache.logging.log4j.docgen.processor.internal.BlockImpl;
         import org.apache.logging.log4j.docgen.processor.internal.CellImpl;
        @@ -45,7 +48,8 @@
         
         abstract class AbstractAsciidocTreeVisitor extends SimpleDocTreeVisitor {
         
        -    private static final String SOURCE_STYLE = "source";
        +    private static final String JAVA_SOURCE_STYLE = "source,java";
        +    private static final String XML_SOURCE_STYLE = "source,xml";
             // These are not supported by AsciiDoctor and are only used internally
             private static final String CODE_STYLE = "code";
             private static final String CODE_DELIM = "`";
        @@ -54,14 +58,17 @@ abstract class AbstractAsciidocTreeVisitor extends SimpleDocTreeVisitor");
        +
        +    /**
        +     * Used to convert entities into strings.
        +     */
        +    private final DocTrees docTrees;
        +
        +    AbstractAsciidocTreeVisitor(final DocTrees docTrees) {
        +        this.docTrees = docTrees;
             }
         
             @Override
        @@ -120,7 +127,6 @@ public Void visitStartElement(final StartElementTree node, final AsciidocData da
                     case "pre":
                         data.newParagraph();
                         data.getCurrentParagraph().setContext(BlockImpl.LISTING_CONTEXT);
        -                data.getCurrentParagraph().setStyle(SOURCE_STYLE);
                         break;
                     case "code":
                         data.newTextSpan(CODE_STYLE);
        @@ -175,7 +181,23 @@ public Void visitEndElement(final EndElementTree node, final AsciidocData data)
                         data.getCurrentNode().setTitle(title);
                         break;
                     case "pre":
        -                data.newParagraph();
        +                final Block codeBlock = data.newParagraph();
        +                final java.util.List lines = codeBlock.getLines();
        +                // Trim common indentation and detect language
        +                int commonIndentSize = Integer.MAX_VALUE;
        +                for (final String line : lines) {
        +                    final int firstNotSpace = StringUtils.indexOfAnyBut(line, SPACE);
        +                    if (0 <= firstNotSpace && firstNotSpace < commonIndentSize) {
        +                        commonIndentSize = firstNotSpace;
        +                    }
        +                }
        +                final boolean isXml = XML_TAG.matcher(String.join(SPACE, lines)).find();
        +                final java.util.List newLines = new ArrayList<>(lines.size());
        +                for (final String line : lines) {
        +                    newLines.add(StringUtils.substring(line, commonIndentSize));
        +                }
        +                codeBlock.setLines(newLines);
        +                codeBlock.setStyle(isXml ? XML_SOURCE_STYLE : JAVA_SOURCE_STYLE);
                         break;
                     case "tr":
                         // We group the new cells into a row
        @@ -247,16 +269,13 @@ public Void visitLiteral(final LiteralTree node, final AsciidocData data) {
                 return super.visitLiteral(node, data);
             }
         
        -    private void appendSpan(final AsciidocData data, final String delimiter) {
        -        final String body = data.popTextSpan();
        -        data.append(delimiter);
        -        final boolean needsEscaping = body.contains(delimiter);
        -        if (needsEscaping) {
        -            data.append("++").append(body).append("++");
        -        } else {
        -            data.append(body);
        +    @Override
        +    public Void visitEntity(final EntityTree node, final AsciidocData asciidocData) {
        +        final String text = docTrees.getCharacters(node);
        +        if (text != null) {
        +            asciidocData.append(text);
                 }
        -        data.append(delimiter);
        +        return super.visitEntity(node, asciidocData);
             }
         
             @Override
        @@ -269,4 +288,26 @@ public Void visitText(final TextTree node, final AsciidocData data) {
                 }
                 return super.visitText(node, data);
             }
        +
        +    private static void appendSentences(final String text, final AsciidocData data) {
        +        final String[] sentences = text.split("(?<=\\w{2}[.!?])", -1);
        +        // Full sentences
        +        for (int i = 0; i < sentences.length - 1; i++) {
        +            data.appendAdjustingSpace(sentences[i]).newLine();
        +        }
        +        // Partial sentence
        +        data.appendAdjustingSpace(sentences[sentences.length - 1]);
        +    }
        +
        +    private void appendSpan(final AsciidocData data, final String delimiter) {
        +        final String body = data.popTextSpan();
        +        data.append(delimiter);
        +        final boolean needsEscaping = body.contains(delimiter);
        +        if (needsEscaping) {
        +            data.append("++").append(body).append("++");
        +        } else {
        +            data.append(body);
        +        }
        +        data.append(delimiter);
        +    }
         }
        diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocConverter.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocConverter.java
        index 93224c2b..2fa76180 100644
        --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocConverter.java
        +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocConverter.java
        @@ -28,13 +28,14 @@
          */
         final class AsciidocConverter {
         
        -    private static final DocTreeVisitor DOC_COMMENT_TREE_VISITOR = new DocCommentTreeVisitor();
        -    private static final DocTreeVisitor PARAM_TREE_VISITOR = new ParamTreeVisitor();
        -
             private final DocTrees docTrees;
        +    private final DocTreeVisitor docCommentTreeVisitor;
        +    private final DocTreeVisitor paramTreeVisitor;
         
             AsciidocConverter(final DocTrees docTrees) {
                 this.docTrees = docTrees;
        +        this.docCommentTreeVisitor = new DocCommentTreeVisitor(docTrees);
        +        this.paramTreeVisitor = new ParamTreeVisitor(docTrees);
             }
         
             public String toAsciiDoc(final Element element) {
        @@ -44,17 +45,21 @@ public String toAsciiDoc(final Element element) {
         
             public String toAsciiDoc(final DocCommentTree tree) {
                 final AsciidocData data = new AsciidocData();
        -        tree.accept(DOC_COMMENT_TREE_VISITOR, data);
        +        tree.accept(docCommentTreeVisitor, data);
                 return data.getDocument().convert();
             }
         
             public String toAsciiDoc(final ParamTree tree) {
                 final AsciidocData data = new AsciidocData();
        -        tree.accept(PARAM_TREE_VISITOR, data);
        +        tree.accept(paramTreeVisitor, data);
                 return data.getDocument().convert();
             }
         
             private static final class DocCommentTreeVisitor extends AbstractAsciidocTreeVisitor {
        +        public DocCommentTreeVisitor(final DocTrees docTrees) {
        +            super(docTrees);
        +        }
        +
                 @Override
                 public Void visitDocComment(final DocCommentTree node, final AsciidocData data) {
                     // Summary block wrapped in a new paragraph.
        @@ -73,6 +78,10 @@ public Void visitDocComment(final DocCommentTree node, final AsciidocData data)
             }
         
             private static final class ParamTreeVisitor extends AbstractAsciidocTreeVisitor {
        +        public ParamTreeVisitor(final DocTrees docTrees) {
        +            super(docTrees);
        +        }
        +
                 @Override
                 public Void visitParam(final ParamTree node, final AsciidocData data) {
                     for (final DocTree docTree : node.getDescription()) {
        diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java
        index c9c7628f..1208490e 100644
        --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java
        +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java
        @@ -106,11 +106,11 @@ public String popTextSpan() {
                 return String.join(SPACE, paragraphs.pop().getLines());
             }
         
        -    public void newParagraph() {
        -        newParagraph(currentNode);
        +    public Block newParagraph() {
        +        return newParagraph(currentNode);
             }
         
        -    private void newParagraph(final StructuralNode parent) {
        +    private Block newParagraph(final StructuralNode parent) {
                 newLine();
                 final Block currentParagraph = paragraphs.pop();
                 final java.util.List lines = currentParagraph.getLines();
        @@ -118,12 +118,15 @@ private void newParagraph(final StructuralNode parent) {
                 for (int i = lines.size() - 1; i >= 0; i--) {
                     if (lines.get(i).isEmpty()) {
                         lines.remove(i);
        +            } else {
        +                break;
                     }
                 }
                 if (!currentParagraph.getLines().isEmpty()) {
                     currentNode.append(currentParagraph);
                 }
                 paragraphs.push(new BlockImpl(parent));
        +        return currentParagraph;
             }
         
             public StructuralNode getCurrentNode() {
        @@ -143,13 +146,21 @@ public Document getDocument() {
             }
         
             public void setCurrentSectionLevel(final int sectionLevel) {
        -        while (sectionLevel < currentSectionLevel) {
        +        if (sectionLevel < currentSectionLevel) {
        +            // Close all subsections
        +            while (sectionLevel < currentSectionLevel) {
        +                popNode();
        +                currentSectionLevel--;
        +            }
        +            // Create sibling section
                     popNode();
        -            currentSectionLevel--;
        -        }
        -        while (sectionLevel > currentSectionLevel) {
                     pushChildNode(SectionImpl::new);
        -            currentSectionLevel++;
        +        } else {
        +            // Create missing section levels
        +            while (sectionLevel > currentSectionLevel) {
        +                pushChildNode(SectionImpl::new);
        +                currentSectionLevel++;
        +            }
                 }
             }
         
        diff --git a/log4j-docgen/src/test/it/example/JavadocExample.java b/log4j-docgen/src/test/it/example/JavadocExample.java
        index bc76bfd0..80e8fd43 100644
        --- a/log4j-docgen/src/test/it/example/JavadocExample.java
        +++ b/log4j-docgen/src/test/it/example/JavadocExample.java
        @@ -101,7 +101,28 @@
          * 
          * 

        Subsection

        *
        - *     private static final Logger logger = LogManager.getLogger();
        + *     public final class Main {
        + *         private static final Logger logger = LogManager.getLogger();
        + *
        + *         public static void sayHello() {
        + *             logger.info("Hello world!");
        + *         }
        + *     }
        + * 
        + *

        Minimal configuration example

        + *
        + *     <Configuration>
        + *       <Appenders>
        + *         <Console name="CONSOLE">
        + *           <PatternLayout/>
        + *         </Console>
        + *       </Appenders>
        + *       <Loggers>
        + *         <Root level="INFO">
        + *           <AppenderRef ref="CONSOLE"/>
        + *         </Root>
        + *       </Loggers>
        + *     </Configuration>
          * 
        */ public class JavadocExample {} diff --git a/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc b/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc index 9986f396..82bd7dd6 100644 --- a/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc +++ b/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc @@ -91,8 +91,8 @@ Sed varius justo eget congue lacinia. [cols="1,1",options="headers"] |=== -| Key -| Value +h| Key +h| Value | A | 1 @@ -104,7 +104,31 @@ Sed varius justo eget congue lacinia. === Subsection -[source] +[source,java] ---- - private static final Logger logger = LogManager.getLogger(); +public final class Main { + private static final Logger logger = LogManager.getLogger(); + + public static void sayHello() { + logger.info("Hello world!"); + } +} +---- + +== Minimal configuration example + +[source,xml] +---- + + + + + + + + + + + + ---- From 8c3cc521a28c0e9cf09dddfd77f1457e9a42bcaf Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Thu, 8 Feb 2024 12:03:45 +0100 Subject: [PATCH 5/6] Improve support for table headers --- .../AbstractAsciidocTreeVisitor.java | 15 ++++----------- .../log4j/docgen/processor/AsciidocData.java | 3 ++- .../docgen/processor/internal/CellImpl.java | 2 ++ .../docgen/processor/internal/TableImpl.java | 19 ++++++------------- .../expected/processor/JavadocExample.adoc | 2 +- 5 files changed, 15 insertions(+), 26 deletions(-) diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java index 1901579d..2fd9ee0e 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java @@ -155,10 +155,12 @@ public Void visitEndElement(final EndElementTree node, final AsciidocData data) case "ul": case "li": case "table": - case "th": case "td": data.popNode(); break; + case "th": + data.popNode().setContext(CellImpl.HEADER_CONTEXT); + break; case "h1": case "h2": case "h3": @@ -205,26 +207,17 @@ public Void visitEndElement(final EndElementTree node, final AsciidocData data) final java.util.List cells = table.getBlocks(); // First index of the row int idx = 0; - for (final Row row : table.getHeader()) { - idx += row.getCells().size(); - } for (final Row row : table.getBody()) { idx += row.getCells().size(); } final Row row = new RowImpl(); - String context = CellImpl.BODY_CONTEXT; for (int i = idx; i < table.getBlocks().size(); i++) { final StructuralNode cell = cells.get(i); - context = cell.getContext(); if (cell instanceof Cell) { row.getCells().add((Cell) cell); } } - if (CellImpl.HEADER_CONTEXT.equals(context)) { - table.getHeader().add(row); - } else { - table.getBody().add(row); - } + table.getBody().add(row); break; case "code": appendSpan(data, CODE_DELIM); diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java index 1208490e..c94d0b23 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java @@ -179,7 +179,7 @@ public StructuralNode pushChildNode(final Function 1) { buffer.append('a'); + } else if (HEADER_CONTEXT.equals(getContext())) { + buffer.append('h'); } buffer.append("| "); getBlocks().forEach(node -> { diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/TableImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/TableImpl.java index e0016db4..28a821f7 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/TableImpl.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/TableImpl.java @@ -26,7 +26,6 @@ public final class TableImpl extends StructuralNodeImpl implements Table { - private final List header = new ArrayList<>(); private final List body = new ArrayList<>(); public TableImpl(final ContentNode parent) { @@ -35,17 +34,11 @@ public TableImpl(final ContentNode parent) { @Override public void formatTo(final StringBuilder buffer) { - final int colCount = header.isEmpty() - ? body.isEmpty() ? 1 : body.get(0).getCells().size() - : header.get(0).getCells().size(); + final int colCount = body.isEmpty() ? 1 : body.get(0).getCells().size(); buffer.append("[cols=\""); formatColSpecifier(colCount, buffer); - if (!header.isEmpty()) { - buffer.append("\",options=\"headers"); - } buffer.append("\"]\n").append("|===\n"); - getHeader().forEach(row -> formatRow(row, buffer)); getBody().forEach(row -> formatRow(row, buffer)); buffer.append("\n|===\n"); } @@ -70,11 +63,6 @@ private void formatRow(final Row row, final StringBuilder buffer) { }); } - @Override - public List getHeader() { - return header; - } - @Override public List getBody() { return body; @@ -83,6 +71,11 @@ public List getBody() { // // All methods below this line are not implemented. // + @Override + public List getHeader() { + throw new UnsupportedOperationException(); + } + @Override public List getFooter() { throw new UnsupportedOperationException(); diff --git a/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc b/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc index 82bd7dd6..7eafa535 100644 --- a/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc +++ b/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc @@ -88,7 +88,7 @@ Sed varius justo eget congue lacinia. == First section -[cols="1,1",options="headers"] +[cols="1,1"] |=== h| Key From 6cda1cbc58cf2ad70f7ff455475b0bfb8cd94123 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Thu, 8 Feb 2024 13:32:37 +0100 Subject: [PATCH 6/6] Remove `StringBuilderFormattable` and clean up code --- log4j-docgen/pom.xml | 11 ------- .../AbstractAsciidocTreeVisitor.java | 13 +++----- .../log4j/docgen/processor/AsciidocData.java | 3 +- .../docgen/processor/internal/BlockImpl.java | 2 +- .../docgen/processor/internal/CellImpl.java | 11 ++----- .../processor/internal/DocumentImpl.java | 18 ++--------- .../docgen/processor/internal/ListImpl.java | 12 +++---- .../processor/internal/ListItemImpl.java | 16 +--------- .../processor/internal/SectionImpl.java | 19 +----------- .../internal/StructuralNodeImpl.java | 31 +++++++++++++++++-- .../docgen/processor/internal/TableImpl.java | 2 +- 11 files changed, 47 insertions(+), 91 deletions(-) diff --git a/log4j-docgen/pom.xml b/log4j-docgen/pom.xml index a3b5e606..498b4a7a 100644 --- a/log4j-docgen/pom.xml +++ b/log4j-docgen/pom.xml @@ -58,22 +58,11 @@ freemarker - - org.apache.logging.log4j - log4j-api - - org.apache.logging.log4j log4j-plugins - - org.apache.logging.log4j - log4j-core - test - - org.junit.jupiter junit-jupiter-api diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java index 2fd9ee0e..cd061fef 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java @@ -50,12 +50,9 @@ abstract class AbstractAsciidocTreeVisitor extends SimpleDocTreeVisitor 1) { buffer.append('a'); } else if (HEADER_CONTEXT.equals(getContext())) { buffer.append('h'); } buffer.append("| "); - getBlocks().forEach(node -> { - if (node instanceof final StringBuilderFormattable formattable) { - formattable.formatTo(buffer); - } else { - buffer.append(node.convert()); - } - }); + formatNodeCollection(getBlocks(), " ", buffer); } // diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/DocumentImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/DocumentImpl.java index 7573c8ac..77df46bd 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/DocumentImpl.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/DocumentImpl.java @@ -19,12 +19,10 @@ import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.util.StringBuilderFormattable; import org.asciidoctor.ast.Author; import org.asciidoctor.ast.Catalog; import org.asciidoctor.ast.Document; import org.asciidoctor.ast.RevisionInfo; -import org.asciidoctor.ast.StructuralNode; import org.asciidoctor.ast.Title; public final class DocumentImpl extends StructuralNodeImpl implements Document { @@ -34,23 +32,11 @@ public DocumentImpl() { } @Override - public void formatTo(final StringBuilder buffer) { + protected void formatTo(final StringBuilder buffer) { if (!StringUtils.isBlank(getTitle())) { buffer.append("= ").append(getTitle()).append("\n\n"); } - boolean first = true; - for (final StructuralNode node : getBlocks()) { - if (!first) { - buffer.append('\n'); - } else { - first = false; - } - if (node instanceof final StringBuilderFormattable formattable) { - formattable.formatTo(buffer); - } else { - buffer.append(node.convert()); - } - } + formatNodeCollection(getBlocks(), "\n", buffer); } @Override diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListImpl.java index 64a3ebaf..b6cc957c 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListImpl.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListImpl.java @@ -17,7 +17,6 @@ package org.apache.logging.log4j.docgen.processor.internal; import java.util.Objects; -import org.apache.logging.log4j.util.StringBuilderFormattable; import org.asciidoctor.ast.ContentNode; import org.asciidoctor.ast.List; import org.asciidoctor.ast.ListItem; @@ -38,14 +37,11 @@ public ListImpl(final ContentNode parent) { @Override public void formatTo(final StringBuilder buffer) { final String prefix = computeItemPrefix(); - getBlocks().forEach(node -> { + final java.util.List blocks = getBlocks(); + if (!blocks.isEmpty()) { buffer.append(prefix); - if (node instanceof final StringBuilderFormattable formattable) { - formattable.formatTo(buffer); - } else { - buffer.append(node.convert()); - } - }); + } + formatNodeCollection(blocks, prefix, buffer); } /** diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListItemImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListItemImpl.java index 0508fd62..e1e2d65e 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListItemImpl.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListItemImpl.java @@ -16,10 +16,8 @@ */ package org.apache.logging.log4j.docgen.processor.internal; -import org.apache.logging.log4j.util.StringBuilderFormattable; import org.asciidoctor.ast.ContentNode; import org.asciidoctor.ast.ListItem; -import org.asciidoctor.ast.StructuralNode; public final class ListItemImpl extends StructuralNodeImpl implements ListItem { @@ -29,19 +27,7 @@ public ListItemImpl(final ContentNode parent) { @Override public void formatTo(final StringBuilder buffer) { - boolean first = true; - for (final StructuralNode node : getBlocks()) { - if (!first) { - buffer.append("+\n"); - } else { - first = false; - } - if (node instanceof final StringBuilderFormattable formattable) { - formattable.formatTo(buffer); - } else { - buffer.append(node.convert()); - } - } + formatNodeCollection(getBlocks(), "+\n", buffer); } // diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/SectionImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/SectionImpl.java index 4a61c0c4..33cfee6f 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/SectionImpl.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/SectionImpl.java @@ -16,9 +16,6 @@ */ package org.apache.logging.log4j.docgen.processor.internal; -import java.util.ArrayList; -import java.util.List; -import org.apache.logging.log4j.util.StringBuilderFormattable; import org.asciidoctor.ast.ContentNode; import org.asciidoctor.ast.Document; import org.asciidoctor.ast.Section; @@ -26,8 +23,6 @@ public final class SectionImpl extends StructuralNodeImpl implements Section { - private final List content = new ArrayList<>(); - public SectionImpl(final ContentNode parent) { super(parent); } @@ -41,19 +36,7 @@ public void formatTo(final StringBuilder buffer) { .append(title) .append("\n\n"); } - boolean first = true; - for (final StructuralNode node : getBlocks()) { - if (!first) { - buffer.append('\n'); - } else { - first = false; - } - if (node instanceof final StringBuilderFormattable formattable) { - formattable.formatTo(buffer); - } else { - buffer.append(node.convert()); - } - } + formatNodeCollection(getBlocks(), "\n", buffer); } private static int computeSectionLevel(final StructuralNode section) { diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/StructuralNodeImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/StructuralNodeImpl.java index 402ae54d..b0639ca7 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/StructuralNodeImpl.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/StructuralNodeImpl.java @@ -24,7 +24,7 @@ import org.asciidoctor.ast.Cursor; import org.asciidoctor.ast.StructuralNode; -public abstract class StructuralNodeImpl extends ContentNodeImpl implements StructuralNode, StringBuilderFormattable { +public abstract class StructuralNodeImpl extends ContentNodeImpl implements StructuralNode { private int level; private String title; @@ -67,12 +67,39 @@ public void append(final StructuralNode structuralNode) { } @Override - public String convert() { + public final String convert() { final StringBuilder sb = new StringBuilder(); formatTo(sb); return sb.toString(); } + protected abstract void formatTo(final StringBuilder buffer); + + protected static void formatNode(final StructuralNode node, final StringBuilder buffer) { + if (node instanceof final StructuralNodeImpl impl) { + impl.formatTo(buffer); + } else { + buffer.append(node.convert()); + } + } + + protected static void formatNodeCollection( + final Iterable nodes, final String separator, final StringBuilder buffer) { + boolean first = true; + for (final StructuralNode node : nodes) { + if (!first) { + buffer.append(separator); + } else { + first = false; + } + if (node instanceof final StringBuilderFormattable formattable) { + formattable.formatTo(buffer); + } else { + buffer.append(node.convert()); + } + } + } + @Override public int getLevel() { return level; diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/TableImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/TableImpl.java index 28a821f7..aaebadac 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/TableImpl.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/TableImpl.java @@ -52,7 +52,7 @@ private static void formatColSpecifier(final int colCount, final StringBuilder b } } - private void formatRow(final Row row, final StringBuilder buffer) { + private static void formatRow(final Row row, final StringBuilder buffer) { buffer.append('\n'); row.getCells().forEach(cell -> { if (cell instanceof final StringBuilderFormattable formattable) {