DIContainer.java

package space.sunqian.fs.di;

import space.sunqian.annotation.Nonnull;
import space.sunqian.annotation.Nullable;
import space.sunqian.fs.Fs;
import space.sunqian.fs.collect.CollectKit;
import space.sunqian.fs.collect.ListKit;
import space.sunqian.fs.reflect.TypeRef;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

/**
 * This interface represents a DI container that manages the injection of dependencies into objects.
 * <p>
 * A DI container is typically created via {@link #newBuilder()}, which is used to configure the container's components
 * and behaviors. See {@linkplain space.sunqian.fs.di DI documentation} for more information.
 *
 * @author sunqian
 */
public interface DIContainer {

    /**
     * Returns a new builder for {@link DIContainer}.
     *
     * @return a new builder for {@link DIContainer}
     */
    static @Nonnull Builder newBuilder() {
        return new Builder();
    }

    /**
     * Initializes this DI container.
     * <p>
     * All {@code post-construct} methods of this DI container's components are executed sequentially according to their
     * dependency relationships. If an exception occurs during the execution, an {@link DIInitializeException} will be
     * thrown and the initialization process will terminate immediately, successfully initialized components will not be
     * rolled back and the uninitialized components will not be initialized. See
     * {@linkplain space.sunqian.fs.di DI documentation} for more information. A DI container can only initialize once
     * and cannot re-initialize.
     * <p>
     * This method blocks current thread until the initialization operation is completed.
     *
     * @return this DI container
     * @throws DIInitializeException if any {@code post-construct} method execution fails
     * @throws DIException           if any other error occurs during initialization operation
     */
    DIContainer initialize() throws DIInitializeException, DIException;

    /**
     * Returns whether this DI container is initialized.
     *
     * @return {@code true} if this DI container is initialized; {@code false} otherwise
     */
    boolean isInitialized();

    /**
     * Shuts down this DI container.
     * <p>
     * All {@code pre-destroy} methods of this DI container's components are executed sequentially according to their
     * dependency relationships. If an exception occurs during the execution, an {@link DIShutdownException} will be
     * thrown and the shutdown process will terminate immediately, successfully destroyed components will not be rolled
     * back and the un-destroyed components will not be destroyed. See {@linkplain space.sunqian.fs.di DI documentation}
     * for more information. A DI container can only shut down once and cannot re-shutdown.
     * <p>
     * Sub-containers are not automatically shut down along with this container, but components that sub-containers
     * depend on from this DI container will be destroyed by this DI container.
     * <p>
     * This method blocks current thread until the shutdown operation is completed.
     *
     * @return this DI container
     * @throws DIShutdownException if any {@code pre-destroy} method execution fails
     * @throws DIException         if any other error occurs during shutdown operation
     */
    DIContainer shutdown() throws DIShutdownException, DIException;

    /**
     * Returns whether this DI container is shut down.
     *
     * @return {@code true} if this DI container is shut down; {@code false} otherwise
     */
    boolean isShutdown();

    /**
     * Returns all parent containers of this container.
     *
     * @return all parent containers of this container
     */
    @Nonnull
    List<@Nonnull DIContainer> parentContainers();

    /**
     * Returns the component map that are directly owned by this container, excluding the components inherited from
     * parent containers.
     *
     * @return the component map that are directly owned by this container, excluding the components inherited from
     * parent containers
     */
    @Nonnull
    Map<@Nonnull Type, @Nonnull DIComponent> localComponents();

    /**
     * Returns all component map that constitute this container, including the components inherited from parent
     * containers.
     *
     * @return all component map that constitute this container, including the components inherited from parent
     * containers
     */
    @Nonnull
    Map<@Nonnull Type, @Nonnull DIComponent> components();

    /**
     * Returns the component whose type is assignable to the specified type, or {@code null} if no such component
     * exists.
     * <p>
     * This method first attempts to find a component whose type exactly matches the specified type using
     * {@link Object#equals(Object)}. If no exact match is found, it will randomly select one component that can be
     * assigned to the specified type from all components that constitute this container.
     *
     * @param type the specified type
     * @return the component whose type is assignable to the specified type, or {@code null} if no such component exists
     */
    @Nullable
    DIComponent getComponent(@Nonnull Type type);

    /**
     * Returns the component object whose type is assignable to the specified type, or {@code null} if no such component
     * exists.
     * <p>
     * This method first attempts to find a component whose type exactly matches the specified type using
     * {@link Object#equals(Object)}. If no exact match is found, it will randomly select one component object that can
     * be assigned to the specified type from all components that constitute this container.
     *
     * @param <T>  the specified type
     * @param type the specified type
     * @return the component object whose type is assignable to the specified type, or {@code null} if no such component
     * exists
     */
    default <T> @Nullable T getObject(@Nonnull Class<T> type) {
        return Fs.as(getObject((Type) type));
    }

    /**
     * Returns the component object whose type is assignable to the specified type, or {@code null} if no such component
     * exists.
     * <p>
     * This method first attempts to find a component whose type exactly matches the specified type using
     * {@link Object#equals(Object)}. If no exact match is found, it will randomly select one component object that can
     * be assigned to the specified type from all components that constitute this container.
     *
     * @param <T>  the specified type
     * @param type the {@link TypeRef} for the specified type
     * @return the component object whose type is assignable to the specified type, or {@code null} if no such component
     * exists
     */
    default <T> @Nullable T getObject(@Nonnull TypeRef<T> type) {
        return Fs.as(getObject(type.type()));
    }

    /**
     * Returns the component object whose type is assignable to the specified type, or {@code null} if no such component
     * exists.
     * <p>
     * This method first attempts to find a component whose type exactly matches the specified type using
     * {@link Object#equals(Object)}. If no exact match is found, it will randomly select one component object that can
     * be assigned to the specified type from all components that constitute this container.
     *
     * @param type the specified type
     * @return the component object whose type is assignable to the specified type, or {@code null} if no such component
     * exists
     */
    default @Nullable Object getObject(@Nonnull Type type) {
        DIComponent component = getComponent(type);
        if (component != null) {
            return component.instance();
        }
        return null;
    }

    /**
     * Builder for {@link DIContainer}.
     */
    class Builder {

        private static final @Nonnull List<@Nonnull String> RESOURCE_ANNOTATIONS =
            ListKit.list("javax.annotation.Resource", "jakarta.annotation.Resource");
        private static final @Nonnull List<@Nonnull String> POST_CONSTRUCT_ANNOTATIONS =
            ListKit.list("javax.annotation.PostConstruct", "jakarta.annotation.PostConstruct");
        private static final @Nonnull List<@Nonnull String> PRE_DESTROY_ANNOTATIONS =
            ListKit.list("javax.annotation.PreDestroy", "jakarta.annotation.PreDestroy");

        private final @Nonnull Collection<Type> componentTypes = new LinkedHashSet<>();
        private final @Nonnull Collection<@Nonnull DIContainer> parentContainers = new LinkedHashSet<>();
        private final @Nonnull Collection<@Nonnull String> componentAnnotations = new LinkedHashSet<>();
        private final @Nonnull Collection<@Nonnull String> postConstructAnnotations = new LinkedHashSet<>();
        private final @Nonnull Collection<@Nonnull String> preDestroyAnnotations = new LinkedHashSet<>();

        private @Nonnull DIComponent.Resolver componentResolver = DIComponent.defaultResolver();
        private @Nonnull DIComponent.FieldSetter fieldSetter = DIComponent.defaultFieldSetter();

        /**
         * Adds a component annotation type that marks a {@link Field} references a component from the container.
         * <p>
         * Multiple component annotation types can be specified, and any of them will be recognized as marking a
         * component dependency. If no component annotation types are explicitly specified, the default annotations used
         * are:
         * <lu>
         * <li>{@code javax.annotation.Resource}</li>
         * <li>{@code jakarta.annotation.Resource}</li>
         * </lu>
         *
         * @param componentAnnotation a component annotation type that marks a {@link Field} as referencing a component
         *                            from the container
         * @return this builder
         */
        public @Nonnull Builder componentAnnotation(@Nonnull Class<? extends Annotation> componentAnnotation) {
            this.componentAnnotations.add(componentAnnotation.getName());
            return this;
        }

        /**
         * Adds a post-construct annotation type that marks a {@link Method} is a post-construct method that will be
         * executed in dependency order in initialization process of the container.
         * <p>
         * Multiple post-construct annotation types can be specified, and any of them will be recognized as marking a
         * post-construct method. If no post-construct annotation types are explicitly specified, the default
         * annotations used are:
         * <lu>
         * <li>{@code javax.annotation.PostConstruct}</li>
         * <li>{@code jakarta.annotation.PostConstruct}</li>
         * </lu>
         *
         * @param postConstructAnnotation a post-construct annotation type that marks a {@link Method} is a
         *                                post-construct method
         * @return this builder
         */
        public @Nonnull Builder postConstructAnnotation(@Nonnull Class<? extends Annotation> postConstructAnnotation) {
            this.postConstructAnnotations.add(postConstructAnnotation.getName());
            return this;
        }

        /**
         * Adds a pre-destroy annotation type that marks a {@link Method} is a pre-destroy method that will be executed
         * in dependency order in shutdown process of the container.
         * <p>
         * Multiple pre-destroy annotation types can be specified, and any of them will be recognized as marking a
         * pre-destroy method. If no pre-destroy annotation types are explicitly specified, the default annotations used
         * are:
         * <lu>
         * <li>{@code javax.annotation.PreDestroy}</li>
         * <li>{@code jakarta.annotation.PreDestroy}</li>
         * </lu>
         *
         * @param preDestroyAnnotation a pre-destroy annotation type that marks a {@link Method} is a pre-destroy
         *                             method
         * @return this builder
         */
        public @Nonnull Builder preDestroyAnnotation(@Nonnull Class<? extends Annotation> preDestroyAnnotation) {
            this.preDestroyAnnotations.add(preDestroyAnnotation.getName());
            return this;
        }

        /**
         * Adds root component types to this builder, each type should be a {@link Class} or {@link ParameterizedType}.
         * Any previously-added type will be ignored.
         * <p>
         * These types serve as root types for dependency injection. The dependency resolver will recursively analyze
         * all {@link Field}s of these types to build the dependency injection graph.
         * <p>
         * Note each type only generates singleton component instance.
         *
         * @param componentTypes the component types to be added
         * @return this builder
         */
        public @Nonnull Builder componentTypes(@Nonnull Type @Nonnull ... componentTypes) {
            CollectKit.addAll(this.componentTypes, componentTypes);
            return this;
        }

        /**
         * Adds root component types to this builder, each type should be a {@link Class} or {@link ParameterizedType}.
         * Any previously-added type will be ignored.
         * <p>
         * These types serve as root types for dependency injection. The dependency resolver will recursively analyze
         * all {@link Field}s of these types to build the dependency injection graph.
         * <p>
         * Note each type only generates singleton component instance.
         *
         * @param componentTypes the component types to be added
         * @return this builder
         */
        public @Nonnull Builder componentTypes(@Nonnull Iterable<@Nonnull Type> componentTypes) {
            CollectKit.addAll(this.componentTypes, componentTypes);
            return this;
        }

        /**
         * Adds parent containers to inherit and share their components. If a component type has already been registered
         * in the parent container, the sub-container will not generate another instance of that type.
         * <p>
         * Once a parent container is shut down, its sub-containers are not automatically shut down along with it. The
         * inherited components will still be held by the sub-containers, but theirs pre-destroy methods will be
         * executed.
         *
         * @param parentContainers the parent containers
         * @return this builder
         */
        public @Nonnull Builder parentContainers(@Nullable DIContainer @Nonnull ... parentContainers) {
            CollectKit.addAll(this.parentContainers, parentContainers);
            return this;
        }

        /**
         * Adds parent containers to inherit and share their components. If a component type has already been registered
         * in the parent container, the sub-container will not generate another instance of that type.
         * <p>
         * Once a parent container is shut down, its sub-containers are not automatically shut down along with it. The
         * inherited components will still be held by the sub-containers, but theirs pre-destroy methods will be
         * executed.
         *
         * @param parentContainers the parent containers
         * @return this builder
         */
        public @Nonnull Builder parentContainers(@Nonnull Iterable<@Nonnull DIContainer> parentContainers) {
            CollectKit.addAll(this.parentContainers, parentContainers);
            return this;
        }

        /**
         * Sets the component resolver for this container. The default is {@link DIComponent#defaultResolver()}.
         *
         * @param componentResolver the component resolver
         * @return this builder
         */
        public @Nonnull Builder componentResolver(@Nonnull DIComponent.Resolver componentResolver) {
            this.componentResolver = componentResolver;
            return this;
        }

        /**
         * Sets the field setter for this container. The default is {@link DIComponent#defaultFieldSetter()}.
         *
         * @param fieldSetter the field setter
         * @return this builder
         */
        public @Nonnull Builder fieldSetter(@Nonnull DIComponent.FieldSetter fieldSetter) {
            this.fieldSetter = fieldSetter;
            return this;
        }

        /**
         * Builds and returns a new DI container instance, the returned instance is not initialized.
         *
         * @return a new DI container instance
         * @throws DIException if any error occurs
         */
        public @Nonnull DIContainer build() throws DIException {
            if (componentAnnotations.isEmpty()) {
                componentAnnotations.addAll(RESOURCE_ANNOTATIONS);
            }
            if (postConstructAnnotations.isEmpty()) {
                postConstructAnnotations.addAll(POST_CONSTRUCT_ANNOTATIONS);
            }
            if (preDestroyAnnotations.isEmpty()) {
                preDestroyAnnotations.addAll(PRE_DESTROY_ANNOTATIONS);
            }
            return new DIContainerImpl(
                componentTypes,
                parentContainers,
                componentAnnotations,
                postConstructAnnotations,
                preDestroyAnnotations,
                componentResolver,
                fieldSetter
            );
        }
    }
}