diff --git a/log4j-docgen/pom.xml b/log4j-docgen/pom.xml index d8d735f4..498b4a7a 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,27 @@ provided + + org.asciidoctor + asciidoctorj-api + ${asciidoctorj-api.version} + + + + org.apache.commons + commons-lang3 + + org.freemarker freemarker + + org.apache.logging.log4j + log4j-plugins + + 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..cd061fef --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java @@ -0,0 +1,303 @@ +/* + * 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.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; +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; + +abstract class AbstractAsciidocTreeVisitor extends SimpleDocTreeVisitor { + + private static final String JAVA_SOURCE_STYLE = "source,java"; + private static final String XML_SOURCE_STYLE = "source,xml"; + + private static final String CODE_DELIM = "`"; + private static final String EMPHASIS_DELIM = "_"; + private static final String STRONG_EMPHASIS_DELIM = "*"; + + private static final String SPACE = " "; + // Simple pattern to match (most) XML opening tags + private static final Pattern XML_TAG = Pattern.compile("<\\w+\\s*(\\w+=[\"'][^\"']*[\"']\\s*)*/?>"); + + /** + * Used to convert entities into strings. + */ + private final DocTrees docTrees; + + AbstractAsciidocTreeVisitor(final DocTrees docTrees) { + this.docTrees = docTrees; + } + + @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(); + data.getCurrentParagraph().setContext(BlockImpl.LISTING_CONTEXT); + break; + case "code": + data.newTextSpan(); + break; + case "em": + case "i": + data.newTextSpan(); + break; + case "strong": + case "b": + data.newTextSpan(); + 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 "td": + data.popNode(); + break; + case "th": + data.popNode().setContext(CellImpl.HEADER_CONTEXT); + 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": + 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 + 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.getBody()) { + idx += row.getCells().size(); + } + final Row row = new RowImpl(); + for (int i = idx; i < table.getBlocks().size(); i++) { + final StructuralNode cell = cells.get(i); + if (cell instanceof Cell) { + row.getCells().add((Cell) cell); + } + } + 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); + } + + @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) { + data.newTextSpan(); + node.getBody().accept(this, data); + appendSpan(data, "`"); + } else { + node.getBody().accept(this, data); + } + return super.visitLiteral(node, data); + } + + @Override + public Void visitEntity(final EntityTree node, final AsciidocData asciidocData) { + final String text = docTrees.getCharacters(node); + if (text != null) { + asciidocData.append(text); + } + return super.visitEntity(node, asciidocData); + } + + @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); + } + + 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 new file mode 100644 index 00000000..2fa76180 --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocConverter.java @@ -0,0 +1,95 @@ +/* + * 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. + */ +final class AsciidocConverter { + + 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) { + 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(docCommentTreeVisitor, data); + return data.getDocument().convert(); + } + + public String toAsciiDoc(final ParamTree tree) { + final AsciidocData data = new AsciidocData(); + 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. + 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 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()) { + 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..d7bc1f0c --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java @@ -0,0 +1,194 @@ +/* + * 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.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; +import org.asciidoctor.ast.Block; +import org.asciidoctor.ast.Document; +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 final Document document; + private int currentSectionLevel; + private StructuralNode currentNode; + // 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; + paragraphs.push(new BlockImpl(currentNode)); + lines.push(new StringBuilder()); + } + + public void newLine() { + // Remove trailing space + final String line = getCurrentLine().toString().stripTrailing(); + // Ignore leading empty lines + if (!getCurrentParagraph().getLines().isEmpty() || !line.isEmpty()) { + getCurrentParagraph().getLines().add(line); + } + 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++) { + getCurrentLine().append(lines[i]); + if (i != lines.length - 1) { + newLine(); + } + } + return this; + } + + 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); + } + } + return this; + } + + public void newTextSpan() { + 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(); + } + lines.pop(); + return String.join(SPACE, paragraphs.pop().getLines()); + } + + public Block newParagraph() { + return newParagraph(currentNode); + } + + private Block 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--) { + 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() { + return currentNode; + } + + public Block getCurrentParagraph() { + return paragraphs.peek(); + } + + public StringBuilder getCurrentLine() { + return lines.peek(); + } + + public Document getDocument() { + return document; + } + + public void setCurrentSectionLevel(final int sectionLevel) { + if (sectionLevel < currentSectionLevel) { + // Close all subsections + while (sectionLevel < currentSectionLevel) { + popNode(); + currentSectionLevel--; + } + // Create sibling section + popNode(); + pushChildNode(SectionImpl::new); + } else { + // Create missing section levels + 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) { + final StructuralNode child = supplier.apply(currentNode); + + // Flushes and reparents the current paragraph + newParagraph(child); + + currentNode.append(child); + return currentNode = child; + } + + public StructuralNode popNode() { + final StructuralNode currentNode = this.currentNode; + + final StructuralNode parent = (StructuralNode) currentNode.getParent(); + if (parent == null) { + throw new EmptyStackException(); + } + // Flushes and creates a new current paragraph + newParagraph(parent); + + this.currentNode = parent; + return currentNode; + } +} 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..9ed82350 --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/BlockImpl.java @@ -0,0 +1,72 @@ +/* + * 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"; + + private List lines = new ArrayList<>(); + + public BlockImpl(final ContentNode parent) { + super(parent); + setContext(PARAGRAPH_CONTEXT); + } + + @Override + protected 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..5a46977d --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/CellImpl.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 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 + protected void formatTo(final StringBuilder buffer) { + if (getBlocks().size() > 1) { + buffer.append('a'); + } else if (HEADER_CONTEXT.equals(getContext())) { + buffer.append('h'); + } + buffer.append("| "); + formatNodeCollection(getBlocks(), " ", buffer); + } + + // + // 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..519e25c2 --- /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; + +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..77df46bd --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/DocumentImpl.java @@ -0,0 +1,109 @@ +/* + * 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.asciidoctor.ast.Author; +import org.asciidoctor.ast.Catalog; +import org.asciidoctor.ast.Document; +import org.asciidoctor.ast.RevisionInfo; +import org.asciidoctor.ast.Title; + +public final class DocumentImpl extends StructuralNodeImpl implements Document { + + public DocumentImpl() { + super(null); + } + + @Override + protected void formatTo(final StringBuilder buffer) { + if (!StringUtils.isBlank(getTitle())) { + buffer.append("= ").append(getTitle()).append("\n\n"); + } + formatNodeCollection(getBlocks(), "\n", buffer); + } + + @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..b6cc957c --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListImpl.java @@ -0,0 +1,92 @@ +/* + * 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.asciidoctor.ast.ContentNode; +import org.asciidoctor.ast.List; +import org.asciidoctor.ast.ListItem; +import org.asciidoctor.ast.StructuralNode; + +public final 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(); + final java.util.List blocks = getBlocks(); + if (!blocks.isEmpty()) { + buffer.append(prefix); + } + formatNodeCollection(blocks, prefix, buffer); + } + + /** + * 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..e1e2d65e --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/ListItemImpl.java @@ -0,0 +1,60 @@ +/* + * 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.asciidoctor.ast.ContentNode; +import org.asciidoctor.ast.ListItem; + +public final class ListItemImpl extends StructuralNodeImpl implements ListItem { + + public ListItemImpl(final ContentNode parent) { + super(parent); + } + + @Override + public void formatTo(final StringBuilder buffer) { + formatNodeCollection(getBlocks(), "+\n", buffer); + } + + // + // 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..c9dd3085 --- /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 final 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..33cfee6f --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/SectionImpl.java @@ -0,0 +1,91 @@ +/* + * 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.asciidoctor.ast.ContentNode; +import org.asciidoctor.ast.Document; +import org.asciidoctor.ast.Section; +import org.asciidoctor.ast.StructuralNode; + +public final class SectionImpl extends StructuralNodeImpl implements Section { + + 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"); + } + formatNodeCollection(getBlocks(), "\n", buffer); + } + + 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..b0639ca7 --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/StructuralNodeImpl.java @@ -0,0 +1,185 @@ +/* + * 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 { + + 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 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; + } + + @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..aaebadac --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/TableImpl.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 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 final class TableImpl extends StructuralNodeImpl implements Table { + + private final List body = new ArrayList<>(); + + public TableImpl(final ContentNode parent) { + super(parent); + } + + @Override + public void formatTo(final StringBuilder buffer) { + final int colCount = body.isEmpty() ? 1 : body.get(0).getCells().size(); + + buffer.append("[cols=\""); + formatColSpecifier(colCount, buffer); + buffer.append("\"]\n").append("|===\n"); + 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 static 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 getBody() { + return body; + } + + // + // All methods below this line are not implemented. + // + @Override + public List getHeader() { + throw new UnsupportedOperationException(); + } + + @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..80e8fd43 --- /dev/null +++ b/log4j-docgen/src/test/it/example/JavadocExample.java @@ -0,0 +1,128 @@ +/* + * 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. + *

        + *

        + * 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 + * 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

        + *
        + *     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/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..7eafa535 --- /dev/null +++ b/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc @@ -0,0 +1,134 @@ +//// +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. + +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. +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"] +|=== + +h| Key +h| Value + +| A +| 1 + +| B +| 2 + +|=== + +=== Subsection + +[source,java] +---- +public final class Main { + private static final Logger logger = LogManager.getLogger(); + + public static void sayHello() { + logger.info("Hello world!"); + } +} +---- + +== Minimal configuration example + +[source,xml] +---- + + + + + + + + + + + + +---- 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