This is an automated email from the ASF dual-hosted git repository.

mattsicker pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit a7ca777c74a99f97703676fbfc96773b6400c3c8
Merge: 69398f2 4bf8c3e
Author: Matt Sicker <[email protected]>
AuthorDate: Sat Jan 1 19:07:44 2022 -0600

    Merge branch 'mean-bean-machine'

 .../java/org/apache/logging/log4j/LoggerTest.java  |   2 +-
 .../logging/log4j/NoopThreadContextTest.java       |   2 +
 .../log4j/core/config/di/BeanManagerTest.java      | 605 +++++++++++++++++++++
 .../log4j/core/config/di/InjectionPointTest.java   | 136 +++++
 .../log4j/core/config/di/InjectionTargetTest.java  | 109 ++++
 log4j-core-test/src/test/java9/module-info.java    |   2 +
 log4j-core/src/main/java/module-info.java          |   2 +
 .../core/config/di/AmbiguousBeanException.java     |  12 +-
 .../apache/logging/log4j/core/config/di/Bean.java  |  98 ++++
 .../logging/log4j/core/config/di/BeanManager.java  | 265 +++++++++
 .../log4j/core/config/di/DefinitionException.java  |  10 +-
 .../core/config/di/IllegalProductException.java    |  10 +-
 .../core/config/di/InitializationContext.java      |  54 ++
 .../core/config/di/InitializationException.java    |  10 +-
 .../log4j/core/config/di/InjectionException.java   |  14 +-
 .../log4j/core/config/di/InjectionPoint.java       |  55 ++
 .../log4j/core/config/di/InjectionTarget.java      |  63 +++
 .../core/config/di/InjectionTargetFactory.java     |   8 +-
 .../LoggerContextScoped.java}                      |  21 +-
 .../logging/log4j/core/config/di/Producer.java     |  67 +++
 .../log4j/core/config/di/ProducerFactory.java      |   9 +-
 .../log4j/core/config/di/ResolutionException.java  |  10 +-
 .../logging/log4j/core/config/di/ScopeContext.java |  63 +++
 .../core/config/di/UnsatisfiedBeanException.java   |  18 +-
 .../log4j/core/config/di/ValidationException.java  |  25 +-
 .../log4j/core/config/di/impl/AbstractBean.java    |  76 +++
 .../core/config/di/impl/AbstractProducer.java      |  74 +++
 .../config/di/impl/AbstractProducerFactory.java    |  42 ++
 .../core/config/di/impl/DefaultBeanManager.java    | 460 ++++++++++++++++
 .../di/impl/DefaultInitializationContext.java      | 134 +++++
 .../core/config/di/impl/DefaultInjectionPoint.java | 121 +++++
 .../config/di/impl/DefaultInjectionTarget.java     | 136 +++++
 .../di/impl/DefaultInjectionTargetFactory.java     | 160 ++++++
 .../core/config/di/impl/DefaultScopeContext.java   |  72 +++
 .../core/config/di/impl/DependentScopeContext.java |  58 ++
 .../log4j/core/config/di/impl/FieldProducer.java   |  67 +++
 .../core/config/di/impl/FieldProducerFactory.java  |  44 ++
 .../core/config/di/impl/InjectionTargetBean.java   |  86 +++
 .../log4j/core/config/di/impl/Injector.java        | 111 ++++
 .../log4j/core/config/di/impl/MethodProducer.java  |  63 +++
 .../core/config/di/impl/MethodProducerFactory.java |  47 ++
 .../log4j/core/config/di/impl/OptionalBean.java    |  83 +++
 .../log4j/core/config/di/impl/ProducerBean.java    |  94 ++++
 .../log4j/core/config/di/impl/ProvidedBean.java    |  77 +++
 .../log4j/core/config/di/impl/ProviderBean.java    |  77 +++
 .../log4j/core/config/plugins/PluginAliases.java   |  10 +-
 .../log4j/core/config/plugins/PluginAttribute.java |   5 +
 .../config/plugins/PluginBuilderAttribute.java     |   5 +
 .../core/config/plugins/PluginBuilderFactory.java  |   9 +-
 .../core/config/plugins/PluginConfiguration.java   |   2 +
 .../log4j/core/config/plugins/PluginElement.java   |   5 +
 .../log4j/core/config/plugins/PluginFactory.java   |   9 +-
 .../log4j/core/config/plugins/PluginNode.java      |   2 +
 .../log4j/core/config/plugins/PluginValue.java     |   5 +
 .../config/plugins/util/PluginAliasesProvider.java |  20 +-
 .../plugins/util/PluginAttributeNameProvider.java  |  20 +-
 .../util/PluginBuilderAttributeNameProvider.java   |  20 +-
 .../plugins/util/PluginElementNameProvider.java    |  20 +-
 .../plugins/util/PluginValueNameProvider.java      |  20 +-
 .../log4j/plugin/processor/BeanProcessor.java      | 516 ++++++++++++++++++
 .../log4j/plugin/processor/PluginProcessor.java    |  15 +-
 .../src/main/java9/module-info.java                |   4 +-
 .../services/javax.annotation.processing.Processor |   1 +
 log4j-plugins-test/pom.xml                         |   8 +
 .../plugins/test/validation/ExampleBean.java}      |  34 +-
 .../log4j/plugins/test/validation}/FakePlugin.java |   2 +-
 .../plugins/test/validation/ImplicitBean.java}     |  33 +-
 .../test/validation/ImplicitMethodBean.java}       |  42 +-
 .../plugins/test/validation/ProductionBean.java    |  56 ++
 log4j-plugins-test/src/main/java9/module-info.java |   1 +
 .../log4j/plugin/processor/BeanProcessorTest.java  |  51 ++
 .../plugin/processor/PluginProcessorTest.java      |   9 +-
 .../plugins/convert/TypeConverterRegistryTest.java |  10 +-
 log4j-plugins/src/main/java/module-info.java       |   4 +-
 .../org/apache/logging/log4j/plugins/Plugin.java   |   3 +
 .../logging/log4j/plugins/PluginAliases.java       |   6 +
 .../logging/log4j/plugins/PluginAttribute.java     |   2 +
 .../log4j/plugins/PluginBuilderAttribute.java      |   2 +
 .../logging/log4j/plugins/PluginElement.java       |   2 +
 .../logging/log4j/plugins/PluginFactory.java       |   3 +
 .../apache/logging/log4j/plugins/PluginNode.java   |   2 +
 .../apache/logging/log4j/plugins/PluginValue.java  |   2 +
 .../logging/log4j/plugins/di/DependentScoped.java  |  27 +-
 .../plugins/{PluginNode.java => di/Disposes.java}  |  20 +-
 .../apache/logging/log4j/plugins/di/Inject.java    |  51 ++
 .../org/apache/logging/log4j/plugins/di/Named.java |  26 +-
 .../{PluginAliases.java => di/NamedAliases.java}   |  25 +-
 .../logging/log4j/plugins/di/PostConstruct.java    |  18 +-
 .../logging/log4j/plugins/di/PreDestroy.java       |  16 +-
 .../apache/logging/log4j/plugins/di/Producer.java  |  19 +-
 .../apache/logging/log4j/plugins/di/Produces.java  |  53 ++
 .../apache/logging/log4j/plugins/di/Provider.java  |   8 +-
 .../apache/logging/log4j/plugins/di/Qualifier.java |  19 +-
 .../apache/logging/log4j/plugins/di/ScopeType.java |  21 +-
 .../logging/log4j/plugins/di/SingletonScoped.java  |  19 +-
 .../log4j/plugins/di/model/DisposesMethod.java     |  53 ++
 .../log4j/plugins/di/model/GenericPlugin.java      |  35 +-
 .../log4j/plugins/di/model/InjectionTarget.java    |  49 ++
 .../log4j/plugins/di/model/PluginModule.java       |  18 +-
 .../log4j/plugins/di/model/PluginSource.java       |  15 +-
 .../log4j/plugins/di/model/ProducerField.java      |  55 ++
 .../log4j/plugins/di/model/ProducerMethod.java     |  62 +++
 .../logging/log4j/plugins/di/package-info.java     |  15 +-
 .../inject/AbstractConfigurationInjector.java      |   8 +-
 .../log4j/plugins/name/AliasesProvider.java        |  16 +-
 .../name/AnnotatedElementAliasesProvider.java      |  46 ++
 .../plugins/name/AnnotatedElementNameProvider.java |  56 +-
 .../log4j/plugins/name/NamedAliasesProvider.java   |  54 ++
 .../plugins/name/NamedQualifierNameProvider.java   |  19 +-
 .../log4j/plugins/name/PluginAliasesProvider.java  |  17 +-
 .../log4j/plugins/name/PluginNameProvider.java     |  16 +-
 .../log4j/plugins/processor/PluginService.java     |   4 +-
 .../logging/log4j/plugins/util/AnnotationUtil.java |  25 +-
 .../apache/logging/log4j/plugins/util/Cache.java   |  11 +-
 .../logging/log4j/plugins/util/LazyValue.java      |  64 +++
 .../log4j/plugins/util/ParameterizedTypeImpl.java  |  67 +++
 .../logging/log4j/plugins/util/TypeUtil.java       | 355 +++++++++++-
 .../apache/logging/log4j/plugins/util/Value.java   |  11 +-
 .../logging/log4j/plugins/util/WeakCache.java      |  60 ++
 .../logging/log4j/plugins/util/WeakLazyValue.java  |  58 ++
 120 files changed, 5798 insertions(+), 425 deletions(-)

diff --cc log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerTest.java
index d98f0ea,0000000..27a3a8e
mode 100644,000000..100644
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerTest.java
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerTest.java
@@@ -1,634 -1,0 +1,634 @@@
 +/*
 + * 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;
 +
 +import org.apache.logging.log4j.message.EntryMessage;
 +import org.apache.logging.log4j.message.JsonMessage;
 +import org.apache.logging.log4j.message.Message;
 +import org.apache.logging.log4j.message.MessageFactory;
 +import org.apache.logging.log4j.message.ObjectMessage;
 +import org.apache.logging.log4j.message.ParameterizedMessageFactory;
 +import org.apache.logging.log4j.message.SimpleMessage;
 +import org.apache.logging.log4j.message.SimpleMessageFactory;
 +import org.apache.logging.log4j.message.StringFormatterMessageFactory;
 +import org.apache.logging.log4j.message.StructuredDataMessage;
 +import org.apache.logging.log4j.test.TestLogger;
 +import org.apache.logging.log4j.test.junit.UsingThreadContextMap;
 +import org.apache.logging.log4j.util.Strings;
 +import org.apache.logging.log4j.util.Supplier;
 +import org.junit.jupiter.api.BeforeEach;
 +import org.junit.jupiter.api.Test;
 +import org.junit.jupiter.api.parallel.ResourceLock;
 +
 +import java.util.Date;
 +import java.util.List;
 +import java.util.Locale;
 +import java.util.Properties;
 +
 +import static org.hamcrest.CoreMatchers.*;
 +import static org.hamcrest.MatcherAssert.assertThat;
 +import static org.junit.jupiter.api.Assertions.*;
 +
 +@ResourceLock("log4j2.MarkerManager")
 +@ResourceLock("log4j2.TestLogger")
 +@UsingThreadContextMap
 +public class LoggerTest {
 +
 +    private static class TestParameterizedMessageFactory {
 +        // empty
 +    }
 +
 +    private static class TestStringFormatterMessageFactory {
 +        // empty
 +    }
 +
 +    private final TestLogger logger = (TestLogger) 
LogManager.getLogger("LoggerTest");
 +    private final Marker marker = MarkerManager.getMarker("test");
 +    private final List<String> results = logger.getEntries();
 +
 +    @Test
 +    public void builder() {
 +        logger.atDebug().withLocation().log("Hello");
 +        logger.atError().withMarker(marker).log("Hello {}", "John");
 +        logger.atWarn().withThrowable(new Throwable("This is a 
test")).log((Message) new SimpleMessage("Log4j rocks!"));
 +        assertEquals(3, results.size());
 +        assertThat("Incorrect message 1", results.get(0),
 +                equalTo(" DEBUG 
org.apache.logging.log4j.LoggerTest.builder(LoggerTest.java:65) Hello"));
 +        assertThat("Incorrect message 2", results.get(1), equalTo("test ERROR 
Hello John"));
 +        assertThat("Incorrect message 3", results.get(2),
 +                startsWith(" WARN Log4j rocks! java.lang.Throwable: This is a 
test"));
 +        assertThat("Throwable incorrect in message 3", results.get(2),
 +                
containsString("org.apache.logging.log4j.LoggerTest.builder(LoggerTest.java:67)"));
 +    }
 +
 +    @Test
 +    public void basicFlow() {
 +        logger.traceEntry();
 +        logger.traceExit();
 +        assertEquals(2, results.size());
 +        assertThat("Incorrect Entry", results.get(0), equalTo("ENTER[ FLOW ] 
TRACE Enter"));
 +        assertThat("incorrect Exit", results.get(1), equalTo("EXIT[ FLOW ] 
TRACE Exit"));
 +
 +    }
 +
 +    @Test
 +    public void flowTracingMessage() {
 +        Properties props = new Properties();
 +        props.setProperty("foo", "bar");
 +        logger.traceEntry(new JsonMessage(props));
 +        final Response response = new Response(-1, "Generic error");
 +        logger.traceExit(new JsonMessage(response),  response);
 +        assertEquals(2, results.size());
 +        assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW 
] TRACE Enter"));
 +        assertThat("Missing entry data", results.get(0), 
containsString("\"foo\":\"bar\""));
 +        assertThat("incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] 
TRACE Exit"));
 +        assertThat("Missing exit data", results.get(1), 
containsString("\"message\":\"Generic error\""));
 +    }
 +
 +    @Test
 +    public void flowTracingString_ObjectArray1() {
 +        logger.traceEntry("doFoo(a={}, b={})", 1, 2);
 +        logger.traceExit("doFoo(a=1, b=2): {}", 3);
 +        assertEquals(2, results.size());
 +        assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW 
] TRACE Enter"));
 +        assertThat("Missing entry data", results.get(0), 
containsString("doFoo(a=1, b=2)"));
 +        assertThat("Incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] 
TRACE Exit"));
 +        assertThat("Missing exit data", results.get(1), 
containsString("doFoo(a=1, b=2): 3"));
 +    }
 +
 +    @Test
 +    public void flowTracingExitValueOnly() {
 +        logger.traceEntry("doFoo(a={}, b={})", 1, 2);
 +        logger.traceExit(3);
 +        assertEquals(2, results.size());
 +        assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW 
] TRACE Enter"));
 +        assertThat("Missing entry data", results.get(0), 
containsString("doFoo(a=1, b=2)"));
 +        assertThat("Incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] 
TRACE Exit"));
 +        assertThat("Missing exit data", results.get(1), containsString("3"));
 +    }
 +
 +    @Test
 +    public void flowTracingString_ObjectArray2() {
 +        final EntryMessage msg = logger.traceEntry("doFoo(a={}, b={})", 1, 2);
 +        logger.traceExit(msg, 3);
 +        assertEquals(2, results.size());
 +        assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW 
] TRACE Enter"));
 +        assertThat("Missing entry data", results.get(0), 
containsString("doFoo(a=1, b=2)"));
 +        assertThat("Incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] 
TRACE Exit"));
 +        assertThat("Missing exit data", results.get(1), 
containsString("doFoo(a=1, b=2): 3"));
 +    }
 +
 +    @Test
 +    public void flowTracingVoidReturn() {
 +        final EntryMessage msg = logger.traceEntry("doFoo(a={}, b={})", 1, 2);
 +        logger.traceExit(msg);
 +        assertEquals(2, results.size());
 +        assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW 
] TRACE Enter"));
 +        assertThat("Missing entry data", results.get(0), 
containsString("doFoo(a=1, b=2)"));
 +        assertThat("Incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] 
TRACE Exit"));
 +        assertThat("Missing exit data", results.get(1), endsWith("doFoo(a=1, 
b=2)"));
 +    }
 +
 +    @Test
 +    public void flowTracingNoExitArgs() {
 +        logger.traceEntry();
 +        logger.traceExit();
 +        assertEquals(2, results.size());
 +        assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW 
] TRACE Enter"));
 +        assertThat("Incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] 
TRACE Exit"));
 +    }
 +
 +    @Test
 +    public void flowTracingNoArgs() {
 +        final EntryMessage message = logger.traceEntry();
 +        logger.traceExit(message);
 +        assertEquals(2, results.size());
 +        assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW 
] TRACE Enter"));
 +        assertThat("Incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] 
TRACE Exit"));
 +    }
 +
 +    @Test
 +    public void flowTracingString_SupplierOfObjectMessages() {
 +        final EntryMessage msg = logger.traceEntry("doFoo(a={}, b={})", new 
Supplier<Message>() {
 +            @Override
 +            public Message get() {
 +                return new ObjectMessage(1);
 +            }
 +        }, new Supplier<Message>() {
 +            @Override
 +            public Message get() {
 +                return new ObjectMessage(2);
 +            }
 +        });
 +        logger.traceExit(msg, 3);
 +        assertEquals(2, results.size());
 +        assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW 
] TRACE Enter"));
 +        assertThat("Missing entry data", results.get(0), 
containsString("doFoo(a=1, b=2)"));
 +        assertThat("Incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] 
TRACE Exit"));
 +        assertThat("Missing exit data", results.get(1), 
containsString("doFoo(a=1, b=2): 3"));
 +    }
 +
 +    @Test
 +    public void flowTracingString_SupplierOfStrings() {
 +        final EntryMessage msg = logger.traceEntry("doFoo(a={}, b={})", new 
Supplier<String>() {
 +            @Override
 +            public String get() {
 +                return "1";
 +            }
 +        }, new Supplier<String>() {
 +            @Override
 +            public String get() {
 +                return "2";
 +            }
 +        });
 +        logger.traceExit(msg, 3);
 +        assertEquals(2, results.size());
 +        assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW 
] TRACE Enter"));
 +        assertThat("Missing entry data", results.get(0), 
containsString("doFoo(a=1, b=2)"));
 +        assertThat("Incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] 
TRACE Exit"));
 +        assertThat("Missing exit data", results.get(1), 
containsString("doFoo(a=1, b=2): 3"));
 +    }
 +
 +    @Test
 +    public void catching() {
 +        try {
 +            throw new NullPointerException();
 +        } catch (final Exception e) {
 +            logger.catching(e);
 +            assertEquals(1, results.size());
 +            assertThat("Incorrect Catching",
 +                    results.get(0), startsWith("CATCHING[ EXCEPTION ] ERROR 
Catching java.lang.NullPointerException"));
 +        }
 +    }
 +
 +    @Test
 +    public void debug() {
 +        logger.debug("Debug message");
 +        assertEquals(1, results.size());
 +        assertTrue(results.get(0).startsWith(" DEBUG Debug message"), 
"Incorrect message");
 +    }
 +
 +    @Test
 +    public void debugObject() {
 +        logger.debug(new Date());
 +        assertEquals(1, results.size());
 +        assertTrue(results.get(0).length() > 7, "Invalid length");
 +    }
 +
 +    @Test
 +    public void debugWithParms() {
 +        logger.debug("Hello, {}", "World");
 +        assertEquals(1, results.size());
 +        assertTrue(results.get(0).startsWith(" DEBUG Hello, World"), 
"Incorrect substitution");
 +    }
 +
 +    @Test
 +    public void debugWithParmsAndThrowable() {
 +        logger.debug("Hello, {}", "World", new RuntimeException("Test 
Exception"));
 +        assertEquals(1, results.size());
 +        assertTrue(
 +                results.get(0).startsWith(" DEBUG Hello, World 
java.lang.RuntimeException: Test Exception"),
 +                "Unexpected results: " + results.get(0));
 +    }
 +
 +    @Test
 +    public void getFormatterLogger() {
 +        // The TestLogger logger was already created in an instance variable 
for this class.
 +        // The message factory is only used when the logger is created.
 +        final TestLogger testLogger = (TestLogger) 
LogManager.getFormatterLogger();
 +        final TestLogger altLogger = (TestLogger) 
LogManager.getFormatterLogger(getClass());
 +        assertEquals(testLogger.getName(), altLogger.getName());
 +        assertNotNull(testLogger);
 +        assertMessageFactoryInstanceOf(testLogger.getMessageFactory(), 
StringFormatterMessageFactory.class);
 +        assertEqualMessageFactory(StringFormatterMessageFactory.INSTANCE, 
testLogger);
 +        testLogger.debug("%,d", Integer.MAX_VALUE);
 +        assertEquals(1, testLogger.getEntries().size());
 +        assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), 
testLogger.getEntries().get(0));
 +    }
 +
 +    @Test
 +    public void getFormatterLogger_Class() {
 +        // The TestLogger logger was already created in an instance variable 
for this class.
 +        // The message factory is only used when the logger is created.
 +        final TestLogger testLogger = (TestLogger) 
LogManager.getFormatterLogger(TestStringFormatterMessageFactory.class);
 +        assertNotNull(testLogger);
 +        assertMessageFactoryInstanceOf(testLogger.getMessageFactory(), 
StringFormatterMessageFactory.class);
 +        assertEqualMessageFactory(StringFormatterMessageFactory.INSTANCE, 
testLogger);
 +        testLogger.debug("%,d", Integer.MAX_VALUE);
 +        assertEquals(1, testLogger.getEntries().size());
 +        assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), 
testLogger.getEntries().get(0));
 +    }
 +
 +    private static void assertMessageFactoryInstanceOf(MessageFactory 
factory, final Class<?> cls) {
 +        assertTrue(factory.getClass().isAssignableFrom(cls));
 +    }
 +
 +    @Test
 +    public void getFormatterLogger_Object() {
 +        // The TestLogger logger was already created in an instance variable 
for this class.
 +        // The message factory is only used when the logger is created.
 +        final TestLogger testLogger = (TestLogger) 
LogManager.getFormatterLogger(new TestStringFormatterMessageFactory());
 +        assertNotNull(testLogger);
 +        assertMessageFactoryInstanceOf(testLogger.getMessageFactory(), 
StringFormatterMessageFactory.class);
 +        assertEqualMessageFactory(StringFormatterMessageFactory.INSTANCE, 
testLogger);
 +        testLogger.debug("%,d", Integer.MAX_VALUE);
 +        assertEquals(1, testLogger.getEntries().size());
 +        assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), 
testLogger.getEntries().get(0));
 +    }
 +
 +    @Test
 +    public void getFormatterLogger_String() {
 +        final StringFormatterMessageFactory messageFactory = 
StringFormatterMessageFactory.INSTANCE;
 +        final TestLogger testLogger = (TestLogger) 
LogManager.getFormatterLogger("getLogger_String_StringFormatterMessageFactory");
 +        assertNotNull(testLogger);
 +        assertMessageFactoryInstanceOf(testLogger.getMessageFactory(), 
StringFormatterMessageFactory.class);
 +        assertEqualMessageFactory(messageFactory, testLogger);
 +        testLogger.debug("%,d", Integer.MAX_VALUE);
 +        assertEquals(1, testLogger.getEntries().size());
 +        assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), 
testLogger.getEntries().get(0));
 +    }
 +
 +    @Test
 +    public void getLogger_Class_ParameterizedMessageFactory() {
 +        // The TestLogger logger was already created in an instance variable 
for this class.
 +        // The message factory is only used when the logger is created.
 +        final ParameterizedMessageFactory messageFactory = 
ParameterizedMessageFactory.INSTANCE;
 +        final TestLogger testLogger = (TestLogger) 
LogManager.getLogger(TestParameterizedMessageFactory.class,
 +                messageFactory);
 +        assertNotNull(testLogger);
 +        assertEqualMessageFactory(messageFactory, testLogger);
 +        testLogger.debug("{}", Integer.MAX_VALUE);
 +        assertEquals(1, testLogger.getEntries().size());
 +        assertEquals(" DEBUG " + Integer.MAX_VALUE, 
testLogger.getEntries().get(0));
 +    }
 +
 +    @Test
 +    public void getLogger_Class_StringFormatterMessageFactory() {
 +        // The TestLogger logger was already created in an instance variable 
for this class.
 +        // The message factory is only used when the logger is created.
 +        final TestLogger testLogger = (TestLogger) 
LogManager.getLogger(TestStringFormatterMessageFactory.class,
 +                StringFormatterMessageFactory.INSTANCE);
 +        assertNotNull(testLogger);
 +        assertEqualMessageFactory(StringFormatterMessageFactory.INSTANCE, 
testLogger);
 +        testLogger.debug("%,d", Integer.MAX_VALUE);
 +        assertEquals(1, testLogger.getEntries().size());
 +        assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), 
testLogger.getEntries().get(0));
 +    }
 +
 +    @Test
 +    public void getLogger_Object_ParameterizedMessageFactory() {
 +        // The TestLogger logger was already created in an instance variable 
for this class.
 +        // The message factory is only used when the logger is created.
 +        final ParameterizedMessageFactory messageFactory =  
ParameterizedMessageFactory.INSTANCE;
 +        final TestLogger testLogger = (TestLogger) LogManager.getLogger(new 
TestParameterizedMessageFactory(),
 +                messageFactory);
 +        assertNotNull(testLogger);
 +        assertEqualMessageFactory(messageFactory, testLogger);
 +        testLogger.debug("{}", Integer.MAX_VALUE);
 +        assertEquals(1, testLogger.getEntries().size());
 +        assertEquals(" DEBUG " + Integer.MAX_VALUE, 
testLogger.getEntries().get(0));
 +    }
 +
 +    private void assertEqualMessageFactory(final MessageFactory 
messageFactory, final TestLogger testLogger) {
 +        MessageFactory actual = testLogger.getMessageFactory();
 +        assertEquals(messageFactory, actual);
 +    }
 +
 +    @Test
 +    public void getLogger_Object_StringFormatterMessageFactory() {
 +        // The TestLogger logger was already created in an instance variable 
for this class.
 +        // The message factory is only used when the logger is created.
 +        final StringFormatterMessageFactory messageFactory = 
StringFormatterMessageFactory.INSTANCE;
 +        final TestLogger testLogger = (TestLogger) LogManager.getLogger(new 
TestStringFormatterMessageFactory(),
 +                messageFactory);
 +        assertNotNull(testLogger);
 +        assertEqualMessageFactory(messageFactory, testLogger);
 +        testLogger.debug("%,d", Integer.MAX_VALUE);
 +        assertEquals(1, testLogger.getEntries().size());
 +        assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), 
testLogger.getEntries().get(0));
 +    }
 +
 +    @Test
 +    public void getLogger_String_MessageFactoryMismatch() {
 +        final StringFormatterMessageFactory messageFactory = 
StringFormatterMessageFactory.INSTANCE;
 +        final TestLogger testLogger = (TestLogger) 
LogManager.getLogger("getLogger_String_MessageFactoryMismatch",
 +                messageFactory);
 +        assertNotNull(testLogger);
 +        assertEqualMessageFactory(messageFactory, testLogger);
 +        final TestLogger testLogger2 = (TestLogger) 
LogManager.getLogger("getLogger_String_MessageFactoryMismatch",
 +                ParameterizedMessageFactory.INSTANCE);
 +        assertNotNull(testLogger2);
 +        //TODO: How to test?
 +        //This test context always creates new loggers, other test context 
impls I tried fail other tests.
 +        //assertEquals(messageFactory, testLogger2.getMessageFactory());
 +        testLogger.debug("%,d", Integer.MAX_VALUE);
 +        assertEquals(1, testLogger.getEntries().size());
 +        assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), 
testLogger.getEntries().get(0));
 +    }
 +
 +    @Test
 +    public void getLogger_String_ParameterizedMessageFactory() {
 +        final ParameterizedMessageFactory messageFactory =  
ParameterizedMessageFactory.INSTANCE;
 +        final TestLogger testLogger = (TestLogger) 
LogManager.getLogger("getLogger_String_ParameterizedMessageFactory",
 +                messageFactory);
 +        assertNotNull(testLogger);
 +        assertEqualMessageFactory(messageFactory, testLogger);
 +        testLogger.debug("{}", Integer.MAX_VALUE);
 +        assertEquals(1, testLogger.getEntries().size());
 +        assertEquals(" DEBUG " + Integer.MAX_VALUE, 
testLogger.getEntries().get(0));
 +    }
 +
 +    @Test
 +    public void getLogger_String_SimpleMessageFactory() {
 +        final SimpleMessageFactory messageFactory = 
SimpleMessageFactory.INSTANCE;
 +        final TestLogger testLogger = (TestLogger) 
LogManager.getLogger("getLogger_String_StringFormatterMessageFactory",
 +                messageFactory);
 +        assertNotNull(testLogger);
 +        assertEqualMessageFactory(messageFactory, testLogger);
 +        testLogger.debug("{} %,d {foo}", Integer.MAX_VALUE);
 +        assertEquals(1, testLogger.getEntries().size());
 +        assertEquals(" DEBUG {} %,d {foo}", testLogger.getEntries().get(0));
 +    }
 +
 +    @Test
 +    public void getLogger_String_StringFormatterMessageFactory() {
 +        final StringFormatterMessageFactory messageFactory = 
StringFormatterMessageFactory.INSTANCE;
 +        final TestLogger testLogger = (TestLogger) 
LogManager.getLogger("getLogger_String_StringFormatterMessageFactory",
 +                messageFactory);
 +        assertNotNull(testLogger);
 +        assertEqualMessageFactory(messageFactory, testLogger);
 +        testLogger.debug("%,d", Integer.MAX_VALUE);
 +        assertEquals(1, testLogger.getEntries().size());
 +        assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), 
testLogger.getEntries().get(0));
 +    }
 +
 +    @Test
 +    public void getLoggerByClass() {
 +        final Logger classLogger = LogManager.getLogger(LoggerTest.class);
 +        assertNotNull(classLogger);
 +    }
 +
 +    @Test
 +    public void getLoggerByNullClass() {
 +        // Returns a SimpleLogger
 +        assertNotNull(LogManager.getLogger((Class<?>) null));
 +    }
 +
 +    @Test
 +    public void getLoggerByNullObject() {
 +        // Returns a SimpleLogger
 +        assertNotNull(LogManager.getLogger((Object) null));
 +    }
 +
 +    @Test
 +    public void getLoggerByNullString() {
 +        // Returns a SimpleLogger
 +        assertNotNull(LogManager.getLogger((String) null));
 +    }
 +
 +    @Test
 +    public void getLoggerByObject() {
 +        final Logger classLogger = LogManager.getLogger(this);
 +        assertNotNull(classLogger);
 +        assertEquals(classLogger, LogManager.getLogger(LoggerTest.class));
 +    }
 +
 +    @Test
 +    public void getRootLogger() {
 +        assertNotNull(LogManager.getRootLogger());
 +        assertNotNull(LogManager.getLogger(Strings.EMPTY));
 +        assertNotNull(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME));
 +        assertEquals(LogManager.getRootLogger(), 
LogManager.getLogger(Strings.EMPTY));
 +        assertEquals(LogManager.getRootLogger(), 
LogManager.getLogger(LogManager.ROOT_LOGGER_NAME));
 +    }
 +
 +    @Test
 +    public void isAllEnabled() {
 +        assertTrue(logger.isEnabled(Level.ALL), "Incorrect level");
 +    }
 +
 +    @Test
 +    public void isDebugEnabled() {
 +        assertTrue(logger.isDebugEnabled(), "Incorrect level");
 +        assertTrue(logger.isEnabled(Level.DEBUG), "Incorrect level");
 +    }
 +
 +    @Test
 +    public void isErrorEnabled() {
 +        assertTrue(logger.isErrorEnabled(), "Incorrect level");
 +        assertTrue(logger.isEnabled(Level.ERROR), "Incorrect level");
 +    }
 +
 +    @Test
 +    public void isFatalEnabled() {
 +        assertTrue(logger.isFatalEnabled(), "Incorrect level");
 +        assertTrue(logger.isEnabled(Level.FATAL), "Incorrect level");
 +    }
 +
 +    @Test
 +    public void isInfoEnabled() {
 +        assertTrue(logger.isInfoEnabled(), "Incorrect level");
 +        assertTrue(logger.isEnabled(Level.INFO), "Incorrect level");
 +    }
 +
 +    @Test
 +    public void isOffEnabled() {
 +        assertTrue(logger.isEnabled(Level.OFF), "Incorrect level");
 +    }
 +
 +    @Test
 +    public void isTraceEnabled() {
 +        assertTrue(logger.isTraceEnabled(), "Incorrect level");
 +        assertTrue(logger.isEnabled(Level.TRACE), "Incorrect level");
 +    }
 +
 +    @Test
 +    public void isWarnEnabled() {
 +        assertTrue(logger.isWarnEnabled(), "Incorrect level");
 +        assertTrue(logger.isEnabled(Level.WARN), "Incorrect level");
 +    }
 +
 +    @Test
 +    public void isAllEnabledWithMarker() {
 +        assertTrue(logger.isEnabled(Level.ALL, marker), "Incorrect level");
 +    }
 +
 +    @Test
 +    public void isDebugEnabledWithMarker() {
 +        assertTrue(logger.isDebugEnabled(marker), "Incorrect level");
 +        assertTrue(logger.isEnabled(Level.DEBUG, marker), "Incorrect level");
 +    }
 +
 +    @Test
 +    public void isErrorEnabledWithMarker() {
 +        assertTrue(logger.isErrorEnabled(marker), "Incorrect level");
 +        assertTrue(logger.isEnabled(Level.ERROR, marker), "Incorrect level");
 +    }
 +
 +    @Test
 +    public void isFatalEnabledWithMarker() {
 +        assertTrue(logger.isFatalEnabled(marker), "Incorrect level");
 +        assertTrue(logger.isEnabled(Level.FATAL, marker), "Incorrect level");
 +    }
 +
 +    @Test
 +    public void isInfoEnabledWithMarker() {
 +        assertTrue(logger.isInfoEnabled(marker), "Incorrect level");
 +        assertTrue(logger.isEnabled(Level.INFO, marker), "Incorrect level");
 +    }
 +
 +    @Test
 +    public void isOffEnabledWithMarker() {
 +        assertTrue(logger.isEnabled(Level.OFF, marker), "Incorrect level");
 +    }
 +
 +    @Test
 +    public void isTraceEnabledWithMarker() {
 +        assertTrue(logger.isTraceEnabled(marker), "Incorrect level");
 +        assertTrue(logger.isEnabled(Level.TRACE, marker), "Incorrect level");
 +    }
 +
 +    @Test
 +    public void isWarnEnabledWithMarker() {
 +        assertTrue(logger.isWarnEnabled(marker), "Incorrect level");
 +        assertTrue(logger.isEnabled(Level.WARN, marker), "Incorrect level");
 +    }
 +
 +    @Test
 +    public void mdc() {
-         ThreadContext.clearAll();
++        ThreadContext.clearMap();
 +        ThreadContext.put("TestYear", Integer.valueOf(2010).toString());
 +        logger.debug("Debug message");
 +        String testYear = ThreadContext.get("TestYear");
 +        assertNotNull(testYear, "Test Year is null");
 +        assertEquals("2010", testYear, "Incorrect test year: " + testYear);
 +        ThreadContext.clearMap();
 +        logger.debug("Debug message");
 +        assertEquals(2, results.size());
 +        System.out.println("Log line 1: " + results.get(0));
 +        System.out.println("log line 2: " + results.get(1));
 +        assertTrue(
 +                results.get(0).startsWith(" DEBUG Debug message 
{TestYear=2010}"), "Incorrect MDC: " + results.get(0));
 +        assertTrue(
 +                results.get(1).startsWith(" DEBUG Debug message"), "MDC not 
cleared?: " + results.get(1));
 +    }
 +
 +    @Test
 +    public void printf() {
 +        logger.printf(Level.DEBUG, "Debug message %d", 1);
 +        logger.printf(Level.DEBUG, MarkerManager.getMarker("Test"), "Debug 
message %d", 2);
 +        assertEquals(2, results.size());
 +        assertThat("Incorrect message", results.get(0), startsWith(" DEBUG 
Debug message 1"));
 +        assertThat("Incorrect message", results.get(1), startsWith("Test 
DEBUG Debug message 2"));
 +    }
 +
 +    @BeforeEach
 +    public void setup() {
 +        results.clear();
 +    }
 +
 +    @Test
 +    public void structuredData() {
 +        ThreadContext.put("loginId", "JohnDoe");
 +        ThreadContext.put("ipAddress", "192.168.0.120");
 +        ThreadContext.put("locale", Locale.US.getDisplayName());
 +        final StructuredDataMessage msg = new 
StructuredDataMessage("Audit@18060", "Transfer Complete", "Transfer");
 +        msg.put("ToAccount", "123456");
 +        msg.put("FromAccount", "123457");
 +        msg.put("Amount", "200.00");
 +        logger.info(MarkerManager.getMarker("EVENT"), msg);
 +        ThreadContext.clearMap();
 +        assertEquals(1, results.size());
 +        assertThat("Incorrect structured data: ", results.get(0), startsWith(
 +                "EVENT INFO Transfer [Audit@18060 Amount=\"200.00\" 
FromAccount=\"123457\" ToAccount=\"123456\"] Transfer Complete"));
 +    }
 +
 +    @Test
 +    public void throwing() {
 +        logger.throwing(new IllegalArgumentException("Test Exception"));
 +        assertEquals(1, results.size());
 +        assertThat("Incorrect Throwing",
 +                results.get(0), startsWith("THROWING[ EXCEPTION ] ERROR 
Throwing java.lang.IllegalArgumentException: Test Exception"));
 +    }
 +
 +
 +    private static class Response {
 +        int status;
 +        String message;
 +
 +        public Response(final int status, final String message) {
 +            this.status = status;
 +            this.message = message;
 +        }
 +
 +        public int getStatus() {
 +            return status;
 +        }
 +
 +        public void setStatus(final int status) {
 +            this.status = status;
 +        }
 +
 +        public String getMessage() {
 +            return message;
 +        }
 +
 +        public void setMessage(final String message) {
 +            this.message = message;
 +        }
 +    }
 +}
diff --cc 
log4j-api-test/src/test/java/org/apache/logging/log4j/NoopThreadContextTest.java
index 5c79106,0000000..6a38598
mode 100644,000000..100644
--- 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/NoopThreadContextTest.java
+++ 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/NoopThreadContextTest.java
@@@ -1,59 -1,0 +1,61 @@@
 +/*
 + * 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;
 +
++import org.apache.logging.log4j.test.junit.UsingThreadContextMap;
 +import org.junit.jupiter.api.AfterAll;
 +import org.junit.jupiter.api.BeforeAll;
 +import org.junit.jupiter.api.Test;
 +import org.junit.jupiter.api.parallel.ResourceLock;
 +import org.junit.jupiter.api.parallel.Resources;
 +
 +import static org.junit.jupiter.api.Assertions.assertNull;
 +
 +/**
 + * Tests {@link ThreadContext}.
 + */
 +@ResourceLock(Resources.SYSTEM_PROPERTIES)
++@UsingThreadContextMap
 +public class NoopThreadContextTest {
 +
 +    private static final String TRUE = "true";
 +    private static final String PROPERY_KEY_ALL = "disableThreadContext";
 +    private static final String PROPERY_KEY_MAP = "disableThreadContextMap";
 +
 +    @BeforeAll
 +    public static void before() {
 +        System.setProperty(PROPERY_KEY_ALL, TRUE);
 +        System.setProperty(PROPERY_KEY_MAP, TRUE);
 +        ThreadContext.init();
 +    }
 +
 +    @AfterAll
 +    public static void after() {
 +        System.clearProperty(PROPERY_KEY_ALL);
 +        System.clearProperty(PROPERY_KEY_MAP);
 +        ThreadContext.init();
 +    }
 +
 +    @Test
 +    public void testNoop() {
 +        ThreadContext.put("Test", "Test");
 +        final String value = ThreadContext.get("Test");
 +        assertNull(value, "value was saved");
 +    }
 +
 +
 +}
diff --cc 
log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/di/BeanManagerTest.java
index 0000000,3f67266..3f67266
mode 000000,100644..100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/di/BeanManagerTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/di/BeanManagerTest.java
diff --cc 
log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/di/InjectionPointTest.java
index 0000000,60e89e4..60e89e4
mode 000000,100644..100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/di/InjectionPointTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/di/InjectionPointTest.java
diff --cc 
log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/di/InjectionTargetTest.java
index 0000000,c06d2ad..c06d2ad
mode 000000,100644..100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/di/InjectionTargetTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/di/InjectionTargetTest.java
diff --cc log4j-core-test/src/test/java9/module-info.java
index c427d5c,0000000..d02fec4
mode 100644,000000..100644
--- a/log4j-core-test/src/test/java9/module-info.java
+++ b/log4j-core-test/src/test/java9/module-info.java
@@@ -1,92 -1,0 +1,94 @@@
 +/*
 + * 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.
 + */
 +open module org.apache.logging.log4j.core {
 +    exports org.apache.logging.log4j.core;
 +    exports org.apache.logging.log4j.core.appender;
 +    exports org.apache.logging.log4j.core.appender.db;
 +    exports org.apache.logging.log4j.core.appender.nosql;
 +    exports org.apache.logging.log4j.core.appender.rewrite;
 +    exports org.apache.logging.log4j.core.appender.rolling;
 +    exports org.apache.logging.log4j.core.appender.rolling.action;
 +    exports org.apache.logging.log4j.core.appender.routing;
 +    exports org.apache.logging.log4j.core.async;
 +    exports org.apache.logging.log4j.core.config;
 +    exports org.apache.logging.log4j.core.config.arbiters;
 +    exports org.apache.logging.log4j.core.config.builder;
++    exports org.apache.logging.log4j.core.config.di;
++    exports org.apache.logging.log4j.core.config.di.impl;
 +    exports org.apache.logging.log4j.core.config.plugins;
 +    exports org.apache.logging.log4j.core.config.plugins.convert;
 +    exports org.apache.logging.log4j.core.config.plugins.util;
 +    exports 
org.apache.logging.log4j.core.config.plugins.validation.validators;
 +    exports org.apache.logging.log4j.core.config.properties;
 +    exports org.apache.logging.log4j.core.config.xml;
 +    exports org.apache.logging.log4j.core.filter;
 +    exports org.apache.logging.log4j.core.impl;
 +    exports org.apache.logging.log4j.core.jmx;
 +    exports org.apache.logging.log4j.core.layout;
 +    exports org.apache.logging.log4j.core.lookup;
 +    exports org.apache.logging.log4j.core.message;
 +    exports org.apache.logging.log4j.core.net;
 +    exports org.apache.logging.log4j.core.net.ssl;
 +    exports org.apache.logging.log4j.core.parser;
 +    exports org.apache.logging.log4j.core.pattern;
 +    exports org.apache.logging.log4j.core.script;
 +    exports org.apache.logging.log4j.core.selector;
 +    exports org.apache.logging.log4j.core.test;
 +    exports org.apache.logging.log4j.core.test.appender;
 +    exports org.apache.logging.log4j.core.test.hamcrest;
 +    exports org.apache.logging.log4j.core.test.junit;
 +    exports org.apache.logging.log4j.core.time;
 +    exports org.apache.logging.log4j.core.tools;
 +    exports org.apache.logging.log4j.core.util;
 +
 +    requires transitive java.compiler;
 +    requires transitive java.desktop;
 +    requires transitive java.management;
 +    requires transitive java.sql;
 +    requires transitive java.rmi;
 +    requires transitive java.scripting;
 +    requires transitive java.xml;
 +    requires transitive org.apache.logging.log4j;
 +    requires transitive org.apache.logging.log4j.test;
 +    requires transitive org.apache.logging.log4j.plugins;
 +    requires transitive org.apache.logging.log4j.plugins.test;
 +    requires transitive com.lmax.disruptor;
 +    requires transitive org.jctools.core;
 +    requires transitive org.osgi.framework;
 +    requires transitive com.conversantmedia.disruptor;
 +    requires transitive net.bytebuddy;
 +    requires com.fasterxml.jackson.core;
 +    requires com.fasterxml.jackson.databind;
 +    requires transitive com.fasterxml.jackson.dataformat.xml;
 +    requires transitive com.fasterxml.jackson.dataformat.yaml;
 +    requires transitive org.apache.commons.compress;
 +    requires transitive org.fusesource.jansi;
 +    requires transitive org.junit.jupiter.api;
 +    requires transitive org.junit.jupiter.engine;
 +    requires transitive org.junit.jupiter.params;
 +    requires transitive org.junit.platform.commons;
 +    requires transitive org.junit.platform.engine;
 +    requires transitive junit;
 +
 +    uses org.apache.logging.log4j.core.util.ContextDataProvider;
 +    uses org.apache.logging.log4j.core.util.WatchEventService;
 +    provides 
org.apache.logging.log4j.message.ThreadDumpMessage.ThreadInfoFactory with 
org.apache.logging.log4j.core.message.ExtendedThreadInfoFactory;
 +    provides org.apache.logging.log4j.core.util.ContextDataProvider with 
org.apache.logging.log4j.core.impl.ThreadContextDataProvider;
 +    provides org.apache.logging.log4j.spi.Provider with 
org.apache.logging.log4j.core.impl.Log4jProvider;
 +    provides org.apache.logging.log4j.plugins.processor.PluginService with 
org.apache.logging.log4j.core.plugins.Log4jPlugins,
 +            org.apache.logging.log4j.core.test.plugins.Log4jPlugins;
 +}
diff --cc log4j-core/src/main/java/module-info.java
index 9133d77,0000000..d4b3d93
mode 100644,000000..100644
--- a/log4j-core/src/main/java/module-info.java
+++ b/log4j-core/src/main/java/module-info.java
@@@ -1,86 -1,0 +1,88 @@@
 +/*
 + * 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.
 + */
 +module org.apache.logging.log4j.core {
 +    exports org.apache.logging.log4j.core;
 +    exports org.apache.logging.log4j.core.appender;
 +    exports org.apache.logging.log4j.core.appender.db;
 +    exports org.apache.logging.log4j.core.appender.nosql;
 +    exports org.apache.logging.log4j.core.appender.rewrite;
 +    exports org.apache.logging.log4j.core.appender.rolling;
 +    exports org.apache.logging.log4j.core.appender.rolling.action;
 +    exports org.apache.logging.log4j.core.appender.routing;
 +    exports org.apache.logging.log4j.core.async;
 +    exports org.apache.logging.log4j.core.config;
 +    exports org.apache.logging.log4j.core.config.arbiters;
 +    exports org.apache.logging.log4j.core.config.builder.api;
 +    exports org.apache.logging.log4j.core.config.builder.impl;
 +    exports org.apache.logging.log4j.core.config.composite;
++    exports org.apache.logging.log4j.core.config.di;
++    exports org.apache.logging.log4j.core.config.di.impl;
 +    exports org.apache.logging.log4j.core.config.json;
 +    exports org.apache.logging.log4j.core.config.plugins;
 +    exports org.apache.logging.log4j.core.config.plugins.convert;
 +    exports org.apache.logging.log4j.core.config.plugins.inject;
 +    exports org.apache.logging.log4j.core.config.plugins.util;
 +    exports org.apache.logging.log4j.core.config.plugins.visitors;
 +    exports org.apache.logging.log4j.core.config.properties;
 +    exports org.apache.logging.log4j.core.config.status;
 +    exports org.apache.logging.log4j.core.config.xml;
 +    exports org.apache.logging.log4j.core.config.yaml;
 +    exports org.apache.logging.log4j.core.filter;
 +    exports org.apache.logging.log4j.core.impl;
 +    exports org.apache.logging.log4j.core.jmx;
 +    exports org.apache.logging.log4j.core.layout;
 +    exports org.apache.logging.log4j.core.lookup;
 +    exports org.apache.logging.log4j.core.message;
 +    exports org.apache.logging.log4j.core.net;
 +    exports org.apache.logging.log4j.core.net.ssl;
 +    exports org.apache.logging.log4j.core.osgi;
 +    exports org.apache.logging.log4j.core.parser;
 +    exports org.apache.logging.log4j.core.pattern;
 +    exports org.apache.logging.log4j.core.script;
 +    exports org.apache.logging.log4j.core.selector;
 +    exports org.apache.logging.log4j.core.time;
 +    exports org.apache.logging.log4j.core.tools;
 +    exports org.apache.logging.log4j.core.tools.picocli;
 +    exports org.apache.logging.log4j.core.util;
 +    exports org.apache.logging.log4j.core.util.datetime;
 +
 +    requires transitive java.desktop;
 +    requires transitive java.management;
 +    requires transitive java.naming;
 +    requires transitive java.sql;
 +    requires transitive java.rmi;
 +    requires transitive java.scripting;
 +    requires transitive java.xml;
 +    requires transitive org.apache.logging.log4j;
 +    requires transitive org.apache.logging.log4j.plugins;
 +    requires transitive com.lmax.disruptor;
 +    requires transitive org.jctools.core;
 +    requires transitive org.osgi.framework;
 +    requires transitive com.conversantmedia.disruptor;
 +    requires transitive com.fasterxml.jackson.core;
 +    requires transitive com.fasterxml.jackson.databind;
 +    requires transitive com.fasterxml.jackson.dataformat.xml;
 +    requires transitive com.fasterxml.jackson.dataformat.yaml;
 +    requires transitive org.apache.commons.compress;
 +    requires transitive org.fusesource.jansi;
 +    uses org.apache.logging.log4j.core.util.ContextDataProvider;
 +    uses org.apache.logging.log4j.core.util.WatchEventService;
 +    provides 
org.apache.logging.log4j.message.ThreadDumpMessage.ThreadInfoFactory with 
org.apache.logging.log4j.core.message.ExtendedThreadInfoFactory;
 +    provides org.apache.logging.log4j.core.util.ContextDataProvider with 
org.apache.logging.log4j.core.impl.ThreadContextDataProvider;
 +    provides org.apache.logging.log4j.spi.Provider with 
org.apache.logging.log4j.core.impl.Log4jProvider;
 +    provides org.apache.logging.log4j.plugins.processor.PluginService with 
org.apache.logging.log4j.core.plugins.Log4jPlugins;
 +}
diff --cc 
log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/BeanProcessor.java
index 0000000,0000000..3ab23ca
new file mode 100644
--- /dev/null
+++ 
b/log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/BeanProcessor.java
@@@ -1,0 -1,0 +1,516 @@@
++/*
++ * 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.plugin.processor;
++
++import org.apache.logging.log4j.plugins.di.DependentScoped;
++import org.apache.logging.log4j.plugins.di.Disposes;
++import org.apache.logging.log4j.plugins.di.Inject;
++import org.apache.logging.log4j.plugins.di.Producer;
++import org.apache.logging.log4j.plugins.di.Qualifier;
++import org.apache.logging.log4j.plugins.di.ScopeType;
++
++import javax.annotation.processing.AbstractProcessor;
++import javax.annotation.processing.RoundEnvironment;
++import javax.annotation.processing.SupportedAnnotationTypes;
++import javax.annotation.processing.SupportedOptions;
++import javax.lang.model.SourceVersion;
++import javax.lang.model.element.AnnotationMirror;
++import javax.lang.model.element.Element;
++import javax.lang.model.element.ElementKind;
++import javax.lang.model.element.ExecutableElement;
++import javax.lang.model.element.Modifier;
++import javax.lang.model.element.Name;
++import javax.lang.model.element.PackageElement;
++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.ElementKindVisitor9;
++import javax.lang.model.util.Elements;
++import javax.lang.model.util.SimpleTypeVisitor9;
++import javax.lang.model.util.Types;
++import javax.tools.StandardLocation;
++import java.io.IOException;
++import java.io.PrintWriter;
++import java.io.UncheckedIOException;
++import java.util.ArrayList;
++import java.util.Comparator;
++import java.util.HashSet;
++import java.util.LinkedHashSet;
++import java.util.List;
++import java.util.Set;
++import java.util.function.Predicate;
++import java.util.stream.Collectors;
++import java.util.stream.Stream;
++
++@SupportedAnnotationTypes({"org.apache.logging.log4j.plugins.*", 
"org.apache.logging.log4j.core.config.plugins.*"})
++@SupportedOptions({"pluginPackage", "pluginClassName"})
++public class BeanProcessor extends AbstractProcessor {
++    public static final String PLUGIN_MODULE_SERVICE_FILE = 
"META-INF/services/org.apache.logging.log4j.plugins.di.model.PluginModule";
++
++    public BeanProcessor() {
++    }
++
++    @Override
++    public SourceVersion getSupportedSourceVersion() {
++        return SourceVersion.latestSupported();
++    }
++
++    private static class ProducerAnnotationVisitor extends 
ElementKindVisitor9<Void, Void> {
++        private final List<ProducerMethodMirror> producerMethods = new 
ArrayList<>();
++        private final List<ProducerFieldMirror> producerFields = new 
ArrayList<>();
++
++        @Override
++        public Void visitVariableAsField(final VariableElement e, final Void 
unused) {
++            producerFields.add(new ProducerFieldMirror(e));
++            return null;
++        }
++
++        @Override
++        public Void visitExecutableAsMethod(final ExecutableElement e, final 
Void unused) {
++            producerMethods.add(new ProducerMethodMirror(e));
++            return null;
++        }
++    }
++
++    private static class DisposesAnnotationVisitor extends 
ElementKindVisitor9<Void, Void> {
++        private final List<DisposesMirror> disposesParameters = new 
ArrayList<>();
++
++        @Override
++        public Void visitVariableAsParameter(final VariableElement e, final 
Void unused) {
++            disposesParameters.add(new DisposesMirror(e));
++            return null;
++        }
++    }
++
++    private static class InjectAnnotationVisitor extends 
ElementKindVisitor9<Void, Void> {
++        private final List<InjectionTargetMirror> injectableClasses = new 
ArrayList<>();
++
++        @Override
++        public Void visitVariableAsField(final VariableElement e, final Void 
unused) {
++            injectableClasses.add(new InjectionTargetMirror(((TypeElement) 
e.getEnclosingElement())));
++            return null;
++        }
++
++        @Override
++        public Void visitExecutableAsConstructor(final ExecutableElement e, 
final Void unused) {
++            injectableClasses.add(new InjectionTargetMirror((TypeElement) 
e.getEnclosingElement()));
++            return null;
++        }
++
++        @Override
++        public Void visitExecutableAsMethod(final ExecutableElement e, final 
Void unused) {
++            injectableClasses.add(new InjectionTargetMirror((TypeElement) 
e.getEnclosingElement()));
++            return null;
++        }
++    }
++
++    private static class QualifiedAnnotationVisitor extends 
ElementKindVisitor9<Void, Void> {
++        private final Predicate<AnnotationMirror> isProducerAnnotation;
++        private final List<InjectionTargetMirror> injectableClasses = new 
ArrayList<>();
++
++        private QualifiedAnnotationVisitor(final Predicate<AnnotationMirror> 
isProducerAnnotation) {
++            this.isProducerAnnotation = isProducerAnnotation;
++        }
++
++        @Override
++        public Void visitVariableAsField(final VariableElement e, final Void 
unused) {
++            if 
(e.getAnnotationMirrors().stream().noneMatch(isProducerAnnotation)) {
++                injectableClasses.add(new InjectionTargetMirror((TypeElement) 
e.getEnclosingElement()));
++            }
++            return null;
++        }
++
++        @Override
++        public Void visitVariableAsParameter(final VariableElement e, final 
Void unused) {
++            final Element enclosingExecutable = e.getEnclosingElement();
++            final TypeElement typeElement = (TypeElement) 
enclosingExecutable.getEnclosingElement();
++            if (enclosingExecutable.getKind() == ElementKind.CONSTRUCTOR ||
++                    
enclosingExecutable.getAnnotationMirrors().stream().noneMatch(isProducerAnnotation))
 {
++                injectableClasses.add(new InjectionTargetMirror(typeElement));
++            }
++            return null;
++        }
++    }
++
++    private static class PluginAnnotationVisitor extends 
ElementKindVisitor9<Void, Void> {
++        private final List<GenericPluginMirror> plugins = new ArrayList<>();
++
++        @Override
++        public Void visitTypeAsClass(final TypeElement e, final Void unused) {
++            plugins.add(new GenericPluginMirror(e));
++            return null;
++        }
++    }
++
++    private static class ScopeTypeVisitor extends 
ElementKindVisitor9<TypeElement, Types> {
++        protected ScopeTypeVisitor(final TypeElement defaultValue) {
++            super(defaultValue);
++        }
++
++        @Override
++        public TypeElement visitType(final TypeElement e, final Types types) {
++            for (final AnnotationMirror annotationMirror : 
e.getAnnotationMirrors()) {
++                final DeclaredType annotationType = 
annotationMirror.getAnnotationType();
++                if (annotationType.getAnnotation(ScopeType.class) != null) {
++                    return (TypeElement) annotationType.asElement();
++                }
++            }
++            return super.visitType(e, types);
++        }
++
++        @Override
++        public TypeElement visitVariableAsField(final VariableElement e, 
final Types types) {
++            return Stream.concat(e.getAnnotationMirrors().stream(), 
e.asType().getAnnotationMirrors().stream())
++                    .map(AnnotationMirror::getAnnotationType)
++                    .filter(type -> type.getAnnotation(ScopeType.class) != 
null)
++                    .findFirst()
++                    .map(type -> (TypeElement) type.asElement())
++                    .orElse(super.DEFAULT_VALUE);
++        }
++
++        @Override
++        public TypeElement visitExecutableAsMethod(final ExecutableElement e, 
final Types types) {
++            return Stream.concat(e.getAnnotationMirrors().stream(), 
e.getReturnType().getAnnotationMirrors().stream())
++                    .map(AnnotationMirror::getAnnotationType)
++                    .filter(type -> type.getAnnotation(ScopeType.class) != 
null)
++                    .findFirst()
++                    .map(type -> (TypeElement) type.asElement())
++                    .orElse(super.DEFAULT_VALUE);
++        }
++    }
++
++    interface PluginSourceMirror<E extends Element> {
++        E getElement();
++
++        TypeElement getDeclaringElement();
++
++        TypeMirror getType();
++    }
++
++    static class ProducerMethodMirror implements 
PluginSourceMirror<ExecutableElement> {
++        private final ExecutableElement element;
++
++        ProducerMethodMirror(final ExecutableElement element) {
++            this.element = element;
++        }
++
++        @Override
++        public ExecutableElement getElement() {
++            return element;
++        }
++
++        @Override
++        public TypeElement getDeclaringElement() {
++            return (TypeElement) element.getEnclosingElement();
++        }
++
++        @Override
++        public TypeMirror getType() {
++            return element.getReturnType();
++        }
++    }
++
++    static class ProducerFieldMirror implements 
PluginSourceMirror<VariableElement> {
++        private final VariableElement element;
++
++        ProducerFieldMirror(final VariableElement element) {
++            this.element = element;
++        }
++
++        @Override
++        public VariableElement getElement() {
++            return element;
++        }
++
++        @Override
++        public TypeElement getDeclaringElement() {
++            return (TypeElement) element.getEnclosingElement();
++        }
++
++        @Override
++        public TypeMirror getType() {
++            return element.asType();
++        }
++    }
++
++    static class InjectionTargetMirror implements 
PluginSourceMirror<TypeElement> {
++        private final TypeElement element;
++
++        InjectionTargetMirror(final TypeElement element) {
++            this.element = element;
++        }
++
++        @Override
++        public TypeElement getElement() {
++            return element;
++        }
++
++        @Override
++        public TypeElement getDeclaringElement() {
++            return element;
++        }
++
++        @Override
++        public TypeMirror getType() {
++            return element.asType();
++        }
++    }
++
++    static class DisposesMirror implements 
PluginSourceMirror<VariableElement> {
++        private final VariableElement element;
++
++        DisposesMirror(final VariableElement element) {
++            this.element = element;
++        }
++
++        @Override
++        public VariableElement getElement() {
++            return element;
++        }
++
++        @Override
++        public TypeElement getDeclaringElement() {
++            return (TypeElement) 
element.getEnclosingElement().getEnclosingElement();
++        }
++
++        @Override
++        public TypeMirror getType() {
++            return element.asType();
++        }
++    }
++
++    static class GenericPluginMirror implements 
PluginSourceMirror<TypeElement> {
++        private final TypeElement element;
++
++        GenericPluginMirror(final TypeElement element) {
++            this.element = element;
++        }
++
++        @Override
++        public TypeElement getElement() {
++            return element;
++        }
++
++        @Override
++        public TypeElement getDeclaringElement() {
++            return element;
++        }
++
++        @Override
++        public TypeMirror getType() {
++            return element.asType();
++        }
++    }
++
++    @Override
++    public boolean process(final Set<? extends TypeElement> annotations, 
final RoundEnvironment roundEnv) {
++        if (annotations.isEmpty()) {
++            return false;
++        }
++
++        final TypeElement[] producerAnnotations = annotations.stream()
++                .filter(e -> e.getAnnotation(Producer.class) != null)
++                .toArray(TypeElement[]::new);
++        final var producesAnnotationVisitor = new ProducerAnnotationVisitor();
++        
roundEnv.getElementsAnnotatedWithAny(producerAnnotations).forEach(producesAnnotationVisitor::visit);
++
++        final var disposesAnnotationVisitor = new DisposesAnnotationVisitor();
++        
roundEnv.getElementsAnnotatedWith(Disposes.class).forEach(disposesAnnotationVisitor::visit);
++
++        final var injectAnnotationVisitor = new InjectAnnotationVisitor();
++        
roundEnv.getElementsAnnotatedWith(Inject.class).forEach(injectAnnotationVisitor::visit);
++
++        final Types types = processingEnv.getTypeUtils();
++        final var qualifiedAnnotationVisitor = new 
QualifiedAnnotationVisitor(annotationMirror -> {
++            for (final TypeElement producerAnnotation : producerAnnotations) {
++                if (types.isSameType(producerAnnotation.asType(), 
annotationMirror.getAnnotationType())) {
++                    return true;
++                }
++            }
++            return false;
++        });
++        final TypeElement[] qualifierAnnotations = annotations.stream()
++                .filter(e -> e.getAnnotation(Qualifier.class) != null)
++                .toArray(TypeElement[]::new);
++        
roundEnv.getElementsAnnotatedWithAny(qualifierAnnotations).forEach(qualifiedAnnotationVisitor::visit);
++
++        final TypeElement[] pluginAnnotations = annotations.stream()
++                .filter(e -> e.getSimpleName().contentEquals("Plugin"))
++                .toArray(TypeElement[]::new);
++        final var pluginAnnotationVisitor = new PluginAnnotationVisitor();
++        
roundEnv.getElementsAnnotatedWithAny(pluginAnnotations).forEach(pluginAnnotationVisitor::visit);
++
++        final Set<PackageElement> packageElements = new HashSet<>();
++        final Set<TypeElement> declaringTypes = new HashSet<>();
++
++        final Elements elements = processingEnv.getElementUtils();
++        final List<PluginSourceMirror<?>> mirrors = new 
ArrayList<>(producesAnnotationVisitor.producerMethods);
++        mirrors.addAll(producesAnnotationVisitor.producerFields);
++        mirrors.addAll(injectAnnotationVisitor.injectableClasses);
++        mirrors.addAll(disposesAnnotationVisitor.disposesParameters);
++        mirrors.forEach(mirror -> {
++            declaringTypes.add(mirror.getDeclaringElement());
++            
packageElements.add(elements.getPackageOf(mirror.getDeclaringElement()));
++        });
++
++        qualifiedAnnotationVisitor.injectableClasses.stream()
++                .filter(mirror -> 
!declaringTypes.contains(mirror.getDeclaringElement()))
++                .forEach(mirror -> {
++                    mirrors.add(mirror);
++                    declaringTypes.add(mirror.getDeclaringElement());
++                    
packageElements.add(elements.getPackageOf(mirror.getDeclaringElement()));
++                });
++
++        pluginAnnotationVisitor.plugins.stream()
++                .filter(mirror -> 
!declaringTypes.contains(mirror.getDeclaringElement()))
++                .forEach(mirror -> {
++                    mirrors.add(mirror);
++                    declaringTypes.add(mirror.getDeclaringElement());
++                    
packageElements.add(elements.getPackageOf(mirror.getDeclaringElement()));
++                });
++
++        String packageName = processingEnv.getOptions().get("pluginPackage");
++        if (packageName == null) {
++            packageName = packageElements.stream()
++                    .map(PackageElement::getQualifiedName)
++                    .map(CharSequence.class::cast)
++                    .reduce(BeanProcessor::commonPrefix)
++                    .orElseThrow()
++                    .toString();
++        }
++        String className = 
processingEnv.getOptions().getOrDefault("pluginClassName", "Log4jModule");
++        try {
++            writePluginModule(packageName, className, mirrors);
++            return false;
++        } catch (IOException e) {
++            throw new UncheckedIOException(e);
++        }
++    }
++
++    private void writePluginModule(final CharSequence packageName, final 
CharSequence className,
++                                   final List<PluginSourceMirror<?>> mirrors) 
throws IOException {
++        try (final PrintWriter out = new 
PrintWriter(processingEnv.getFiler().createResource(
++                StandardLocation.CLASS_OUTPUT, "", 
PLUGIN_MODULE_SERVICE_FILE).openWriter())) {
++            out.println(packageName + ".plugins." + className);
++        }
++        try (final PrintWriter out = new 
PrintWriter(processingEnv.getFiler().createSourceFile(
++                packageName + ".plugins." + className).openWriter())) {
++            out.println("package " + packageName + ".plugins;");
++            out.println();
++            out.println("import 
org.apache.logging.log4j.plugins.di.model.*;");
++            out.println();
++            out.println("import java.util.List;");
++            out.println("import java.util.Set;");
++            out.println();
++            out.println("public class " + className + " extends PluginModule 
{");
++            out.println();
++            out.println("  private static final List<PluginSource> PLUGINS = 
List.of(" + javaListOfPlugins(mirrors) + ");");
++            out.println();
++            out.println("  public " + className + "() {");
++            out.println("    super(PLUGINS);");
++            out.println("  }");
++            out.println();
++            out.println("}");
++        }
++    }
++
++    private String javaListOfPlugins(final List<PluginSourceMirror<?>> 
mirrors) {
++        final Elements elements = processingEnv.getElementUtils();
++        final Types types = processingEnv.getTypeUtils();
++        final var scopeTypeVisitor = new 
ScopeTypeVisitor(elements.getTypeElement(DependentScoped.class.getCanonicalName()));
++        return mirrors.stream()
++                .sorted(Comparator.<PluginSourceMirror<?>, String>comparing(m 
-> m.getClass().getName())
++                        .thenComparing(m -> 
elements.getBinaryName(m.getDeclaringElement()), CharSequence::compare))
++                .map(mirror -> {
++                    final String declaringClassName = '"' + 
elements.getBinaryName(mirror.getDeclaringElement()).toString() + '"';
++                    final String setOfImplementedInterfaces = 
javaSetOfImplementedInterfaces(mirror.getType());
++                    final String scopeTypeClassReference = 
mirror.getElement().accept(scopeTypeVisitor, types).getQualifiedName() + 
".class";
++                    if (mirror instanceof ProducerMethodMirror) {
++                        return "new ProducerMethod(" + declaringClassName + 
", \"" +
++                                mirror.getType().toString() + "\", \"" +
++                                mirror.getElement().getSimpleName() + "\", " +
++                                setOfImplementedInterfaces + ", " +
++                                scopeTypeClassReference + ")";
++                    } else if (mirror instanceof ProducerFieldMirror) {
++                        return "new ProducerField(" + declaringClassName + ", 
\"" +
++                                mirror.getElement().getSimpleName() + "\", " +
++                                setOfImplementedInterfaces + ", " +
++                                scopeTypeClassReference + ")";
++                    } else if (mirror instanceof InjectionTargetMirror) {
++                        return "new InjectionTarget(" + declaringClassName + 
", " +
++                                setOfImplementedInterfaces + ", " +
++                                scopeTypeClassReference + ")";
++                    } else if (mirror instanceof DisposesMirror) {
++                        return "new DisposesMethod(" + declaringClassName + 
", \"" +
++                                elements.getBinaryName((TypeElement) 
types.asElement(mirror.getElement().asType())) + "\")";
++                    } else if (mirror instanceof GenericPluginMirror) {
++                        return "new GenericPlugin(" + declaringClassName + ", 
" + setOfImplementedInterfaces + ")";
++                    } else {
++                        throw new 
UnsupportedOperationException(mirror.getClass().getName());
++                    }
++                })
++                .collect(Collectors.joining(",\n", "\n", "\n"));
++    }
++
++    private String javaSetOfImplementedInterfaces(final TypeMirror base) {
++        final Set<Name> implementedInterfaces = 
getImplementedInterfaces(base);
++        return implementedInterfaces.isEmpty() ? "Set.of()" : "Set.of(" +
++                implementedInterfaces.stream().map(name -> name + 
".class").collect(Collectors.joining(", ")) +
++                ")";
++    }
++
++    private Set<Name> getImplementedInterfaces(final TypeMirror base) {
++        final Set<Name> implementedInterfaces = new LinkedHashSet<>();
++        final Types types = processingEnv.getTypeUtils();
++        base.accept(new SimpleTypeVisitor9<Void, Void>() {
++            @Override
++            public Void visitDeclared(final DeclaredType t, final Void 
unused) {
++                for (final TypeMirror directSupertype : 
types.directSupertypes(t)) {
++                    directSupertype.accept(this, null);
++                }
++                t.asElement().accept(new ElementKindVisitor9<Void, Void>() {
++                    @Override
++                    public Void visitTypeAsInterface(final TypeElement e, 
final Void unused) {
++                        if (e.getModifiers().contains(Modifier.PUBLIC)) {
++                            implementedInterfaces.add(e.getQualifiedName());
++                        }
++                        return null;
++                    }
++                }, null);
++                return null;
++            }
++        }, null);
++        return implementedInterfaces;
++    }
++
++    private static CharSequence commonPrefix(final CharSequence str1, final 
CharSequence str2) {
++        final int minLength = Math.min(str1.length(), str2.length());
++        for (int i = 0; i < minLength; i++) {
++            if (str1.charAt(i) != str2.charAt(i)) {
++                if (i > 1 && str1.charAt(i - 1) == '.') {
++                    return str1.subSequence(0, i - 1);
++                } else {
++                    return str1.subSequence(0, i);
++                }
++            }
++        }
++        return str1.subSequence(0, minLength);
++    }
++
++}
diff --cc 
log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/PluginProcessor.java
index df16f58,0000000..bd3d693
mode 100644,000000..100644
--- 
a/log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/PluginProcessor.java
+++ 
b/log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/PluginProcessor.java
@@@ -1,276 -1,0 +1,273 @@@
 +/*
 + * 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.plugin.processor;
 +
 +import org.apache.logging.log4j.LoggingException;
 +import org.apache.logging.log4j.plugins.Plugin;
 +import org.apache.logging.log4j.plugins.PluginAliases;
 +import org.apache.logging.log4j.plugins.processor.PluginEntry;
- import org.apache.logging.log4j.plugins.util.PluginManager;
 +import org.apache.logging.log4j.util.Strings;
 +
 +import javax.annotation.processing.AbstractProcessor;
 +import javax.annotation.processing.Messager;
 +import javax.annotation.processing.RoundEnvironment;
 +import javax.annotation.processing.SupportedAnnotationTypes;
 +import javax.lang.model.SourceVersion;
 +import javax.lang.model.element.Element;
 +import javax.lang.model.element.ElementVisitor;
 +import javax.lang.model.element.Name;
 +import javax.lang.model.element.TypeElement;
 +import javax.lang.model.util.Elements;
- import javax.lang.model.util.SimpleElementVisitor7;
++import javax.lang.model.util.SimpleElementVisitor8;
 +import javax.tools.Diagnostic.Kind;
 +import javax.tools.FileObject;
 +import javax.tools.JavaFileObject;
 +import javax.tools.StandardLocation;
 +import java.io.BufferedWriter;
 +import java.io.IOException;
 +import java.io.OutputStreamWriter;
 +import java.io.PrintWriter;
 +import java.util.ArrayList;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.List;
 +import java.util.Locale;
 +import java.util.Map;
 +import java.util.Objects;
 +import java.util.Set;
 +
 +import static java.nio.charset.StandardCharsets.UTF_8;
 +
 +/**
 + * Annotation processor for pre-scanning Log4j 2 plugins.
 + */
 +@SupportedAnnotationTypes({"org.apache.logging.log4j.plugins.*", 
"org.apache.logging.log4j.core.config.plugins.*"})
 +public class PluginProcessor extends AbstractProcessor {
 +
 +    // TODO: this could be made more abstract to allow for compile-time and 
run-time plugin processing
 +
 +    private static final String SERVICE_FILE_NAME =
 +            
"META-INF/services/org.apache.logging.log4j.plugins.processor.PluginService";
 +
 +    public PluginProcessor() {
 +    }
 +
 +    @Override
 +    public SourceVersion getSupportedSourceVersion() {
 +        return SourceVersion.latest();
 +    }
 +
 +    @Override
 +    public boolean process(final Set<? extends TypeElement> annotations, 
final RoundEnvironment roundEnv) {
 +        Map<String, String> options = processingEnv.getOptions();
 +        String packageName = options.get("pluginPackage");
 +        Messager messager = processingEnv.getMessager();
 +        messager.printMessage(Kind.NOTE, "Processing Log4j annotations");
 +        try {
 +            final Set<? extends Element> elements = 
roundEnv.getElementsAnnotatedWith(Plugin.class);
 +            if (elements.isEmpty()) {
 +                messager.printMessage(Kind.NOTE, "No elements to process");
 +                return false;
 +            }
 +            messager.printMessage(Kind.NOTE, "Retrieved " + elements.size() + 
" Plugin elements");
 +            List<PluginEntry> list = new ArrayList<>();
 +            packageName = collectPlugins(packageName, elements, list);
 +            writeClassFile(packageName, list);
 +            writeServiceFile(packageName);
 +            messager.printMessage(Kind.NOTE, "Annotations processed");
-             return true;
 +        } catch (final Exception ex) {
-             ex.printStackTrace();
 +            error(ex.getMessage());
-             return false;
 +        }
++        return false;
 +    }
 +
 +    private void error(final CharSequence message) {
 +        processingEnv.getMessager().printMessage(Kind.ERROR, message);
 +    }
 +
 +    private String collectPlugins(String packageName, final Iterable<? 
extends Element> elements, List<PluginEntry> list) {
 +        boolean calculatePackage = packageName == null;
 +        final Elements elementUtils = processingEnv.getElementUtils();
 +        final ElementVisitor<PluginEntry, Plugin> pluginVisitor = new 
PluginElementVisitor(elementUtils);
 +        final ElementVisitor<Collection<PluginEntry>, Plugin> 
pluginAliasesVisitor = new PluginAliasesElementVisitor(
 +                elementUtils);
 +        for (final Element element : elements) {
 +            final Plugin plugin = element.getAnnotation(Plugin.class);
 +            if (plugin == null) {
 +                continue;
 +            }
 +            final PluginEntry entry = element.accept(pluginVisitor, plugin);
 +            list.add(entry);
 +            if (calculatePackage) {
 +                packageName = calculatePackage(elementUtils, element, 
packageName);
 +            }
 +            final Collection<PluginEntry> entries = 
element.accept(pluginAliasesVisitor, plugin);
 +            for (final PluginEntry pluginEntry : entries) {
 +                list.add(pluginEntry);
 +            }
 +        }
 +        return packageName;
 +    }
 +
 +    private String calculatePackage(Elements elements, Element element, 
String packageName) {
 +        Name name = elements.getPackageOf(element).getQualifiedName();
 +        if (name == null) {
 +            return null;
 +        }
 +        String pkgName = name.toString();
 +        if (packageName == null) {
 +            return pkgName;
 +        }
 +        if (pkgName.length() == packageName.length()) {
 +            return packageName;
 +        }
 +        if (pkgName.length() < packageName.length() && 
packageName.startsWith(pkgName)) {
 +            return pkgName;
 +        }
 +
 +        return commonPrefix(pkgName, packageName);
 +    }
 +
 +    private void writeServiceFile(String pkgName) throws IOException {
 +        final FileObject fileObject = 
processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, 
Strings.EMPTY,
 +                SERVICE_FILE_NAME);
 +        try (final PrintWriter writer = new PrintWriter(new 
BufferedWriter(new OutputStreamWriter(fileObject.openOutputStream(), UTF_8)))) {
 +            writer.println(createFqcn(pkgName));
 +        }
 +    }
 +
 +    private void writeClassFile(String pkg, List<PluginEntry> list) {
 +        String fqcn = createFqcn(pkg);
 +        try (final PrintWriter writer = createSourceFile(fqcn)) {
 +            writer.println("package " + pkg + ".plugins;");
 +            writer.println("");
 +            writer.println("import 
org.apache.logging.log4j.plugins.processor.PluginEntry;");
 +            writer.println("import 
org.apache.logging.log4j.plugins.processor.PluginService;");
 +            writer.println("");
 +            writer.println("public class Log4jPlugins extends PluginService 
{");
 +            writer.println("");
 +            writer.println("    private static PluginEntry[] entries = new 
PluginEntry[] {");
 +            StringBuilder sb = new StringBuilder();
 +            int max = list.size() - 1;
 +            for (int i = 0; i < list.size(); ++i) {
 +                PluginEntry entry = list.get(i);
 +                sb.append("        ").append("new PluginEntry(\"");
 +                sb.append(entry.getKey()).append("\", \"");
 +                sb.append(entry.getClassName()).append("\", \"");
 +                sb.append(entry.getName()).append("\", ");
 +                sb.append(entry.isPrintable()).append(", ");
 +                sb.append(entry.isDefer()).append(", \"");
 +                sb.append(entry.getCategory()).append("\")");
 +                if (i < max) {
 +                    sb.append(",");
 +                }
 +                writer.println(sb.toString());
 +                sb.setLength(0);
 +            }
 +            writer.println("    };");
 +            writer.println("    @Override");
 +            writer.println("    public PluginEntry[] getEntries() { return 
entries;}");
 +            writer.println("}");
 +        }
 +    }
 +
 +    private PrintWriter createSourceFile(String fqcn) {
 +        try {
 +            JavaFileObject sourceFile = 
processingEnv.getFiler().createSourceFile(fqcn);
 +            return new PrintWriter(sourceFile.openWriter());
 +        } catch (IOException e) {
 +            throw new LoggingException("Unable to create Plugin Service Class 
" + fqcn, e);
 +        }
 +    }
 +
 +    private String createFqcn(String packageName) {
 +        return packageName + ".plugins.Log4jPlugins";
 +    }
 +
 +    /**
 +     * ElementVisitor to scan the Plugin annotation.
 +     */
-     private static class PluginElementVisitor extends 
SimpleElementVisitor7<PluginEntry, Plugin> {
++    private static class PluginElementVisitor extends 
SimpleElementVisitor8<PluginEntry, Plugin> {
 +
 +        private final Elements elements;
 +
 +        private PluginElementVisitor(final Elements elements) {
 +            this.elements = elements;
 +        }
 +
 +        @Override
 +        public PluginEntry visitType(final TypeElement e, final Plugin 
plugin) {
 +            Objects.requireNonNull(plugin, "Plugin annotation is null.");
 +            final PluginEntry entry = new PluginEntry();
 +            entry.setKey(plugin.name().toLowerCase(Locale.US));
 +            entry.setClassName(elements.getBinaryName(e).toString());
 +            entry.setName(Plugin.EMPTY.equals(plugin.elementType()) ? 
plugin.name() : plugin.elementType());
 +            entry.setPrintable(plugin.printObject());
 +            entry.setDefer(plugin.deferChildren());
 +            entry.setCategory(plugin.category());
 +            return entry;
 +        }
 +    }
 +
 +    private String commonPrefix(String str1, String str2) {
-         int minLength = str1.length() < str2.length() ? str1.length() : 
str2.length();
++        int minLength = Math.min(str1.length(), str2.length());
 +        for (int i = 0; i < minLength; i++) {
 +            if (str1.charAt(i) != str2.charAt(i)) {
 +                if (i > 1 && str1.charAt(i-1) == '.') {
 +                    return str1.substring(0, i-1);
 +                } else {
 +                    return str1.substring(0, i);
 +                }
 +            }
 +        }
 +        return str1.substring(0, minLength);
 +    }
 +
 +    /**
 +     * ElementVisitor to scan the PluginAliases annotation.
 +     */
-     private static class PluginAliasesElementVisitor extends 
SimpleElementVisitor7<Collection<PluginEntry>, Plugin> {
++    private static class PluginAliasesElementVisitor extends 
SimpleElementVisitor8<Collection<PluginEntry>, Plugin> {
 +
 +        private final Elements elements;
 +
 +        private PluginAliasesElementVisitor(final Elements elements) {
-             super(Collections.<PluginEntry> emptyList());
++            super(Collections.emptyList());
 +            this.elements = elements;
 +        }
 +
 +        @Override
 +        public Collection<PluginEntry> visitType(final TypeElement e, final 
Plugin plugin) {
 +            final PluginAliases aliases = 
e.getAnnotation(PluginAliases.class);
 +            if (aliases == null) {
 +                return DEFAULT_VALUE;
 +            }
 +            final Collection<PluginEntry> entries = new 
ArrayList<>(aliases.value().length);
 +            for (final String alias : aliases.value()) {
 +                final PluginEntry entry = new PluginEntry();
 +                entry.setKey(alias.toLowerCase(Locale.US));
 +                entry.setClassName(elements.getBinaryName(e).toString());
 +                entry.setName(Plugin.EMPTY.equals(plugin.elementType()) ? 
alias : plugin.elementType());
 +                entry.setPrintable(plugin.printObject());
 +                entry.setDefer(plugin.deferChildren());
 +                entry.setCategory(plugin.category());
 +                entries.add(entry);
 +            }
 +            return entries;
 +        }
 +    }
 +}
diff --cc log4j-plugin-processor/src/main/java9/module-info.java
index dae6806,0000000..49dbeba
mode 100644,000000..100644
--- a/log4j-plugin-processor/src/main/java9/module-info.java
+++ b/log4j-plugin-processor/src/main/java9/module-info.java
@@@ -1,26 -1,0 +1,28 @@@
 +/*
 + * 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.
 + */
 +module org.apache.logging.log4j.plugin.processor {
 +    exports org.apache.logging.log4j.plugin.processor;
 +
 +    requires java.compiler;
 +    requires org.apache.logging.log4j;
 +    requires org.apache.logging.log4j.plugins;
 +    requires transitive org.osgi.framework;
 +
-     provides javax.annotation.processing.Processor with 
org.apache.logging.log4j.plugin.processor.PluginProcessor;
++    provides javax.annotation.processing.Processor with
++            org.apache.logging.log4j.plugin.processor.PluginProcessor,
++            org.apache.logging.log4j.plugin.processor.BeanProcessor;
 +}
diff --cc 
log4j-plugin-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor
index e1c421c,0000000..1c97759
mode 100644,000000..100644
--- 
a/log4j-plugin-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor
+++ 
b/log4j-plugin-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@@ -1,17 -1,0 +1,18 @@@
 +#
 +# 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.
 +#
 +org.apache.logging.log4j.plugin.processor.PluginProcessor
++org.apache.logging.log4j.plugin.processor.BeanProcessor
diff --cc log4j-plugins-test/pom.xml
index c21c306,0000000..6c32550
mode 100644,000000..100644
--- a/log4j-plugins-test/pom.xml
+++ b/log4j-plugins-test/pom.xml
@@@ -1,312 -1,0 +1,320 @@@
 +<?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.
 +  -->
 +<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
 +  <modelVersion>4.0.0</modelVersion>
 +  <parent>
 +    <groupId>org.apache.logging.log4j</groupId>
 +    <artifactId>log4j</artifactId>
 +    <version>3.0.0-SNAPSHOT</version>
 +    <relativePath>../</relativePath>
 +  </parent>
 +  <artifactId>log4j-plugins-test</artifactId>
 +  <packaging>jar</packaging>
 +  <name>Apache Log4j Plugins Test</name>
 +  <description>Log4j Plugin Test Support</description>
 +  <properties>
 +    <log4jParentDir>${basedir}/..</log4jParentDir>
 +    <docLabel>Plugin Documentation</docLabel>
 +    <projectDir>/plugins</projectDir>
 +    <maven.doap.skip>true</maven.doap.skip>
 +  </properties>
 +  <dependencies>
 +    <dependency>
 +      <groupId>org.apache.logging.log4j</groupId>
 +      <artifactId>log4j-api</artifactId>
 +    </dependency>
 +    <dependency>
 +      <groupId>org.apache.logging.log4j</groupId>
 +      <artifactId>log4j-plugin-processor</artifactId>
 +    </dependency>
 +    <dependency>
 +      <groupId>org.apache.logging.log4j</groupId>
 +      <artifactId>log4j-plugins</artifactId>
 +    </dependency>
 +    <!-- Used for OSGi bundle support -->
 +    <dependency>
 +      <groupId>org.osgi</groupId>
 +      <artifactId>org.osgi.framework</artifactId>
 +      <scope>provided</scope>
 +    </dependency>
 +    <dependency>
 +      <groupId>org.osgi</groupId>
 +      <artifactId>org.osgi.resource</artifactId>
 +      <scope>provided</scope>
 +    </dependency>
 +
 +    <!-- TEST DEPENDENCIES -->
 +
 +    <!-- Pull in useful test classes from API -->
 +    <dependency>
 +      <groupId>org.apache.logging.log4j</groupId>
 +      <artifactId>log4j-api-test</artifactId>
 +      <scope>test</scope>
 +    </dependency>
 +    <dependency>
 +      <groupId>org.junit.vintage</groupId>
 +      <artifactId>junit-vintage-engine</artifactId>
 +      <scope>test</scope>
 +    </dependency>
 +    <dependency>
 +      <groupId>org.junit.jupiter</groupId>
 +      <artifactId>junit-jupiter-engine</artifactId>
 +      <scope>test</scope>
 +    </dependency>
 +    <dependency>
 +      <groupId>org.hamcrest</groupId>
 +      <artifactId>hamcrest</artifactId>
 +      <scope>test</scope>
 +    </dependency>
 +  </dependencies>
 +  <build>
 +    <plugins>
 +      <plugin>
 +        <groupId>org.apache.maven.plugins</groupId>
 +        <artifactId>maven-compiler-plugin</artifactId>
 +        <version>${compiler.plugin.version}</version>
 +        <configuration>
 +          <source>${maven.compiler.source}</source>
 +          <target>${maven.compiler.target}</target>
 +          <release>${maven.compiler.release}</release>
 +          <showDeprecation>true</showDeprecation>
 +          <showWarnings>true</showWarnings>
 +          <verbose>false</verbose>
 +          <encoding>UTF-8</encoding>
 +          <fork>true</fork>
 +          <meminitial>256</meminitial>
 +          <maxmem>1024</maxmem>
 +          <compilerArgs>
 +            <arg>-XDcompilePolicy=simple</arg>
 +            <arg>-Xplugin:ErrorProne</arg>
 +            <!--
 +            https://errorprone.info/docs/installation
 +            in Java 16+, https://openjdk.java.net/jeps/396 encapsulates 
internals that errorprone needs
 +            -->
 +            
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
 +            
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
 +            
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
 +            
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
 +            
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
 +            
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
 +            
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
 +            
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
 +            
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
 +            
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
 +          </compilerArgs>
 +          <compilerArguments>
 +            <Xmaxwarns>10000</Xmaxwarns>
 +            <Xlint />
 +          </compilerArguments>
 +          <annotationProcessorPaths>
 +            <path>
 +              <groupId>org.apache.logging.log4j</groupId>
 +              <artifactId>log4j-plugin-processor</artifactId>
 +              <version>${project.version}</version>
 +            </path>
 +            <path>
 +              <groupId>com.google.errorprone</groupId>
 +              <artifactId>error_prone_core</artifactId>
 +              <version>${errorprone.version}</version>
 +            </path>
 +          </annotationProcessorPaths>
 +          <forceJavacCompilerUse>true</forceJavacCompilerUse>
 +          <parameters>true</parameters>
 +        </configuration>
 +        <executions>
 +          <execution>
 +            <id>compile-module-info</id>
 +            <goals>
 +              <goal>compile</goal>
 +            </goals>
 +            <phase>prepare-package</phase>
 +            <configuration>
 +              <compileSourceRoots>
 +                
<compileSourceRoot>${project.basedir}/src/main/java9</compileSourceRoot>
 +              </compileSourceRoots>
 +            </configuration>
 +          </execution>
++          <execution>
++            <id>default-testCompile</id>
++            <configuration>
++              <compilerArgs>
++                
<arg>-ApluginPackage=org.apache.logging.log4j.plugins.convert.test</arg>
++              </compilerArgs>
++            </configuration>
++          </execution>
 +        </executions>
 +      </plugin>
 +      <plugin>
 +        <artifactId>maven-surefire-plugin</artifactId>
 +        <configuration>
 +          <useModulePath>false</useModulePath>
 +          <excludedGroups>
 +            org.apache.logging.log4j.categories.PerformanceTests
 +          </excludedGroups>
 +          <systemPropertyVariables>
 +            
<org.apache.activemq.SERIALIZABLE_PACKAGES>*</org.apache.activemq.SERIALIZABLE_PACKAGES>
 +          </systemPropertyVariables>
 +          <argLine>--add-opens java.base/java.net=ALL-UNNAMED</argLine>
 +        </configuration>
 +      </plugin>
 +      <plugin>
 +        <groupId>org.apache.maven.plugins</groupId>
 +        <artifactId>maven-failsafe-plugin</artifactId>
 +        <configuration>
 +          <skipTests>true</skipTests>
 +        </configuration>
 +      </plugin>
 +      <plugin>
 +        <groupId>org.apache.felix</groupId>
 +        <artifactId>maven-bundle-plugin</artifactId>
 +        <configuration>
 +          <instructions>
 +            
<Bundle-SymbolicName>org.apache.logging.log4j.plugins</Bundle-SymbolicName>
 +            <!-- TODO: exclude internal classes from export -->
 +            
<Export-Package>org.apache.logging.log4j.plugins.*</Export-Package>
 +            <Import-Package>
 +              org.apache.logging.log4j,
 +              org.apache.logging.log4j.status,
 +              org.apache.logging.log4j.util,
 +              org.osgi.framework.*
 +            </Import-Package>
 +            
<Bundle-Activator>org.apache.logging.log4j.plugins.osgi.Activator</Bundle-Activator>
 +          </instructions>
 +        </configuration>
 +      </plugin>
 +    </plugins>
 +  </build>
 +  <reporting>
 +    <plugins>
 +      <plugin>
 +        <groupId>org.apache.maven.plugins</groupId>
 +        <artifactId>maven-changes-plugin</artifactId>
 +        <version>${changes.plugin.version}</version>
 +        <reportSets>
 +          <reportSet>
 +            <reports>
 +              <report>changes-report</report>
 +            </reports>
 +          </reportSet>
 +        </reportSets>
 +        <configuration>
 +          <issueLinkTemplate>%URL%/%ISSUE%</issueLinkTemplate>
 +          <useJql>true</useJql>
 +          <component>Plugins</component>
 +        </configuration>
 +      </plugin>
 +      <plugin>
 +        <groupId>org.apache.maven.plugins</groupId>
 +        <artifactId>maven-checkstyle-plugin</artifactId>
 +        <version>${checkstyle.plugin.version}</version>
 +        <configuration>
 +          
<!--<propertiesLocation>${vfs.parent.dir}/checkstyle.properties</propertiesLocation>
 -->
 +          <configLocation>${log4jParentDir}/checkstyle.xml</configLocation>
 +          
<suppressionsLocation>${log4jParentDir}/checkstyle-suppressions.xml</suppressionsLocation>
 +          <enableRulesSummary>false</enableRulesSummary>
 +          <propertyExpansion>basedir=${basedir}</propertyExpansion>
 +          
<propertyExpansion>licensedir=${log4jParentDir}/checkstyle-header.txt</propertyExpansion>
 +        </configuration>
 +      </plugin>
 +      <plugin>
 +        <groupId>org.apache.maven.plugins</groupId>
 +        <artifactId>maven-javadoc-plugin</artifactId>
 +        <version>${javadoc.plugin.version}</version>
 +        <configuration>
 +          <failOnError>false</failOnError>
 +          <source>8</source>
 +          <bottom><![CDATA[<p align="center">Copyright &#169; 
{inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br />
 +            Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather 
logo, the Apache Logging project logo,
 +            and the Apache Log4j logo are trademarks of The Apache Software 
Foundation.</p>]]></bottom>
 +          <!-- module link generation is completely broken in the javadoc 
plugin for a multi-module non-aggregating
 +               project -->
 +          <additionalparam>${javadoc.opts}</additionalparam>
 +          <detectOfflineLinks>false</detectOfflineLinks>
 +          <linksource>true</linksource>
 +          <links>
 +            <link>http://docs.oracle.com/javaee/6/api/</link>
 +            <link>http://www.osgi.org/javadoc/r4v43/core/</link>
 +            
<link>https://commons.apache.org/proper/commons-lang/javadocs/api-release/</link>
 +          </links>
 +          <groups>
 +            <group>
 +              <title>Core API</title>
 +              <packages>org.apache.logging.log4j.core</packages>
 +            </group>
 +            <group>
 +              <title>Configuration</title>
 +              
<packages>org.apache.logging.log4j.core.config*:org.apache.logging.log4j.core.selector</packages>
 +            </group>
 +            <group>
 +              <title>Core Plugins</title>
 +              
<packages>org.apache.logging.log4j.core.appender*:org.apache.logging.log4j.core.filter:org.apache.logging.log4j.core.layout:org.apache.logging.log4j.core.lookup:org.apache.logging.log4j.core.pattern:org.apache.logging.log4j.core.script</packages>
 +            </group>
 +            <group>
 +              <title>Tools</title>
 +              
<packages>org.apache.logging.log4j.core.net*:org.apache.logging.log4j.core.tools</packages>
 +            </group>
 +            <group>
 +              <title>Internals</title>
 +              
<packages>org.apache.logging.log4j.core.async:org.apache.logging.log4j.core.impl:org.apache.logging.log4j.core.util*:org.apache.logging.log4j.core.osgi:org.apache.logging.log4j.core.jackson:org.apache.logging.log4j.core.jmx</packages>
 +            </group>
 +          </groups>
 +        </configuration>
 +        <reportSets>
 +          <reportSet>
 +            <id>non-aggregate</id>
 +            <reports>
 +              <report>javadoc</report>
 +            </reports>
 +          </reportSet>
 +        </reportSets>
 +      </plugin>
 +      <plugin>
 +        <groupId>com.github.spotbugs</groupId>
 +        <artifactId>spotbugs-maven-plugin</artifactId>
 +      </plugin>
 +      <plugin>
 +        <groupId>org.apache.maven.plugins</groupId>
 +        <artifactId>maven-jxr-plugin</artifactId>
 +        <version>${jxr.plugin.version}</version>
 +        <reportSets>
 +          <reportSet>
 +            <id>non-aggregate</id>
 +            <reports>
 +              <report>jxr</report>
 +            </reports>
 +          </reportSet>
 +          <reportSet>
 +            <id>aggregate</id>
 +            <reports>
 +              <report>aggregate</report>
 +            </reports>
 +          </reportSet>
 +        </reportSets>
 +      </plugin>
 +      <plugin>
 +        <groupId>org.apache.maven.plugins</groupId>
 +        <artifactId>maven-pmd-plugin</artifactId>
 +        <version>${pmd.plugin.version}</version>
 +        <configuration>
 +          <targetJdk>${maven.compiler.target}</targetJdk>
 +        </configuration>
 +      </plugin>
 +    </plugins>
 +  </reporting>
 +</project>
 +
diff --cc 
log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/FakePlugin.java
index 48ea7dc,0000000..d257e51
mode 100644,000000..100644
--- 
a/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/FakePlugin.java
+++ 
b/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/FakePlugin.java
@@@ -1,33 -1,0 +1,33 @@@
 +/*
 + * 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.plugins.processor;
++package org.apache.logging.log4j.plugins.test.validation;
 +
 +import org.apache.logging.log4j.plugins.Plugin;
 +import org.apache.logging.log4j.plugins.PluginAliases;
 +
 +/**
 + * Test plugin class for unit tests.
 + */
 +@Plugin(name = "Fake", category = "Test")
 +@PluginAliases({"AnotherFake", "StillFake"})
 +public class FakePlugin {
 +
 +    @Plugin(name = "Nested", category = "Test")
 +    public static class Nested {
 +    }
 +}
diff --cc 
log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/ProductionBean.java
index 0000000,51cd722..51cd722
mode 000000,100644..100644
--- 
a/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/ProductionBean.java
+++ 
b/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/ProductionBean.java
diff --cc log4j-plugins-test/src/main/java9/module-info.java
index 6884125,0000000..885afe6
mode 100644,000000..100644
--- a/log4j-plugins-test/src/main/java9/module-info.java
+++ b/log4j-plugins-test/src/main/java9/module-info.java
@@@ -1,24 -1,0 +1,25 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one or more
 + * contributor license agreements. See the NOTICE file distributed with
 + * this work for additional information regarding copyright ownership.
 + * The ASF licenses this file to You under the Apache license, Version 2.0
 + * (the "License"); you may not use this file except in compliance with
 + * the License. You may obtain a copy of the License at
 + *
 + *      http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS,
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 + * See the license for the specific language governing permissions and
 + * limitations under the license.
 + */
 +module org.apache.logging.log4j.plugins.test {
 +    exports org.apache.logging.log4j.plugins.test.validation;
 +
 +    requires org.apache.logging.log4j;
 +    requires org.apache.logging.log4j.plugins;
 +
 +    provides org.apache.logging.log4j.plugins.processor.PluginService with 
org.apache.logging.log4j.plugins.test.validation.plugins.Log4jPlugins;
++    provides org.apache.logging.log4j.plugins.di.model.PluginModule with 
org.apache.logging.log4j.plugins.test.validation.plugins.Log4jModule;
 +}
diff --cc 
log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugin/processor/BeanProcessorTest.java
index 0000000,0000000..624c803
new file mode 100644
--- /dev/null
+++ 
b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugin/processor/BeanProcessorTest.java
@@@ -1,0 -1,0 +1,51 @@@
++/*
++ * 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.plugin.processor;
++
++import org.apache.logging.log4j.plugins.di.model.DisposesMethod;
++import org.apache.logging.log4j.plugins.di.model.GenericPlugin;
++import org.apache.logging.log4j.plugins.di.model.InjectionTarget;
++import org.apache.logging.log4j.plugins.di.model.PluginModule;
++import org.apache.logging.log4j.plugins.di.model.ProducerField;
++import org.apache.logging.log4j.plugins.test.validation.FakePlugin;
++import org.apache.logging.log4j.plugins.test.validation.plugins.Log4jModule;
++import org.junit.jupiter.api.Test;
++
++import static org.junit.jupiter.api.Assertions.assertTrue;
++
++class BeanProcessorTest {
++    @Test
++    void smokeTests() {
++        final PluginModule module = new Log4jModule();
++        final var plugins = module.getPluginSources();
++        assertTrue(plugins.stream().anyMatch(plugin -> plugin instanceof 
InjectionTarget &&
++                
plugin.getDeclaringClassName().equals("org.apache.logging.log4j.plugins.test.validation.ExampleBean")));
++        assertTrue(plugins.stream().anyMatch(plugin -> plugin instanceof 
InjectionTarget &&
++                
plugin.getDeclaringClassName().equals("org.apache.logging.log4j.plugins.test.validation.ImplicitBean")));
++        assertTrue(plugins.stream().anyMatch(plugin -> plugin instanceof 
InjectionTarget &&
++                
plugin.getDeclaringClassName().equals("org.apache.logging.log4j.plugins.test.validation.ImplicitMethodBean")));
++        assertTrue(plugins.stream().anyMatch(plugin -> plugin instanceof 
InjectionTarget &&
++                
plugin.getDeclaringClassName().equals("org.apache.logging.log4j.plugins.test.validation.ProductionBean$Builder")));
++        assertTrue(plugins.stream().anyMatch(plugin -> plugin instanceof 
ProducerField &&
++                
plugin.getDeclaringClassName().equals("org.apache.logging.log4j.plugins.test.validation.ProductionBean")));
++        assertTrue(plugins.stream().anyMatch(plugin -> plugin instanceof 
DisposesMethod &&
++                
plugin.getDeclaringClassName().equals("org.apache.logging.log4j.plugins.test.validation.ProductionBean")));
++        assertTrue(plugins.stream().anyMatch(plugin -> plugin instanceof 
GenericPlugin &&
++                
plugin.getDeclaringClassName().equals(FakePlugin.class.getName())));
++    }
++}
diff --cc 
log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugin/processor/PluginProcessorTest.java
index 7a0a7fe,0000000..8d8f02e
mode 100644,000000..100644
--- 
a/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugin/processor/PluginProcessorTest.java
+++ 
b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugin/processor/PluginProcessorTest.java
@@@ -1,112 -1,0 +1,111 @@@
 +/*
 + * 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.plugin.processor;
 +
 +import org.apache.logging.log4j.plugins.Plugin;
 +import org.apache.logging.log4j.plugins.PluginAliases;
- import org.apache.logging.log4j.plugins.processor.FakePlugin;
 +import org.apache.logging.log4j.plugins.processor.PluginEntry;
 +import org.apache.logging.log4j.plugins.processor.PluginService;
++import org.apache.logging.log4j.plugins.test.validation.FakePlugin;
++import org.apache.logging.log4j.plugins.test.validation.plugins.Log4jPlugins;
 +import org.apache.logging.log4j.plugins.util.PluginType;
 +import org.junit.BeforeClass;
 +import org.junit.Test;
 +import org.junit.runner.RunWith;
 +import org.junit.runners.JUnit4;
 +
 +import java.util.List;
 +
 +import static org.junit.Assert.*;
 +
 +@RunWith(JUnit4.class)
 +public class PluginProcessorTest {
 +
 +    private static PluginService pluginService;
 +
 +    private final Plugin p = FakePlugin.class.getAnnotation(Plugin.class);
 +
 +    @BeforeClass
-     public static void setUpClass() throws Exception {
-         Class<?> clazz = 
PluginProcessor.class.getClassLoader().loadClass("org.apache.logging.log4j.plugins.plugins.Log4jPlugins");
-         assertNotNull("Could not locate plugins class", clazz);
-         pluginService = (PluginService) 
clazz.getDeclaredConstructor().newInstance();;
++    public static void setUpClass() {
++        pluginService = new Log4jPlugins();
 +    }
 +
 +    @Test
 +    public void testTestCategoryFound() throws Exception {
 +        assertNotNull("No plugin annotation on FakePlugin.", p);
 +        final List<PluginType<?>> testCategory = 
pluginService.getCategory(p.category());
 +        assertNotEquals("No plugins were found.", 0, pluginService.size());
 +        assertNotNull("The category '" + p.category() + "' was not found.", 
testCategory);
 +        assertFalse(testCategory.isEmpty());
 +    }
 +
 +    @Test
 +    public void testFakePluginFoundWithCorrectInformation() throws Exception {
 +        final List<PluginType<?>> list = 
pluginService.getCategory(p.category());
 +        assertNotNull(list);
 +        final PluginEntry fake = getEntry(list, p.name());
 +        assertNotNull(fake);
 +        verifyFakePluginEntry(p.name(), fake);
 +    }
 +
 +    @Test
 +    public void testFakePluginAliasesContainSameInformation() throws 
Exception {
 +        final PluginAliases aliases = 
FakePlugin.class.getAnnotation(PluginAliases.class);
 +        for (final String alias : aliases.value()) {
 +            final List<PluginType<?>> list = 
pluginService.getCategory(p.category());
 +            assertNotNull(list);
 +            final PluginEntry fake = getEntry(list, alias);
 +            assertNotNull(fake);
 +            verifyFakePluginEntry(alias, fake);
 +        }
 +    }
 +
 +    private void verifyFakePluginEntry(final String name, final PluginEntry 
fake) {
 +        assertNotNull("The plugin '" + name.toLowerCase() + "' was not 
found.", fake);
 +        assertEquals(FakePlugin.class.getName(), fake.getClassName());
 +        assertEquals(name.toLowerCase(), fake.getKey());
 +        assertEquals(Plugin.EMPTY, p.elementType());
 +        assertEquals(name, fake.getName());
 +        assertEquals(p.printObject(), fake.isPrintable());
 +        assertEquals(p.deferChildren(), fake.isDefer());
 +    }
 +
 +    @Test
 +    public void testNestedPlugin() throws Exception {
 +        final Plugin p = FakePlugin.Nested.class.getAnnotation(Plugin.class);
 +        final List<PluginType<?>> list = 
pluginService.getCategory(p.category());
 +        assertNotNull(list);
 +        final PluginEntry nested = getEntry(list, p.name());
 +        assertNotNull(nested);
 +        assertEquals(p.name().toLowerCase(), nested.getKey());
 +        assertEquals(FakePlugin.Nested.class.getName(), 
nested.getClassName());
 +        assertEquals(p.name(), nested.getName());
 +        assertEquals(Plugin.EMPTY, p.elementType());
 +        assertEquals(p.printObject(), nested.isPrintable());
 +        assertEquals(p.deferChildren(), nested.isDefer());
 +    }
 +
 +    private PluginEntry getEntry(List<PluginType<?>> list, String name) {
 +        for (PluginType<?> type : list) {
 +            if (type.getPluginEntry().getName().equalsIgnoreCase(name)) {
 +                return type.getPluginEntry();
 +            }
 +        }
 +        return null;
 +    }
 +}
diff --cc 
log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/convert/TypeConverterRegistryTest.java
index 008c7bd,0000000..bb37ba8
mode 100644,000000..100644
--- 
a/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/convert/TypeConverterRegistryTest.java
+++ 
b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/convert/TypeConverterRegistryTest.java
@@@ -1,160 -1,0 +1,162 @@@
 +/*
 + * 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.plugins.convert;
 +
 +import org.apache.logging.log4j.plugins.Plugin;
- import org.junit.Test;
++import org.junit.jupiter.api.Test;
 +
++import static org.hamcrest.MatcherAssert.*;
 +import static org.hamcrest.Matchers.instanceOf;
- import static org.junit.Assert.*;
++import static org.junit.jupiter.api.Assertions.*;
 +
 +public class TypeConverterRegistryTest {
 +
-     @Test(expected = NullPointerException.class)
++    @Test
 +    public void testFindNullConverter() {
-         TypeConverterRegistry.getInstance().findCompatibleConverter(null);
++        assertThrows(NullPointerException.class,
++                () -> 
TypeConverterRegistry.getInstance().findCompatibleConverter(null));
 +    }
 +
 +    @Test
 +    public void testFindBooleanConverter() throws Exception {
 +        final TypeConverter<?> converter = 
TypeConverterRegistry.getInstance().findCompatibleConverter(Boolean.class);
 +        assertNotNull(converter);
 +        assertTrue((Boolean) converter.convert("TRUE"));
 +    }
 +
 +    @Test
 +    public void testFindPrimitiveBooleanConverter() throws Exception {
 +        final TypeConverter<?> converter = 
TypeConverterRegistry.getInstance().findCompatibleConverter(Boolean.TYPE);
 +        assertNotNull(converter);
 +        assertTrue((Boolean) converter.convert("tRUe"));
 +    }
 +
 +    @SuppressWarnings("unchecked")
 +    @Test
 +    public void testFindCharSequenceConverterUsingStringConverter() throws 
Exception {
 +        final TypeConverter<CharSequence> converter = 
(TypeConverter<CharSequence>)
 +            
TypeConverterRegistry.getInstance().findCompatibleConverter(CharSequence.class);
 +        assertNotNull(converter);
 +        assertThat(converter, 
instanceOf(TypeConverters.StringConverter.class));
 +        final CharSequence expected = "This is a test sequence of characters";
 +        final CharSequence actual = converter.convert(expected.toString());
 +        assertEquals(expected, actual);
 +    }
 +
 +    @SuppressWarnings("unchecked")
 +    @Test
 +    public void testFindNumberConverter() throws Exception {
 +        final TypeConverter<Number> numberTypeConverter = 
(TypeConverter<Number>)
 +            
TypeConverterRegistry.getInstance().findCompatibleConverter(Number.class);
 +        assertNotNull(numberTypeConverter);
 +        // TODO: is there a specific converter this should return?
 +    }
 +
 +    public enum Foo {
 +        I, PITY, THE
 +    }
 +
 +    @SuppressWarnings("unchecked")
 +    @Test
 +    public void testFindEnumConverter() throws Exception {
 +        final TypeConverter<Foo> fooTypeConverter = (TypeConverter<Foo>)
 +            
TypeConverterRegistry.getInstance().findCompatibleConverter(Foo.class);
 +        assertNotNull(fooTypeConverter);
 +        assertEquals(Foo.I, fooTypeConverter.convert("i"));
 +        assertEquals(Foo.PITY, fooTypeConverter.convert("pity"));
 +        assertEquals(Foo.THE, fooTypeConverter.convert("THE"));
 +    }
 +
 +    public static final class CustomTestClass1 {
 +
 +        private CustomTestClass1() {}
 +
 +    }
 +
 +    @Plugin(name = "CustomTestClass1Converter1", category = 
TypeConverters.CATEGORY)
 +    public static final class CustomTestClass1Converter1
 +            implements TypeConverter<CustomTestClass1> {
 +
 +        @Override
 +        public CustomTestClass1 convert(final String ignored) {
 +            return new CustomTestClass1();
 +        }
 +
 +    }
 +
 +    @SuppressWarnings("ComparableType")
 +    @Plugin(name = "CustomTestClass1Converter2", category = 
TypeConverters.CATEGORY)
 +    public static final class CustomTestClass1Converter2
 +            implements TypeConverter<CustomTestClass1>, 
Comparable<TypeConverter<?>> {
 +
 +        @Override
 +        public CustomTestClass1 convert(final String ignored) {
 +            return new CustomTestClass1();
 +        }
 +
 +        @Override
 +        public int compareTo(@SuppressWarnings("NullableProblems") final 
TypeConverter<?> converter) {
 +            return -1;
 +        }
 +
 +    }
 +
 +    @Test
 +    public void testMultipleComparableConverters() {
 +        final TypeConverter<?> converter = TypeConverterRegistry
 +                .getInstance()
 +                .findCompatibleConverter(CustomTestClass1.class);
 +        assertThat(converter, instanceOf(CustomTestClass1Converter2.class));
 +    }
 +
 +    public static final class CustomTestClass2 {
 +
 +        private CustomTestClass2() {}
 +
 +    }
 +
 +    @Plugin(name = "CustomTestClass2Converter1", category = 
TypeConverters.CATEGORY)
 +    public static final class CustomTestClass2Converter1
 +            implements TypeConverter<CustomTestClass2> {
 +
 +        @Override
 +        public CustomTestClass2 convert(final String ignored) {
 +            return new CustomTestClass2();
 +        }
 +
 +    }
 +
 +    @Plugin(name = "CustomTestClass2Converter2", category = 
TypeConverters.CATEGORY)
 +    public static final class CustomTestClass2Converter2
 +            implements TypeConverter<CustomTestClass2> {
 +
 +        @Override
 +        public CustomTestClass2 convert(final String ignored) {
 +            return new CustomTestClass2();
 +        }
 +
 +    }
 +
 +    @Test
 +    public void testMultipleIncomparableConverters() {
 +        final TypeConverter<?> converter = TypeConverterRegistry
 +                .getInstance()
 +                .findCompatibleConverter(CustomTestClass2.class);
 +        assertThat(converter, instanceOf(CustomTestClass2Converter1.class));
 +    }
 +
 +}
diff --cc log4j-plugins/src/main/java/module-info.java
index 37820a4,0000000..20f1f17
mode 100644,000000..100644
--- a/log4j-plugins/src/main/java/module-info.java
+++ b/log4j-plugins/src/main/java/module-info.java
@@@ -1,34 -1,0 +1,36 @@@
 +/*
 + * 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.
 + */
 +module org.apache.logging.log4j.plugins {
 +    exports org.apache.logging.log4j.plugins;
 +    exports org.apache.logging.log4j.plugins.convert;
++    exports org.apache.logging.log4j.plugins.di;
++    exports org.apache.logging.log4j.plugins.di.model;
 +    exports org.apache.logging.log4j.plugins.processor;
 +    exports org.apache.logging.log4j.plugins.util;
 +    exports org.apache.logging.log4j.plugins.validation;
 +    exports org.apache.logging.log4j.plugins.validation.constraints;
 +    exports org.apache.logging.log4j.plugins.validation.validators;
 +    exports org.apache.logging.log4j.plugins.bind;
 +    exports org.apache.logging.log4j.plugins.inject;
 +    exports org.apache.logging.log4j.plugins.name;
 +
-     requires java.compiler;
 +    requires org.apache.logging.log4j;
 +    requires transitive org.osgi.framework;
 +
 +    uses org.apache.logging.log4j.plugins.processor.PluginService;
++    provides org.apache.logging.log4j.plugins.processor.PluginService with 
org.apache.logging.log4j.plugins.convert.plugins.Log4jPlugins;
 +}

Reply via email to