diff --git a/log4j-docgen/pom.xml b/log4j-docgen/pom.xml index 93efe4e5..cf97aa3f 100644 --- a/log4j-docgen/pom.xml +++ b/log4j-docgen/pom.xml @@ -27,21 +27,36 @@ log4j-docgen - 17 + 11 false 3.0.0-alpha.2 + + 2.22.1 + 3.0.0-beta1 + + biz.aQute.bnd + biz.aQute.bnd.annotation + provided + + jakarta.inject jakarta.inject-api provided + + org.jspecify + jspecify + provided + + org.asciidoctor asciidoctorj-api @@ -59,13 +74,28 @@ - org.apache.logging.log4j - log4j-plugins + org.junit.jupiter + junit-jupiter-api + test org.junit.jupiter - junit-jupiter-api + junit-jupiter-params + test + + + + org.apache.logging.log4j + log4j-core + ${log4j-core.version} + test + + + + org.apache.logging.log4j + log4j-plugins + ${log4j-plugins.version} test @@ -80,14 +110,14 @@ - org.apache.maven.plugins maven-compiler-plugin + default-compile none diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java index 4e5d96f5..4144b8aa 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java @@ -23,7 +23,6 @@ import java.util.Collection; import java.util.List; import java.util.Set; -import java.util.TreeSet; import javax.inject.Named; import javax.inject.Singleton; import javax.xml.XMLConstants; @@ -190,15 +189,13 @@ private static void writeAbstractType( } writer.writeStartElement(XSD_NAMESPACE, "choice"); - final Set implementations = new TreeSet<>(abstractType.getImplementations()); + final Set implementations = abstractType.getImplementations(); if (abstractType instanceof PluginType) { implementations.add(abstractType.getClassName()); } for (final String implementation : implementations) { final PluginType pluginType = (PluginType) lookup.get(implementation); - final Collection keys = new TreeSet<>(pluginType.getAliases()); - keys.add(pluginType.getName()); - for (final String key : keys) { + for (final String key : getKeyAndAliases(pluginType)) { writer.writeEmptyElement(XSD_NAMESPACE, "element"); writer.writeAttribute("name", key); writer.writeAttribute("type", LOG4J_PREFIX + ":" + pluginType.getClassName()); @@ -248,9 +245,7 @@ private static void writePluginElement( writeDocumentation(element.getDescription(), writer); writer.writeEndElement(); } else { - final Collection keys = new TreeSet<>(pluginType.getAliases()); - keys.add(pluginType.getName()); - for (final String key : keys) { + for (final String key : getKeyAndAliases(pluginType)) { writer.writeStartElement(XSD_NAMESPACE, "element"); writer.writeAttribute("name", key); writer.writeAttribute("type", xmlType); @@ -303,4 +298,11 @@ private static void writeMultiplicity( writer.writeAttribute("maxOccurs", "unbounded"); } } + + private static Collection getKeyAndAliases(final PluginType pluginType) { + final Collection keys = new ArrayList<>(); + keys.add(pluginType.getName()); + keys.addAll(pluginType.getAliases()); + return keys; + } } 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 cd061fef..cf78c225 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 @@ -16,6 +16,7 @@ */ package org.apache.logging.log4j.docgen.processor; +import static org.apache.commons.lang3.StringUtils.isEmpty; import static org.apache.commons.lang3.StringUtils.substringBefore; import com.sun.source.doctree.DocTree; @@ -25,7 +26,6 @@ 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; @@ -59,15 +59,6 @@ abstract class AbstractAsciidocTreeVisitor extends SimpleDocTreeVisitor"); - /** - * 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(); @@ -165,7 +156,7 @@ public Void visitEndElement(final EndElementTree node, final AsciidocData data) case "h5": case "h6": // Only flush the current line - if (!data.getCurrentLine().isEmpty()) { + if (!isEmpty(data.getCurrentLine())) { data.newLine(); } // The current paragraph contains the title @@ -236,10 +227,7 @@ public Void visitEndElement(final EndElementTree node, final AsciidocData data) 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:") + data.appendAdjustingSpace(" xref:") .append(className) .append(".adoc[") .append(simpleName) @@ -261,7 +249,26 @@ public Void visitLiteral(final LiteralTree node, final AsciidocData data) { @Override public Void visitEntity(final EntityTree node, final AsciidocData asciidocData) { - final String text = docTrees.getCharacters(node); + final String text; + switch (node.getName().toString()) { + case "amp": + text = "&"; + break; + case "apos": + text = "'"; + break; + case "gt": + text = ">"; + break; + case "lt": + text = "<"; + break; + case "quot": + text = "\""; + break; + default: + text = null; + } if (text != null) { asciidocData.append(text); } diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/Annotations.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/Annotations.java new file mode 100644 index 00000000..13efceb2 --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/Annotations.java @@ -0,0 +1,239 @@ +/* + * 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.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import javax.lang.model.AnnotatedConstruct; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.Types; + +final class Annotations { + + private static final Collection FACTORY_ANNOTATION_NAMES = Arrays.asList( + "org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory", + "org.apache.logging.log4j.core.config.plugins.PluginFactory", + "org.apache.logging.log4j.plugins.Factory", + "org.apache.logging.log4j.plugins.PluginFactory"); + private static final String NAMESPACE_ANNOTATION_NAME = "org.apache.logging.log4j.plugins.Namespace"; + private static final String PLUGIN_V2_ANNOTATION_NAME = "org.apache.logging.log4j.core.config.plugins.Plugin"; + private static final String PLUGIN_V3_ANNOTATION_NAME = "org.apache.logging.log4j.plugins.Plugin"; + private static final Collection PLUGIN_ALIAS_ANNOTATION_NAMES = Arrays.asList( + "org.apache.logging.log4j.core.config.plugins.PluginAliases", + "org.apache.logging.log4j.plugins.PluginAliases"); + private static final Collection PLUGIN_ATTRIBUTE_ANNOTATION_NAMES = Arrays.asList( + "org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute", + "org.apache.logging.log4j.core.config.plugins.PluginAttribute", + "org.apache.logging.log4j.plugins.PluginAttribute", + "org.apache.logging.log4j.plugins.PluginBuilderAttribute"); + private static final Collection PLUGIN_ELEMENT_ANNOTATION_NAMES = Arrays.asList( + "org.apache.logging.log4j.core.config.plugins.PluginElement", + "org.apache.logging.log4j.plugins.PluginElement"); + private static final Collection REQUIRED_CONSTRAINT_ANNOTATION_NAMES = Arrays.asList( + "org.apache.logging.log4j.core.config.plugins.validation.constraints.Required", + "org.apache.logging.log4j.plugins.validation.constraints.Required"); + + private final Elements elements; + private final Types types; + + private final Name category; + private final Name name; + private final Name value; + + private final TypeElement[] pluginAnnotationElements; + // Contain the namespace of the plugin + private final DeclaredType namespaceAnnotation; + private final DeclaredType pluginV2Annotation; + private final DeclaredType pluginV3Annotation; + + private final Collection factoryAnnotations = new HashSet<>(); + private final Collection pluginAliasAnnotations = new HashSet<>(); + private final Collection pluginAttributeAnnotations = new HashSet<>(); + private final Collection pluginAttributeAndElementAnnotations = new HashSet<>(); + private final Collection requiredConstraintAnnotations = new HashSet<>(); + + Annotations(final Elements elements, final Types types) { + this.elements = elements; + this.types = types; + + this.category = elements.getName("category"); + this.name = elements.getName("name"); + this.value = elements.getName("value"); + + final Collection pluginAnnotations = new ArrayList<>(); + final TypeElement pluginV2Annotation = elements.getTypeElement(PLUGIN_V2_ANNOTATION_NAME); + this.pluginV2Annotation = pluginV2Annotation != null ? (DeclaredType) pluginV2Annotation.asType() : null; + if (pluginV2Annotation != null) { + pluginAnnotations.add(pluginV2Annotation); + } + final TypeElement pluginV3Annotation = elements.getTypeElement(PLUGIN_V3_ANNOTATION_NAME); + this.pluginV3Annotation = pluginV3Annotation != null ? (DeclaredType) pluginV3Annotation.asType() : null; + if (pluginV3Annotation != null) { + pluginAnnotations.add(pluginV3Annotation); + } + final TypeElement namespaceAnnotation = elements.getTypeElement(NAMESPACE_ANNOTATION_NAME); + this.namespaceAnnotation = namespaceAnnotation != null ? (DeclaredType) namespaceAnnotation.asType() : null; + this.pluginAnnotationElements = pluginAnnotations.toArray(new TypeElement[0]); + + FACTORY_ANNOTATION_NAMES.forEach(name -> addDeclaredTypeIfExists(name, factoryAnnotations)); + PLUGIN_ALIAS_ANNOTATION_NAMES.forEach(name -> addDeclaredTypeIfExists(name, pluginAliasAnnotations)); + PLUGIN_ATTRIBUTE_ANNOTATION_NAMES.forEach(name -> addDeclaredTypeIfExists(name, pluginAttributeAnnotations)); + pluginAttributeAndElementAnnotations.addAll(pluginAttributeAnnotations); + PLUGIN_ELEMENT_ANNOTATION_NAMES.forEach( + name -> addDeclaredTypeIfExists(name, pluginAttributeAndElementAnnotations)); + REQUIRED_CONSTRAINT_ANNOTATION_NAMES.forEach( + name -> addDeclaredTypeIfExists(name, requiredConstraintAnnotations)); + } + + public TypeElement[] getPluginAnnotations() { + return pluginAnnotationElements; + } + + public Optional getAttributeSpecifiedName(final AnnotationMirror annotation) { + return Optional.ofNullable(getValueAsString(annotation, value)); + } + + public Optional getPluginSpecifiedName(final AnnotatedConstruct element) { + return getAnnotationValue(element, pluginV2Annotation, name, pluginV3Annotation, value); + } + + public Optional getPluginSpecifiedNamespace(final AnnotatedConstruct element) { + return getAnnotationValue(element, pluginV2Annotation, category, namespaceAnnotation, value); + } + + public boolean hasFactoryAnnotation(final Element element) { + return hasAnyDirectAnnotation(element, factoryAnnotations); + } + + public boolean hasRequiredConstraint(final Element element) { + return hasAnyDirectAnnotation(element, requiredConstraintAnnotations); + } + + public boolean isAttributeAnnotation(final AnnotationMirror annotation) { + return contains(pluginAttributeAnnotations, annotation.getAnnotationType()); + } + + /** + * Find all plugin element and attribute annotations on the element and its children. + */ + public Collection findAttributeAndPropertyAnnotations(final Element element) { + final Collection annotations = new HashSet<>(); + element.accept( + new SimpleElementVisitor8>() { + @Override + protected Void defaultAction( + final Element e, final Collection annotations) { + for (final AnnotationMirror annotation : e.getAnnotationMirrors()) { + if (contains(pluginAttributeAndElementAnnotations, annotation.getAnnotationType())) { + annotations.add(annotation); + } + } + return null; + } + + @Override + public Void visitExecutable( + final ExecutableElement e, final Collection annotations) { + for (final VariableElement param : e.getParameters()) { + param.accept(this, annotations); + } + return super.visitExecutable(e, annotations); + } + }, + annotations); + return annotations; + } + + private String getValueAsString(final AnnotationMirror annotation, final Name property) { + final Object value = getValue(annotation, property); + return value != null ? value.toString() : null; + } + + private boolean hasAnyDirectAnnotation( + final Element element, final Iterable annotationTypes) { + return elements.getAllAnnotationMirrors(element).stream() + .anyMatch(mirror -> contains(annotationTypes, mirror.getAnnotationType())); + } + + private Object getValue(final AnnotationMirror annotation, final Name property) { + for (final Map.Entry entry : + annotation.getElementValues().entrySet()) { + if (entry.getKey().getSimpleName().equals(property)) { + return entry.getValue().getValue(); + } + } + return null; + } + + /** + * Necessary, since TypeMirror does not implement a valid equals. + */ + private boolean contains(final Iterable collection, final T type) { + for (final T t : collection) { + if (types.isSameType(t, type)) { + return true; + } + } + return false; + } + + private void addDeclaredTypeIfExists( + final CharSequence className, final Collection collection) { + final TypeElement element = elements.getTypeElement(className); + if (element != null) { + final TypeMirror type = element.asType(); + if (type instanceof DeclaredType) { + collection.add((DeclaredType) type); + } + } + } + + private Optional getAnnotationValue( + final AnnotatedConstruct element, + final TypeMirror v2Annotation, + final Name v2Property, + final TypeMirror v3Annotation, + final Name v3Property) { + return element.getAnnotationMirrors().stream() + .map(annotation -> { + final DeclaredType annotationType = annotation.getAnnotationType(); + if (v2Annotation != null && types.isSameType(v2Annotation, annotationType)) { + return getValueAsString(annotation, v2Property); + } + return v3Annotation != null && types.isSameType(v3Annotation, annotationType) + ? getValueAsString(annotation, v3Property) + : null; + }) + .filter(Objects::nonNull) + .findAny(); + } +} 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 2fa76180..1266ec6b 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 @@ -34,8 +34,8 @@ final class AsciidocConverter { AsciidocConverter(final DocTrees docTrees) { this.docTrees = docTrees; - this.docCommentTreeVisitor = new DocCommentTreeVisitor(docTrees); - this.paramTreeVisitor = new ParamTreeVisitor(docTrees); + this.docCommentTreeVisitor = new DocCommentTreeVisitor(); + this.paramTreeVisitor = new ParamTreeVisitor(); } public String toAsciiDoc(final Element element) { @@ -56,10 +56,6 @@ public String toAsciiDoc(final ParamTree tree) { } 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. @@ -78,10 +74,6 @@ 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 d7bc1f0c..90f55313 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,6 +16,9 @@ */ package org.apache.logging.log4j.docgen.processor; +import static org.apache.commons.lang3.StringUtils.isEmpty; +import static org.apache.commons.lang3.StringUtils.isNotEmpty; + import java.util.ArrayDeque; import java.util.Deque; import java.util.EmptyStackException; @@ -75,7 +78,7 @@ public AsciidocData appendAdjustingSpace(final CharSequence text) { 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); + final char lineLastChar = isEmpty(currentLine) ? SPACE_CHAR : currentLine.charAt(currentLine.length() - 1); // First char of test final char textFirstChar = normalized.charAt(0); if (lineLastChar == SPACE_CHAR && textFirstChar == SPACE_CHAR) { @@ -98,7 +101,7 @@ public void newTextSpan() { public String popTextSpan() { // Flush the paragraph final StringBuilder line = lines.peek(); - if (line != null && !line.isEmpty()) { + if (isNotEmpty(line)) { newLine(); } lines.pop(); diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/DocGenProcessor.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/DocGenProcessor.java new file mode 100644 index 00000000..42f9a0b6 --- /dev/null +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/DocGenProcessor.java @@ -0,0 +1,657 @@ +/* + * 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.defaultIfEmpty; +import static org.apache.commons.lang3.StringUtils.defaultString; + +import aQute.bnd.annotation.Resolution; +import aQute.bnd.annotation.spi.ServiceProvider; +import com.sun.source.doctree.DocCommentTree; +import com.sun.source.doctree.DocTree; +import com.sun.source.doctree.ParamTree; +import com.sun.source.util.DocTrees; +import com.sun.source.util.SimpleDocTreeVisitor; +import java.io.IOException; +import java.io.Writer; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.QualifiedNameable; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.NoType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.util.Elements; +import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; +import javax.tools.FileObject; +import javax.tools.StandardLocation; +import javax.xml.stream.XMLStreamException; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.docgen.AbstractType; +import org.apache.logging.log4j.docgen.Description; +import org.apache.logging.log4j.docgen.PluginAttribute; +import org.apache.logging.log4j.docgen.PluginElement; +import org.apache.logging.log4j.docgen.PluginSet; +import org.apache.logging.log4j.docgen.PluginType; +import org.apache.logging.log4j.docgen.ScalarType; +import org.apache.logging.log4j.docgen.ScalarValue; +import org.apache.logging.log4j.docgen.Type; +import org.apache.logging.log4j.docgen.io.stax.PluginBundleStaxWriter; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@ServiceProvider(value = Processor.class, resolution = Resolution.OPTIONAL) +@SupportedAnnotationTypes({"org.apache.logging.log4j.core.config.plugins.*", "org.apache.logging.log4j.plugins.*"}) +@NullMarked +public class DocGenProcessor extends AbstractProcessor { + + private static final String MULTIPLICITY_UNBOUNDED = "*"; + private static final CharSequence[] GETTER_SETTER_PREFIXES = {"get", "is", "set"}; + /** + * Reference types from the {@code java.*} namespace that are described + * in {@code org/apache/logging/log4j/docgen/internal/configuration.xml} + */ + private static final Set KNOWN_SCALAR_TYPES = Set.of( + "java.lang.Boolean", + "java.lang.Character", + "java.lang.Byte", + "java.lang.Short", + "java.lang.Integer", + "java.lang.Long", + "java.lang.Float", + "java.lang.Double", + "java.lang.String"); + + private final PluginSet pluginSet; + // Abstract types to process + private final Collection abstractTypesToDocument = new HashSet<>(); + // Scalar types to process + private final Collection scalarTypesToDocument = new HashSet<>(); + + private AsciidocConverter converter; + private DocTrees docTrees; + private Elements elements; + private Types types; + private Messager messager; + private Annotations annotations; + // Type corresponding to java.util.Collection + private DeclaredType collectionType; + // Type corresponding to java.lang.Enum + private DeclaredType enumType; + + // Used by reflection + @SuppressWarnings("unused") + public DocGenProcessor() { + this(new PluginSet()); + } + + @SuppressWarnings("DataFlowIssue") + public DocGenProcessor(final PluginSet pluginSet) { + this.pluginSet = pluginSet; + // Will be initialized later + annotations = null; + collectionType = null; + converter = null; + docTrees = null; + elements = null; + enumType = null; + messager = null; + types = null; + } + + @Override + public synchronized void init(final ProcessingEnvironment processingEnv) { + super.init(processingEnv); + docTrees = DocTrees.instance(processingEnv); + elements = processingEnv.getElementUtils(); + messager = processingEnv.getMessager(); + types = processingEnv.getTypeUtils(); + + converter = new AsciidocConverter(docTrees); + + annotations = new Annotations(elements, types); + collectionType = (DeclaredType) + types.erasure(elements.getTypeElement("java.util.Collection").asType()); + enumType = (DeclaredType) + types.erasure(elements.getTypeElement("java.lang.Enum").asType()); + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(final Set unused, final RoundEnvironment roundEnv) { + // First step: document plugins + roundEnv.getElementsAnnotatedWithAny(annotations.getPluginAnnotations()).forEach(this::addPluginDocumentation); + // Second step: document abstract types + abstractTypesToDocument.forEach(this::addAbstractTypeDocumentation); + // Second step: document scalars + scalarTypesToDocument.forEach(this::addScalarTypeDocumentation); + // Write the result file + if (roundEnv.processingOver()) { + writePluginDescriptor(); + } + return false; + } + + private void addPluginDocumentation(final Element element) { + if (element instanceof TypeElement) { + final PluginType pluginType = new PluginType(); + pluginType.setName(annotations.getPluginSpecifiedName(element).orElseGet(() -> element.getSimpleName() + .toString())); + pluginType.setNamespace( + annotations.getPluginSpecifiedNamespace(element).orElse("Core")); + populatePlugin((TypeElement) element, pluginType); + pluginSet.addPlugin(pluginType); + } else { + messager.printMessage(Diagnostic.Kind.WARNING, "Found @Plugin annotation on unexpected element.", element); + } + } + + private void addAbstractTypeDocumentation(final QualifiedNameable element) { + final AbstractType abstractType = new AbstractType(); + populateAbstractType(element, abstractType); + if (!abstractType.getDescription().getText().isEmpty()) { + pluginSet.addAbstractType(abstractType); + } + } + + private void addScalarTypeDocumentation(final TypeElement element) { + final ScalarType scalarType = new ScalarType(); + populateScalarType(element, scalarType); + pluginSet.addScalar(scalarType); + } + + private void writePluginDescriptor() { + try { + final FileObject output = processingEnv + .getFiler() + .createResource(StandardLocation.CLASS_OUTPUT, "", "META-INF/log4j/plugins.xml"); + + try (final Writer writer = output.openWriter()) { + new PluginBundleStaxWriter().write(writer, pluginSet); + } + } catch (final IOException | XMLStreamException e) { + messager.printMessage( + Diagnostic.Kind.ERROR, + "An error occurred while writing to `META-INF/log4j/plugins.xml`: " + e.getMessage()); + } + } + + private void populateType(final QualifiedNameable element, final Type docgenType) { + // Class name + docgenType.setClassName(element.getQualifiedName().toString()); + // Description + docgenType.setDescription(createDescription(element, null)); + } + + private void populateAbstractType(final QualifiedNameable element, final AbstractType abstractType) { + populateType(element, abstractType); + } + + private void populateScalarType(final TypeElement element, final ScalarType scalarType) { + populateType(element, scalarType); + if (types.isSubtype(element.asType(), enumType)) { + for (final Element member : element.getEnclosedElements()) { + if (member instanceof VariableElement + && member.getModifiers().contains(Modifier.STATIC) + && types.isSameType(member.asType(), element.asType())) { + final VariableElement field = (VariableElement) member; + final ScalarValue value = new ScalarValue(); + value.setDescription(createDescription(field, null)); + value.setName(field.getSimpleName().toString()); + scalarType.addValue(value); + } + } + } + } + + private Map getParameterDescriptions(final Element element) { + final Map descriptions = new HashMap<>(); + final DocCommentTree docCommentTree = docTrees.getDocCommentTree(element); + if (docCommentTree != null) { + docCommentTree.accept( + new SimpleDocTreeVisitor>() { + @Override + public Void visitDocComment(final DocCommentTree node, final Map descriptions) { + for (final DocTree docTree : node.getBlockTags()) { + docTree.accept(this, descriptions); + } + return null; + } + + @Override + public Void visitParam(final ParamTree paramTree, final Map descriptions) { + final String name = paramTree.getName().getName().toString(); + descriptions.put(name, defaultString(converter.toAsciiDoc(paramTree))); + return null; + } + }, + descriptions); + } + return descriptions; + } + + private void populatePlugin(final TypeElement element, final PluginType pluginType) { + populateAbstractType(element, pluginType); + // Supertypes + registerSupertypes(element).forEach(pluginType::addSupertype); + // Plugin factory + for (final Element member : element.getEnclosedElements()) { + if (annotations.hasFactoryAnnotation(member) && member instanceof ExecutableElement) { + final ExecutableElement executable = (ExecutableElement) member; + final Map descriptions = getParameterDescriptions(executable); + final List parameters = executable.getParameters(); + if (parameters.isEmpty()) { + // We have a builder + final TypeElement returnType = getReturnType(executable); + if (returnType != null) { + populateConfigurationProperties(getAllMembers(returnType), descriptions, pluginType); + } else { + messager.printMessage( + Diagnostic.Kind.WARNING, + "The return type of a @PluginFactory annotated method should be a concrete class.", + member); + } + } else { + // Old style factory method + populateConfigurationProperties(parameters, descriptions, pluginType); + } + } + } + } + + private void populateConfigurationProperties( + final Iterable members, + final Map descriptions, + final PluginType pluginType) { + final Collection pluginAttributes = + new TreeSet<>(Comparator.comparing(a -> defaultString(a.getName()))); + final Collection pluginElements = + new TreeSet<>(Comparator.comparing(e -> defaultString(e.getType()))); + // Gather documentation, which can be on any member. + for (final Element member : members) { + final String name = getAttributeOrPropertyName(member); + final String asciidoc = converter.toAsciiDoc(member); + descriptions.compute(name, (key, value) -> Stream.of(value, asciidoc) + .filter(StringUtils::isNotEmpty) + .collect(Collectors.joining("\n"))); + } + // Creates attributes and elements + for (final Element member : members) { + final String description = descriptions.get(getAttributeOrPropertyName(member)); + for (final AnnotationMirror annotation : annotations.findAttributeAndPropertyAnnotations(member)) { + if (annotations.isAttributeAnnotation(annotation)) { + pluginAttributes.add(createPluginAttribute( + member, + description, + annotations + .getAttributeSpecifiedName(annotation) + .orElseGet(() -> getAttributeOrPropertyName(member)))); + } else { + pluginElements.add(createPluginElement(member, description)); + } + } + } + pluginAttributes.forEach(pluginType::addAttribute); + pluginElements.forEach(pluginType::addElement); + } + + private Description createDescription(final String asciidoc) { + final Description description = new Description(); + description.setText(StringUtils.stripToEmpty(asciidoc)); + return description; + } + + private Description createDescription(final Element element, final @Nullable String fallback) { + return createDescription(defaultIfEmpty(converter.toAsciiDoc(element), defaultString(fallback))); + } + + private PluginAttribute createPluginAttribute( + final Element element, final String description, final String specifiedName) { + final PluginAttribute attribute = new PluginAttribute(); + // Name + attribute.setName(specifiedName.isEmpty() ? getAttributeOrPropertyName(element) : specifiedName); + // Type + final TypeMirror type = getMemberType(element); + final String className = getClassName(type); + // If type is not a well-known declared type, add it to the scanning queue. + if (className != null && !KNOWN_SCALAR_TYPES.contains(className) && type instanceof DeclaredType) { + scalarTypesToDocument.add(asTypeElement((DeclaredType) type)); + } + attribute.setType(className); + // Description + attribute.setDescription(createDescription(element, description)); + // Required + attribute.setRequired(annotations.hasRequiredConstraint(element)); + // Default value + final Object defaultValue = + element instanceof VariableElement ? ((VariableElement) element).getConstantValue() : null; + if (defaultValue != null) { + attribute.setDefaultValue(elements.getConstantExpression(defaultValue)); + } + // TODO: add the value of the property used, when we add it to the annotation. + return attribute; + } + + private PluginElement createPluginElement(final Element element, final String description) { + final PluginElement pluginElement = new PluginElement(); + // Type and multiplicity + final TypeMirror elementType = getMemberType(element); + if (elementType == null) { + messager.printMessage(Diagnostic.Kind.WARNING, "Unable to determine type of plugin element.", element); + } else { + pluginElement.setType(getComponentClassName(elementType)); + pluginElement.setMultiplicity(getMultiplicity(elementType)); + } + // Required + pluginElement.setRequired(annotations.hasRequiredConstraint(element)); + // Description + pluginElement.setDescription(createDescription(element, description)); + return pluginElement; + } + + /** + * Register all the supertypes of the given type for doc processing. + * @param element a plugin class, + * @return the set of FQCN of all supertypes. + */ + private Set registerSupertypes(final TypeElement element) { + final Set supertypes = new TreeSet<>(); + element.accept( + new SimpleElementVisitor8>() { + @Override + public Void visitType(final TypeElement element, final Set supertypes) { + registerAndVisit(element.getSuperclass(), supertypes); + element.getInterfaces().forEach(iface -> registerAndVisit(iface, supertypes)); + return null; + } + + private void registerAndVisit(final TypeMirror type, final Set supertypes) { + if (type instanceof DeclaredType) { + final TypeElement element = asTypeElement((DeclaredType) type); + final String className = element.getQualifiedName().toString(); + abstractTypesToDocument.add(element); + if (supertypes.add(className)) { + element.accept(this, supertypes); + } + } + } + }, + supertypes); + return supertypes; + } + + private @Nullable TypeMirror getMemberType(final Element element) { + return element.accept( + new SimpleElementVisitor8<@Nullable TypeMirror, @Nullable Void>() { + @Override + protected @Nullable TypeMirror defaultAction(final Element element, final Void unused) { + messager.printMessage( + Diagnostic.Kind.WARNING, + "Unexpected plugin annotation on element of type " + + element.getKind().name(), + element); + return null; + } + + @Override + public TypeMirror visitVariable(final VariableElement element, final Void unused) { + return element.asType(); + } + + @Override + public @Nullable TypeMirror visitExecutable(final ExecutableElement element, final Void unused) { + final TypeMirror returnType = element.getReturnType(); + final List parameters = element.getParameters(); + switch (parameters.size()) { + // A getter + case 0: + return returnType; + // A setter + case 1: + return parameters.get(0).asType(); + // Invalid property + default: + return super.visitExecutable(element, unused); + } + } + }, + null); + } + + private String getAttributeOrPropertyName(final Element element) { + return element.accept( + new SimpleElementVisitor8() { + @Override + protected String defaultAction(final Element e, @Nullable final Void unused) { + return e.getSimpleName().toString(); + } + + @Override + public String visitExecutable(final ExecutableElement e, final Void unused) { + final Name name = e.getSimpleName(); + if (StringUtils.startsWithAny(name, GETTER_SETTER_PREFIXES)) { + final int prefixLen = StringUtils.startsWith(name, "is") ? 2 : 3; + if (name.length() > prefixLen) { + return Character.toLowerCase(name.charAt(prefixLen)) + + name.toString().substring(prefixLen + 1); + } + } + return super.visitExecutable(e, unused); + } + }, + null); + } + + /** + * Returns the appropriate type element for the return type of this method. + *

+ * If the return type is a type variable, returns its upper bound. + *

+ *

+ * If the return type is {@code void} or primitive, {@code null} is returned. + *

+ */ + private @Nullable TypeElement getReturnType(final ExecutableElement method) { + return method.getReturnType() + .accept( + new SimpleTypeVisitor8<@Nullable TypeElement, @Nullable Void>() { + @Override + public TypeElement visitDeclared(final DeclaredType t, final Void unused) { + return asTypeElement(t); + } + + @Override + public @Nullable TypeElement visitTypeVariable(final TypeVariable t, final Void unused) { + // If the return type is a variable, try the upper bound + return t.getUpperBound().accept(this, unused); + } + }, + null); + } + + /** + * Returns all the members of this type or its ancestors. + */ + private Collection getAllMembers(final TypeElement element) { + final Collection members = new HashSet<>(); + TypeElement currentElement = element; + while (currentElement != null) { + members.addAll(currentElement.getEnclosedElements()); + currentElement = getSuperclass(currentElement); + } + return members; + } + + private @Nullable TypeElement getSuperclass(final TypeElement element) { + final TypeMirror superclass = element.getSuperclass(); + return superclass instanceof DeclaredType ? asTypeElement((DeclaredType) superclass) : null; + } + + // TODO: Can the element associated to a declared type be anything else than a type element? + private TypeElement asTypeElement(final DeclaredType type) { + return (TypeElement) type.asElement(); + } + + /** + * Gets the class name of the erasure of this type. + *

+ * If this is an array type, {@code null} is returned. + *

+ */ + private @Nullable String getClassName(final @Nullable TypeMirror type) { + return type != null + ? types.erasure(type) + .accept( + new SimpleTypeVisitor8() { + + @Override + public String visitDeclared(final DeclaredType t, final Void unused) { + return asTypeElement(t) + .getQualifiedName() + .toString(); + } + + @Override + public String visitPrimitive(final PrimitiveType t, final Void unused) { + switch (t.getKind()) { + case BOOLEAN: + return "boolean"; + case BYTE: + return "byte"; + case SHORT: + return "short"; + case INT: + return "int"; + case LONG: + return "long"; + case CHAR: + return "char"; + case FLOAT: + return "float"; + case DOUBLE: + return "double"; + default: + throw new IllegalArgumentException(); + } + } + + @Override + public String visitNoType(final NoType t, final Void unused) { + return "void"; + } + }, + null) + : null; + } + + /** + * If this is an array or collection type, returns the class name of its component. + *

+ * If this is not an array or collection type, {@link #getClassName(TypeMirror)} is returned. + *

+ */ + private @Nullable String getComponentClassName(final TypeMirror type) { + return type.accept( + new SimpleTypeVisitor8<@Nullable String, @Nullable Void>() { + @Override + protected @Nullable String defaultAction(final TypeMirror e, final Void unused) { + return getClassName(e); + } + + @Override + public @Nullable String visitArray(final ArrayType t, final Void unused) { + return getClassName(t.getComponentType()); + } + + @Override + public @Nullable String visitDeclared(final DeclaredType t, final Void unused) { + if (types.isAssignable(t, collectionType)) { + // Bind T in Collection + final DeclaredType asCollection = findCollectionSupertype(t); + if (asCollection != null) { + final List typeArguments = asCollection.getTypeArguments(); + if (!typeArguments.isEmpty()) { + return getClassName(typeArguments.get(0)); + } + } + } + return super.visitDeclared(t, unused); + } + + private @Nullable DeclaredType findCollectionSupertype(final TypeMirror type) { + if (types.isSameType(types.erasure(type), collectionType)) { + return (DeclaredType) type; + } + for (final TypeMirror supertype : types.directSupertypes(type)) { + final DeclaredType result = findCollectionSupertype(supertype); + if (result != null) { + return result; + } + } + return null; + } + }, + null); + } + + private @Nullable String getMultiplicity(final TypeMirror type) { + return type.accept( + new SimpleTypeVisitor8<@Nullable String, @Nullable Void>() { + @Override + public String visitArray(final ArrayType t, final Void unused) { + return MULTIPLICITY_UNBOUNDED; + } + + @Override + public @Nullable String visitDeclared(final DeclaredType t, final Void unused) { + return types.isAssignable(t, collectionType) ? MULTIPLICITY_UNBOUNDED : null; + } + }, + null); + } +} 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 33cfee6f..58a7442c 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 @@ -31,10 +31,10 @@ public SectionImpl(final ContentNode parent) { public void formatTo(final StringBuilder buffer) { final String title = getTitle(); if (title != null) { - buffer.append("=".repeat(computeSectionLevel(this))) - .append(' ') - .append(title) - .append("\n\n"); + for (int i = 0; i < computeSectionLevel(this); i++) { + buffer.append('='); + } + buffer.append(' ').append(title).append("\n\n"); } formatNodeCollection(getBlocks(), "\n", buffer); } 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 65f3e660..58b0152e 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 @@ -75,8 +75,8 @@ public final String convert() { 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); + if (node instanceof StructuralNodeImpl) { + ((StructuralNodeImpl) node).formatTo(buffer); } else { buffer.append(node.convert()); } diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/util/TypeLookup.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/util/TypeLookup.java index 15774fa7..10986a4c 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/util/TypeLookup.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/util/TypeLookup.java @@ -16,7 +16,6 @@ */ package org.apache.logging.log4j.docgen.util; -import java.io.Serial; import java.util.HashSet; import java.util.Set; import java.util.TreeMap; @@ -28,7 +27,6 @@ public final class TypeLookup extends TreeMap { - @Serial private static final long serialVersionUID = 1L; public static TypeLookup of(final Iterable sets) { diff --git a/log4j-docgen/src/main/mdo/plugins-model.xml b/log4j-docgen/src/main/mdo/plugins-model.xml index 815eeb37..d556f147 100644 --- a/log4j-docgen/src/main/mdo/plugins-model.xml +++ b/log4j-docgen/src/main/mdo/plugins-model.xml @@ -30,6 +30,10 @@ package org.apache.logging.log4j.docgen + + java.util.Set + new java.util.TreeSet<?>() + @@ -67,6 +71,7 @@ scalars + Set ScalarType * @@ -75,6 +80,7 @@ plugins + Set PluginType * @@ -83,6 +89,7 @@ abstractTypes + Set AbstractType * @@ -111,6 +118,9 @@ Type Any Java type used in a Log4j configuration. Documented + + java.lang.Comparable<Type> + className @@ -119,6 +129,16 @@ Fully qualified name of the class implementing the plugin. + + + + + @@ -128,6 +148,7 @@ implementations + Set String * @@ -166,6 +187,7 @@ This element is filled in automatically. aliases true + Set String * @@ -177,6 +199,7 @@ This element is filled in automatically. supertypes + Set String * @@ -209,6 +232,9 @@ This element is filled in automatically. PluginAttribute A scalar configuration value for the plugin. + + java.lang.Comparable<PluginAttribute> + name @@ -247,6 +273,16 @@ The type must be an enum or must have a type converter. A description of the property. + + + + + @@ -309,6 +345,9 @@ The type must be an enum or must have a type converter. PluginElement Describes a nested configuration component. + + java.lang.Comparable<PluginElement> + multiplicity @@ -337,6 +376,16 @@ If the type is an array or collection, this returns the type of the element.An HTML description of this element. + + + + + 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 index 7ca4f0c8..5dc18880 100644 --- 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 @@ -28,6 +28,7 @@ import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.stream.Collectors; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.tools.Diagnostic; @@ -66,8 +67,10 @@ void convertToAsciidoc() throws Exception { 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 sourcePath = Paths.get(AsciidocConverterTest.class + .getResource("/processor/asciidoc/example/JavadocExample.java") + .toURI()); + final Iterable sources = fileManager.getJavaFileObjects(sourcePath); final Path destPath = basePath.resolve("target/test-site"); Files.createDirectories(destPath); @@ -79,7 +82,7 @@ void convertToAsciidoc() throws Exception { final List warnings = ds.getDiagnostics().stream() .filter(d -> d.getKind() != Diagnostic.Kind.NOTE) .map(d -> d.getMessage(Locale.ROOT)) - .toList(); + .collect(Collectors.toList()); assertThat(warnings).isEmpty(); final Path expectedPath = Paths.get(AsciidocConverterTest.class .getResource("/expected/processor/JavadocExample.adoc") @@ -104,7 +107,7 @@ public Set getSupportedOptions() { @Override public SourceVersion getSupportedSourceVersion() { - return SourceVersion.RELEASE_17; + return SourceVersion.latestSupported(); } @Override diff --git a/log4j-docgen/src/test/java/org/apache/logging/log4j/docgen/processor/DocGenProcessorTest.java b/log4j-docgen/src/test/java/org/apache/logging/log4j/docgen/processor/DocGenProcessorTest.java new file mode 100644 index 00000000..98bde56f --- /dev/null +++ b/log4j-docgen/src/test/java/org/apache/logging/log4j/docgen/processor/DocGenProcessorTest.java @@ -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 + * + * 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.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaCompiler.CompilationTask; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; +import org.apache.logging.log4j.docgen.xsd.SchemaGenerator; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.xmlunit.assertj3.XmlAssert; + +public class DocGenProcessorTest { + + static Stream descriptorGenerationSucceeds() { + return Stream.of("v2", "v3"); + } + + @ParameterizedTest + @MethodSource + void descriptorGenerationSucceeds(final String version) { + final Path basePath = Paths.get(System.getProperty("basedir", ".")); + final Path schema = basePath.resolve("target/generated-site/resources/xsd/plugins-0.1.0.xsd"); + final URL expected = SchemaGenerator.class.getResource("/expected/processor/META-INF/log4j/plugins.xml"); + final Path actual = assertDoesNotThrow(() -> generateDescriptor(version)); + XmlAssert.assertThat(actual) + .isValidAgainst(schema) + .and(expected) + .ignoreComments() + .ignoreWhitespace() + .areIdentical(); + } + + private static Path generateDescriptor(final String version) throws Exception { + final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + final DiagnosticCollector ds = new DiagnosticCollector<>(); + final StandardJavaFileManager fileManager = + compiler.getStandardFileManager(null, Locale.ROOT, StandardCharsets.UTF_8); + + final Path basePath = Paths.get(System.getProperty("basedir", ".")); + final Path sourcePath = Paths.get( + DocGenProcessorTest.class.getResource("/processor/" + version).toURI()); + final Iterable sources; + try (final Stream files = Files.walk(sourcePath)) { + sources = fileManager.getJavaFileObjects( + files.filter(Files::isRegularFile).toArray(Path[]::new)); + } + + final Path destPath = basePath.resolve("target/test-site/processor/" + version); + Files.createDirectories(destPath); + fileManager.setLocationFromPaths(StandardLocation.CLASS_OUTPUT, Set.of(destPath)); + + final CompilationTask task = compiler.getTask( + null, + fileManager, + ds, + Arrays.asList("-proc:only", "-processor", DocGenProcessor.class.getName()), + null, + sources); + task.call(); + + final List warnings = ds.getDiagnostics().stream() + .filter(d -> d.getKind() != Diagnostic.Kind.NOTE) + .map(d -> d.getMessage(Locale.ROOT)) + .collect(Collectors.toList()); + assertThat(warnings).isEmpty(); + + final Path descriptor = destPath.resolve("META-INF/log4j/plugins.xml"); + assertThat(descriptor).isNotEmptyFile(); + return descriptor; + } +} diff --git a/log4j-docgen/src/test/resources/expected/processor/META-INF/log4j/plugins.xml b/log4j-docgen/src/test/resources/expected/processor/META-INF/log4j/plugins.xml new file mode 100644 index 00000000..274d7431 --- /dev/null +++ b/log4j-docgen/src/test/resources/expected/processor/META-INF/log4j/plugins.xml @@ -0,0 +1,182 @@ + + + + + + + + Makes things go boom! + + + A second choice. + + + Value C. + + + Value D. + + + A very important enum. + + + + + + example.AbstractAppender + example.Appender + example.BaseAppender + java.lang.Object + + + + An attribute of type `double`. + + + An attribute whose name differs from the field name. + + + A `boolean` attribute with annotated type. + + + A `byte` attribute with annotated parameter. + + + A `char` attribute. + + + An attribute that is an enumeration annotated on type. + + + An attribute of type `float`. + + + An `int` attribute. + + + A `long` attribute annotated on type. + + + A `short` attribute annotated on type. + + + A `String` attribute. + + + + + An element that is not an interface with annotated parameter. + + + An element with an annotated type. + + + An element with multiplicity n with annotated setter. + + + An element with multiplicity 1. + + + A collection element. + + + A set of layouts + + + A setter with a varargs type. + + + Example plugin + +This is an example plugin. +It has the following characteristics: + +. Plugin name: `MyPlugin`, +. Namespace: default (i.e. `Core`). + +It also implements: + +* xref:Appender.adoc[], +* xref:BaseAppender.adoc[] + + + + example.Layout + java.lang.Object + + + + A `boolean` attribute. + + + A `byte` attribute. + + + A `char` attribute. + + + A `double` attribute. + + + An `enum` attribute. + + + A `float` attribute. + + + An `int` attribute. + + + A `long` attribute. + + + An attribute with overwritten name. + + + A `short` attribute. + + + A xref:String.adoc[] attribute. + + + + + An element with multiplicity `n`. + + + An element with multiplicity `1`. + + + Example plugin without a builder. + + + + + An example of base abstract class. + + + Extended interface that also allows to do `baz`. + + + Base interface for appenders. + + + Formats messages. + + + diff --git a/log4j-docgen/src/test/it/example/JavadocExample.java b/log4j-docgen/src/test/resources/processor/asciidoc/example/JavadocExample.java similarity index 100% rename from log4j-docgen/src/test/it/example/JavadocExample.java rename to log4j-docgen/src/test/resources/processor/asciidoc/example/JavadocExample.java diff --git a/log4j-docgen/src/test/resources/processor/v2/example/AbstractAppender.java b/log4j-docgen/src/test/resources/processor/v2/example/AbstractAppender.java new file mode 100644 index 00000000..61f1122e --- /dev/null +++ b/log4j-docgen/src/test/resources/processor/v2/example/AbstractAppender.java @@ -0,0 +1,23 @@ +/* + * 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; + +/** + * An example of base abstract class. + */ +public abstract class AbstractAppender implements BaseAppender { +} diff --git a/log4j-docgen/src/test/resources/processor/v2/example/Appender.java b/log4j-docgen/src/test/resources/processor/v2/example/Appender.java new file mode 100644 index 00000000..a2045ae7 --- /dev/null +++ b/log4j-docgen/src/test/resources/processor/v2/example/Appender.java @@ -0,0 +1,23 @@ +/* + * 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; + +/** + * Extended interface that also allows to do {@code baz}. + */ +public interface Appender extends BaseAppender { +} diff --git a/log4j-docgen/src/test/resources/processor/v2/example/BaseAppender.java b/log4j-docgen/src/test/resources/processor/v2/example/BaseAppender.java new file mode 100644 index 00000000..e405fec8 --- /dev/null +++ b/log4j-docgen/src/test/resources/processor/v2/example/BaseAppender.java @@ -0,0 +1,23 @@ +/* + * 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; + +/** + * Base interface for appenders. + */ +public interface BaseAppender { +} diff --git a/log4j-docgen/src/test/resources/processor/v2/example/Filter.java b/log4j-docgen/src/test/resources/processor/v2/example/Filter.java new file mode 100644 index 00000000..6250ce21 --- /dev/null +++ b/log4j-docgen/src/test/resources/processor/v2/example/Filter.java @@ -0,0 +1,23 @@ +/* + * 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; + +/** + * Filters messages. + */ +public interface Filter { +} diff --git a/log4j-docgen/src/test/resources/processor/v2/example/Layout.java b/log4j-docgen/src/test/resources/processor/v2/example/Layout.java new file mode 100644 index 00000000..5884c6da --- /dev/null +++ b/log4j-docgen/src/test/resources/processor/v2/example/Layout.java @@ -0,0 +1,23 @@ +/* + * 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; + +/** + * Formats messages. + */ +public interface Layout { +} diff --git a/log4j-docgen/src/test/resources/processor/v2/example/MyAppender.java b/log4j-docgen/src/test/resources/processor/v2/example/MyAppender.java new file mode 100644 index 00000000..f99d07fb --- /dev/null +++ b/log4j-docgen/src/test/resources/processor/v2/example/MyAppender.java @@ -0,0 +1,188 @@ +/* + * 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; + +import java.util.List; +import java.util.Set; +import javax.lang.model.element.TypeElement; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; + +/** + * Example plugin + *

+ * This is an example plugin. It has the following characteristics: + *

+ *
    + *
  1. Plugin name: {@code MyPlugin},
  2. + *
  3. Namespace: default (i.e. {@code Core}).
  4. + *
+ *

+ * It also implements: + *

+ *
    + *
  • {@link Appender},
  • + *
  • {@link BaseAppender}
  • + *
+ */ +@Plugin(name = "MyAppender", category = "namespace") +public final class MyAppender extends AbstractAppender implements Appender { + + /** + * Parent builder with some private fields that are not returned by + * {@link javax.lang.model.util.Elements#getAllMembers(TypeElement)}. + */ + public static class ParentBuilder { + + /** + * A {@code char} attribute. + */ + @PluginBuilderAttribute + private char charAtt = 'L'; + + /** + * An {@code int} attribute. + */ + @PluginBuilderAttribute + private int intAtt = 4242; + + /** + * An element with multiplicity 1. + */ + @PluginElement("layout") + private Layout layout; + } + + public static final class Builder extends ParentBuilder + implements org.apache.logging.log4j.plugins.util.Builder { + + /** + * A {@code short} attribute annotated on type. + */ + private @PluginBuilderAttribute short shortAtt = 42; + + /** + * A {@code long} attribute annotated on type. + */ + private @PluginBuilderAttribute long longAtt = 424242L; + + /** + * A {@code String} attribute. + */ + @PluginBuilderAttribute + @Required + private String stringAtt; + + /** + * An attribute whose name differs from the field name. + */ + @PluginBuilderAttribute("anotherName") + private String origName; + + /** + * An attribute that is an enumeration annotated on type. + */ + private @PluginBuilderAttribute MyEnum enumAtt; + + /** + * An attribute of type {@code float}. + */ + private @PluginBuilderAttribute float floatAtt; + + /** + * An attribute of type {@code double}. + */ + private @PluginBuilderAttribute double aDouble; + + private Object notAnAttribute; + + /** + * A collection element. + */ + @PluginElement("appenderList") + private List appenderList; + + /** + * A set of layouts + */ + @PluginElement("layoutSet") + private LayoutSet layoutSet; + + /** + * A {@code boolean} attribute with annotated type. + */ + public Builder setBooleanAtt(final @PluginBuilderAttribute boolean booleanAtt) { + return this; + } + + /** + * A {@code byte} attribute with annotated parameter. + */ + public Builder setByteAtt(@PluginBuilderAttribute final byte byteAtt) { + return this; + } + + /** + * An element with multiplicity n with annotated setter. + */ + public Builder setFilters(final @PluginElement("filters") Filter[] filters) { + return this; + } + + /** + * An element that is not an interface with annotated parameter. + */ + public Builder setAbstractElement(@PluginElement("abstractAppender") final AbstractAppender abstractAppender) { + return this; + } + + /** + * An element with an annotated type. + */ + public Builder setNestedAppender(final @PluginElement("nestedAppender") Appender nestedAppender) { + return this; + } + + /** + * A setter with a varargs type. + */ + public Builder setVarargs(@PluginElement("layouts") final Layout3... layouts) { + return this; + } + + @Override + public MyAppender build() { + return null; + } + } + + @PluginBuilderFactory + public static Builder newBuilder() { + return new Builder(); + } + + public static interface Appender2 {} + + public static interface Layout2 {} + + public static interface Layout3 {} + + public abstract static class LayoutSet implements Set {} +} diff --git a/log4j-docgen/src/test/resources/processor/v2/example/MyEnum.java b/log4j-docgen/src/test/resources/processor/v2/example/MyEnum.java new file mode 100644 index 00000000..4d7afa81 --- /dev/null +++ b/log4j-docgen/src/test/resources/processor/v2/example/MyEnum.java @@ -0,0 +1,39 @@ +/* + * 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; + +/** + * A very important enum. + */ +public enum MyEnum { + /** + * Makes things go boom! + */ + A, + /** + * A second choice. + */ + B, + /** + * Value C. + */ + C, + /** + * Value D. + */ + D; +} diff --git a/log4j-docgen/src/test/resources/processor/v2/example/MyOldLayout.java b/log4j-docgen/src/test/resources/processor/v2/example/MyOldLayout.java new file mode 100644 index 00000000..670b7faa --- /dev/null +++ b/log4j-docgen/src/test/resources/processor/v2/example/MyOldLayout.java @@ -0,0 +1,63 @@ +/* + * 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; + +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; + +/** + * Example plugin without a builder. + */ +@Plugin(name = "MyLayout", category = "Core") +public final class MyOldLayout implements Layout { + + /** + * @param boolAttr A {@code boolean} attribute. + * @param byteAttr A {@code byte} attribute. + * @param charAttr A {@code char} attribute. + * @param doubleAttr A {@code double} attribute. + * @param floatAttr A {@code float} attribute. + * @param intAttr An {@code int} attribute. + * @param longAttr A {@code long} attribute. + * @param shortAttr A {@code short} attribute. + * @param stringAttr A {@link String} attribute. + * @param origName An attribute with overwritten name. + * @param enumAttr An {@code enum} attribute. + * @param nestedLayout An element with multiplicity {@code 1}. + * @param filters An element with multiplicity {@code n}. + */ + @PluginFactory + public static MyOldLayout newLayout( + final @PluginAttribute(value = "boolAttr", defaultBoolean = false) boolean boolAttr, + final @PluginAttribute(value = "byteAttr", defaultByte = 'L') byte byteAttr, + final @PluginAttribute(value = "charAttr", defaultChar = 'L') char charAttr, + final @PluginAttribute(value = "doubleAttr", defaultDouble = 42.0) double doubleAttr, + final @PluginAttribute(value = "floatAttr", defaultFloat = 42.0f) float floatAttr, + final @PluginAttribute(value = "intAttr", defaultInt = 424242) int intAttr, + final @PluginAttribute(value = "longAttr", defaultLong = 42424242L) long longAttr, + final @PluginAttribute(value = "shortAttr", defaultShort = 4242) short shortAttr, + final @PluginAttribute("stringAttr") @Required String stringAttr, + final @PluginAttribute("otherName") String origName, + final @PluginAttribute("enumAttr") MyEnum enumAttr, + final @PluginElement("nestedLayout") Layout nestedLayout, + final @PluginElement("filters") Filter[] filters) { + return null; + } +} diff --git a/log4j-docgen/src/test/resources/processor/v3/example/AbstractAppender.java b/log4j-docgen/src/test/resources/processor/v3/example/AbstractAppender.java new file mode 100644 index 00000000..61f1122e --- /dev/null +++ b/log4j-docgen/src/test/resources/processor/v3/example/AbstractAppender.java @@ -0,0 +1,23 @@ +/* + * 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; + +/** + * An example of base abstract class. + */ +public abstract class AbstractAppender implements BaseAppender { +} diff --git a/log4j-docgen/src/test/resources/processor/v3/example/Appender.java b/log4j-docgen/src/test/resources/processor/v3/example/Appender.java new file mode 100644 index 00000000..a2045ae7 --- /dev/null +++ b/log4j-docgen/src/test/resources/processor/v3/example/Appender.java @@ -0,0 +1,23 @@ +/* + * 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; + +/** + * Extended interface that also allows to do {@code baz}. + */ +public interface Appender extends BaseAppender { +} diff --git a/log4j-docgen/src/test/resources/processor/v3/example/BaseAppender.java b/log4j-docgen/src/test/resources/processor/v3/example/BaseAppender.java new file mode 100644 index 00000000..e405fec8 --- /dev/null +++ b/log4j-docgen/src/test/resources/processor/v3/example/BaseAppender.java @@ -0,0 +1,23 @@ +/* + * 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; + +/** + * Base interface for appenders. + */ +public interface BaseAppender { +} diff --git a/log4j-docgen/src/test/resources/processor/v3/example/Filter.java b/log4j-docgen/src/test/resources/processor/v3/example/Filter.java new file mode 100644 index 00000000..6250ce21 --- /dev/null +++ b/log4j-docgen/src/test/resources/processor/v3/example/Filter.java @@ -0,0 +1,23 @@ +/* + * 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; + +/** + * Filters messages. + */ +public interface Filter { +} diff --git a/log4j-docgen/src/test/resources/processor/v3/example/Layout.java b/log4j-docgen/src/test/resources/processor/v3/example/Layout.java new file mode 100644 index 00000000..5884c6da --- /dev/null +++ b/log4j-docgen/src/test/resources/processor/v3/example/Layout.java @@ -0,0 +1,23 @@ +/* + * 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; + +/** + * Formats messages. + */ +public interface Layout { +} diff --git a/log4j-docgen/src/test/resources/processor/v3/example/MyAppender.java b/log4j-docgen/src/test/resources/processor/v3/example/MyAppender.java new file mode 100644 index 00000000..e6a36c2e --- /dev/null +++ b/log4j-docgen/src/test/resources/processor/v3/example/MyAppender.java @@ -0,0 +1,191 @@ +/* + * 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; + +import java.util.List; +import java.util.Set; +import javax.lang.model.element.TypeElement; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; + +/** + * Example plugin + *

+ * This is an example plugin. It has the following characteristics: + *

+ *
    + *
  1. Plugin name: {@code MyPlugin},
  2. + *
  3. Namespace: default (i.e. {@code Core}).
  4. + *
+ *

+ * It also implements: + *

+ *
    + *
  • {@link Appender},
  • + *
  • {@link BaseAppender}
  • + *
+ */ +@Plugin +@Namespace("namespace") +public final class MyAppender extends AbstractAppender implements Appender { + + /** + * Parent builder with some private fields that are not returned by + * {@link javax.lang.model.util.Elements#getAllMembers(TypeElement)}. + */ + public static class ParentBuilder { + + /** + * A {@code char} attribute. + */ + @PluginBuilderAttribute + private char charAtt = 'L'; + + /** + * An {@code int} attribute. + */ + @PluginBuilderAttribute + private int intAtt = 4242; + + /** + * An element with multiplicity 1. + */ + @PluginElement + private Layout layout; + } + + public static final class Builder extends ParentBuilder + implements org.apache.logging.log4j.plugins.util.Builder { + + /** + * A {@code short} attribute annotated on type. + */ + private @PluginBuilderAttribute short shortAtt = 42; + + /** + * A {@code long} attribute annotated on type. + */ + private @PluginBuilderAttribute long longAtt = 424242L; + + /** + * A {@code String} attribute. + */ + @PluginBuilderAttribute + @Required + private String stringAtt; + + /** + * An attribute whose name differs from the field name. + */ + @PluginBuilderAttribute("anotherName") + private String origName; + + /** + * An attribute that is an enumeration annotated on type. + */ + private @PluginBuilderAttribute MyEnum enumAtt; + + /** + * An attribute of type {@code float}. + */ + private @PluginBuilderAttribute float floatAtt; + + /** + * An attribute of type {@code double}. + */ + private @PluginBuilderAttribute double aDouble; + + private Object notAnAttribute; + + /** + * A collection element. + */ + @PluginElement + private List appenderList; + + /** + * A set of layouts + */ + @PluginElement + private LayoutSet layoutSet; + + /** + * A {@code boolean} attribute with annotated type. + */ + public Builder setBooleanAtt(final @PluginBuilderAttribute boolean booleanAtt) { + return this; + } + + /** + * A {@code byte} attribute with annotated parameter. + */ + public Builder setByteAtt(@PluginBuilderAttribute final byte byteAtt) { + return this; + } + + /** + * An element with multiplicity n with annotated setter. + */ + @PluginElement + public Builder setFilters(final Filter[] filters) { + return this; + } + + /** + * An element that is not an interface with annotated parameter. + */ + public Builder setAbstractElement(@PluginElement final AbstractAppender abstractAppender) { + return this; + } + + /** + * An element with an annotated type. + */ + public Builder setNestedAppender(final @PluginElement Appender nestedAppender) { + return this; + } + + /** + * A setter with a varargs type. + */ + public Builder setVarargs(@PluginElement final Layout3... layouts) { + return this; + } + + @Override + public MyAppender build() { + return null; + } + } + + @PluginFactory + public static Builder newBuilder() { + return new Builder(); + } + + public static interface Appender2 {} + + public static interface Layout2 {} + + public static interface Layout3 {} + + public abstract static class LayoutSet implements Set {} +} diff --git a/log4j-docgen/src/test/resources/processor/v3/example/MyEnum.java b/log4j-docgen/src/test/resources/processor/v3/example/MyEnum.java new file mode 100644 index 00000000..4d7afa81 --- /dev/null +++ b/log4j-docgen/src/test/resources/processor/v3/example/MyEnum.java @@ -0,0 +1,39 @@ +/* + * 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; + +/** + * A very important enum. + */ +public enum MyEnum { + /** + * Makes things go boom! + */ + A, + /** + * A second choice. + */ + B, + /** + * Value C. + */ + C, + /** + * Value D. + */ + D; +} diff --git a/log4j-docgen/src/test/resources/processor/v3/example/MyOldLayout.java b/log4j-docgen/src/test/resources/processor/v3/example/MyOldLayout.java new file mode 100644 index 00000000..41b08c73 --- /dev/null +++ b/log4j-docgen/src/test/resources/processor/v3/example/MyOldLayout.java @@ -0,0 +1,63 @@ +/* + * 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; + +import org.apache.logging.log4j.plugins.Factory; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.validation.constraints.Required; + +/** + * Example plugin without a builder. + */ +@Plugin("MyLayout") +public final class MyOldLayout implements Layout { + + /** + * @param boolAttr A {@code boolean} attribute. + * @param byteAttr A {@code byte} attribute. + * @param charAttr A {@code char} attribute. + * @param doubleAttr A {@code double} attribute. + * @param floatAttr A {@code float} attribute. + * @param intAttr An {@code int} attribute. + * @param longAttr A {@code long} attribute. + * @param shortAttr A {@code short} attribute. + * @param stringAttr A {@link String} attribute. + * @param origName An attribute with overwritten name. + * @param enumAttr An {@code enum} attribute. + * @param nestedLayout An element with multiplicity {@code 1}. + * @param filters An element with multiplicity {@code n}. + */ + @Factory + public static MyOldLayout newLayout( + final @PluginAttribute(defaultBoolean = false) boolean boolAttr, + final @PluginAttribute(defaultByte = 'L') byte byteAttr, + final @PluginAttribute(defaultChar = 'L') char charAttr, + final @PluginAttribute(defaultDouble = 42.0) double doubleAttr, + final @PluginAttribute(defaultFloat = 42.0f) float floatAttr, + final @PluginAttribute(defaultInt = 424242) int intAttr, + final @PluginAttribute(defaultLong = 42424242L) long longAttr, + final @PluginAttribute(defaultShort = 4242) short shortAttr, + final @PluginAttribute @Required String stringAttr, + final @PluginAttribute("otherName") String origName, + final @PluginAttribute MyEnum enumAttr, + final @PluginElement Layout nestedLayout, + final @PluginElement Filter[] filters) { + return null; + } +} diff --git a/log4j-tools-parent/pom.xml b/log4j-tools-parent/pom.xml index dff618b3..4e0ecf33 100644 --- a/log4j-tools-parent/pom.xml +++ b/log4j-tools-parent/pom.xml @@ -38,7 +38,6 @@ 2.3.32 1.0.5 5.10.2 - 3.0.0-beta1 2.1.2 2.9.1 @@ -53,14 +52,6 @@ - - org.apache.logging.log4j - log4j-bom - ${log4j-bom.version} - pom - import - - org.junit junit-bom