This is an automated email from the ASF dual-hosted git repository.
mattsicker pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
The following commit(s) were added to refs/heads/main by this push:
new 313a18800d Pull out common Injector code and add hierarchical lookup
313a18800d is described below
commit 313a18800d9c3f716b48e516736ced1f7e9b3d0b
Author: Matt Sicker <[email protected]>
AuthorDate: Sat Mar 18 19:33:18 2023 -0500
Pull out common Injector code and add hierarchical lookup
This is in preparation for breaking up the Injector API and DefaultInjector
into more digestible chunks.
- Extract TypeConverterFactory for simpler registration of type converters
- Create DependencyChain interface for tracking the set of dependency keys
during instance creation
- Create HierarchicalMap for supporting hierarchical lookups of bindings
- Make Binding and InjectionPoint public
- Extract private inner classes into package-private classes
Signed-off-by: Matt Sicker <[email protected]>
---
.../logging/log4j/plugins/di/InjectorTest.java | 7 +-
.../plugins/convert/TypeConverterFactory.java | 143 +++++++++
.../apache/logging/log4j/plugins/di/Binding.java | 49 ++-
.../logging/log4j/plugins/di/BindingMap.java | 74 -----
.../logging/log4j/plugins/di/DefaultInjector.java | 336 +++++----------------
.../plugins/di/{Binding.java => DefaultScope.java} | 25 +-
.../logging/log4j/plugins/di/DependencyChain.java | 53 ++++
.../logging/log4j/plugins/di/DependencyChains.java | 132 ++++++++
.../logging/log4j/plugins/di/InjectionPoint.java | 11 +-
.../di/{Binding.java => SingletonScope.java} | 28 +-
.../log4j/plugins/internal/util/BeanUtils.java | 99 ++++++
.../internal/util/HierarchicalCollections.java | 195 ++++++++++++
.../util/{BeanUtils.java => HierarchicalMap.java} | 27 +-
pom.xml | 2 +-
14 files changed, 772 insertions(+), 409 deletions(-)
diff --git
a/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/di/InjectorTest.java
b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/di/InjectorTest.java
index 5166d69346..f3903acb94 100644
---
a/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/di/InjectorTest.java
+++
b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/di/InjectorTest.java
@@ -289,7 +289,7 @@ class InjectorTest {
void unknownInstanceError() {
final Key<UnknownInstance> key = new Key<>() {};
assertThatThrownBy(() -> DI.createInjector().getInstance(key))
- .hasMessage("No @Inject constructors or no-arg constructor
found for " + key);
+ .hasMessage("No @Inject constructor or default constructor
found for " + key);
}
@Test
@@ -483,7 +483,10 @@ class InjectorTest {
final Injector injector = DI.createInjector(new UppercaseBundle());
final Function<String, String> function =
injector.getInstance(Keys.SUBSTITUTOR_KEY);
assertThat(function.apply("foo")).isEqualTo("FOO");
- assertThatThrownBy(() ->
injector.getInstance(Function.class)).hasMessageContaining("No @Inject
constructors");
+ final Key<Function<String, String>> unqualifiedFunctionKey = new
Key<>() {};
+ assertThatThrownBy(() -> injector.getInstance(unqualifiedFunctionKey))
+ .isInstanceOf(NotInjectableException.class)
+ .hasMessage("No @Inject constructor or default constructor
found for " + unqualifiedFunctionKey);
}
@Namespace("Test")
diff --git
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterFactory.java
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterFactory.java
new file mode 100644
index 0000000000..8332258268
--- /dev/null
+++
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterFactory.java
@@ -0,0 +1,143 @@
+/*
+ * 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 java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+import java.util.UnknownFormatConversionException;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.Inject;
+import org.apache.logging.log4j.plugins.Singleton;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.EnglishEnums;
+
+@Singleton
+public class TypeConverterFactory {
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private final Map<Type, TypeConverter<?>> typeConverters = new
ConcurrentHashMap<>();
+
+ @Inject
+ public TypeConverterFactory(@TypeConverters List<TypeConverter<?>>
typeConverters) {
+ typeConverters.forEach(converter ->
registerTypeConverter(getTypeConverterSupportedType(converter.getClass()),
converter));
+ registerTypeConverter(Boolean.class, Boolean::valueOf);
+ registerTypeAlias(Boolean.class, Boolean.TYPE);
+ registerTypeConverter(Byte.class, Byte::valueOf);
+ registerTypeAlias(Byte.class, Byte.TYPE);
+ registerTypeConverter(Character.class, s -> {
+ if (s.length() != 1) {
+ throw new IllegalArgumentException("Character string must be
of length 1: " + s);
+ }
+ return s.toCharArray()[0];
+ });
+ registerTypeAlias(Character.class, Character.TYPE);
+ registerTypeConverter(Double.class, Double::valueOf);
+ registerTypeAlias(Double.class, Double.TYPE);
+ registerTypeConverter(Float.class, Float::valueOf);
+ registerTypeAlias(Float.class, Float.TYPE);
+ registerTypeConverter(Integer.class, Integer::valueOf);
+ registerTypeAlias(Integer.class, Integer.TYPE);
+ registerTypeConverter(Long.class, Long::valueOf);
+ registerTypeAlias(Long.class, Long.TYPE);
+ registerTypeConverter(Short.class, Short::valueOf);
+ registerTypeAlias(Short.class, Short.TYPE);
+ registerTypeConverter(String.class, s -> s);
+ }
+
+ public TypeConverter<?> getTypeConverter(final Type type) {
+ final TypeConverter<?> primary = typeConverters.get(type);
+ // cached type converters
+ if (primary != null) {
+ return primary;
+ }
+ // dynamic enum support
+ if (type instanceof Class<?>) {
+ final Class<?> clazz = (Class<?>) type;
+ if (clazz.isEnum()) {
+ return registerTypeConverter(type, s ->
EnglishEnums.valueOf(clazz.asSubclass(Enum.class), s));
+ }
+ }
+ // look for compatible converters
+ for (final Map.Entry<Type, TypeConverter<?>> entry :
typeConverters.entrySet()) {
+ final Type key = entry.getKey();
+ if (TypeUtil.isAssignable(type, key)) {
+ LOGGER.debug("Found compatible TypeConverter<{}> for type
[{}].", key, type);
+ final TypeConverter<?> value = entry.getValue();
+ return registerTypeConverter(type, value);
+ }
+ }
+ throw new UnknownFormatConversionException(type.toString());
+ }
+
+ private void registerTypeAlias(final Type knownType, final Type aliasType)
{
+ final TypeConverter<?> converter = typeConverters.get(knownType);
+ if (converter != null) {
+ typeConverters.put(aliasType, converter);
+ } else {
+ LOGGER.error("Cannot locate type converter for {}", knownType);
+ }
+ }
+
+ private TypeConverter<?> registerTypeConverter(final Type type, final
TypeConverter<?> converter) {
+ final TypeConverter<?> conflictingConverter = typeConverters.get(type);
+ if (conflictingConverter != null) {
+ final boolean overridable;
+ if (converter instanceof Comparable) {
+ @SuppressWarnings("unchecked") final
Comparable<TypeConverter<?>> comparableConverter =
+ (Comparable<TypeConverter<?>>) converter;
+ overridable =
comparableConverter.compareTo(conflictingConverter) < 0;
+ } else if (conflictingConverter instanceof Comparable) {
+ @SuppressWarnings("unchecked") final
Comparable<TypeConverter<?>> comparableConflictingConverter =
+ (Comparable<TypeConverter<?>>) conflictingConverter;
+ overridable =
comparableConflictingConverter.compareTo(converter) > 0;
+ } else {
+ overridable = false;
+ }
+ if (overridable) {
+ LOGGER.debug(
+ "Replacing TypeConverter [{}] for type [{}] with [{}]
after comparison.",
+ conflictingConverter, type, converter);
+ typeConverters.put(type, converter);
+ return converter;
+ } else {
+ LOGGER.warn(
+ "Ignoring TypeConverter [{}] for type [{}] that
conflicts with [{}], since they are not comparable.",
+ converter, type, conflictingConverter);
+ return conflictingConverter;
+ }
+ } else {
+ typeConverters.put(type, converter);
+ return converter;
+ }
+ }
+
+ private static Type getTypeConverterSupportedType(final Class<?> clazz) {
+ for (final Type type : clazz.getGenericInterfaces()) {
+ if (type instanceof ParameterizedType) {
+ final ParameterizedType parameterizedType =
(ParameterizedType) type;
+ if (parameterizedType.getRawType() == TypeConverter.class) {
+ return parameterizedType.getActualTypeArguments()[0];
+ }
+ }
+ }
+ return Void.TYPE;
+ }
+}
diff --git
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Binding.java
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Binding.java
index 88e707fd9d..080d3f5cf6 100644
---
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Binding.java
+++
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Binding.java
@@ -14,29 +14,60 @@
* See the license for the specific language governing permissions and
* limitations under the license.
*/
-
package org.apache.logging.log4j.plugins.di;
import java.util.function.Supplier;
-class Binding<T> {
+import org.apache.logging.log4j.util.Lazy;
+
+/**
+ * Bindings combine a {@link Key} with a factory {@link Supplier} to get
instances of the key's type.
+ *
+ * @param <T> type of key to bind instance factory
+ */
+public final class Binding<T> implements Supplier<T> {
private final Key<T> key;
- private final Supplier<T> supplier;
+ private final Supplier<? extends T> factory;
- private Binding(final Key<T> key, final Supplier<T> supplier) {
+ private Binding(final Key<T> key, final Supplier<? extends T> factory) {
this.key = key;
- this.supplier = supplier;
+ this.factory = factory;
}
public Key<T> getKey() {
return key;
}
- public Supplier<T> getSupplier() {
- return supplier;
+ @Override
+ public T get() {
+ return factory.get();
+ }
+
+ public static <T> DSL<T> from(final Key<T> key) {
+ return new DSL<>(key);
}
- public static <T> Binding<T> bind(final Key<T> key, final Supplier<T>
supplier) {
- return new Binding<>(key, supplier);
+ public static <T> DSL<T> from(final Class<T> type) {
+ return new DSL<>(Key.forClass(type));
+ }
+
+ public static class DSL<T> {
+ private final Key<T> key;
+
+ private DSL(final Key<T> key) {
+ this.key = key;
+ }
+
+ public Binding<T> to(final Supplier<? extends T> factory) {
+ return new Binding<>(key, factory);
+ }
+
+ public Binding<T> toSingleton(final Supplier<? extends T> factory) {
+ return new Binding<>(key, Lazy.lazy(factory));
+ }
+
+ public Binding<T> toInstance(final T instance) {
+ return new Binding<>(key, () -> instance);
+ }
}
}
diff --git
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/BindingMap.java
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/BindingMap.java
deleted file mode 100644
index 21462eaaef..0000000000
---
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/BindingMap.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.di;
-
-import java.util.Collection;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Supplier;
-
-import org.apache.logging.log4j.util.Cast;
-
-class BindingMap {
- private final Map<Key<?>, Binding<?>> bindings;
-
- BindingMap() {
- bindings = new ConcurrentHashMap<>();
- }
-
- BindingMap(final BindingMap bindings) {
- this.bindings = new ConcurrentHashMap<>(bindings.bindings);
- }
-
- public <T> Binding<T> get(final Key<T> key) {
- return Cast.cast(bindings.get(key));
- }
-
- public <T> Binding<T> get(final Key<T> key, final Collection<String>
aliases) {
- var binding = bindings.get(key);
- if (binding == null) {
- for (final String alias : aliases) {
- binding = bindings.get(key.withName(alias));
- if (binding != null) {
- break;
- }
- }
- }
- return Cast.cast(binding);
- }
-
- public <T> void put(final Key<T> key, final Supplier<T> factory) {
- bindings.put(key, Binding.bind(key, factory));
- }
-
- /**
- * @see org.apache.logging.log4j.plugins.util.OrderedComparator
- */
- public <T> Supplier<T> merge(final Key<T> key, final Supplier<T> factory) {
- final Binding<?> newBinding = bindings.merge(key, Binding.bind(key,
factory), (oldBinding, binding) ->
- oldBinding.getKey().getOrder() <= binding.getKey().getOrder()
? oldBinding : binding);
- return Cast.cast(newBinding.getSupplier());
- }
-
- public <T> void bindIfAbsent(final Key<T> key, final Supplier<T> factory) {
- bindings.putIfAbsent(key, Binding.bind(key, factory));
- }
-
- public void remove(final Key<?> key) {
- bindings.remove(key);
- }
-}
diff --git
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java
index 0cde8a2e79..0c0f6264c1 100644
---
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java
+++
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java
@@ -23,7 +23,6 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
@@ -37,23 +36,21 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
-import java.util.UnknownFormatConversionException;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.plugins.FactoryType;
-import org.apache.logging.log4j.plugins.Inject;
import org.apache.logging.log4j.plugins.Node;
import org.apache.logging.log4j.plugins.PluginException;
-import org.apache.logging.log4j.plugins.QualifierType;
import org.apache.logging.log4j.plugins.ScopeType;
import org.apache.logging.log4j.plugins.Singleton;
import org.apache.logging.log4j.plugins.condition.Conditional;
import org.apache.logging.log4j.plugins.convert.TypeConverter;
-import org.apache.logging.log4j.plugins.convert.TypeConverters;
+import org.apache.logging.log4j.plugins.convert.TypeConverterFactory;
+import org.apache.logging.log4j.plugins.internal.util.BeanUtils;
+import org.apache.logging.log4j.plugins.internal.util.HierarchicalMap;
import org.apache.logging.log4j.plugins.model.PluginNamespace;
import org.apache.logging.log4j.plugins.model.PluginRegistry;
import org.apache.logging.log4j.plugins.model.PluginType;
@@ -66,7 +63,6 @@ import
org.apache.logging.log4j.plugins.validation.ConstraintValidator;
import org.apache.logging.log4j.plugins.visit.NodeVisitor;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.Cast;
-import org.apache.logging.log4j.util.EnglishEnums;
import org.apache.logging.log4j.util.Lazy;
import org.apache.logging.log4j.util.ServiceRegistry;
import org.apache.logging.log4j.util.StringBuilders;
@@ -76,21 +72,20 @@ class DefaultInjector implements Injector {
private static final Set<Class<?>> COLLECTION_INJECTION_TYPES = Set.of(
Collection.class, Iterable.class, List.class, Map.class,
Optional.class, Set.class, Stream.class);
- private final BindingMap bindingMap;
- private final Map<Class<? extends Annotation>, Scope> scopes = new
ConcurrentHashMap<>();
- private final Map<Type, TypeConverter<?>> typeConverters = new
ConcurrentHashMap<>();
+ private final HierarchicalMap<Key<?>, Binding<?>> bindings;
+ private final HierarchicalMap<Class<? extends Annotation>, Scope> scopes;
private ReflectionAccessor accessor = object -> object.setAccessible(true);
DefaultInjector() {
- bindingMap = new BindingMap();
- bindingMap.put(KEY, () -> this);
+ bindings = HierarchicalMap.newRootMap();
+ bindings.put(KEY, Binding.from(KEY).toInstance(this));
+ scopes = HierarchicalMap.newRootMap();
scopes.put(Singleton.class, new SingletonScope());
}
DefaultInjector(final DefaultInjector original) {
- bindingMap = new BindingMap(original.bindingMap);
- scopes.putAll(original.scopes);
- typeConverters.putAll(original.typeConverters);
+ bindings = original.bindings.newChildMap();
+ scopes = original.scopes.newChildMap();
accessor = original.accessor;
}
@@ -115,46 +110,17 @@ class DefaultInjector implements Injector {
@Override
public <T> Supplier<T> getFactory(final Key<T> key) {
- return getFactory(key, Set.of(), null, Set.of());
+ return getFactory(key, Set.of(), null, DependencyChain.empty());
}
@Override
public TypeConverter<?> getTypeConverter(final Type type) {
- if (typeConverters.isEmpty()) {
- synchronized (typeConverters) {
- if (typeConverters.isEmpty()) {
- LOGGER.trace("Initializing type converters");
- initializeTypeConverters();
- }
- }
- }
- final TypeConverter<?> primary = typeConverters.get(type);
- // cached type converters
- if (primary != null) {
- return primary;
- }
- // dynamic enum support
- if (type instanceof Class<?>) {
- final Class<?> clazz = (Class<?>) type;
- if (clazz.isEnum()) {
- return registerTypeConverter(type, s ->
EnglishEnums.valueOf(clazz.asSubclass(Enum.class), s));
- }
- }
- // look for compatible converters
- for (final Map.Entry<Type, TypeConverter<?>> entry :
typeConverters.entrySet()) {
- final Type key = entry.getKey();
- if (TypeUtil.isAssignable(type, key)) {
- LOGGER.debug("Found compatible TypeConverter<{}> for type
[{}].", key, type);
- final TypeConverter<?> value = entry.getValue();
- return registerTypeConverter(type, value);
- }
- }
- throw new UnknownFormatConversionException(type.toString());
+ return getInstance(TypeConverterFactory.class).getTypeConverter(type);
}
@Override
public void injectMembers(final Object instance) {
- injectMembers(Key.forClass(instance.getClass()), null, instance,
Set.of(), null);
+ injectMembers(Key.forClass(instance.getClass()), null, instance,
DependencyChain.empty(), null);
}
@Override
@@ -198,24 +164,24 @@ class DefaultInjector implements Injector {
@Override
public <T> Injector registerBinding(final Key<T> key, final Supplier<?
extends T> factory) {
- bindingMap.put(key, factory::get);
+ bindings.put(key, Binding.from(key).to(factory));
return this;
}
@Override
public <T> Injector registerBindingIfAbsent(final Key<T> key, final
Supplier<? extends T> factory) {
- bindingMap.bindIfAbsent(key, factory::get);
+ bindings.putIfAbsent(key, Binding.from(key).to(factory));
return this;
}
@Override
public void removeBinding(final Key<?> key) {
- bindingMap.remove(key);
+ bindings.remove(key);
}
@Override
public boolean hasBinding(final Key<?> key) {
- return bindingMap.get(key) != null;
+ return bindings.containsKey(key);
}
@Override
@@ -224,7 +190,7 @@ class DefaultInjector implements Injector {
}
private <T> Supplier<T> getFactory(
- final InjectionPoint<T> point, final Node node, final Set<Key<?>>
chain, final StringBuilder debugLog) {
+ final InjectionPoint<T> point, final Node node, final
DependencyChain chain, final StringBuilder debugLog) {
final AnnotatedElement element = point.getElement();
final Key<? extends NodeVisitor> visitorKey =
NodeVisitor.keyFor(element);
final NodeVisitor visitor = visitorKey != null ?
getInstance(visitorKey) : null;
@@ -238,14 +204,23 @@ class DefaultInjector implements Injector {
final Key<T> key = point.getKey();
final Collection<String> aliases = point.getAliases();
final Key<T> suppliedType = key.getSuppliedType();
- return suppliedType != null ? getFactory(suppliedType, aliases, node,
Set.of()) : getFactory(key, aliases, node, chain);
+ return suppliedType != null
+ ? getFactory(suppliedType, aliases, node,
DependencyChain.empty())
+ : getFactory(key, aliases, node, chain);
}
private <T> Supplier<T> getFactory(
- final Key<T> key, final Collection<String> aliases, final Node
node, final Set<Key<?>> chain) {
- final Binding<T> existing = bindingMap.get(key, aliases);
+ final Key<T> key, final Collection<String> aliases, final Node
node, final DependencyChain chain) {
+ final Binding<?> existing = bindings.get(key);
if (existing != null) {
- return existing.getSupplier();
+ return Cast.cast(existing);
+ }
+ for (final String alias : aliases) {
+ final Key<T> keyAlias = key.withName(alias);
+ final Binding<?> existingAlias = bindings.get(keyAlias);
+ if (existingAlias != null) {
+ return Cast.cast(existingAlias);
+ }
}
final Class<T> rawType = key.getRawType();
@@ -255,7 +230,7 @@ class DefaultInjector implements Injector {
if (rawType == PluginNamespace.class && !key.getNamespace().isEmpty())
{
final Key<PluginNamespace> pluginNamespaceKey = Cast.cast(key);
final Supplier<PluginNamespace> pluginNamespaceFactory =
createPluginNamespaceFactory(pluginNamespaceKey);
- return Cast.cast(bindingMap.merge(pluginNamespaceKey,
pluginNamespaceFactory));
+ return Cast.cast(merge(pluginNamespaceKey,
pluginNamespaceFactory));
}
// @Namespace Collection<T>/Map<String, T>/Stream<T>/etc. injection
@@ -264,23 +239,23 @@ class DefaultInjector implements Injector {
final Key<Stream<T>> streamKey = Cast.cast(key);
final Supplier<Stream<T>> streamFactory =
() ->
streamPluginInstancesFromNamespace(key.getParameterizedTypeArgument(0));
- return Cast.cast(bindingMap.merge(streamKey, streamFactory));
+ return Cast.cast(merge(streamKey, streamFactory));
} else if (Set.class.isAssignableFrom(rawType)) {
final Key<Set<T>> setKey = Cast.cast(key);
final Supplier<Set<T>> setFactory = () ->
getPluginSet(key.getParameterizedTypeArgument(0));
- return Cast.cast(bindingMap.merge(setKey, setFactory));
+ return Cast.cast(merge(setKey, setFactory));
} else if (Map.class.isAssignableFrom(rawType)) {
final Key<Map<String, T>> mapKey = Cast.cast(key);
final Supplier<Map<String, T>> mapFactory = () ->
getPluginMap(key.getParameterizedTypeArgument(1));
- return Cast.cast(bindingMap.merge(mapKey, mapFactory));
+ return Cast.cast(merge(mapKey, mapFactory));
} else if (Iterable.class.isAssignableFrom(rawType)) {
final Key<Iterable<T>> iterableKey = Cast.cast(key);
final Supplier<Iterable<T>> iterableFactory = () ->
getPluginList(key.getParameterizedTypeArgument(0));
- return Cast.cast(bindingMap.merge(iterableKey,
iterableFactory));
+ return Cast.cast(merge(iterableKey, iterableFactory));
} else if (Optional.class.isAssignableFrom(rawType)) {
final Key<Optional<T>> optionalKey = Cast.cast(key);
final Supplier<Optional<T>> optionalFactory = () ->
getOptionalPlugin(key.getParameterizedTypeArgument(0));
- return Cast.cast(bindingMap.merge(optionalKey,
optionalFactory));
+ return Cast.cast(merge(optionalKey, optionalFactory));
} else {
throw new InjectException("Cannot inject plugins into " + key);
}
@@ -291,17 +266,23 @@ class DefaultInjector implements Injector {
final Key<Optional<T>> optionalKey = Cast.cast(key);
final Supplier<Optional<T>> optionalFactory = () ->
getOptionalInstance(key.getParameterizedTypeArgument(0),
aliases, node, chain);
- return Cast.cast(bindingMap.merge(optionalKey, optionalFactory));
+ return Cast.cast(merge(optionalKey, optionalFactory));
}
// default namespace generic T injection
final Supplier<T> instanceSupplier = () -> {
final StringBuilder debugLog = new StringBuilder();
- final T instance = Cast.cast(getInjectableInstance(key, node,
chain, debugLog));
+ final T instance = getInjectableInstance(key, node, chain,
debugLog);
injectMembers(key, node, instance, chain, debugLog);
return instance;
};
- return bindingMap.merge(key, scope.get(key, instanceSupplier));
+ return merge(key, scope.get(key, instanceSupplier));
+ }
+
+ private <T> Supplier<T> merge(final Key<T> key, final Supplier<T> factory)
{
+ final Binding<?> binding = bindings.merge(key,
Binding.from(key).to(factory), (oldValue, value) ->
+ oldValue.getKey().getOrder() <= value.getKey().getOrder() ?
oldValue : value);
+ return Cast.cast(binding);
}
private Supplier<PluginNamespace> createPluginNamespaceFactory(final
Key<PluginNamespace> key) {
@@ -364,7 +345,7 @@ class DefaultInjector implements Injector {
}
private <T> Optional<T> getOptionalInstance(
- final Key<T> key, final Collection<String> aliases, final Node
node, final Set<Key<?>> chain) {
+ final Key<T> key, final Collection<String> aliases, final Node
node, final DependencyChain chain) {
try {
return Optional.ofNullable(getFactory(key, aliases, node,
chain).get());
} catch (final PluginException e) {
@@ -372,11 +353,11 @@ class DefaultInjector implements Injector {
}
}
- private Object getInjectableInstance(
- final Key<?> key, final Node node, final Set<Key<?>> chain, final
StringBuilder debugLog) {
- final Class<?> rawType = key.getRawType();
+ private <T> T getInjectableInstance(
+ final Key<T> key, final Node node, final DependencyChain chain,
final StringBuilder debugLog) {
+ final Class<T> rawType = key.getRawType();
validate(rawType, key.getName(), rawType);
- final Constructor<?> constructor = getInjectableConstructor(key,
chain);
+ final Constructor<T> constructor =
BeanUtils.getInjectableConstructor(key, chain);
final List<InjectionPoint<?>> points =
InjectionPoint.fromExecutable(constructor);
final var args = getArguments(key, node, points, chain, debugLog);
return accessor.newInstance(constructor, args);
@@ -400,77 +381,8 @@ class DefaultInjector implements Injector {
}
}
- private void initializeTypeConverters() {
- final List<TypeConverter<?>> converters = getPluginList(new
@TypeConverters Key<>() {});
- converters.forEach(converter ->
registerTypeConverter(getTypeConverterSupportedType(converter.getClass()),
converter));
- registerTypeConverter(Boolean.class, Boolean::valueOf);
- registerTypeAlias(Boolean.class, Boolean.TYPE);
- registerTypeConverter(Byte.class, Byte::valueOf);
- registerTypeAlias(Byte.class, Byte.TYPE);
- registerTypeConverter(Character.class, s -> {
- if (s.length() != 1) {
- throw new IllegalArgumentException("Character string must be
of length 1: " + s);
- }
- return s.toCharArray()[0];
- });
- registerTypeAlias(Character.class, Character.TYPE);
- registerTypeConverter(Double.class, Double::valueOf);
- registerTypeAlias(Double.class, Double.TYPE);
- registerTypeConverter(Float.class, Float::valueOf);
- registerTypeAlias(Float.class, Float.TYPE);
- registerTypeConverter(Integer.class, Integer::valueOf);
- registerTypeAlias(Integer.class, Integer.TYPE);
- registerTypeConverter(Long.class, Long::valueOf);
- registerTypeAlias(Long.class, Long.TYPE);
- registerTypeConverter(Short.class, Short::valueOf);
- registerTypeAlias(Short.class, Short.TYPE);
- registerTypeConverter(String.class, s -> s);
- }
-
- private TypeConverter<?> registerTypeConverter(final Type type, final
TypeConverter<?> converter) {
- final TypeConverter<?> conflictingConverter = typeConverters.get(type);
- if (conflictingConverter != null) {
- final boolean overridable;
- if (converter instanceof Comparable) {
- @SuppressWarnings("unchecked") final
Comparable<TypeConverter<?>> comparableConverter =
- (Comparable<TypeConverter<?>>) converter;
- overridable =
comparableConverter.compareTo(conflictingConverter) < 0;
- } else if (conflictingConverter instanceof Comparable) {
- @SuppressWarnings("unchecked") final
Comparable<TypeConverter<?>> comparableConflictingConverter =
- (Comparable<TypeConverter<?>>) conflictingConverter;
- overridable =
comparableConflictingConverter.compareTo(converter) > 0;
- } else {
- overridable = false;
- }
- if (overridable) {
- LOGGER.debug(
- "Replacing TypeConverter [{}] for type [{}] with [{}]
after comparison.",
- conflictingConverter, type, converter);
- typeConverters.put(type, converter);
- return converter;
- } else {
- LOGGER.warn(
- "Ignoring TypeConverter [{}] for type [{}] that
conflicts with [{}], since they are not comparable.",
- converter, type, conflictingConverter);
- return conflictingConverter;
- }
- } else {
- typeConverters.put(type, converter);
- return converter;
- }
- }
-
- private void registerTypeAlias(final Type knownType, final Type aliasType)
{
- final TypeConverter<?> converter = typeConverters.get(knownType);
- if (converter != null) {
- typeConverters.put(aliasType, converter);
- } else {
- LOGGER.error("Cannot locate type converter for {}", knownType);
- }
- }
-
private void injectMembers(
- final Key<?> key, final Node node, final Object instance, final
Set<Key<?>> chain, final StringBuilder debugLog) {
+ final Key<?> key, final Node node, final Object instance, final
DependencyChain chain, final StringBuilder debugLog) {
injectFields(key.getRawType(), node, instance, debugLog);
injectMethods(key, node, instance, chain, debugLog);
}
@@ -478,7 +390,7 @@ class DefaultInjector implements Injector {
private void injectFields(final Class<?> rawType, final Node node, final
Object instance, final StringBuilder debugLog) {
for (Class<?> clazz = rawType; clazz != Object.class; clazz =
clazz.getSuperclass()) {
for (final Field field : clazz.getDeclaredFields()) {
- if (isInjectable(field)) {
+ if (BeanUtils.isInjectable(field)) {
injectField(field, node, instance, debugLog);
}
}
@@ -487,7 +399,7 @@ class DefaultInjector implements Injector {
private <T> void injectField(final Field field, final Node node, final
Object instance, final StringBuilder debugLog) {
final InjectionPoint<T> point = InjectionPoint.forField(field);
- final Supplier<T> factory = getFactory(point, node, Set.of(),
debugLog);
+ final Supplier<T> factory = getFactory(point, node,
DependencyChain.empty(), debugLog);
final Key<T> key = point.getKey();
final Object value = key.getRawType() == Supplier.class ? factory :
factory.get();
if (value != null) {
@@ -500,12 +412,12 @@ class DefaultInjector implements Injector {
}
private void injectMethods(
- final Key<?> key, final Node node, final Object instance, final
Set<Key<?>> chain, final StringBuilder debugLog) {
+ final Key<?> key, final Node node, final Object instance, final
DependencyChain chain, final StringBuilder debugLog) {
final Class<?> rawType = key.getRawType();
final List<Method> injectMethodsWithNoArgs = new ArrayList<>();
for (Class<?> clazz = rawType; clazz != Object.class; clazz =
clazz.getSuperclass()) {
for (final Method method : clazz.getDeclaredMethods()) {
- if (isInjectable(method)) {
+ if (BeanUtils.isInjectable(method)) {
accessor.makeAccessible(method, instance);
if (method.getParameterCount() == 0) {
injectMethodsWithNoArgs.add(method);
@@ -544,7 +456,7 @@ class DefaultInjector implements Injector {
final Object instance = getInjectablePluginInstance(node,
debugLog);
if (instance instanceof Supplier<?>) {
// configure plugin builder class and obtain plugin from that
- injectMembers(Key.forClass(instance.getClass()), node,
instance, Set.of(), debugLog);
+ injectMembers(Key.forClass(instance.getClass()), node,
instance, DependencyChain.empty(), debugLog);
node.setObject(((Supplier<?>) instance).get());
} else {
// usually created via static plugin factory method, but
otherwise assume this is the final plugin instance
@@ -567,12 +479,12 @@ class DefaultInjector implements Injector {
.allMatch(condition -> condition.matches(key, rawType))) {
return null;
}
- final Executable factory = getInjectablePluginFactory(rawType);
+ final Executable factory = BeanUtils.getInjectableFactory(rawType);
final List<InjectionPoint<?>> points =
InjectionPoint.fromExecutable(factory);
if (!factory.canAccess(null)) {
accessor.makeAccessible(factory);
}
- final var args = getArguments(key, node, points, Set.of(), debugLog);
+ final var args = getArguments(key, node, points,
DependencyChain.empty(), debugLog);
if (factory instanceof Method) {
return accessor.invokeMethod((Method) factory, null, args);
} else {
@@ -593,7 +505,7 @@ class DefaultInjector implements Injector {
final var bindings = createMethodBindings(bundle,
method);
if (!bindings.isEmpty()) {
providerMethods.add(method);
- bindings.forEach(binding ->
bindingMap.merge(binding.getKey(), binding.getSupplier()));
+ bindings.forEach(binding ->
merge(binding.getKey(), binding));
}
}
});
@@ -611,7 +523,7 @@ class DefaultInjector implements Injector {
return List.of();
}
final List<InjectionPoint<?>> points =
InjectionPoint.fromExecutable(method);
- final var argumentFactories = getArgumentFactories(primaryKey, null,
points, Set.of(primaryKey), null);
+ final var argumentFactories = getArgumentFactories(primaryKey, null,
points, DependencyChain.of(primaryKey), null);
final Supplier<T> unscoped = () -> {
final var args = argumentFactories.entrySet()
.stream()
@@ -628,15 +540,15 @@ class DefaultInjector implements Injector {
final Supplier<T> factory = getScopeForMethod(method).get(primaryKey,
unscoped);
final Collection<String> aliases = Keys.getAliases(method);
final List<Binding<T>> bindings = new ArrayList<>(1 + aliases.size());
- bindings.add(Binding.bind(primaryKey, factory));
+ bindings.add(Binding.from(primaryKey).to(factory));
for (final String alias : aliases) {
- bindings.add(Binding.bind(primaryKey.withName(alias), factory));
+ bindings.add(Binding.from(primaryKey.withName(alias)).to(factory));
}
return bindings;
}
private Object[] getArguments(
- final Key<?> key, final Node node, final List<InjectionPoint<?>>
points, final Set<Key<?>> chain,
+ final Key<?> key, final Node node, final List<InjectionPoint<?>>
points, final DependencyChain chain,
final StringBuilder debugLog) {
return getArgumentFactories(key, node, points, chain, debugLog)
.entrySet()
@@ -652,7 +564,7 @@ class DefaultInjector implements Injector {
}
private Map<Parameter, Supplier<?>> getArgumentFactories(
- final Key<?> key, final Node node, final List<InjectionPoint<?>>
points, final Set<Key<?>> chain,
+ final Key<?> key, final Node node, final List<InjectionPoint<?>>
points, final DependencyChain chain,
final StringBuilder debugLog) {
final Map<Parameter, Supplier<?>> argFactories = new LinkedHashMap<>();
for (final InjectionPoint<?> point : points) {
@@ -661,14 +573,9 @@ class DefaultInjector implements Injector {
if (parameterKey.getRawType().equals(Supplier.class)) {
argFactories.put(parameter, () -> getFactory(point, node,
chain, debugLog));
} else {
- final var newChain = chain(chain, key);
- if (newChain.contains(parameterKey)) {
- final StringBuilder sb = new StringBuilder("Circular
dependency encountered: ");
- for (final Key<?> chainKey : newChain) {
- sb.append(chainKey).append(" -> ");
- }
- sb.append(parameterKey);
- throw new InjectException(sb.toString());
+ final var newChain = chain.withDependency(key);
+ if (newChain.hasDependency(parameterKey)) {
+ throw new CircularDependencyException(parameterKey,
newChain);
}
argFactories.put(parameter, () -> getFactory(point, node,
newChain, debugLog).get());
}
@@ -707,18 +614,6 @@ class DefaultInjector implements Injector {
((ConstraintValidator) validator).initialize(annotation);
}
- private static Type getTypeConverterSupportedType(final Class<?> clazz) {
- for (final Type type : clazz.getGenericInterfaces()) {
- if (type instanceof ParameterizedType) {
- final ParameterizedType parameterizedType =
(ParameterizedType) type;
- if (parameterizedType.getRawType() == TypeConverter.class) {
- return parameterizedType.getActualTypeArguments()[0];
- }
- }
- }
- return Void.TYPE;
- }
-
private static void verifyAttributesConsumed(final Node node) {
final Map<String, String> attrs = node.getAttributes();
if (!attrs.isEmpty()) {
@@ -751,99 +646,4 @@ class DefaultInjector implements Injector {
}
}
}
-
- private static Set<Key<?>> chain(final Set<Key<?>> chain, final Key<?>
newKey) {
- if (chain == null || chain.isEmpty()) {
- return Set.of(newKey);
- }
- final var newChain = new LinkedHashSet<>(chain);
- newChain.add(newKey);
- return newChain;
- }
-
- private static Executable getInjectablePluginFactory(final Class<?>
pluginClass) {
- return Stream.of(pluginClass.getDeclaredMethods())
- .filter(method -> Modifier.isStatic(method.getModifiers()) &&
- AnnotationUtil.isMetaAnnotationPresent(method,
FactoryType.class))
-
.min(Comparator.comparingInt(Method::getParameterCount).thenComparing(Method::getReturnType,
(c1, c2) -> {
- if (c1.equals(c2)) {
- return 0;
- } else if (Supplier.class.isAssignableFrom(c1)) {
- return -1;
- } else if (Supplier.class.isAssignableFrom(c2)) {
- return 1;
- } else {
- return c1.getName().compareTo(c2.getName());
- }
- }))
- .map(Executable.class::cast)
- .orElseGet(() ->
getInjectableConstructor(Key.forClass(pluginClass), Set.of()));
- }
-
- private static <T> Constructor<T> getInjectableConstructor(final Key<T>
key, final Set<Key<?>> chain) {
- final Class<T> rawType = key.getRawType();
- final List<Constructor<?>> injectConstructors =
Stream.of(rawType.getDeclaredConstructors())
- .filter(constructor ->
constructor.isAnnotationPresent(Inject.class))
- .collect(Collectors.toList());
- if (injectConstructors.size() > 1) {
- throw new InjectException("Multiple @Inject constructors found in
" + rawType);
- }
- if (injectConstructors.size() == 1) {
- return Cast.cast(injectConstructors.get(0));
- }
- try {
- return rawType.getDeclaredConstructor();
- } catch (final NoSuchMethodException ignored) {
- }
- try {
- return rawType.getConstructor();
- } catch (final NoSuchMethodException ignored) {
- }
- final List<Key<?>> keys = new ArrayList<>(chain);
- keys.add(0, key);
- final String prefix = chain.isEmpty() ? "" : "chain ";
- final String keysToString =
- prefix +
keys.stream().map(Key::toString).collect(Collectors.joining(" -> "));
- throw new InjectException(
- "No @Inject constructors or no-arg constructor found for " +
keysToString);
- }
-
- private static boolean isInjectable(final Field field) {
- return field.isAnnotationPresent(Inject.class) ||
AnnotationUtil.isMetaAnnotationPresent(field, QualifierType.class);
- }
-
- private static boolean isInjectable(final Method method) {
- return method.isAnnotationPresent(Inject.class) ||
- !AnnotationUtil.isMetaAnnotationPresent(method,
FactoryType.class) &&
- Stream.of(method.getParameters()).anyMatch(
- parameter ->
AnnotationUtil.isMetaAnnotationPresent(parameter, QualifierType.class));
- }
-
- private static class SingletonScope implements Scope {
- private final Map<Key<?>, Supplier<?>> singletonProviders = new
ConcurrentHashMap<>();
-
- @Override
- public <T> Supplier<T> get(final Key<T> key, final Supplier<T>
unscoped) {
- return Cast.cast(singletonProviders.computeIfAbsent(key, ignored
-> Lazy.lazy(unscoped)::value));
- }
-
- @Override
- public String toString() {
- return "[SingletonScope; size=" + singletonProviders.size() + "]";
- }
- }
-
- private enum DefaultScope implements Scope {
- INSTANCE;
-
- @Override
- public <T> Supplier<T> get(final Key<T> key, final Supplier<T>
unscoped) {
- return unscoped;
- }
-
- @Override
- public String toString() {
- return "[Unscoped]";
- }
- }
}
diff --git
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Binding.java
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultScope.java
similarity index 65%
copy from
log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Binding.java
copy to
log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultScope.java
index 88e707fd9d..5699c7f8b3 100644
---
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Binding.java
+++
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultScope.java
@@ -14,29 +14,20 @@
* See the license for the specific language governing permissions and
* limitations under the license.
*/
-
package org.apache.logging.log4j.plugins.di;
import java.util.function.Supplier;
-class Binding<T> {
- private final Key<T> key;
- private final Supplier<T> supplier;
-
- private Binding(final Key<T> key, final Supplier<T> supplier) {
- this.key = key;
- this.supplier = supplier;
- }
-
- public Key<T> getKey() {
- return key;
- }
+enum DefaultScope implements Scope {
+ INSTANCE;
- public Supplier<T> getSupplier() {
- return supplier;
+ @Override
+ public <T> Supplier<T> get(final Key<T> key, final Supplier<T> unscoped) {
+ return unscoped;
}
- public static <T> Binding<T> bind(final Key<T> key, final Supplier<T>
supplier) {
- return new Binding<>(key, supplier);
+ @Override
+ public String toString() {
+ return "[Unscoped]";
}
}
diff --git
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DependencyChain.java
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DependencyChain.java
new file mode 100644
index 0000000000..2751d05dbe
--- /dev/null
+++
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DependencyChain.java
@@ -0,0 +1,53 @@
+/*
+ * 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.di;
+
+/**
+ * Immutable chain of {@linkplain Key dependency keys} assembled while
constructing dependency-injected
+ * instances.
+ */
+public interface DependencyChain extends Iterable<Key<?>> {
+ /**
+ * Indicates if the given key is contained in this dependency chain.
+ */
+ boolean hasDependency(final Key<?> key);
+
+ /**
+ * Returns a new dependency chain containing the provided key as an
additional dependency.
+ * If the given key already exists, then it will not be added to the chain.
+ */
+ DependencyChain withDependency(final Key<?> key);
+
+ /**
+ * Indicates whether this dependency chain is empty.
+ */
+ boolean isEmpty();
+
+ /**
+ * Returns an empty dependency chain.
+ */
+ static DependencyChain empty() {
+ return DependencyChains.EMPTY;
+ }
+
+ /**
+ * Returns a dependency chain containing a single key.
+ */
+ static DependencyChain of(final Key<?> key) {
+ return DependencyChains.singleton(key);
+ }
+}
diff --git
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DependencyChains.java
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DependencyChains.java
new file mode 100644
index 0000000000..750ce81724
--- /dev/null
+++
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DependencyChains.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.plugins.di;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Objects;
+
+class DependencyChains {
+ static final DependencyChain EMPTY = new EmptyChain();
+
+ static DependencyChain singleton(final Key<?> key) {
+ return new LinkedChain(key);
+ }
+
+ private static final class EmptyChain implements DependencyChain {
+ @Override
+ public boolean hasDependency(final Key<?> key) {
+ return false;
+ }
+
+ @Override
+ public DependencyChain withDependency(final Key<?> key) {
+ return new LinkedChain(key);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return true;
+ }
+
+ @Override
+ public Iterator<Key<?>> iterator() {
+ return Collections.emptyIterator();
+ }
+
+ @Override
+ public int hashCode() {
+ return 1;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ return this == o || o != null && getClass() == o.getClass();
+ }
+ }
+
+ private static final class LinkedChain implements DependencyChain {
+ private final Key<?> head;
+ private final LinkedChain tail;
+
+ private LinkedChain(final Key<?> head) {
+ this(head, null);
+ }
+
+ private LinkedChain(final Key<?> head, final LinkedChain tail) {
+ this.head = head;
+ this.tail = tail;
+ }
+
+ @Override
+ public boolean hasDependency(final Key<?> key) {
+ return key.equals(head) || tail != null && tail.hasDependency(key);
+ }
+
+ @Override
+ public LinkedChain withDependency(final Key<?> key) {
+ if (key.equals(head)) {
+ return this;
+ }
+ final LinkedChain newTail = tail != null ?
tail.withDependency(key) : new LinkedChain(key);
+ return new LinkedChain(head, newTail);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public Iterator<Key<?>> iterator() {
+ return new Iter(this);
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ final LinkedChain keys = (LinkedChain) o;
+ return head.equals(keys.head) && Objects.equals(tail, keys.tail);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(head, tail);
+ }
+
+ private static class Iter implements Iterator<Key<?>> {
+ private LinkedChain current;
+
+ private Iter(final LinkedChain current) {
+ this.current = current;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return current != null;
+ }
+
+ @Override
+ public Key<?> next() {
+ final Key<?> head = current.head;
+ current = current.tail;
+ return head;
+ }
+ }
+ }
+}
diff --git
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/InjectionPoint.java
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/InjectionPoint.java
index 56e3aebc9f..0c0b8c48ed 100644
---
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/InjectionPoint.java
+++
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/InjectionPoint.java
@@ -14,7 +14,6 @@
* See the license for the specific language governing permissions and
* limitations under the license.
*/
-
package org.apache.logging.log4j.plugins.di;
import java.lang.reflect.AnnotatedElement;
@@ -27,13 +26,13 @@ import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-class InjectionPoint<T> {
+public class InjectionPoint<T> {
private final Key<T> key;
private final Collection<String> aliases;
private final Member member;
private final AnnotatedElement element;
- InjectionPoint(final Key<T> key, final Collection<String> aliases, final
Member member, final AnnotatedElement element) {
+ private InjectionPoint(final Key<T> key, final Collection<String> aliases,
final Member member, final AnnotatedElement element) {
this.key = key;
this.aliases = aliases;
this.member = member;
@@ -56,19 +55,19 @@ class InjectionPoint<T> {
return element;
}
- static <T> InjectionPoint<T> forField(final Field field) {
+ public static <T> InjectionPoint<T> forField(final Field field) {
final Key<T> key = Key.forField(field);
final Collection<String> aliases = Keys.getAliases(field);
return new InjectionPoint<>(key, aliases, field, field);
}
- static <T> InjectionPoint<T> forParameter(final Executable executable,
final Parameter parameter) {
+ public static <T> InjectionPoint<T> forParameter(final Executable
executable, final Parameter parameter) {
final Key<T> key = Key.forParameter(parameter);
final Collection<String> aliases = Keys.getAliases(parameter);
return new InjectionPoint<>(key, aliases, executable, parameter);
}
- static List<InjectionPoint<?>> fromExecutable(final Executable executable)
{
+ public static List<InjectionPoint<?>> fromExecutable(final Executable
executable) {
return Stream.of(executable.getParameters())
.map(parameter -> forParameter(executable, parameter))
.collect(Collectors.toList());
diff --git
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Binding.java
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/SingletonScope.java
similarity index 59%
copy from
log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Binding.java
copy to
log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/SingletonScope.java
index 88e707fd9d..f8c9cdaa7f 100644
---
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Binding.java
+++
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/SingletonScope.java
@@ -14,29 +14,25 @@
* See the license for the specific language governing permissions and
* limitations under the license.
*/
-
package org.apache.logging.log4j.plugins.di;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
-class Binding<T> {
- private final Key<T> key;
- private final Supplier<T> supplier;
-
- private Binding(final Key<T> key, final Supplier<T> supplier) {
- this.key = key;
- this.supplier = supplier;
- }
+import org.apache.logging.log4j.util.Cast;
+import org.apache.logging.log4j.util.Lazy;
- public Key<T> getKey() {
- return key;
- }
+class SingletonScope implements Scope {
+ private final Map<Key<?>, Supplier<?>> singletonProviders = new
ConcurrentHashMap<>();
- public Supplier<T> getSupplier() {
- return supplier;
+ @Override
+ public <T> Supplier<T> get(final Key<T> key, final Supplier<T> unscoped) {
+ return Cast.cast(singletonProviders.computeIfAbsent(key, ignored ->
Lazy.lazy(unscoped)::value));
}
- public static <T> Binding<T> bind(final Key<T> key, final Supplier<T>
supplier) {
- return new Binding<>(key, supplier);
+ @Override
+ public String toString() {
+ return "[SingletonScope; size=" + singletonProviders.size() + "]";
}
}
diff --git
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/BeanUtils.java
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/BeanUtils.java
index 2b98f3bdf2..f8078a57ec 100644
---
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/BeanUtils.java
+++
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/BeanUtils.java
@@ -16,9 +16,33 @@
*/
package org.apache.logging.log4j.plugins.internal.util;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Parameter;
+import java.util.Comparator;
+import java.util.List;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.logging.log4j.plugins.FactoryType;
+import org.apache.logging.log4j.plugins.Inject;
+import org.apache.logging.log4j.plugins.QualifierType;
+import org.apache.logging.log4j.plugins.di.AmbiguousInjectConstructorException;
+import org.apache.logging.log4j.plugins.di.DependencyChain;
+import org.apache.logging.log4j.plugins.di.Key;
+import org.apache.logging.log4j.plugins.di.NotInjectableException;
+import org.apache.logging.log4j.plugins.util.AnnotationUtil;
+import org.apache.logging.log4j.util.Cast;
+import org.apache.logging.log4j.util.InternalApi;
+
/**
* Utility methods.
*/
+@InternalApi
public final class BeanUtils {
private BeanUtils() {
}
@@ -34,4 +58,79 @@ public final class BeanUtils {
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
+
+ public static Executable getInjectableFactory(final Class<?> clazz) {
+ return Stream.of(clazz.getDeclaredMethods())
+ .filter(method -> Modifier.isStatic(method.getModifiers()) &&
+ AnnotationUtil.isMetaAnnotationPresent(method,
FactoryType.class))
+
.min(Comparator.comparingInt(Method::getParameterCount).thenComparing(Method::getReturnType,
(c1, c2) -> {
+ if (c1.equals(c2)) {
+ return 0;
+ } else if (Supplier.class.isAssignableFrom(c1)) {
+ return -1;
+ } else if (Supplier.class.isAssignableFrom(c2)) {
+ return 1;
+ } else {
+ return c1.getName().compareTo(c2.getName());
+ }
+ }))
+ .map(Executable.class::cast)
+ .orElseGet(() -> getInjectableConstructor(clazz));
+ }
+
+ public static <T> Constructor<T> getInjectableConstructor(final Class<T>
clazz) {
+ final Constructor<T> constructor = findInjectableConstructor(clazz);
+ if (constructor == null) {
+ throw new NotInjectableException(clazz);
+ }
+ return constructor;
+ }
+
+ public static <T> Constructor<T> getInjectableConstructor(final Key<T>
key, final DependencyChain chain) {
+ final Constructor<T> constructor =
findInjectableConstructor(key.getRawType());
+ if (constructor == null) {
+ throw new NotInjectableException(key, chain);
+ }
+ return constructor;
+ }
+
+ public static boolean isInjectable(final Field field) {
+ return field.isAnnotationPresent(Inject.class) ||
AnnotationUtil.isMetaAnnotationPresent(field, QualifierType.class);
+ }
+
+ public static boolean isInjectable(final Method method) {
+ if (method.isAnnotationPresent(Inject.class)) {
+ return true;
+ }
+ if (!AnnotationUtil.isMetaAnnotationPresent(method,
FactoryType.class)) {
+ for (final Parameter parameter : method.getParameters()) {
+ if (AnnotationUtil.isMetaAnnotationPresent(parameter,
QualifierType.class)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static <T> Constructor<T> findInjectableConstructor(final Class<T>
clazz) {
+ final List<Constructor<?>> constructors =
Stream.of(clazz.getDeclaredConstructors())
+ .filter(constructor ->
constructor.isAnnotationPresent(Inject.class))
+ .collect(Collectors.toList());
+ final int size = constructors.size();
+ if (size > 1) {
+ throw new AmbiguousInjectConstructorException(clazz);
+ }
+ if (size == 1) {
+ return Cast.cast(constructors.get(0));
+ }
+ try {
+ return clazz.getDeclaredConstructor();
+ } catch (final NoSuchMethodException ignored) {
+ }
+ try {
+ return clazz.getConstructor();
+ } catch (final NoSuchMethodException ignored) {
+ }
+ return null;
+ }
}
diff --git
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/HierarchicalCollections.java
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/HierarchicalCollections.java
new file mode 100644
index 0000000000..ad47c27427
--- /dev/null
+++
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/HierarchicalCollections.java
@@ -0,0 +1,195 @@
+/*
+ * 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.internal.util;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+class HierarchicalCollections {
+ static <K, V> HierarchicalMap<K, V> newRootMap() {
+ return new RootMap<>();
+ }
+
+ private static class RootMap<K, V> extends AbstractMap<K, V> implements
HierarchicalMap<K, V> {
+ private final Map<K, V> map = new LinkedHashMap<>();
+
+ @Override
+ public Set<Entry<K, V>> entrySet() {
+ return map.entrySet();
+ }
+
+ @Override
+ public V put(final K key, final V value) {
+ return map.put(key, value);
+ }
+
+ @Override
+ public boolean containsKey(final Object key) {
+ return map.containsKey(key);
+ }
+
+ @Override
+ public boolean containsLocalKey(final K key) {
+ return map.containsKey(key);
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return map.keySet();
+ }
+
+ @Override
+ public HierarchicalMap<K, V> newChildMap() {
+ return new ChildMap<>(this);
+ }
+ }
+
+ private static class ChildMap<K, V> extends RootMap<K, V> {
+ private final Map<K, V> parent;
+
+ private ChildMap(final Map<K, V> parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public Set<Entry<K, V>> entrySet() {
+ return new HierarchicalSet<>(super.entrySet(), parent.entrySet());
+ }
+
+ @Override
+ public boolean containsKey(final Object key) {
+ return super.containsKey(key) || parent.containsKey(key);
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return new HierarchicalSet<>(super.keySet(), parent.keySet());
+ }
+
+ @Override
+ public HierarchicalMap<K, V> newChildMap() {
+ return new ChildMap<>(this);
+ }
+ }
+
+ private static class HierarchicalSet<E> extends AbstractSet<E> {
+ private final Set<E> delegate;
+ private final Set<E> parent;
+
+ private HierarchicalSet(final Set<E> delegate, final Set<E> parent) {
+ this.delegate = delegate;
+ this.parent = parent;
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ return new HierarchicalIterator<>(delegate.iterator(),
parent.iterator());
+ }
+
+ @Override
+ public int size() {
+ return delegate.size() + parent.size();
+ }
+
+ @Override
+ public boolean contains(final Object o) {
+ return delegate.contains(o) || parent.contains(o);
+ }
+ }
+
+ private static class HierarchicalIterator<E> implements Iterator<E> {
+ private final Iterator<E> delegate;
+ private final Iterator<E> parent;
+ private State state;
+
+ private HierarchicalIterator(final Iterator<E> delegate, final
Iterator<E> parent) {
+ this.delegate = delegate;
+ this.parent = parent;
+ this.state = State.ITERATING_DELEGATE;
+ }
+
+ @Override
+ public boolean hasNext() {
+ switch (state) {
+ case ITERATING_DELEGATE:
+ if (delegate.hasNext()) {
+ return true;
+ }
+ state = State.ITERATING_PARENT;
+
+ case ITERATING_PARENT:
+ if (parent.hasNext()) {
+ return true;
+ }
+ state = State.DONE;
+
+ case DONE:
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public E next() {
+ switch (state) {
+ case ITERATING_DELEGATE:
+ if (delegate.hasNext()) {
+ return delegate.next();
+ }
+ state = State.ITERATING_PARENT;
+
+ case ITERATING_PARENT:
+ if (parent.hasNext()) {
+ return parent.next();
+ }
+ state = State.DONE;
+
+ case DONE:
+ default:
+ throw new NoSuchElementException("Completed iteration");
+ }
+ }
+
+ @Override
+ public void remove() {
+ switch (state) {
+ case ITERATING_DELEGATE:
+ delegate.remove();
+ break;
+
+ case ITERATING_PARENT:
+ parent.remove();
+ break;
+
+ case DONE:
+ default:
+ throw new IllegalStateException("Completed iteration");
+ }
+ }
+
+ private enum State {
+ ITERATING_DELEGATE,
+ ITERATING_PARENT,
+ DONE
+ }
+ }
+}
diff --git
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/BeanUtils.java
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/HierarchicalMap.java
similarity index 64%
copy from
log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/BeanUtils.java
copy to
log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/HierarchicalMap.java
index 2b98f3bdf2..f6db5d9d91 100644
---
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/BeanUtils.java
+++
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/HierarchicalMap.java
@@ -16,22 +16,17 @@
*/
package org.apache.logging.log4j.plugins.internal.util;
-/**
- * Utility methods.
- */
-public final class BeanUtils {
- private BeanUtils() {
- }
+import java.util.Map;
+
+import org.apache.logging.log4j.util.InternalApi;
+
+@InternalApi
+public interface HierarchicalMap<K, V> extends Map<K, V> {
+ boolean containsLocalKey(final K key);
+
+ HierarchicalMap<K, V> newChildMap();
- public static String decapitalize(String string) {
- if (string.isEmpty()) {
- return string;
- }
- char[] chars = string.toCharArray();
- if (chars.length >= 2 && Character.isUpperCase(chars[0]) &&
Character.isUpperCase(chars[1])) {
- return string;
- }
- chars[0] = Character.toLowerCase(chars[0]);
- return new String(chars);
+ static <K, V> HierarchicalMap<K, V> newRootMap() {
+ return HierarchicalCollections.newRootMap();
}
}
diff --git a/pom.xml b/pom.xml
index 28d806a62a..786d2c52aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1700,10 +1700,10 @@
</configuration>
<executions>
<execution>
- <phase>validate</phase>
<goals>
<goal>validate</goal>
</goals>
+ <phase>validate</phase>
</execution>
</executions>
</plugin>