You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by ma...@apache.org on 2020/02/23 23:52:04 UTC
[logging-log4j2] 02/03: Add v3 dependency injection SPI
This is an automated email from the ASF dual-hosted git repository.
mattsicker pushed a commit to branch mean-bean-machine
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit bc2e7674e21f24a2a7cec1a5c7b95ade843ce6cc
Author: Matt Sicker <bo...@gmail.com>
AuthorDate: Sun Feb 23 17:46:40 2020 -0600
Add v3 dependency injection SPI
Signed-off-by: Matt Sicker <bo...@gmail.com>
---
.../log4j/plugins/spi/AmbiguousBeanException.java | 28 ++
.../log4j/plugins/spi/IllegalProductException.java | 28 ++
.../log4j/plugins/spi/InitializationException.java | 28 ++
.../log4j/plugins/spi/InjectionException.java | 31 ++
.../log4j/plugins/spi/ResolutionException.java | 28 ++
.../plugins/spi/UnsatisfiedBeanException.java | 32 ++
.../log4j/plugins/spi/ValidationException.java | 27 ++
.../logging/log4j/plugins/spi/bean/Bean.java | 33 ++
.../log4j/plugins/spi/bean/BeanManager.java | 101 ++++++
.../log4j/plugins/spi/bean/InjectionTarget.java | 36 +++
.../plugins/spi/bean/InjectionTargetFactory.java | 22 ++
.../logging/log4j/plugins/spi/bean/Producer.java | 38 +++
.../log4j/plugins/spi/bean/ProducerFactory.java | 23 ++
.../log4j/plugins/spi/model/ElementManager.java | 159 +++++++++
.../log4j/plugins/spi/model/InjectionPoint.java | 59 ++++
.../logging/log4j/plugins/spi/model/MetaClass.java | 42 +++
.../log4j/plugins/spi/model/MetaConstructor.java | 22 ++
.../log4j/plugins/spi/model/MetaElement.java | 61 ++++
.../log4j/plugins/spi/model/MetaExecutable.java | 24 ++
.../logging/log4j/plugins/spi/model/MetaField.java | 24 ++
.../log4j/plugins/spi/model/MetaMember.java | 26 ++
.../log4j/plugins/spi/model/MetaMethod.java | 22 ++
.../log4j/plugins/spi/model/MetaParameter.java | 21 ++
.../logging/log4j/plugins/spi/model/Qualifier.java | 103 ++++++
.../logging/log4j/plugins/spi/model/Variable.java | 41 +++
.../plugins/spi/scope/InitializationContext.java | 49 +++
.../log4j/plugins/spi/scope/ScopeContext.java | 63 ++++
.../logging/log4j/plugins/spi/scope/Scoped.java | 58 ++++
.../logging/log4j/plugins/util/TypeUtil.java | 359 +++++++++++++++++++--
29 files changed, 1570 insertions(+), 18 deletions(-)
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/AmbiguousBeanException.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/AmbiguousBeanException.java
new file mode 100644
index 0000000..1342920
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/AmbiguousBeanException.java
@@ -0,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.
+ */
+
+package org.apache.logging.log4j.plugins.spi;
+
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+
+import java.util.Collection;
+
+public class AmbiguousBeanException extends ResolutionException {
+ public AmbiguousBeanException(final Collection<? extends Bean<?>> beans, final String target) {
+ super("Found " + beans.size() + " ambiguous beans for " + target + ": " + beans);
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/IllegalProductException.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/IllegalProductException.java
new file mode 100644
index 0000000..8d1fc7d3
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/IllegalProductException.java
@@ -0,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.
+ */
+
+package org.apache.logging.log4j.plugins.spi;
+
+public class IllegalProductException extends InjectionException {
+ public IllegalProductException(final String message) {
+ super(message);
+ }
+
+ public IllegalProductException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/InitializationException.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/InitializationException.java
new file mode 100644
index 0000000..8c11613
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/InitializationException.java
@@ -0,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.
+ */
+
+package org.apache.logging.log4j.plugins.spi;
+
+public class InitializationException extends InjectionException {
+ public InitializationException(final String message) {
+ super(message);
+ }
+
+ public InitializationException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/InjectionException.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/InjectionException.java
new file mode 100644
index 0000000..853afa9
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/InjectionException.java
@@ -0,0 +1,31 @@
+/*
+ * 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.spi;
+
+// TODO: create more exception types such as:
+// invalid use of annotations
+// constraint validation errors
+public class InjectionException extends RuntimeException {
+ public InjectionException(final String message) {
+ super(message);
+ }
+
+ public InjectionException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/ResolutionException.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/ResolutionException.java
new file mode 100644
index 0000000..84617cb
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/ResolutionException.java
@@ -0,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.
+ */
+
+package org.apache.logging.log4j.plugins.spi;
+
+public class ResolutionException extends InjectionException {
+ public ResolutionException(final String message) {
+ super(message);
+ }
+
+ public ResolutionException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/UnsatisfiedBeanException.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/UnsatisfiedBeanException.java
new file mode 100644
index 0000000..3b17bf6
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/UnsatisfiedBeanException.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.plugins.spi;
+
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+
+import java.lang.reflect.Type;
+
+public class UnsatisfiedBeanException extends ResolutionException {
+ public UnsatisfiedBeanException(final InjectionPoint<?> point) {
+ super("No beans found for " + point);
+ }
+
+ public UnsatisfiedBeanException(final Type type) {
+ super("No beans found for type " + type);
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/ValidationException.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/ValidationException.java
new file mode 100644
index 0000000..1498d1f
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/ValidationException.java
@@ -0,0 +1,27 @@
+/*
+ * 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.spi;
+
+import java.util.List;
+
+public class ValidationException extends InjectionException {
+ public ValidationException(final List<Throwable> validationErrors) {
+ super("Found " + validationErrors.size() + " error(s) in bean deployment. See suppressed exceptions for details.");
+ validationErrors.forEach(this::addSuppressed);
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/Bean.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/Bean.java
new file mode 100644
index 0000000..831a4a8
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/Bean.java
@@ -0,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.spi.bean;
+
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.model.MetaClass;
+import org.apache.logging.log4j.plugins.spi.model.Variable;
+import org.apache.logging.log4j.plugins.spi.scope.Scoped;
+
+import java.util.Collection;
+
+public interface Bean<T> extends Scoped<T>, Variable<T> {
+ Collection<InjectionPoint<?>> getInjectionPoints();
+
+ // for a managed bean: that class
+ // for a producer field or producer method: the declaring class
+ MetaClass<?> getDeclaringClass();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/BeanManager.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/BeanManager.java
new file mode 100644
index 0000000..d767d50
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/BeanManager.java
@@ -0,0 +1,101 @@
+/*
+ * 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.spi.bean;
+
+import org.apache.logging.log4j.plugins.spi.ValidationException;
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+import org.apache.logging.log4j.plugins.spi.scope.Scoped;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Optional;
+
+/**
+ * Central SPI for injecting and managing beans and their instances.
+ */
+public interface BeanManager extends AutoCloseable {
+
+ /**
+ * Loads beans from the given classes. This looks for injectable classes and producers, validates them, registers
+ * them in this manager, then returns the loaded beans.
+ *
+ * @param beanClasses classes to load beans from
+ * @return beans loaded from the given classes
+ */
+ Collection<Bean<?>> loadBeans(final Collection<Class<?>> beanClasses);
+
+ /**
+ * Loads beans from the given classes. This looks for injectable classes and producers, validates them, registers
+ * them in this manager, then returns the loaded beans.
+ *
+ * @param beanClasses classes to load beans from
+ * @return beans loaded from the given classes
+ */
+ default Collection<Bean<?>> loadBeans(final Class<?>... beanClasses) {
+ return loadBeans(Arrays.asList(beanClasses));
+ }
+
+ /**
+ * Validates beans and throws a {@link ValidationException} if there are any errors.
+ *
+ * @param beans beans to check for validation errors
+ */
+ void validateBeans(final Iterable<Bean<?>> beans);
+
+ // TODO: re-add query methods for beans as needed
+// Collection<Bean<?>> getBeans();
+
+ /**
+ * Creates an InitializationContext for a given Scoped instance for use in dependency injection SPIs.
+ *
+ * @param scoped scoped object to create an initialization context for
+ * @param <T> type of object created by scope
+ * @return new InitializationContext for the given Scoped
+ */
+ <T> InitializationContext<T> createInitializationContext(final Scoped<T> scoped);
+
+ /**
+ * Gets or creates the value for a given bean inside a given InitializationContext.
+ *
+ * @param bean bean to get or create value for
+ * @param parentContext which context this bean is being used in
+ * @param <T> type of value
+ * @return value of the bean in the given context
+ */
+ <T> T getValue(final Bean<T> bean, final InitializationContext<?> parentContext);
+
+ /**
+ * Gets the value to use for injecting into a given InjectionPoint in a given InitializationContext.
+ *
+ * @param point location where injectable value would be injected
+ * @param parentContext which context this value is being injected under
+ * @param <T> type of injectable value
+ * @return value to inject if defined or empty otherwise
+ */
+ <T> Optional<T> getInjectableValue(final InjectionPoint<T> point, final InitializationContext<?> parentContext);
+
+ @Override
+ void close();
+
+ // TODO: integrate with constraint validators
+ // TODO: integrate with TypeConverters
+ // TODO: need some sort of default value strategy to bridge over @PluginAttribute and optional injected values
+ // TODO: need to support @PluginAliases still
+ // TODO: add support for injecting collections and arrays
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/InjectionTarget.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/InjectionTarget.java
new file mode 100644
index 0000000..df5c616
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/InjectionTarget.java
@@ -0,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.
+ */
+
+package org.apache.logging.log4j.plugins.spi.bean;
+
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+
+public interface InjectionTarget<T> extends Producer<T> {
+ // sets values of injected fields and calls initializer methods
+ void inject(final T instance, final InitializationContext<T> context);
+
+ // calls @PostConstruct from top to bottom
+ void postConstruct(final T instance);
+
+ // calls @PreDestroy from bottom to top
+ void preDestroy(final T instance);
+
+ @Override
+ default void dispose(T instance) {
+ // @Disposes only applies to @Produces methods and fields
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/InjectionTargetFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/InjectionTargetFactory.java
new file mode 100644
index 0000000..d5ee0df
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/InjectionTargetFactory.java
@@ -0,0 +1,22 @@
+/*
+ * 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.spi.bean;
+
+public interface InjectionTargetFactory<T> {
+ InjectionTarget<T> createInjectionTarget(final Bean<T> bean);
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/Producer.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/Producer.java
new file mode 100644
index 0000000..6a6d4a0
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/Producer.java
@@ -0,0 +1,38 @@
+/*
+ * 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.spi.bean;
+
+import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
+
+import java.util.Collection;
+
+public interface Producer<T> {
+ // for a class: calls @Inject constructor or default constructor
+ // for a producer method, this is invoked on that instance
+ // for a producer field, the value is gotten from that instance
+ T produce(InitializationContext<T> context);
+
+ // for a class, no-op (preDestroy is relevant there instead)
+ // for a producer method or producer field, calls the disposer method
+ void dispose(T instance);
+
+ // for a class: injected fields, @Inject constructor parameters, and initializer method params
+ // for a producer method: method params
+ Collection<InjectionPoint<?>> getInjectionPoints();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/ProducerFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/ProducerFactory.java
new file mode 100644
index 0000000..c266224
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/bean/ProducerFactory.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.plugins.spi.bean;
+
+public interface ProducerFactory<D> {
+ // only time bean is null is static @Produces potentially?
+ <T> Producer<T> createProducer(final Bean<T> bean);
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/ElementManager.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/ElementManager.java
new file mode 100644
index 0000000..cd9dea1
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/ElementManager.java
@@ -0,0 +1,159 @@
+/*
+ * 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.spi.model;
+
+import org.apache.logging.log4j.plugins.api.Default;
+import org.apache.logging.log4j.plugins.api.Inject;
+import org.apache.logging.log4j.plugins.api.Named;
+import org.apache.logging.log4j.plugins.api.Produces;
+import org.apache.logging.log4j.plugins.api.QualifierType;
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * Manages model metadata of program elements.
+ */
+public interface ElementManager extends AutoCloseable {
+
+ /**
+ * Gets metadata about a class.
+ */
+ <T> MetaClass<T> getMetaClass(final Class<T> clazz);
+
+ /**
+ * Indicates if an annotation type is considered a {@linkplain QualifierType qualifier}.
+ */
+ boolean isQualifierType(Class<? extends Annotation> annotationType);
+
+ /**
+ * Extracts the collection of {@linkplain QualifierType qualifiers} from the annotations on an element.
+ * If no qualifiers other than {@link Named} are present, then the {@link Default} qualifier is also returned in
+ * the collection.
+ *
+ * @param element program element to extract qualifiers from
+ * @return qualifiers present on the element
+ */
+ Collection<Qualifier> getQualifiers(MetaElement<?> element);
+
+ /**
+ * Checks if a class has exactly one injectable constructor. A constructor is <i>injectable</i> if:
+ * <ol>
+ * <li>it is annotated with {@link Inject}; or</li>
+ * <li>it has as least one parameter annotated with {@link Inject} or a {@linkplain QualifierType qualifier annotation}; or</li>
+ * <li>it is the lone no-arg constructor.</li>
+ * </ol>
+ *
+ * @param type class to find an injectable constructor in
+ * @return true if the class has exactly one injectable constructor or false otherwise
+ */
+ default boolean isInjectable(final MetaClass<?> type) {
+ final List<MetaConstructor<?>> injectConstructors = type.getConstructors().stream()
+ .filter(constructor -> constructor.isAnnotationPresent(Inject.class))
+ .collect(Collectors.toList());
+ if (injectConstructors.size() > 1) {
+ return false;
+ }
+ if (injectConstructors.size() == 1) {
+ return true;
+ }
+ final List<MetaConstructor<?>> implicitConstructors = type.getConstructors().stream()
+ .filter(constructor -> constructor.getParameters().stream().anyMatch(this::isInjectable))
+ .collect(Collectors.toList());
+ if (implicitConstructors.size() > 1) {
+ return false;
+ }
+ if (implicitConstructors.size() == 1) {
+ return true;
+ }
+ return type.getDefaultConstructor().isPresent();
+ }
+
+ /**
+ * Checks if an element is injectable. An element is <i>injectable</i> if:
+ * <ol>
+ * <li>it is annotated with {@link Inject}; or</li>
+ * <li>it is annotated with a {@linkplain QualifierType qualifier annotation} and is not annotated with {@link Produces}.</li>
+ * </ol>
+ *
+ * @param element field, method, or parameter to check
+ * @return true if the element is injectable or false otherwise
+ */
+ default boolean isInjectable(final MetaElement<?> element) {
+ return element.isAnnotationPresent(Inject.class) ||
+ (element.getAnnotations().stream().map(Annotation::annotationType).anyMatch(this::isQualifierType) &&
+ !element.isAnnotationPresent(Produces.class));
+ }
+
+ /**
+ * Creates an injection point for a field with an optional owning bean.
+ *
+ * @param field field where injection will take place
+ * @param owner bean where field is located or null for static fields
+ * @param <D> bean type
+ * @param <T> field type
+ * @return an injection point describing the field
+ */
+ <D, T> InjectionPoint<T> createFieldInjectionPoint(final MetaField<D, T> field, final Bean<D> owner);
+
+ /**
+ * Creates an injection point for a method or constructor parameter with an optional owning bean.
+ *
+ * @param executable method or constructor where injection will take place
+ * @param parameter which parameter of that executable to create a point at
+ * @param owner bean where executable is located or null for static methods
+ * @param <D> bean type
+ * @param <P> parameter type
+ * @return an injection point describing the parameter
+ */
+ <D, P> InjectionPoint<P> createParameterInjectionPoint(final MetaExecutable<D, ?> executable,
+ final MetaParameter<P> parameter, final Bean<D> owner);
+
+ /**
+ * Creates a collection of injection points for all the parameters of a method or constructor with an optional
+ * owning bean.
+ *
+ * @param executable method or constructor where injection will take place
+ * @param owner bean where executable is located or null for static methods
+ * @param <D> bean type
+ * @return collection of injection points describing the executable parameters
+ */
+ default <D> Collection<InjectionPoint<?>> createExecutableInjectionPoints(final MetaExecutable<D, ?> executable, final Bean<D> owner) {
+ Objects.requireNonNull(executable);
+ return executable.getParameters().stream()
+ .map(parameter -> createParameterInjectionPoint(executable, parameter, owner))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Creates a variable for an element.
+ */
+ <T> Variable<T> createVariable(final MetaElement<T> element);
+
+ /**
+ * Creates a variable for an injection point.
+ */
+ <T> Variable<T> createVariable(final InjectionPoint<T> point);
+
+ @Override
+ void close();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/InjectionPoint.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/InjectionPoint.java
new file mode 100644
index 0000000..f4e1de9
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/InjectionPoint.java
@@ -0,0 +1,59 @@
+/*
+ * 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.spi.model;
+
+import org.apache.logging.log4j.plugins.spi.bean.Bean;
+
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Optional;
+
+/**
+ * Represents metadata about an element in a program where a value should be injected.
+ *
+ * @param <T> type of element
+ */
+public interface InjectionPoint<T> {
+
+ /**
+ * Gets the generic type information of this point.
+ */
+ Type getType();
+
+ /**
+ * Gets the qualifiers of this point. If no qualifiers other than {@link org.apache.logging.log4j.plugins.api.Named}
+ * are present, then these qualifiers will also include {@link org.apache.logging.log4j.plugins.api.Default}.
+ */
+ Collection<Qualifier> getQualifiers();
+
+ /**
+ * Gets the bean where this injection point is defined or empty for static methods and fields.
+ */
+ Optional<Bean<?>> getBean();
+
+ /**
+ * Gets the field, method, or constructor where injection takes place.
+ */
+ MetaMember<?, ?> getMember();
+
+ /**
+ * Gets the program element corresponding to this injection point.
+ */
+ MetaElement<T> getElement();
+
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaClass.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaClass.java
new file mode 100644
index 0000000..48159bf
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaClass.java
@@ -0,0 +1,42 @@
+/*
+ * 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.spi.model;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Optional;
+
+public interface MetaClass<T> extends MetaElement<T> {
+ Class<T> getJavaClass();
+
+ Collection<MetaConstructor<T>> getConstructors();
+
+ MetaConstructor<T> getMetaConstructor(final Constructor<T> constructor);
+
+ Optional<MetaConstructor<T>> getDefaultConstructor();
+
+ Collection<MetaMethod<T, ?>> getMethods();
+
+ <U> MetaMethod<T, U> getMetaMethod(final Method method);
+
+ Collection<MetaField<T, ?>> getFields();
+
+ <U> MetaField<T, U> getMetaField(final Field field);
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaConstructor.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaConstructor.java
new file mode 100644
index 0000000..766d353
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaConstructor.java
@@ -0,0 +1,22 @@
+/*
+ * 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.spi.model;
+
+public interface MetaConstructor<T> extends MetaExecutable<T, T> {
+ T construct(final Object... args);
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaElement.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaElement.java
new file mode 100644
index 0000000..1dc9b01
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaElement.java
@@ -0,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.plugins.spi.model;
+
+import org.apache.logging.log4j.plugins.api.AliasFor;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+public interface MetaElement<T> {
+
+ /**
+ * Returns the source code name of this element.
+ */
+ String getName();
+
+ /**
+ * Returns all the annotations present on this element.
+ */
+ Collection<Annotation> getAnnotations();
+
+ /**
+ * Indicates whether or not an annotation is present on this element taking into account
+ * {@linkplain AliasFor annotation aliasing}.
+ *
+ * @param annotationType type of annotation to look for
+ * @return whether or not the annotation is directly or indirectly present on this element
+ */
+ default boolean isAnnotationPresent(final Class<? extends Annotation> annotationType) {
+ for (final Annotation annotation : getAnnotations()) {
+ if (annotationType.equals(annotation.annotationType())) {
+ return true;
+ }
+ if (annotation instanceof AliasFor) {
+ return annotationType.equals(((AliasFor) annotation).value());
+ }
+ }
+ return false;
+ }
+
+ Type getBaseType();
+
+ Collection<Type> getTypeClosure();
+
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaExecutable.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaExecutable.java
new file mode 100644
index 0000000..b2ae5e1
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaExecutable.java
@@ -0,0 +1,24 @@
+/*
+ * 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.spi.model;
+
+import java.util.List;
+
+public interface MetaExecutable<D, T> extends MetaMember<D, T> {
+ List<MetaParameter<?>> getParameters();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaField.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaField.java
new file mode 100644
index 0000000..9767eba
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaField.java
@@ -0,0 +1,24 @@
+/*
+ * 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.spi.model;
+
+public interface MetaField<D, T> extends MetaMember<D, T> {
+ T get(final D target);
+
+ void set(final D target, final T value);
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaMember.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaMember.java
new file mode 100644
index 0000000..21601fd
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaMember.java
@@ -0,0 +1,26 @@
+/*
+ * 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.spi.model;
+
+public interface MetaMember<D, T> extends MetaElement<T> {
+ MetaClass<T> getType();
+
+ MetaClass<D> getDeclaringClass();
+
+ boolean isStatic();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaMethod.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaMethod.java
new file mode 100644
index 0000000..80989b8
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaMethod.java
@@ -0,0 +1,22 @@
+/*
+ * 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.spi.model;
+
+public interface MetaMethod<D, T> extends MetaExecutable<D, T> {
+ T invoke(final D target, final Object... args);
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaParameter.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaParameter.java
new file mode 100644
index 0000000..cac9b58
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/MetaParameter.java
@@ -0,0 +1,21 @@
+/*
+ * 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.spi.model;
+
+public interface MetaParameter<T> extends MetaElement<T> {
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Qualifier.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Qualifier.java
new file mode 100644
index 0000000..fba378c
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Qualifier.java
@@ -0,0 +1,103 @@
+/*
+ * 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.spi.model;
+
+import org.apache.logging.log4j.plugins.api.AliasFor;
+import org.apache.logging.log4j.plugins.api.Default;
+import org.apache.logging.log4j.plugins.api.Ignore;
+import org.apache.logging.log4j.plugins.spi.InjectionException;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+// TODO: consider a composite class for qualifier collections or other potential ways to model this
+public final class Qualifier {
+
+ public static final Qualifier DEFAULT_QUALIFIER = new Qualifier(Default.class, Collections.emptyMap());
+
+ public static Qualifier fromAnnotation(final Annotation annotation) {
+ final Class<? extends Annotation> annotationType = annotation.annotationType();
+ final Method[] elements = annotationType.getDeclaredMethods();
+ final Map<String, Object> attributes = new HashMap<>(elements.length);
+ for (final Method element : elements) {
+ if (!element.isAnnotationPresent(Ignore.class)) {
+ attributes.put(element.getName(), getAnnotationElement(annotation, element));
+ }
+ }
+ // FIXME: support default name for @Named when value is blank
+ final AliasFor alias = annotationType.getAnnotation(AliasFor.class);
+ final Class<? extends Annotation> qualifierType = alias != null ? alias.value() : annotationType;
+ return new Qualifier(qualifierType, Collections.unmodifiableMap(attributes));
+ }
+
+ private static Object getAnnotationElement(final Annotation annotation, final Method element) {
+ try {
+ return element.invoke(annotation);
+ } catch (final IllegalAccessException e) {
+ throw new InjectionException("Cannot access element " + element.getName() + " of annotation " + annotation, e);
+ } catch (final InvocationTargetException e) {
+ throw new InjectionException("Cannot access element " + element.getName() + " of annotation " + annotation,
+ e.getCause());
+ }
+ }
+
+ private final Class<? extends Annotation> qualifierType;
+ private final Map<String, Object> attributes;
+
+ private Qualifier(final Class<? extends Annotation> qualifierType, final Map<String, Object> attributes) {
+ this.qualifierType = qualifierType;
+ this.attributes = attributes;
+ }
+
+ public Class<? extends Annotation> getQualifierType() {
+ return qualifierType;
+ }
+
+ public Map<String, Object> getAttributes() {
+ return attributes;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ final Qualifier that = (Qualifier) o;
+ return qualifierType.equals(that.qualifierType) &&
+ attributes.equals(that.attributes);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(qualifierType, attributes);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append('@').append(qualifierType.getSimpleName());
+ if (!attributes.isEmpty()) {
+ sb.append(attributes);
+ }
+ return sb.toString();
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Variable.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Variable.java
new file mode 100644
index 0000000..fa7e02e
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Variable.java
@@ -0,0 +1,41 @@
+/*
+ * 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.spi.model;
+
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+public interface Variable<T> {
+ Collection<Type> getTypes();
+
+ default boolean hasMatchingType(final Type requiredType) {
+ for (final Type type : getTypes()) {
+ if (TypeUtil.typesMatch(requiredType, type)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ Collection<Qualifier> getQualifiers();
+
+ Class<? extends Annotation> getScopeType();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/InitializationContext.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/InitializationContext.java
new file mode 100644
index 0000000..5bf2f4a
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/InitializationContext.java
@@ -0,0 +1,49 @@
+/*
+ * 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.spi.scope;
+
+import java.util.Optional;
+
+/**
+ * Provides operations used by {@link Scoped} implementations for tracking lifecycle state.
+ */
+public interface InitializationContext<T> extends AutoCloseable {
+ /**
+ * Pushes an incomplete instance state. An instance is not completely initialized until it is returned from
+ * {@link Scoped#create(InitializationContext)}.
+ *
+ * @param incompleteInstance incompletely initialized instance
+ */
+ void push(final T incompleteInstance);
+
+ <S> Optional<S> getIncompleteInstance(Scoped<S> scoped);
+
+ Optional<Scoped<T>> getScoped();
+
+ Optional<InitializationContext<?>> getParentContext();
+
+ <S> InitializationContext<S> createDependentContext(Scoped<S> scoped);
+
+ <S> InitializationContext<S> createIndependentContext(Scoped<S> scoped);
+
+ /**
+ * Destroys all dependent objects by propagating them to {@link Scoped#destroy(Object, InitializationContext)}.
+ */
+ @Override
+ void close();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/ScopeContext.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/ScopeContext.java
new file mode 100644
index 0000000..9617a7d
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/ScopeContext.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.plugins.spi.scope;
+
+import org.apache.logging.log4j.plugins.api.ScopeType;
+
+import java.lang.annotation.Annotation;
+import java.util.Optional;
+
+/**
+ * Manages {@link Scoped} instances within a particular {@linkplain ScopeType scope}.
+ */
+public interface ScopeContext extends AutoCloseable {
+
+ /**
+ * Returns the {@linkplain ScopeType scope type} of this context.
+ */
+ Class<? extends Annotation> getScopeType();
+
+ /**
+ * Gets or {@linkplain Scoped#create(InitializationContext) creates} a scoped instance of a specific type.
+ *
+ * @param scoped the managed type
+ * @param context the context to create a new instance in
+ * @param <T> the instance type being managed
+ * @return the new or existing instance
+ */
+ <T> T getOrCreate(final Scoped<T> scoped, final InitializationContext<T> context);
+
+ /**
+ * Returns an existing scoped instance if it exists.
+ *
+ * @param scoped the managed type
+ * @param <T> the instance type being managed
+ * @return the existing instance or empty
+ */
+ <T> Optional<T> getIfExists(final Scoped<T> scoped);
+
+ /**
+ * Destroys the existing scoped instance of a specified type if it exists or otherwise does nothing.
+ *
+ * @param scoped the managed type
+ */
+ void destroy(final Scoped<?> scoped);
+
+ @Override
+ void close();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/Scoped.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/Scoped.java
new file mode 100644
index 0000000..d214662
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/scope/Scoped.java
@@ -0,0 +1,58 @@
+/*
+ * 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.spi.scope;
+
+import org.apache.logging.log4j.plugins.api.PrototypeScoped;
+import org.apache.logging.log4j.plugins.api.SingletonScoped;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Manages the creation and destruction of instances of a specific type within a particular scope context. Scopes
+ * provide managed life-cycles for objects such as {@linkplain SingletonScoped singleton}
+ * and {@linkplain PrototypeScoped prototype}.
+ *
+ * @param <T> type of managed instance
+ * @see org.apache.logging.log4j.plugins.api.ScopeType
+ */
+public interface Scoped<T> {
+
+ /**
+ * Creates a new instance of this managed type. The given {@link InitializationContext} should be used by implementations
+ * to track dependent objects.
+ *
+ * @param context the context in which the instance is being managed
+ * @return a managed, initialized instance
+ */
+ T create(final InitializationContext<T> context);
+
+ /**
+ * Destroys a managed instance in the given context. Implementations should call {@link InitializationContext#close()} to
+ * allow dependent objects to be destroyed.
+ *
+ * @param instance the managed instance to destroy
+ * @param context the context in which the instance is being managed
+ */
+ void destroy(final T instance, final InitializationContext<T> context);
+
+ Class<? extends Annotation> getScopeType();
+
+ default boolean isPrototypeScoped() {
+ return getScopeType() == PrototypeScoped.class;
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/TypeUtil.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/TypeUtil.java
index 6adf661..f27a86f 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/TypeUtil.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/TypeUtil.java
@@ -16,10 +16,22 @@
*/
package org.apache.logging.log4j.plugins.util;
-import java.lang.reflect.*;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
/**
@@ -36,13 +48,13 @@ import java.util.Objects;
* @since 2.1
*/
public final class TypeUtil {
-
+
private TypeUtil() {
}
/**
* Gets all declared fields for the given class (including superclasses).
- *
+ *
* @param cls the class to examine
* @return all declared fields for the given class (including superclasses).
* @see Class#getDeclaredFields()
@@ -50,11 +62,14 @@ public final class TypeUtil {
public static List<Field> getAllDeclaredFields(Class<?> cls) {
final List<Field> fields = new ArrayList<>();
while (cls != null) {
- fields.addAll(Arrays.asList(cls.getDeclaredFields()));
+ final Field[] declaredFields = cls.getDeclaredFields();
+ AccessibleObject.setAccessible(declaredFields, true);
+ fields.addAll(Arrays.asList(declaredFields));
cls = cls.getSuperclass();
}
return fields;
}
+
/**
* Indicates if two {@link Type}s are assignment compatible.
*
@@ -79,31 +94,40 @@ public final class TypeUtil {
if (rhs instanceof Class<?>) {
// no generics involved
final Class<?> rhsClass = (Class<?>) rhs;
- return lhsClass.isAssignableFrom(rhsClass);
+ // possible for primitive types here
+ return isAssignable(lhsClass, rhsClass);
}
if (rhs instanceof ParameterizedType) {
- // check to see if the parameterized type has the same raw type as the lhs; this is legal
- final Type rhsRawType = ((ParameterizedType) rhs).getRawType();
- if (rhsRawType instanceof Class<?>) {
- return lhsClass.isAssignableFrom((Class<?>) rhsRawType);
- }
- }
- if (lhsClass.isArray() && rhs instanceof GenericArrayType) {
+ return isAssignable(lhsClass, getRawType(rhs));
+ } else if (lhsClass.isArray() && rhs instanceof GenericArrayType) {
// check for compatible array component types
return isAssignable(lhsClass.getComponentType(), ((GenericArrayType) rhs).getGenericComponentType());
+ } else if (rhs instanceof TypeVariable<?>) {
+ for (final Type bound : ((TypeVariable<?>) rhs).getBounds()) {
+ if (isAssignable(lhs, bound)) {
+ return true;
+ }
+ }
+ } else {
+ return false;
}
}
// parameterized type on left
if (lhs instanceof ParameterizedType) {
final ParameterizedType lhsType = (ParameterizedType) lhs;
if (rhs instanceof Class<?>) {
- final Type lhsRawType = lhsType.getRawType();
- if (lhsRawType instanceof Class<?>) {
- return ((Class<?>) lhsRawType).isAssignableFrom((Class<?>) rhs);
- }
+ return isAssignable(getRawType(lhs), (Class<?>) rhs);
} else if (rhs instanceof ParameterizedType) {
final ParameterizedType rhsType = (ParameterizedType) rhs;
return isParameterizedAssignable(lhsType, rhsType);
+ } else if (rhs instanceof TypeVariable<?>) {
+ for (final Type bound : ((TypeVariable<?>) rhs).getBounds()) {
+ if (isAssignable(lhsType, bound)) {
+ return true;
+ }
+ }
+ } else {
+ return false;
}
}
// generic array type on left
@@ -127,6 +151,10 @@ public final class TypeUtil {
return false;
}
+ private static boolean isAssignable(final Class<?> lhs, final Class<?> rhs) {
+ return getReferenceType(lhs).isAssignableFrom(getReferenceType(rhs));
+ }
+
private static boolean isParameterizedAssignable(final ParameterizedType lhs, final ParameterizedType rhs) {
if (lhs.equals(rhs)) {
// that was easy
@@ -144,8 +172,8 @@ public final class TypeUtil {
final Type lhsArgument = lhsTypeArguments[i];
final Type rhsArgument = rhsTypeArguments[i];
if (!lhsArgument.equals(rhsArgument) &&
- !(lhsArgument instanceof WildcardType &&
- isWildcardAssignable((WildcardType) lhsArgument, rhsArgument))) {
+ !(lhsArgument instanceof WildcardType &&
+ isWildcardAssignable((WildcardType) lhsArgument, rhsArgument))) {
return false;
}
}
@@ -213,4 +241,299 @@ public final class TypeUtil {
private static boolean isBoundAssignable(final Type lhs, final Type rhs) {
return (rhs == null) || ((lhs != null) && isAssignable(lhs, rhs));
}
+
+ /**
+ * Checks if a type is a raw type.
+ */
+ public static boolean isRawType(final Type type) {
+ if (type instanceof Class<?>) {
+ final Class<?> clazz = (Class<?>) type;
+ if (clazz.isArray()) {
+ return isRawType(clazz.getComponentType());
+ }
+ return clazz.getTypeParameters().length > 0;
+ }
+ return false;
+ }
+
+ /**
+ * Extracts the raw type equivalent of a given type.
+ */
+ public static Class<?> getRawType(final Type type) {
+ if (type instanceof Class<?>) {
+ return (Class<?>) type;
+ }
+ if (type instanceof ParameterizedType) {
+ return getRawType(((ParameterizedType) type).getRawType());
+ }
+ if (type instanceof GenericArrayType) {
+ return Array.newInstance(getRawType(((GenericArrayType) type).getGenericComponentType()), 0).getClass();
+ }
+ if (type instanceof WildcardType) {
+ final Type[] bounds = ((WildcardType) type).getUpperBounds();
+ return bounds.length > 0 ? getRawType(bounds[0]) : Object.class;
+ }
+ if (type instanceof TypeVariable<?>) {
+ final Type[] bounds = ((TypeVariable<?>) type).getBounds();
+ return bounds.length > 0 ? getRawType(bounds[0]) : Object.class;
+ }
+ return Object.class;
+ }
+
+ /**
+ * Checks if a type matches another type.
+ */
+ public static boolean typesMatch(final Type required, final Type found) {
+ if (required instanceof Class<?>) {
+ if (found instanceof Class<?>) {
+ return required.equals(found);
+ }
+ if (found instanceof ParameterizedType) {
+ return required.equals(getRawType(found));
+ }
+ }
+ if (required instanceof ParameterizedType) {
+ if (found instanceof Class<?>) {
+ return getRawType(required).equals(found);
+ }
+ if (found instanceof ParameterizedType) {
+ if (!getRawType(required).equals(getRawType(found))) {
+ return false;
+ }
+ final Type[] requiredArguments = ((ParameterizedType) required).getActualTypeArguments();
+ final Type[] foundArguments = ((ParameterizedType) found).getActualTypeArguments();
+ if (requiredArguments.length != foundArguments.length) {
+ return false;
+ }
+ for (int i = 0; i < requiredArguments.length; i++) {
+ if (!typeParametersMatch(requiredArguments[i], foundArguments[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean typeParametersMatch(final Type required, final Type found) {
+ if (required instanceof Class<?> || required instanceof ParameterizedType || required instanceof GenericArrayType) {
+ if (found instanceof Class<?> || found instanceof ParameterizedType || found instanceof GenericArrayType) {
+ return typesMatch(getReferenceType(required), getReferenceType(found));
+ }
+ if (found instanceof TypeVariable<?>) {
+ return typeParametersMatch(required, (TypeVariable<?>) found);
+ }
+ }
+ if (required instanceof WildcardType) {
+ final WildcardType wildcardType = (WildcardType) required;
+ if (found instanceof Class<?> || found instanceof ParameterizedType || found instanceof GenericArrayType) {
+ return typeParametersMatch(wildcardType, found);
+ }
+ if (found instanceof TypeVariable<?>) {
+ return typeParametersMatch(wildcardType, (TypeVariable<?>) found);
+ }
+ }
+ if (required instanceof TypeVariable<?>) {
+ if (found instanceof TypeVariable<?>) {
+ final Type[] foundBounds = getTopBounds(((TypeVariable<?>) found).getBounds());
+ final Type[] requiredBounds = getTopBounds(((TypeVariable<?>) required).getBounds());
+ return areBoundsStricter(foundBounds, requiredBounds);
+ }
+ }
+ return false;
+ }
+
+ private static boolean typeParametersMatch(final Type required, final TypeVariable<?> found) {
+ for (final Type bound : getTopBounds(found.getBounds())) {
+ if (!isAssignable(bound, required)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean typeParametersMatch(final WildcardType required, final Type found) {
+ return lowerBoundsOfWildcardMatch(required, found) && upperBoundsOfWildcardMatch(required, found);
+ }
+
+ private static boolean typeParametersMatch(final WildcardType required, final TypeVariable<?> found) {
+ final Type[] bounds = getTopBounds(found.getBounds());
+ if (!lowerBoundsOfWildcardMatch(required, bounds)) {
+ return false;
+ }
+ final Type[] upperBounds = required.getUpperBounds();
+ return areBoundsStricter(bounds, upperBounds) || areBoundsStricter(upperBounds, bounds);
+ }
+
+ private static boolean lowerBoundsOfWildcardMatch(final WildcardType required, final Type... found) {
+ final Type[] lowerBounds = required.getLowerBounds();
+ return lowerBounds.length == 0 || areBoundsStricter(found, lowerBounds);
+ }
+
+ private static boolean upperBoundsOfWildcardMatch(final WildcardType required, final Type... found) {
+ return areBoundsStricter(required.getUpperBounds(), found);
+ }
+
+ private static boolean areBoundsStricter(final Type[] upperBounds, final Type[] stricterUpperBounds) {
+ final Type[] stricterBounds = getTopBounds(stricterUpperBounds);
+ for (final Type upperBound : getTopBounds(upperBounds)) {
+ if (!isAssignableFromOneOf(upperBound, stricterBounds)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static Type[] getTopBounds(final Type[] bounds) {
+ return bounds[0] instanceof TypeVariable<?> ? getTopBounds(((TypeVariable<?>) bounds[0]).getBounds()) : bounds;
+ }
+
+ private static boolean isAssignableFromOneOf(final Type type, final Type... types) {
+ for (final Type t : types) {
+ if (isAssignable(type, t)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the type closure of a generic type.
+ */
+ public static Collection<Type> getTypeClosure(final Type type) {
+ // TODO: weak cache?
+ return new TypeResolver(type).types.values();
+ }
+
+ private static class TypeResolver {
+ private final Map<TypeVariable<?>, Type> resolvedTypeVariables = new HashMap<>();
+ private final Map<Class<?>, Type> types = new LinkedHashMap<>();
+
+ private TypeResolver(final Type type) {
+ loadTypes(type);
+ }
+
+ private void loadTypes(final Type type) {
+ if (type instanceof Class<?>) {
+ final Class<?> clazz = (Class<?>) type;
+ types.put(clazz, clazz);
+ loadTypes(clazz);
+ } else if (isRawType(type)) {
+ loadTypes(getRawType(type));
+ } else if (type instanceof GenericArrayType) {
+ final GenericArrayType arrayType = (GenericArrayType) type;
+ final Type componentType = arrayType.getGenericComponentType();
+ final Class<?> rawComponentType = getRawType(componentType);
+ final Class<?> arrayClass = Array.newInstance(rawComponentType, 0).getClass();
+ types.put(arrayClass, arrayType);
+ loadTypes(arrayClass);
+ } else if (type instanceof ParameterizedType) {
+ final ParameterizedType parameterizedType = (ParameterizedType) type;
+ final Type rawType = parameterizedType.getRawType();
+ if (rawType instanceof Class<?>) {
+ final Class<?> clazz = (Class<?>) rawType;
+ processTypeVariables(clazz.getTypeParameters(), parameterizedType.getActualTypeArguments());
+ types.put(clazz, parameterizedType);
+ loadTypes(clazz);
+ }
+ }
+ }
+
+ private void loadTypes(final Class<?> clazz) {
+ if (clazz.getSuperclass() != null) {
+ loadTypes(processAndResolveType(clazz.getGenericSuperclass(), clazz.getSuperclass()));
+ }
+ final Type[] genericInterfaces = clazz.getGenericInterfaces();
+ final Class<?>[] interfaces = clazz.getInterfaces();
+ for (int i = 0; i < interfaces.length; i++) {
+ loadTypes(processAndResolveType(genericInterfaces[i], interfaces[i]));
+ }
+ }
+
+ private Type processAndResolveType(final Type superclass, final Class<?> rawSuperclass) {
+ if (superclass instanceof ParameterizedType) {
+ final ParameterizedType parameterizedSuperclass = (ParameterizedType) superclass;
+ processTypeVariables(rawSuperclass.getTypeParameters(), parameterizedSuperclass.getActualTypeArguments());
+ return resolveType(parameterizedSuperclass);
+ }
+ if (superclass instanceof Class<?>) {
+ return superclass;
+ }
+ throw new IllegalArgumentException("Superclass argument must be parameterized or a class, but got: " + superclass);
+ }
+
+ private void processTypeVariables(final TypeVariable<?>[] variables, final Type[] types) {
+ for (int i = 0; i < variables.length; i++) {
+ final Type type = types[i];
+ final Type resolvedType = type instanceof TypeVariable<?> ? resolveType((TypeVariable<?>) type) : type;
+ resolvedTypeVariables.put(variables[i], resolvedType);
+ }
+ }
+
+ private Type resolveType(final TypeVariable<?> type) {
+ return resolvedTypeVariables.getOrDefault(type, type);
+ }
+
+ private Type resolveType(final ParameterizedType type) {
+ final Type[] unresolved = type.getActualTypeArguments();
+ final Type[] resolved = new Type[unresolved.length];
+ boolean modified = false; // potentially no need to re-create ParameterizedType
+ for (int i = 0; i < unresolved.length; i++) {
+ final Type unresolvedType = unresolved[i];
+ Type resolvedType = unresolvedType;
+ if (resolvedType instanceof TypeVariable<?>) {
+ resolvedType = resolveType((TypeVariable<?>) resolvedType);
+ } // else if?
+ if (resolvedType instanceof ParameterizedType) {
+ resolvedType = resolveType((ParameterizedType) resolvedType);
+ }
+ resolved[i] = resolvedType;
+ if (resolvedType != unresolvedType) {
+ modified = true;
+ }
+ }
+ if (!modified) {
+ return type;
+ }
+ return new ParameterizedTypeImpl(type.getOwnerType(), type.getRawType(), resolved);
+ }
+
+ }
+
+ private static final Map<Class<?>, Class<?>> PRIMITIVE_BOXED_TYPES;
+
+ static {
+ final Map<Class<?>, Class<?>> map = new HashMap<>();
+ map.put(boolean.class, Boolean.class);
+ map.put(byte.class, Byte.class);
+ map.put(char.class, Character.class);
+ map.put(double.class, Double.class);
+ map.put(float.class, Float.class);
+ map.put(int.class, Integer.class);
+ map.put(long.class, Long.class);
+ map.put(short.class, Short.class);
+ PRIMITIVE_BOXED_TYPES = Collections.unmodifiableMap(map);
+ }
+
+ /**
+ * Returns the reference type for a class. For primitives, this is their boxed equivalent. For other types, this is
+ * the class unchanged.
+ */
+ public static Class<?> getReferenceType(final Class<?> clazz) {
+ if (clazz.isPrimitive()) {
+ return PRIMITIVE_BOXED_TYPES.get(clazz);
+ }
+ return clazz;
+ }
+
+ private static Type getReferenceType(final Type type) {
+ return type instanceof Class<?> ? getReferenceType((Class<?>) type) : type;
+ }
+
+ public static <T> T cast(final Object o) {
+ @SuppressWarnings("unchecked") final T t = (T) o;
+ return t;
+ }
+
}