) INFOS.computeIfAbsent(targetClass, MCRClassInfo::new);
+ }
+
+ /**
+ * A {@link MCRClassInfo} is a helper class that gathers and holds some information about a target class.
+ *
+ * This class gathers the following information about the target class:
+ *
+ * -
+ * A {@link Supplier} as a factory for creating instances of the target class.
+ * That factory is
+ *
+ * -
+ * preferably the target classes parameterless public constructor or
+ *
+ * -
+ * a parameterless public static factory method whose name case-insensitively contains
+ * the word "instance".
+ *
+ *
+ * An exception is thrown if neither kind of factory exists or if no suitable constructor but
+ * multiple suitable factory methods exists.
+ *
+ * -
+ * A list of {@link Injector} that are executed after an instance of the target class has been created.
+ * Each injector binds together a {@link MCRSource} and a {@link MCRTarget}.
+ *
+ * Sources implement the varying strategies that create values from configuration properties.
+ * Source implementations exist for all supported annotations:
+ *
+ * - {@link MCRProperty}
+ * - {@link MCRRawProperties}
+ * - {@link MCRInstance}
+ * - {@link MCRInstanceMap}
+ * - {@link MCRInstanceList}
+ * - {@link MCRPostConstruction}
+ *
+ * Targets are:
+ *
+ * - public fields,
+ * - public methods with no parameter or
+ * - public methods with one parameter.
+ *
+ * The list of injectors is created by
+ *
+ * -
+ * examining all possible fields and methods, generalized as {@link MCRInjectable},
+ * (done in {@link MCRClassInfo#findInjectors(Class)}),
+ *
+ * -
+ * creating a corresponding source for supported annotations
+ * (done in {@link MCRInjectable#toSource(MCRSource.Type)}),
+ *
+ * -
+ * creating a corresponding target for that injectable
+ * (using {@link MCRInjectable#toTarget()}).
+ *
+ *
+ * An exception is thrown if multiple sources are detected for the same target (for example, because a method is
+ * annotated with {@link MCRProperty} and {@link MCRPostConstruction}), if the target is not allowed for the
+ * detected source (for example, {@link MCRPostConstruction} is only allowed on methods) or if the values
+ * produced by the source are assignment-incompatible with the target (for example, values produced by a
+ * source for {@link MCRProperty} are not assignment-compatible with {@link String}) (as far as type erasure
+ * allows it to determine this preemptively).
+ *
+ * The list is ordered by the type of target (all fields first, then all methods), the type of source annotation
+ * (first everything other than {@link MCRPostConstruction}, then {@link MCRPostConstruction}) and lastly the
+ * order attribute of the source annotations, if available (for example {@link MCRProperty#order()}).
+ *
+ *
+ *
+ * It is intended that instances of this class are cached, such that the information about the target class
+ * only needs to be gathered once.
+ */
+ private static final class MCRClassInfo {
+
+ private final Class targetClass;
+
+ private final Supplier factory;
+
+ private final List injectors;
+
+ private MCRClassInfo(Class targetClass) {
+ this.targetClass = targetClass;
+ this.factory = getFactory(targetClass);
+ this.injectors = findInjectors(targetClass);
+ }
+
+ private Supplier getFactory(Class targetClass) {
+
+ List declaredFactoryMethods = findFactoryMethods(targetClass, targetClass.getDeclaredMethods());
+ Optional> factory = Stream.>>>of(
+ () -> findSingletonFactoryMethod(declaredFactoryMethods),
+ () -> findAnnotatedFactoryMethod(declaredFactoryMethods),
+ () -> findDefaultConstructor(targetClass))
+ .map(Supplier::get)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .findFirst();
+
+ return factory.orElseThrow(() -> new MCRConfigurationException("Class " + targetClass.getName()
+ + " has no singleton factory method (public, static, matching return type, parameterless, name"
+ + " equals 'getInstance'), no annotated factory method (public, static, matching return type,"
+ + " parameterless, annotated with @MCRFactory) and no public parameterless constructor"));
+
+ }
+
+ private static List findFactoryMethods(Class targetClass, Method[] declaredMethods) {
+ return Stream.of(declaredMethods)
+ .filter(method -> Modifier.isPublic(method.getModifiers()))
+ .filter(method -> Modifier.isStatic(method.getModifiers()))
+ .filter(method -> method.getReturnType().isAssignableFrom(targetClass))
+ .filter(method -> method.getParameterTypes().length == 0)
+ .filter(method -> !method.isVarArgs())
+ .toList();
+ }
+
+ private Optional> findSingletonFactoryMethod(List factoryMethods) {
+
+ List