vy commented on code in PR #88:
URL: 
https://github.com/apache/logging-log4j-tools/pull/88#discussion_r1426428027


##########
log4j-docgen/src/main/mdo/plugins.xml:
##########
@@ -0,0 +1,306 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<model xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xmlns="http://codehaus-plexus.github.io/MODELLO/2.0.0";
+       xsi:schemaLocation="http://codehaus-plexus.github.io/MODELLO/2.0.0 
https://codehaus-plexus.github.io/modello/xsd/modello-2.0.0.xsd";
+       xml.namespace="https://logging.apache.org/log4j/plugins";>
+    <id>plugins</id>
+    <name>PluginBundle</name>
+    <description>Documents a bundle of Log4j plugins.</description>
+    <versionDefinition>
+        <type>field</type>
+        <value>schemaVersion</value>
+    </versionDefinition>
+    <defaults>
+        <default>
+            <key>package</key>
+            <value>org.apache.logging.log4j.docgen.model</value>
+        </default>
+    </defaults>
+
+    <classes>
+        <class rootElement="true">
+            <name>PluginBundle</name>
+            <description>Documents a bundle of Log4j plugins.</description>
+            <fields>
+                <field xml.attribute="true" xml.tagName="version">
+                    <name>schemaVersion</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>The version of the schema used by the XML 
document.</description>
+                </field>
+                <field>
+                    <name>groupId</name>
+                    <type>String</type>
+                    <description>The group id of the bundle.</description>
+                </field>
+                <field>
+                    <name>artifactId</name>
+                    <type>String</type>
+                    <description>The artifact id of the bundle.</description>
+                </field>
+                <field>
+                    <name>version</name>
+                    <type>String</type>
+                    <description>The version of the bundle.</description>
+                </field>
+                <field>
+                    <name>description</name>
+                    <association>
+                        <type>Description</type>
+                    </association>
+                    <description>Description of the bundle.</description>
+                </field>
+                <field>
+                    <name>enums</name>
+                    <association>
+                        <type>EnumType</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>A list of all enums used in 
properties.</description>
+                </field>
+                <field>
+                    <name>plugins</name>
+                    <association>
+                        <type>PluginEntry</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>A list of all plugins in the 
bundle.</description>
+                </field>
+            </fields>
+        </class>
+
+        <class xml.tagName="plugin">
+            <name>PluginEntry</name>
+            <description>Describes the properties available to 
plugins.</description>
+            <fields>
+                <field xml.attribute="true">
+                    <name>name</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>The unique name of this plugin.</description>
+                </field>
+                <field xml.attribute="true">
+                    <name>namespace</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>The namespace of the plugin.</description>
+                </field>
+                <field xml.attribute="true">
+                    <name>className</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>Fully qualified name of the class 
implementing the plugin.</description>
+                </field>
+                <field xml.attribute="true">
+                    <name>deferChildren</name>
+                    <type>boolean</type>
+                    <defaultValue>false</defaultValue>
+                </field>
+                <field>
+                    <name>description</name>
+                    <association>
+                        <type>Description</type>
+                    </association>
+                    <description>Description of the plugin.</description>
+                </field>
+                <field>
+                    <name>keys</name>
+                    <required>true</required>
+                    <association>
+                        <type>String</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>
+                        The different keys (e.g. XML tag names) under which 
the plugin can be used.
+                    </description>
+                </field>
+                <!-- These are necessary to find all possible nested 
components -->
+                <field>
+                    <name>supertypes</name>
+                    <association>
+                        <type>String</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>
+                        List of all the supertypes of a plugin.
+                    </description>
+                </field>
+                <field>
+                    <name>attributes</name>
+                    <required>true</required>
+                    <association>
+                        <type>PluginAttribute</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>List of **all** the configuration attributes 
supported</description>
+                </field>
+                <field>
+                    <name>elements</name>
+                    <required>true</required>
+                    <association>
+                        <type>PluginElement</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>List of **all** possible nested 
components.</description>
+                </field>

Review Comment:
   Attribute-vs-element distinction feels to me an implementation detail. That 
is, from the pov of the user, they are all configuration properties of the 
plugin. As a matter of fact, JSON-, YAML-, and `.properties`-formatted 
configurations cannot even accommodate the _attribute_ concept, for them, every 
property is an _element_. Do we really need two different types, i.e., 
attribute & element? Can't we use a single property/attribute/element/etc. to 
refer the both?



##########
log4j-docgen/src/main/mdo/plugins.xml:
##########
@@ -0,0 +1,306 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<model xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xmlns="http://codehaus-plexus.github.io/MODELLO/2.0.0";
+       xsi:schemaLocation="http://codehaus-plexus.github.io/MODELLO/2.0.0 
https://codehaus-plexus.github.io/modello/xsd/modello-2.0.0.xsd";
+       xml.namespace="https://logging.apache.org/log4j/plugins";>
+    <id>plugins</id>
+    <name>PluginBundle</name>
+    <description>Documents a bundle of Log4j plugins.</description>
+    <versionDefinition>
+        <type>field</type>
+        <value>schemaVersion</value>
+    </versionDefinition>
+    <defaults>
+        <default>
+            <key>package</key>
+            <value>org.apache.logging.log4j.docgen.model</value>
+        </default>
+    </defaults>
+
+    <classes>
+        <class rootElement="true">
+            <name>PluginBundle</name>
+            <description>Documents a bundle of Log4j plugins.</description>

Review Comment:
   What is _"a **bundle** of Log4j plugins"_? Does the _"bundle"_ have a 
particular meaning in this context? Or does it simply refer to a collection of 
Log4j plugins provided by a particular JAR?



##########
log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java:
##########
@@ -0,0 +1,351 @@
+/*
+ * 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.internal;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import javax.xml.XMLConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.apache.logging.log4j.docgen.SchemaGenerator;
+import org.apache.logging.log4j.docgen.model.Description;
+import org.apache.logging.log4j.docgen.model.EnumType;
+import org.apache.logging.log4j.docgen.model.EnumValue;
+import org.apache.logging.log4j.docgen.model.PluginAttribute;
+import org.apache.logging.log4j.docgen.model.PluginBundle;
+import org.apache.logging.log4j.docgen.model.PluginElement;
+import org.apache.logging.log4j.docgen.model.PluginEntry;
+import org.apache.logging.log4j.docgen.model.io.stax.PluginBundleStaxReader;
+
+public class DefaultSchemaGenerator implements SchemaGenerator {
+
+    private static final String PLUGIN_NAMESPACE = "Core";
+    private static final String LOG4J_PREFIX = "log4j";
+    private static final String LOG4J_NAMESPACE = 
"http://logging.apache.org/log4j/2.0/config";;
+    private static final String XSD_NAMESPACE = 
XMLConstants.W3C_XML_SCHEMA_NS_URI;
+    private static final String MULTIPLICITY_UNBOUNDED = "*";
+
+    @Override
+    public void generateSchema(Collection<PluginBundle> bundles, 
XMLStreamWriter writer) throws XMLStreamException {
+        try {
+            final PluginBundle configurationBundle =
+                    new 
PluginBundleStaxReader().read(getClass().getResourceAsStream("configuration.xml"));
+            final Set<PluginBundle> extendedBundles = new HashSet<>(bundles);
+            extendedBundles.add(configurationBundle);
+            final TypeLookup lookup = new TypeLookup(extendedBundles);
+            writeSchema(lookup, writer);
+        } catch (IOException e) {
+            throw new XMLStreamException(e);
+        }
+    }
+
+    private void writeSchema(final TypeLookup lookup, final XMLStreamWriter 
writer) throws XMLStreamException {
+        writer.writeStartDocument("UTF-8", "1.0");
+        writer.setDefaultNamespace(XSD_NAMESPACE);
+        writer.writeStartElement(XSD_NAMESPACE, "schema");
+        writer.writeDefaultNamespace(XSD_NAMESPACE);
+        writer.writeNamespace(LOG4J_PREFIX, LOG4J_NAMESPACE);
+        writer.writeAttribute("elementFormDefault", "qualified");
+        writer.writeAttribute("targetNamespace", LOG4J_NAMESPACE);
+
+        // The root element
+        writer.writeEmptyElement(XSD_NAMESPACE, "element");
+        writer.writeAttribute("type", LOG4J_PREFIX + 
":org.apache.logging.log4j.core.config.Configuration");
+        writer.writeAttribute("name", "Configuration");
+
+        // Simple types
+        writeEnumTypes(lookup.getRequiredEnums(), writer);
+
+        // Plugins
+        writePluginEntries(lookup, writer);
+
+        // Interfaces
+        writeGroups(lookup, lookup.getRequiredGroups(), writer);
+
+        writer.writeEndElement();
+        writer.writeEndDocument();
+    }
+
+    private void writePluginEntries(final TypeLookup lookup, final 
XMLStreamWriter writer) throws XMLStreamException {
+        for (final PluginEntry entry : lookup.getPlugins()) {
+            writePluginEntry(lookup, entry, writer);
+        }
+    }
+
+    private void writePluginEntry(final TypeLookup lookup, final PluginEntry 
entry, final XMLStreamWriter writer)
+            throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "complexType");
+        writer.writeAttribute("name", entry.getClassName());
+
+        writeDocumentation(entry.getDescription(), writer);
+
+        final boolean hasSimpleContent = entry.getElements().isEmpty();
+
+        if (!hasSimpleContent) {
+            writer.writeStartElement(XSD_NAMESPACE, "sequence");
+            for (final PluginElement element : entry.getElements()) {
+                writePluginElement(lookup, element, writer);
+            }
+            writer.writeEndElement();
+        }
+
+        for (final PluginAttribute attribute : entry.getAttributes()) {
+            writePluginAttribute(lookup, attribute, writer);
+        }
+
+        writer.writeEndElement();
+    }
+
+    private void writePluginElement(final TypeLookup lookup, final 
PluginElement element, final XMLStreamWriter writer)
+            throws XMLStreamException {
+        final String type = element.getType();
+        final String xmlType = lookup.getXmlType(type, false);
+        final PluginEntry entry = lookup.getPluginByType(type);
+        /*
+         * If some plugins have `type` as super type or if the type is unknown,
+         * we use a <group> element.
+         */
+        if (lookup.getPluginsByGroup(type) != null || entry == null) {
+            writer.writeStartElement(XSD_NAMESPACE, "group");
+            writer.writeAttribute("ref", xmlType);
+            writeMultiplicity(element.isRequired(), element.getMultiplicity(), 
writer);
+            writeDocumentation(element.getDescription(), writer);
+            writer.writeEndElement();
+        } else {
+            final Set<String> actualKeys = new TreeSet<>(entry.getKeys());
+            actualKeys.add(entry.getName());
+            for (final String key : actualKeys) {
+                writer.writeStartElement(XSD_NAMESPACE, "element");
+                writer.writeAttribute("name", key);
+                writer.writeAttribute("type", xmlType);
+                writeMultiplicity(element.isRequired(), 
element.getMultiplicity(), writer);
+                writeDocumentation(element.getDescription(), writer);
+                writer.writeEndElement();
+            }
+        }
+    }
+
+    private void writeMultiplicity(final boolean required, final String 
multiplicity, final XMLStreamWriter writer)
+            throws XMLStreamException {
+        if (!required) {
+            writer.writeAttribute("minOccurs", "0");
+        }
+        if (MULTIPLICITY_UNBOUNDED.equals(multiplicity)) {
+            writer.writeAttribute("maxOccurs", "unbounded");
+        }
+    }
+
+    private void writePluginAttribute(
+            final TypeLookup lookup, final PluginAttribute attribute, final 
XMLStreamWriter writer)
+            throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "attribute");
+        writer.writeAttribute("name", attribute.getName());
+        writer.writeAttribute("type", lookup.getXmlType(attribute.getType(), 
true));
+        final Description description = attribute.getDescription();
+        if (description != null) {
+            writeDocumentation(description, writer);
+        }
+        writer.writeEndElement();
+    }
+
+    private void writeDocumentation(final Description description, final 
XMLStreamWriter writer)
+            throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "annotation");
+        writer.writeStartElement(XSD_NAMESPACE, "documentation");
+        writer.writeCharacters(normalizeAsciidoc(description.getText()));
+        writer.writeEndElement();
+        writer.writeEndElement();
+    }
+
+    private String normalizeAsciidoc(final String text) {
+        final StringBuilder sb = new StringBuilder();
+        for (final String line : text.split("\r?\n", -1)) {
+            sb.append(line.trim()).append('\n');
+        }
+        final int length = sb.length();
+        if (length > 0) {
+            sb.setLength(length - 1);
+        }
+        return sb.toString();
+    }
+
+    private void writeEnumTypes(final Collection<EnumType> types, final 
XMLStreamWriter writer)
+            throws XMLStreamException {
+        for (final EnumType type : types) {
+            writeEnumType(type, writer);
+        }
+    }
+
+    private void writeGroups(final TypeLookup lookup, final Collection<String> 
groups, final XMLStreamWriter writer)
+            throws XMLStreamException {
+        for (final String group : groups) {
+            writeGroup(
+                    lookup,
+                    group,
+                    
Optional.ofNullable(lookup.getPluginsByGroup(group)).orElse(Collections.emptySet()),
+                    writer);
+        }
+    }
+
+    private void writeGroup(

Review Comment:
   This method (and some others) can be `private static`.



##########
log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java:
##########
@@ -0,0 +1,351 @@
+/*
+ * 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.internal;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import javax.xml.XMLConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.apache.logging.log4j.docgen.SchemaGenerator;
+import org.apache.logging.log4j.docgen.model.Description;
+import org.apache.logging.log4j.docgen.model.EnumType;
+import org.apache.logging.log4j.docgen.model.EnumValue;
+import org.apache.logging.log4j.docgen.model.PluginAttribute;
+import org.apache.logging.log4j.docgen.model.PluginBundle;
+import org.apache.logging.log4j.docgen.model.PluginElement;
+import org.apache.logging.log4j.docgen.model.PluginEntry;
+import org.apache.logging.log4j.docgen.model.io.stax.PluginBundleStaxReader;
+
+public class DefaultSchemaGenerator implements SchemaGenerator {
+
+    private static final String PLUGIN_NAMESPACE = "Core";
+    private static final String LOG4J_PREFIX = "log4j";
+    private static final String LOG4J_NAMESPACE = 
"http://logging.apache.org/log4j/2.0/config";;
+    private static final String XSD_NAMESPACE = 
XMLConstants.W3C_XML_SCHEMA_NS_URI;
+    private static final String MULTIPLICITY_UNBOUNDED = "*";
+
+    @Override
+    public void generateSchema(Collection<PluginBundle> bundles, 
XMLStreamWriter writer) throws XMLStreamException {
+        try {
+            final PluginBundle configurationBundle =
+                    new 
PluginBundleStaxReader().read(getClass().getResourceAsStream("configuration.xml"));
+            final Set<PluginBundle> extendedBundles = new HashSet<>(bundles);
+            extendedBundles.add(configurationBundle);
+            final TypeLookup lookup = new TypeLookup(extendedBundles);
+            writeSchema(lookup, writer);
+        } catch (IOException e) {
+            throw new XMLStreamException(e);
+        }
+    }
+
+    private void writeSchema(final TypeLookup lookup, final XMLStreamWriter 
writer) throws XMLStreamException {
+        writer.writeStartDocument("UTF-8", "1.0");
+        writer.setDefaultNamespace(XSD_NAMESPACE);
+        writer.writeStartElement(XSD_NAMESPACE, "schema");
+        writer.writeDefaultNamespace(XSD_NAMESPACE);
+        writer.writeNamespace(LOG4J_PREFIX, LOG4J_NAMESPACE);
+        writer.writeAttribute("elementFormDefault", "qualified");
+        writer.writeAttribute("targetNamespace", LOG4J_NAMESPACE);
+
+        // The root element
+        writer.writeEmptyElement(XSD_NAMESPACE, "element");
+        writer.writeAttribute("type", LOG4J_PREFIX + 
":org.apache.logging.log4j.core.config.Configuration");
+        writer.writeAttribute("name", "Configuration");
+
+        // Simple types
+        writeEnumTypes(lookup.getRequiredEnums(), writer);
+
+        // Plugins
+        writePluginEntries(lookup, writer);
+
+        // Interfaces
+        writeGroups(lookup, lookup.getRequiredGroups(), writer);
+
+        writer.writeEndElement();
+        writer.writeEndDocument();
+    }
+
+    private void writePluginEntries(final TypeLookup lookup, final 
XMLStreamWriter writer) throws XMLStreamException {
+        for (final PluginEntry entry : lookup.getPlugins()) {
+            writePluginEntry(lookup, entry, writer);
+        }
+    }
+
+    private void writePluginEntry(final TypeLookup lookup, final PluginEntry 
entry, final XMLStreamWriter writer)
+            throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "complexType");
+        writer.writeAttribute("name", entry.getClassName());
+
+        writeDocumentation(entry.getDescription(), writer);
+
+        final boolean hasSimpleContent = entry.getElements().isEmpty();
+
+        if (!hasSimpleContent) {
+            writer.writeStartElement(XSD_NAMESPACE, "sequence");
+            for (final PluginElement element : entry.getElements()) {
+                writePluginElement(lookup, element, writer);
+            }
+            writer.writeEndElement();
+        }
+
+        for (final PluginAttribute attribute : entry.getAttributes()) {
+            writePluginAttribute(lookup, attribute, writer);
+        }
+
+        writer.writeEndElement();
+    }
+
+    private void writePluginElement(final TypeLookup lookup, final 
PluginElement element, final XMLStreamWriter writer)
+            throws XMLStreamException {
+        final String type = element.getType();
+        final String xmlType = lookup.getXmlType(type, false);
+        final PluginEntry entry = lookup.getPluginByType(type);
+        /*
+         * If some plugins have `type` as super type or if the type is unknown,
+         * we use a <group> element.
+         */
+        if (lookup.getPluginsByGroup(type) != null || entry == null) {
+            writer.writeStartElement(XSD_NAMESPACE, "group");
+            writer.writeAttribute("ref", xmlType);
+            writeMultiplicity(element.isRequired(), element.getMultiplicity(), 
writer);
+            writeDocumentation(element.getDescription(), writer);
+            writer.writeEndElement();
+        } else {
+            final Set<String> actualKeys = new TreeSet<>(entry.getKeys());
+            actualKeys.add(entry.getName());
+            for (final String key : actualKeys) {
+                writer.writeStartElement(XSD_NAMESPACE, "element");
+                writer.writeAttribute("name", key);
+                writer.writeAttribute("type", xmlType);
+                writeMultiplicity(element.isRequired(), 
element.getMultiplicity(), writer);
+                writeDocumentation(element.getDescription(), writer);
+                writer.writeEndElement();
+            }
+        }
+    }
+
+    private void writeMultiplicity(final boolean required, final String 
multiplicity, final XMLStreamWriter writer)
+            throws XMLStreamException {
+        if (!required) {
+            writer.writeAttribute("minOccurs", "0");
+        }
+        if (MULTIPLICITY_UNBOUNDED.equals(multiplicity)) {
+            writer.writeAttribute("maxOccurs", "unbounded");
+        }
+    }
+
+    private void writePluginAttribute(
+            final TypeLookup lookup, final PluginAttribute attribute, final 
XMLStreamWriter writer)
+            throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "attribute");
+        writer.writeAttribute("name", attribute.getName());
+        writer.writeAttribute("type", lookup.getXmlType(attribute.getType(), 
true));
+        final Description description = attribute.getDescription();
+        if (description != null) {
+            writeDocumentation(description, writer);
+        }
+        writer.writeEndElement();
+    }
+
+    private void writeDocumentation(final Description description, final 
XMLStreamWriter writer)
+            throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "annotation");
+        writer.writeStartElement(XSD_NAMESPACE, "documentation");
+        writer.writeCharacters(normalizeAsciidoc(description.getText()));
+        writer.writeEndElement();
+        writer.writeEndElement();
+    }
+
+    private String normalizeAsciidoc(final String text) {
+        final StringBuilder sb = new StringBuilder();
+        for (final String line : text.split("\r?\n", -1)) {
+            sb.append(line.trim()).append('\n');
+        }
+        final int length = sb.length();
+        if (length > 0) {
+            sb.setLength(length - 1);
+        }
+        return sb.toString();
+    }
+
+    private void writeEnumTypes(final Collection<EnumType> types, final 
XMLStreamWriter writer)
+            throws XMLStreamException {
+        for (final EnumType type : types) {
+            writeEnumType(type, writer);
+        }
+    }
+
+    private void writeGroups(final TypeLookup lookup, final Collection<String> 
groups, final XMLStreamWriter writer)
+            throws XMLStreamException {
+        for (final String group : groups) {
+            writeGroup(
+                    lookup,
+                    group,
+                    
Optional.ofNullable(lookup.getPluginsByGroup(group)).orElse(Collections.emptySet()),
+                    writer);
+        }
+    }
+
+    private void writeGroup(
+            final TypeLookup lookup,
+            final String groups,
+            final Collection<PluginEntry> entries,
+            final XMLStreamWriter writer)
+            throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "group");
+        writer.writeAttribute("name", groups + ".group");
+        writer.writeStartElement(XSD_NAMESPACE, "choice");
+
+        for (final PluginEntry entry : entries) {
+            final Set<String> actualKeys = new TreeSet<>(entry.getKeys());
+            actualKeys.add(entry.getName());
+            for (final String key : actualKeys) {
+                writer.writeEmptyElement(XSD_NAMESPACE, "element");
+                writer.writeAttribute("name", key);
+                writer.writeAttribute("type", LOG4J_PREFIX + ":" + 
entry.getClassName());
+            }
+        }
+
+        writer.writeEndElement();
+        writer.writeEndElement();
+    }
+
+    private void writeEnumType(final EnumType type, final XMLStreamWriter 
writer) throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "simpleType");
+        writer.writeAttribute("name", type.getClassName());
+
+        writeDocumentation(type.getDescription(), writer);
+
+        writer.writeStartElement(XSD_NAMESPACE, "restriction");
+        writer.writeAttribute("base", "string");
+
+        for (final EnumValue value : type.getValues()) {
+            writeEnumValue(value, writer);
+        }
+
+        writer.writeEndElement();
+        writer.writeEndElement();
+    }
+
+    private void writeEnumValue(final EnumValue value, final XMLStreamWriter 
writer) throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "enumeration");
+        writer.writeAttribute("value", value.getName());
+
+        writeDocumentation(value, writer);
+
+        writer.writeEndElement();
+    }
+
+    private static class TypeLookup {
+
+        private final Map<String, EnumType> enumByName = new HashMap<>();
+        private final Map<String, PluginEntry> pluginsByName = new TreeMap<>();
+        private final Map<String, Set<PluginEntry>> pluginsByGroup = new 
HashMap<>();
+        private final Set<EnumType> requiredEnums = new 
TreeSet<>(Comparator.comparing(EnumType::getClassName));
+        private final Set<String> requiredGroups = new TreeSet<>();
+
+        public TypeLookup(final Collection<PluginBundle> bundles) {

Review Comment:
   ```suggestion
           private TypeLookup(final Collection<PluginBundle> bundles) {
   ```



##########
log4j-docgen/src/main/mdo/plugins.xml:
##########
@@ -0,0 +1,306 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<model xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xmlns="http://codehaus-plexus.github.io/MODELLO/2.0.0";
+       xsi:schemaLocation="http://codehaus-plexus.github.io/MODELLO/2.0.0 
https://codehaus-plexus.github.io/modello/xsd/modello-2.0.0.xsd";
+       xml.namespace="https://logging.apache.org/log4j/plugins";>
+    <id>plugins</id>
+    <name>PluginBundle</name>
+    <description>Documents a bundle of Log4j plugins.</description>
+    <versionDefinition>
+        <type>field</type>
+        <value>schemaVersion</value>
+    </versionDefinition>
+    <defaults>
+        <default>
+            <key>package</key>
+            <value>org.apache.logging.log4j.docgen.model</value>
+        </default>

Review Comment:
   What is the function of this configuration?



##########
log4j-docgen/src/main/mdo/plugins.xml:
##########
@@ -0,0 +1,306 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<model xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xmlns="http://codehaus-plexus.github.io/MODELLO/2.0.0";
+       xsi:schemaLocation="http://codehaus-plexus.github.io/MODELLO/2.0.0 
https://codehaus-plexus.github.io/modello/xsd/modello-2.0.0.xsd";
+       xml.namespace="https://logging.apache.org/log4j/plugins";>
+    <id>plugins</id>
+    <name>PluginBundle</name>
+    <description>Documents a bundle of Log4j plugins.</description>
+    <versionDefinition>
+        <type>field</type>
+        <value>schemaVersion</value>
+    </versionDefinition>
+    <defaults>
+        <default>
+            <key>package</key>
+            <value>org.apache.logging.log4j.docgen.model</value>
+        </default>
+    </defaults>
+
+    <classes>
+        <class rootElement="true">
+            <name>PluginBundle</name>
+            <description>Documents a bundle of Log4j plugins.</description>
+            <fields>
+                <field xml.attribute="true" xml.tagName="version">
+                    <name>schemaVersion</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>The version of the schema used by the XML 
document.</description>
+                </field>
+                <field>
+                    <name>groupId</name>
+                    <type>String</type>
+                    <description>The group id of the bundle.</description>
+                </field>
+                <field>
+                    <name>artifactId</name>
+                    <type>String</type>
+                    <description>The artifact id of the bundle.</description>
+                </field>
+                <field>
+                    <name>version</name>
+                    <type>String</type>
+                    <description>The version of the bundle.</description>
+                </field>
+                <field>
+                    <name>description</name>
+                    <association>
+                        <type>Description</type>
+                    </association>
+                    <description>Description of the bundle.</description>
+                </field>
+                <field>
+                    <name>enums</name>
+                    <association>
+                        <type>EnumType</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>A list of all enums used in 
properties.</description>
+                </field>
+                <field>
+                    <name>plugins</name>
+                    <association>
+                        <type>PluginEntry</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>A list of all plugins in the 
bundle.</description>
+                </field>
+            </fields>
+        </class>
+
+        <class xml.tagName="plugin">
+            <name>PluginEntry</name>
+            <description>Describes the properties available to 
plugins.</description>
+            <fields>
+                <field xml.attribute="true">
+                    <name>name</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>The unique name of this plugin.</description>
+                </field>
+                <field xml.attribute="true">
+                    <name>namespace</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>The namespace of the plugin.</description>
+                </field>
+                <field xml.attribute="true">
+                    <name>className</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>Fully qualified name of the class 
implementing the plugin.</description>
+                </field>
+                <field xml.attribute="true">
+                    <name>deferChildren</name>
+                    <type>boolean</type>
+                    <defaultValue>false</defaultValue>
+                </field>
+                <field>
+                    <name>description</name>
+                    <association>
+                        <type>Description</type>
+                    </association>
+                    <description>Description of the plugin.</description>
+                </field>
+                <field>
+                    <name>keys</name>
+                    <required>true</required>
+                    <association>
+                        <type>String</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>
+                        The different keys (e.g. XML tag names) under which 
the plugin can be used.
+                    </description>
+                </field>
+                <!-- These are necessary to find all possible nested 
components -->
+                <field>
+                    <name>supertypes</name>
+                    <association>
+                        <type>String</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>
+                        List of all the supertypes of a plugin.
+                    </description>
+                </field>
+                <field>
+                    <name>attributes</name>
+                    <required>true</required>
+                    <association>
+                        <type>PluginAttribute</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>List of **all** the configuration attributes 
supported</description>
+                </field>
+                <field>
+                    <name>elements</name>
+                    <required>true</required>
+                    <association>
+                        <type>PluginElement</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>List of **all** possible nested 
components.</description>
+                </field>
+            </fields>
+        </class>
+
+        <class xml.tagName="attribute">
+            <name>PluginAttribute</name>
+            <description>A scalar configuration value for the 
plugin.</description>
+            <fields>
+                <field xml.attribute="true">
+                    <name>name</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>The name of the property.</description>
+                </field>
+                <field xml.attribute="true">
+                    <name>type</name>
+                    <type>String</type>
+                    <defaultValue>java.lang.String</defaultValue>
+                    <description>The Java name of this attribute's type, e.g. 
`boolean`, `java.lang.String`,
+                        fully qualified name of an enum. The type must be an 
enum or must have a type converter.
+                    </description>
+                </field>
+                <field xml.attribute="true">
+                    <name>required</name>
+                    <type>boolean</type>
+                    <defaultValue>false</defaultValue>
+                    <description>If set to `true` the attribute is 
required.</description>
+                </field>
+                <field xml.attribute="true">
+                    <name>defaultValue</name>
+                    <type>String</type>
+                    <description>The default value of this attribute as 
string.</description>
+                </field>
+                <field xml.attribute="true">
+                    <name>defaultProperty</name>
+                    <type>String</type>
+                    <description>The Log4j property that contains the default 
value of this attribute.</description>
+                </field>
+                <field>
+                    <name>description</name>
+                    <association>
+                        <type>Description</type>
+                    </association>
+                    <description>A description of the property.</description>
+                </field>
+            </fields>
+        </class>
+
+        <class xml.tagName="enum" xsd.compositor="sequence">
+            <name>EnumType</name>
+            <description>Describes a Java enum type.</description>
+            <fields>
+                <field xml.attribute="true">
+                    <name>className</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>The Java class name of the enum.</description>
+                </field>
+                <field>
+                    <name>description</name>
+                    <association>
+                        <type>Description</type>
+                    </association>
+                    <description>An HTML description of the 
property.</description>
+                </field>
+                <field>
+                    <name>values</name>
+                    <required>true</required>
+                    <association xml.itemsStyle="flat">
+                        <type>EnumValue</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>The possible values of this 
enum.</description>
+                </field>
+            </fields>
+        </class>
+
+        <class xml.tagName="description">
+            <name>Description</name>
+            <description>General documentation tag.</description>
+            <fields>
+                <field xml.attribute="true">
+                    <name>format</name>
+                    <type>String</type>
+                    <defaultValue>asciidoc</defaultValue>
+                    <description>Format used by the documentation text. 
Currently it **must** be "asciidoc".

Review Comment:
   ```suggestion
                       <description>Format used by the documentation text. 
Currently it **must** be `asciidoc`.
   ```



##########
log4j-docgen/src/main/mdo/plugins.xml:
##########
@@ -0,0 +1,306 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<model xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xmlns="http://codehaus-plexus.github.io/MODELLO/2.0.0";
+       xsi:schemaLocation="http://codehaus-plexus.github.io/MODELLO/2.0.0 
https://codehaus-plexus.github.io/modello/xsd/modello-2.0.0.xsd";
+       xml.namespace="https://logging.apache.org/log4j/plugins";>
+    <id>plugins</id>
+    <name>PluginBundle</name>
+    <description>Documents a bundle of Log4j plugins.</description>
+    <versionDefinition>
+        <type>field</type>
+        <value>schemaVersion</value>
+    </versionDefinition>
+    <defaults>
+        <default>
+            <key>package</key>
+            <value>org.apache.logging.log4j.docgen.model</value>
+        </default>
+    </defaults>
+
+    <classes>
+        <class rootElement="true">
+            <name>PluginBundle</name>
+            <description>Documents a bundle of Log4j plugins.</description>
+            <fields>
+                <field xml.attribute="true" xml.tagName="version">
+                    <name>schemaVersion</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>The version of the schema used by the XML 
document.</description>
+                </field>
+                <field>
+                    <name>groupId</name>
+                    <type>String</type>
+                    <description>The group id of the bundle.</description>
+                </field>
+                <field>
+                    <name>artifactId</name>
+                    <type>String</type>
+                    <description>The artifact id of the bundle.</description>
+                </field>
+                <field>
+                    <name>version</name>
+                    <type>String</type>
+                    <description>The version of the bundle.</description>
+                </field>
+                <field>
+                    <name>description</name>
+                    <association>
+                        <type>Description</type>
+                    </association>
+                    <description>Description of the bundle.</description>
+                </field>
+                <field>
+                    <name>enums</name>
+                    <association>
+                        <type>EnumType</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>A list of all enums used in 
properties.</description>
+                </field>
+                <field>
+                    <name>plugins</name>
+                    <association>
+                        <type>PluginEntry</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>A list of all plugins in the 
bundle.</description>
+                </field>
+            </fields>
+        </class>
+
+        <class xml.tagName="plugin">
+            <name>PluginEntry</name>
+            <description>Describes the properties available to 
plugins.</description>
+            <fields>
+                <field xml.attribute="true">
+                    <name>name</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>The unique name of this plugin.</description>
+                </field>
+                <field xml.attribute="true">
+                    <name>namespace</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>The namespace of the plugin.</description>
+                </field>
+                <field xml.attribute="true">
+                    <name>className</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>Fully qualified name of the class 
implementing the plugin.</description>
+                </field>
+                <field xml.attribute="true">
+                    <name>deferChildren</name>
+                    <type>boolean</type>
+                    <defaultValue>false</defaultValue>
+                </field>
+                <field>
+                    <name>description</name>
+                    <association>
+                        <type>Description</type>
+                    </association>
+                    <description>Description of the plugin.</description>
+                </field>
+                <field>
+                    <name>keys</name>
+                    <required>true</required>
+                    <association>
+                        <type>String</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>
+                        The different keys (e.g. XML tag names) under which 
the plugin can be used.
+                    </description>
+                </field>
+                <!-- These are necessary to find all possible nested 
components -->
+                <field>
+                    <name>supertypes</name>
+                    <association>
+                        <type>String</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>
+                        List of all the supertypes of a plugin.
+                    </description>
+                </field>
+                <field>
+                    <name>attributes</name>
+                    <required>true</required>
+                    <association>
+                        <type>PluginAttribute</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>List of **all** the configuration attributes 
supported</description>
+                </field>
+                <field>
+                    <name>elements</name>
+                    <required>true</required>
+                    <association>
+                        <type>PluginElement</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>List of **all** possible nested 
components.</description>
+                </field>
+            </fields>
+        </class>
+
+        <class xml.tagName="attribute">
+            <name>PluginAttribute</name>
+            <description>A scalar configuration value for the 
plugin.</description>
+            <fields>
+                <field xml.attribute="true">
+                    <name>name</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>The name of the property.</description>
+                </field>
+                <field xml.attribute="true">
+                    <name>type</name>
+                    <type>String</type>
+                    <defaultValue>java.lang.String</defaultValue>
+                    <description>The Java name of this attribute's type, e.g. 
`boolean`, `java.lang.String`,
+                        fully qualified name of an enum. The type must be an 
enum or must have a type converter.
+                    </description>
+                </field>
+                <field xml.attribute="true">
+                    <name>required</name>
+                    <type>boolean</type>
+                    <defaultValue>false</defaultValue>
+                    <description>If set to `true` the attribute is 
required.</description>
+                </field>
+                <field xml.attribute="true">
+                    <name>defaultValue</name>
+                    <type>String</type>
+                    <description>The default value of this attribute as 
string.</description>
+                </field>
+                <field xml.attribute="true">
+                    <name>defaultProperty</name>
+                    <type>String</type>
+                    <description>The Log4j property that contains the default 
value of this attribute.</description>
+                </field>
+                <field>
+                    <name>description</name>
+                    <association>
+                        <type>Description</type>
+                    </association>
+                    <description>A description of the property.</description>
+                </field>
+            </fields>
+        </class>
+
+        <class xml.tagName="enum" xsd.compositor="sequence">
+            <name>EnumType</name>
+            <description>Describes a Java enum type.</description>
+            <fields>
+                <field xml.attribute="true">
+                    <name>className</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>The Java class name of the enum.</description>
+                </field>
+                <field>
+                    <name>description</name>
+                    <association>
+                        <type>Description</type>
+                    </association>
+                    <description>An HTML description of the 
property.</description>
+                </field>
+                <field>
+                    <name>values</name>
+                    <required>true</required>
+                    <association xml.itemsStyle="flat">
+                        <type>EnumValue</type>
+                        <multiplicity>*</multiplicity>
+                    </association>
+                    <description>The possible values of this 
enum.</description>
+                </field>
+            </fields>
+        </class>
+
+        <class xml.tagName="description">
+            <name>Description</name>
+            <description>General documentation tag.</description>
+            <fields>
+                <field xml.attribute="true">
+                    <name>format</name>
+                    <type>String</type>
+                    <defaultValue>asciidoc</defaultValue>
+                    <description>Format used by the documentation text. 
Currently it **must** be "asciidoc".
+                    </description>
+                </field>
+                <field xml.content="true">
+                    <name>text</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>Description of the element.</description>
+                </field>
+            </fields>
+        </class>
+
+        <class xml.tagName="value">
+            <name>EnumValue</name>
+            <superClass>Description</superClass>
+            <description>One of the possible values of an enum.</description>
+            <fields>
+                <field xml.attribute="true">
+                    <name>name</name>
+                    <type>String</type>
+                    <required>true</required>
+                    <description>The name of the property.</description>
+                </field>
+            </fields>
+        </class>
+
+        <class xml.tagName="element">
+            <name>PluginElement</name>
+            <description>Describes a nested configuration 
component.</description>
+            <fields>
+                <field xml.attribute="true">
+                    <name>multiplicity</name>
+                    <type>String</type>
+                    <defaultValue>1</defaultValue>
+                    <description>Either '*' if the field accepts a collection 
of elements or '1'.</description>

Review Comment:
   ```suggestion
                       <description>Either `*`, if the field accepts a 
collection of elements, or `1`.</description>
   ```



##########
log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java:
##########
@@ -0,0 +1,351 @@
+/*
+ * 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.internal;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import javax.xml.XMLConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.apache.logging.log4j.docgen.SchemaGenerator;
+import org.apache.logging.log4j.docgen.model.Description;
+import org.apache.logging.log4j.docgen.model.EnumType;
+import org.apache.logging.log4j.docgen.model.EnumValue;
+import org.apache.logging.log4j.docgen.model.PluginAttribute;
+import org.apache.logging.log4j.docgen.model.PluginBundle;
+import org.apache.logging.log4j.docgen.model.PluginElement;
+import org.apache.logging.log4j.docgen.model.PluginEntry;
+import org.apache.logging.log4j.docgen.model.io.stax.PluginBundleStaxReader;
+
+public class DefaultSchemaGenerator implements SchemaGenerator {
+
+    private static final String PLUGIN_NAMESPACE = "Core";
+    private static final String LOG4J_PREFIX = "log4j";
+    private static final String LOG4J_NAMESPACE = 
"http://logging.apache.org/log4j/2.0/config";;
+    private static final String XSD_NAMESPACE = 
XMLConstants.W3C_XML_SCHEMA_NS_URI;
+    private static final String MULTIPLICITY_UNBOUNDED = "*";
+
+    @Override
+    public void generateSchema(Collection<PluginBundle> bundles, 
XMLStreamWriter writer) throws XMLStreamException {
+        try {
+            final PluginBundle configurationBundle =
+                    new 
PluginBundleStaxReader().read(getClass().getResourceAsStream("configuration.xml"));
+            final Set<PluginBundle> extendedBundles = new HashSet<>(bundles);
+            extendedBundles.add(configurationBundle);
+            final TypeLookup lookup = new TypeLookup(extendedBundles);
+            writeSchema(lookup, writer);
+        } catch (IOException e) {
+            throw new XMLStreamException(e);
+        }
+    }
+
+    private void writeSchema(final TypeLookup lookup, final XMLStreamWriter 
writer) throws XMLStreamException {
+        writer.writeStartDocument("UTF-8", "1.0");
+        writer.setDefaultNamespace(XSD_NAMESPACE);
+        writer.writeStartElement(XSD_NAMESPACE, "schema");
+        writer.writeDefaultNamespace(XSD_NAMESPACE);
+        writer.writeNamespace(LOG4J_PREFIX, LOG4J_NAMESPACE);
+        writer.writeAttribute("elementFormDefault", "qualified");
+        writer.writeAttribute("targetNamespace", LOG4J_NAMESPACE);
+
+        // The root element
+        writer.writeEmptyElement(XSD_NAMESPACE, "element");
+        writer.writeAttribute("type", LOG4J_PREFIX + 
":org.apache.logging.log4j.core.config.Configuration");
+        writer.writeAttribute("name", "Configuration");
+
+        // Simple types
+        writeEnumTypes(lookup.getRequiredEnums(), writer);
+
+        // Plugins
+        writePluginEntries(lookup, writer);
+
+        // Interfaces
+        writeGroups(lookup, lookup.getRequiredGroups(), writer);
+
+        writer.writeEndElement();
+        writer.writeEndDocument();
+    }
+
+    private void writePluginEntries(final TypeLookup lookup, final 
XMLStreamWriter writer) throws XMLStreamException {
+        for (final PluginEntry entry : lookup.getPlugins()) {
+            writePluginEntry(lookup, entry, writer);
+        }
+    }
+
+    private void writePluginEntry(final TypeLookup lookup, final PluginEntry 
entry, final XMLStreamWriter writer)
+            throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "complexType");
+        writer.writeAttribute("name", entry.getClassName());
+
+        writeDocumentation(entry.getDescription(), writer);
+
+        final boolean hasSimpleContent = entry.getElements().isEmpty();
+
+        if (!hasSimpleContent) {
+            writer.writeStartElement(XSD_NAMESPACE, "sequence");
+            for (final PluginElement element : entry.getElements()) {
+                writePluginElement(lookup, element, writer);
+            }
+            writer.writeEndElement();
+        }
+
+        for (final PluginAttribute attribute : entry.getAttributes()) {
+            writePluginAttribute(lookup, attribute, writer);
+        }
+
+        writer.writeEndElement();
+    }
+
+    private void writePluginElement(final TypeLookup lookup, final 
PluginElement element, final XMLStreamWriter writer)
+            throws XMLStreamException {
+        final String type = element.getType();
+        final String xmlType = lookup.getXmlType(type, false);
+        final PluginEntry entry = lookup.getPluginByType(type);
+        /*
+         * If some plugins have `type` as super type or if the type is unknown,
+         * we use a <group> element.
+         */
+        if (lookup.getPluginsByGroup(type) != null || entry == null) {
+            writer.writeStartElement(XSD_NAMESPACE, "group");
+            writer.writeAttribute("ref", xmlType);
+            writeMultiplicity(element.isRequired(), element.getMultiplicity(), 
writer);
+            writeDocumentation(element.getDescription(), writer);
+            writer.writeEndElement();
+        } else {
+            final Set<String> actualKeys = new TreeSet<>(entry.getKeys());
+            actualKeys.add(entry.getName());
+            for (final String key : actualKeys) {
+                writer.writeStartElement(XSD_NAMESPACE, "element");
+                writer.writeAttribute("name", key);
+                writer.writeAttribute("type", xmlType);
+                writeMultiplicity(element.isRequired(), 
element.getMultiplicity(), writer);
+                writeDocumentation(element.getDescription(), writer);
+                writer.writeEndElement();
+            }
+        }
+    }
+
+    private void writeMultiplicity(final boolean required, final String 
multiplicity, final XMLStreamWriter writer)
+            throws XMLStreamException {
+        if (!required) {
+            writer.writeAttribute("minOccurs", "0");
+        }
+        if (MULTIPLICITY_UNBOUNDED.equals(multiplicity)) {
+            writer.writeAttribute("maxOccurs", "unbounded");
+        }
+    }
+
+    private void writePluginAttribute(
+            final TypeLookup lookup, final PluginAttribute attribute, final 
XMLStreamWriter writer)
+            throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "attribute");
+        writer.writeAttribute("name", attribute.getName());
+        writer.writeAttribute("type", lookup.getXmlType(attribute.getType(), 
true));
+        final Description description = attribute.getDescription();
+        if (description != null) {
+            writeDocumentation(description, writer);
+        }
+        writer.writeEndElement();
+    }
+
+    private void writeDocumentation(final Description description, final 
XMLStreamWriter writer)
+            throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "annotation");
+        writer.writeStartElement(XSD_NAMESPACE, "documentation");
+        writer.writeCharacters(normalizeAsciidoc(description.getText()));
+        writer.writeEndElement();
+        writer.writeEndElement();
+    }
+
+    private String normalizeAsciidoc(final String text) {
+        final StringBuilder sb = new StringBuilder();
+        for (final String line : text.split("\r?\n", -1)) {
+            sb.append(line.trim()).append('\n');
+        }
+        final int length = sb.length();
+        if (length > 0) {
+            sb.setLength(length - 1);
+        }
+        return sb.toString();
+    }
+
+    private void writeEnumTypes(final Collection<EnumType> types, final 
XMLStreamWriter writer)
+            throws XMLStreamException {
+        for (final EnumType type : types) {
+            writeEnumType(type, writer);
+        }
+    }
+
+    private void writeGroups(final TypeLookup lookup, final Collection<String> 
groups, final XMLStreamWriter writer)
+            throws XMLStreamException {
+        for (final String group : groups) {
+            writeGroup(
+                    lookup,
+                    group,
+                    
Optional.ofNullable(lookup.getPluginsByGroup(group)).orElse(Collections.emptySet()),
+                    writer);
+        }
+    }
+
+    private void writeGroup(
+            final TypeLookup lookup,

Review Comment:
   `lookup` is not used.



##########
log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java:
##########
@@ -0,0 +1,351 @@
+/*
+ * 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.internal;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import javax.xml.XMLConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.apache.logging.log4j.docgen.SchemaGenerator;
+import org.apache.logging.log4j.docgen.model.Description;
+import org.apache.logging.log4j.docgen.model.EnumType;
+import org.apache.logging.log4j.docgen.model.EnumValue;
+import org.apache.logging.log4j.docgen.model.PluginAttribute;
+import org.apache.logging.log4j.docgen.model.PluginBundle;
+import org.apache.logging.log4j.docgen.model.PluginElement;
+import org.apache.logging.log4j.docgen.model.PluginEntry;
+import org.apache.logging.log4j.docgen.model.io.stax.PluginBundleStaxReader;
+
+public class DefaultSchemaGenerator implements SchemaGenerator {
+
+    private static final String PLUGIN_NAMESPACE = "Core";
+    private static final String LOG4J_PREFIX = "log4j";
+    private static final String LOG4J_NAMESPACE = 
"http://logging.apache.org/log4j/2.0/config";;
+    private static final String XSD_NAMESPACE = 
XMLConstants.W3C_XML_SCHEMA_NS_URI;
+    private static final String MULTIPLICITY_UNBOUNDED = "*";
+
+    @Override
+    public void generateSchema(Collection<PluginBundle> bundles, 
XMLStreamWriter writer) throws XMLStreamException {
+        try {
+            final PluginBundle configurationBundle =
+                    new 
PluginBundleStaxReader().read(getClass().getResourceAsStream("configuration.xml"));
+            final Set<PluginBundle> extendedBundles = new HashSet<>(bundles);

Review Comment:
   There are several places where we use `HashSet`, `HashMap`, etc. in this 
class. Shouldn't we be using deterministic alternatives (`TreeSet`, `TreeMap`, 
etc.) instead for reproducibility?



##########
log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/internal/DefaultSchemaGenerator.java:
##########
@@ -0,0 +1,351 @@
+/*
+ * 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.internal;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import javax.xml.XMLConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.apache.logging.log4j.docgen.SchemaGenerator;
+import org.apache.logging.log4j.docgen.model.Description;
+import org.apache.logging.log4j.docgen.model.EnumType;
+import org.apache.logging.log4j.docgen.model.EnumValue;
+import org.apache.logging.log4j.docgen.model.PluginAttribute;
+import org.apache.logging.log4j.docgen.model.PluginBundle;
+import org.apache.logging.log4j.docgen.model.PluginElement;
+import org.apache.logging.log4j.docgen.model.PluginEntry;
+import org.apache.logging.log4j.docgen.model.io.stax.PluginBundleStaxReader;
+
+public class DefaultSchemaGenerator implements SchemaGenerator {
+
+    private static final String PLUGIN_NAMESPACE = "Core";
+    private static final String LOG4J_PREFIX = "log4j";
+    private static final String LOG4J_NAMESPACE = 
"http://logging.apache.org/log4j/2.0/config";;
+    private static final String XSD_NAMESPACE = 
XMLConstants.W3C_XML_SCHEMA_NS_URI;
+    private static final String MULTIPLICITY_UNBOUNDED = "*";
+
+    @Override
+    public void generateSchema(Collection<PluginBundle> bundles, 
XMLStreamWriter writer) throws XMLStreamException {
+        try {
+            final PluginBundle configurationBundle =
+                    new 
PluginBundleStaxReader().read(getClass().getResourceAsStream("configuration.xml"));
+            final Set<PluginBundle> extendedBundles = new HashSet<>(bundles);
+            extendedBundles.add(configurationBundle);
+            final TypeLookup lookup = new TypeLookup(extendedBundles);
+            writeSchema(lookup, writer);
+        } catch (IOException e) {
+            throw new XMLStreamException(e);
+        }
+    }
+
+    private void writeSchema(final TypeLookup lookup, final XMLStreamWriter 
writer) throws XMLStreamException {
+        writer.writeStartDocument("UTF-8", "1.0");
+        writer.setDefaultNamespace(XSD_NAMESPACE);
+        writer.writeStartElement(XSD_NAMESPACE, "schema");
+        writer.writeDefaultNamespace(XSD_NAMESPACE);
+        writer.writeNamespace(LOG4J_PREFIX, LOG4J_NAMESPACE);
+        writer.writeAttribute("elementFormDefault", "qualified");
+        writer.writeAttribute("targetNamespace", LOG4J_NAMESPACE);
+
+        // The root element
+        writer.writeEmptyElement(XSD_NAMESPACE, "element");
+        writer.writeAttribute("type", LOG4J_PREFIX + 
":org.apache.logging.log4j.core.config.Configuration");
+        writer.writeAttribute("name", "Configuration");
+
+        // Simple types
+        writeEnumTypes(lookup.getRequiredEnums(), writer);
+
+        // Plugins
+        writePluginEntries(lookup, writer);
+
+        // Interfaces
+        writeGroups(lookup, lookup.getRequiredGroups(), writer);
+
+        writer.writeEndElement();
+        writer.writeEndDocument();
+    }
+
+    private void writePluginEntries(final TypeLookup lookup, final 
XMLStreamWriter writer) throws XMLStreamException {
+        for (final PluginEntry entry : lookup.getPlugins()) {
+            writePluginEntry(lookup, entry, writer);
+        }
+    }
+
+    private void writePluginEntry(final TypeLookup lookup, final PluginEntry 
entry, final XMLStreamWriter writer)
+            throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "complexType");
+        writer.writeAttribute("name", entry.getClassName());
+
+        writeDocumentation(entry.getDescription(), writer);
+
+        final boolean hasSimpleContent = entry.getElements().isEmpty();
+
+        if (!hasSimpleContent) {
+            writer.writeStartElement(XSD_NAMESPACE, "sequence");
+            for (final PluginElement element : entry.getElements()) {
+                writePluginElement(lookup, element, writer);
+            }
+            writer.writeEndElement();
+        }
+
+        for (final PluginAttribute attribute : entry.getAttributes()) {
+            writePluginAttribute(lookup, attribute, writer);
+        }
+
+        writer.writeEndElement();
+    }
+
+    private void writePluginElement(final TypeLookup lookup, final 
PluginElement element, final XMLStreamWriter writer)
+            throws XMLStreamException {
+        final String type = element.getType();
+        final String xmlType = lookup.getXmlType(type, false);
+        final PluginEntry entry = lookup.getPluginByType(type);
+        /*
+         * If some plugins have `type` as super type or if the type is unknown,
+         * we use a <group> element.
+         */
+        if (lookup.getPluginsByGroup(type) != null || entry == null) {
+            writer.writeStartElement(XSD_NAMESPACE, "group");
+            writer.writeAttribute("ref", xmlType);
+            writeMultiplicity(element.isRequired(), element.getMultiplicity(), 
writer);
+            writeDocumentation(element.getDescription(), writer);
+            writer.writeEndElement();
+        } else {
+            final Set<String> actualKeys = new TreeSet<>(entry.getKeys());
+            actualKeys.add(entry.getName());
+            for (final String key : actualKeys) {
+                writer.writeStartElement(XSD_NAMESPACE, "element");
+                writer.writeAttribute("name", key);
+                writer.writeAttribute("type", xmlType);
+                writeMultiplicity(element.isRequired(), 
element.getMultiplicity(), writer);
+                writeDocumentation(element.getDescription(), writer);
+                writer.writeEndElement();
+            }
+        }
+    }
+
+    private void writeMultiplicity(final boolean required, final String 
multiplicity, final XMLStreamWriter writer)
+            throws XMLStreamException {
+        if (!required) {
+            writer.writeAttribute("minOccurs", "0");
+        }
+        if (MULTIPLICITY_UNBOUNDED.equals(multiplicity)) {
+            writer.writeAttribute("maxOccurs", "unbounded");
+        }
+    }
+
+    private void writePluginAttribute(
+            final TypeLookup lookup, final PluginAttribute attribute, final 
XMLStreamWriter writer)
+            throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "attribute");
+        writer.writeAttribute("name", attribute.getName());
+        writer.writeAttribute("type", lookup.getXmlType(attribute.getType(), 
true));
+        final Description description = attribute.getDescription();
+        if (description != null) {
+            writeDocumentation(description, writer);
+        }
+        writer.writeEndElement();
+    }
+
+    private void writeDocumentation(final Description description, final 
XMLStreamWriter writer)
+            throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "annotation");
+        writer.writeStartElement(XSD_NAMESPACE, "documentation");
+        writer.writeCharacters(normalizeAsciidoc(description.getText()));
+        writer.writeEndElement();
+        writer.writeEndElement();
+    }
+
+    private String normalizeAsciidoc(final String text) {
+        final StringBuilder sb = new StringBuilder();
+        for (final String line : text.split("\r?\n", -1)) {
+            sb.append(line.trim()).append('\n');
+        }
+        final int length = sb.length();
+        if (length > 0) {
+            sb.setLength(length - 1);
+        }
+        return sb.toString();
+    }
+
+    private void writeEnumTypes(final Collection<EnumType> types, final 
XMLStreamWriter writer)
+            throws XMLStreamException {
+        for (final EnumType type : types) {
+            writeEnumType(type, writer);
+        }
+    }
+
+    private void writeGroups(final TypeLookup lookup, final Collection<String> 
groups, final XMLStreamWriter writer)
+            throws XMLStreamException {
+        for (final String group : groups) {
+            writeGroup(
+                    lookup,
+                    group,
+                    
Optional.ofNullable(lookup.getPluginsByGroup(group)).orElse(Collections.emptySet()),
+                    writer);
+        }
+    }
+
+    private void writeGroup(
+            final TypeLookup lookup,
+            final String groups,
+            final Collection<PluginEntry> entries,
+            final XMLStreamWriter writer)
+            throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "group");
+        writer.writeAttribute("name", groups + ".group");
+        writer.writeStartElement(XSD_NAMESPACE, "choice");
+
+        for (final PluginEntry entry : entries) {
+            final Set<String> actualKeys = new TreeSet<>(entry.getKeys());
+            actualKeys.add(entry.getName());
+            for (final String key : actualKeys) {
+                writer.writeEmptyElement(XSD_NAMESPACE, "element");
+                writer.writeAttribute("name", key);
+                writer.writeAttribute("type", LOG4J_PREFIX + ":" + 
entry.getClassName());
+            }
+        }
+
+        writer.writeEndElement();
+        writer.writeEndElement();
+    }
+
+    private void writeEnumType(final EnumType type, final XMLStreamWriter 
writer) throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "simpleType");
+        writer.writeAttribute("name", type.getClassName());
+
+        writeDocumentation(type.getDescription(), writer);
+
+        writer.writeStartElement(XSD_NAMESPACE, "restriction");
+        writer.writeAttribute("base", "string");
+
+        for (final EnumValue value : type.getValues()) {
+            writeEnumValue(value, writer);
+        }
+
+        writer.writeEndElement();
+        writer.writeEndElement();
+    }
+
+    private void writeEnumValue(final EnumValue value, final XMLStreamWriter 
writer) throws XMLStreamException {
+        writer.writeStartElement(XSD_NAMESPACE, "enumeration");
+        writer.writeAttribute("value", value.getName());
+
+        writeDocumentation(value, writer);
+
+        writer.writeEndElement();
+    }
+
+    private static class TypeLookup {

Review Comment:
   ```suggestion
       private static final class TypeLookup {
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to