All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.smallrye.beanbag.BeanBag Maven / Gradle / Ivy

The newest version!
package io.smallrye.beanbag;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedDeque;

import io.smallrye.common.constraint.Assert;

/**
 * A basic bean container.
 */
public final class BeanBag {

    private final Scope singletonScope;
    private final ScopeDefinition scopeDefinition;

    BeanBag(Builder builder) {
        final List> definitions = new ArrayList<>();
        final List> singletonBeans = new ArrayList<>();
        for (BeanBuilder beanBuilder : builder.beanBuilders) {
            addDefinitionsTo(beanBuilder, beanBuilder.singleton ? singletonBeans : definitions);
        }
        // create a copy of the non-singleton scope so singletons can inject from there
        final ScopeDefinition scopeDefinition = new ScopeDefinition(List.copyOf(definitions));
        singletonScope = new Scope(this, null, scopeDefinition, new ScopeDefinition(singletonBeans));
        this.scopeDefinition = scopeDefinition;
    }

    private  void addDefinitionsTo(final BeanBuilder beanBuilder, List> definitions) {
        final String name = beanBuilder.name;
        final Set aliases = beanBuilder.aliases;
        final Set> restrictedTypes = Set
                .copyOf(Objects.requireNonNullElse(beanBuilder.restrictedTypes, List.of()));
        final BeanSupplier supplier = beanBuilder.supplier;
        final int priority = beanBuilder.priority;
        final Class type = beanBuilder.type;
        BeanDefinition definition = new BeanDefinition<>(name, priority, type, restrictedTypes, supplier);
        definitions.add(definition);
        if (aliases != null) {
            for (String alias : aliases) {
                definitions.add(
                        new BeanDefinition<>(alias, priority, type, restrictedTypes, scope -> scope.requireBean(definition)));
            }
        }
    }

    /**
     * Create a new resolution scope.
     * A resolution scope maintains independent instances of its beans.
     *
     * @return the new resolution scope (not {@code null})
     */
    public Scope newScope() {
        return new Scope(this, singletonScope, null, scopeDefinition);
    }

    /**
     * Get all constructable beans of the given type from a new resolution scope.
     *
     * @param type the allowed bean type class (must not be {@code null})
     * @return the (possibly empty) list of all matching beans
     * @param  the allowed bean type
     */
    public  List getAllBeans(final Class type) {
        return newScope().getAllBeans(type);
    }

    /**
     * Require a single bean with the given type from a new resolution scope.
     *
     * @param type the allowed bean type class (must not be {@code null})
     * @return the single bean (not {@code null})
     * @param  the allowed bean type
     * @throws NoSuchBeanException if the bean is not present
     * @throws BeanInstantiationException if some error occurred when instantiating the bean
     */
    public  T requireBean(Class type) {
        return newScope().requireBean(type);
    }

    /**
     * Require a single bean with the given type and name from a new resolution scope.
     *
     * @param type the allowed bean type class (must not be {@code null})
     * @param name the name of the bean which should be returned, or {@code ""} for any (must not be {@code null})
     * @return the single bean (not {@code null})
     * @param  the allowed bean type
     * @throws NoSuchBeanException if the bean is not present
     * @throws BeanInstantiationException if some error occurred when instantiating the bean
     */
    public  T requireBean(Class type, String name) {
        return newScope().requireBean(type, name);
    }

    /**
     * Get a single bean with the given type from a new resolution scope, if it exists and can be instantiated.
     *
     * @param type the allowed bean type class (must not be {@code null})
     * @return the single bean, or {@code null} if it is not present
     * @param  the allowed bean type
     */
    public  T getOptionalBean(Class type) {
        return newScope().getOptionalBean(type);
    }

    /**
     * Get a single bean with the given type and name from a new resolution scope, if it exists and can be instantiated.
     *
     * @param type the allowed bean type class (must not be {@code null})
     * @param name the name of the bean which should be returned, or {@code ""} for any (must not be {@code null})
     * @return the single bean, or {@code null} if it is not present
     * @param  the allowed bean type
     */
    public  T getOptionalBean(Class type, String name) {
        return newScope().getOptionalBean(type, name);
    }

    /**
     * Construct a new container builder.
     *
     * @return the new builder (not {@code null})
     */
    public static Builder builder() {
        return new Builder();
    }

    /**
     * A builder for a new container.
     */
    public static final class Builder {

        /**
         * Java package names that should be excluded during bean discovery
         */
        private List excludePackages = List.of();

        /**
         * Java package names that should be included during bean discovery,
         * unless they also match {@link #excludePackages}
         */
        private List includePackages = List.of();

        /**
         * Bean builders may be added concurrently
         */
        private final Collection> beanBuilders = new ConcurrentLinkedDeque<>();

        Builder() {
        }

        /**
         * Exclude beans whose Java packages start with the value of the argument.
         *
         * @param packageName Java package name to exclude during bean discovery
         * @return this builder instance
         */
        public Builder excludePackage(String packageName) {
            Assert.checkNotNullParam("packageName", packageName);
            if (excludePackages.isEmpty()) {
                excludePackages = new ArrayList<>();
            }
            excludePackages.add(packageName);
            return this;
        }

        /**
         * Include beans whose Java packages start with the value of the argument,
         * unless there is a matching exclude package filter.
         * If inclusions weren't configured, all packages are assumed to be included.
         *
         * @param packageName Java package name to include during bean discovery
         * @return this builder instance
         */
        public Builder includePackage(String packageName) {
            Assert.checkNotNullParam("packageName", packageName);
            if (includePackages.isEmpty()) {
                includePackages = new ArrayList<>();
            }
            includePackages.add(packageName);
            return this;
        }

        public boolean isTypeFilteredOut(String type) {
            return isPackageExcluded(type) || !isPackageIncluded(type);
        }

        private boolean isPackageIncluded(String type) {
            if (includePackages.isEmpty()) {
                return true;
            }
            for (var filter : includePackages) {
                if (type.startsWith(filter)) {
                    return true;
                }
            }
            return false;
        }

        private boolean isPackageExcluded(String type) {
            for (var filter : excludePackages) {
                if (type.startsWith(filter)) {
                    return true;
                }
            }
            return false;
        }

        /**
         * Add a new bean with the given type, returning a builder to configure it.
         * The given type must be the concrete type of the bean or a class representing a supertype of that concrete
         * type.
         *
         * @param type the bean type class (must not be {@code null})
         * @return the bean builder (not {@code null})
         * @param  the bean type
         */
        public  BeanBuilder addBean(final Class type) {
            Assert.checkNotNullParam("type", type);
            return new BeanBuilder(this, type);
        }

        /**
         * Add a new bean which resolves to the given instance.
         *
         * @param bean the bean instance (must not be {@code null})
         * @return this builder (not {@code null})
         * @param  the bean type
         */
        @SuppressWarnings("unchecked")
        public  Builder addBeanInstance(final T bean) {
            Assert.checkNotNullParam("bean", bean);
            return addBean((Class) bean.getClass()).setInstance(bean).build();
        }

        /**
         * Build a new container instance with the beans that were previously configured in this builder.
         *
         * @return the new container (not {@code null})
         */
        public BeanBag build() {
            return new BeanBag(this);
        }
    }

    /**
     * A builder for an individual bean's configuration.
     *
     * @param  the bean type
     */
    public static final class BeanBuilder {
        private final Builder builder;
        private final Class type;

        private int priority = 0;
        private List> restrictedTypes;
        private String name = "";
        private Set aliases;
        private BeanSupplier supplier;
        private boolean singleton;

        BeanBuilder(final Builder builder, final Class type) {
            this.builder = builder;
            this.type = type;
        }

        /**
         * Set the bean priority. Higher numbers have higher precedence.
         * Users should normally configure beans with a priority of {@code 0} or higher.
         *
         * @param priority the bean priority
         * @return this builder (not {@code null})
         */
        public BeanBuilder setPriority(final int priority) {
            this.priority = priority;
            return this;
        }

        /**
         * Set the bean name. Beans with no name have a name of the empty string {@code ""}.
         *
         * @param name the bean name (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public BeanBuilder setName(final String name) {
            Assert.checkNotNullParam("name", name);
            this.name = name;
            return this;
        }

        /**
         * Add another name that this bean can be identified by.
         *
         * @param alias the bean alias (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public BeanBuilder addAlias(final String alias) {
            Assert.checkNotNullParam("alias", alias);
            if (aliases == null) {
                aliases = new HashSet<>();
            }
            aliases.add(alias);
            return this;
        }

        /**
         * Set the supplier for this bean.
         * Setting a supplier will overwrite a supplier created via {@link #buildSupplier()} (if any).
         *
         * @param supplier the supplier instance (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public BeanBuilder setSupplier(final BeanSupplier supplier) {
            Assert.checkNotNullParam("supplier", supplier);
            this.supplier = supplier;
            return this;
        }

        /**
         * Set the supplier for this bean to a literal instance.
         * Setting a supplier will overwrite a supplier created via {@link #buildSupplier()} (if any).
         * The bean will be marked as a singleton.
         *
         * @param instance the bean instance (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public BeanBuilder setInstance(final T instance) {
            Assert.checkNotNullParam("instance", instance);
            this.supplier = scope -> instance;
            singleton = true;
            return this;
        }

        /**
         * Construct a reflective supplier for this bean.
         * Completing this builder will overwrite the supplier created via {@link #setSupplier(BeanSupplier)} (if any).
         *
         * @return a new supplier builder (not {@code null})
         */
        public SupplierBuilder buildSupplier() {
            return new SupplierBuilder<>(this);
        }

        /**
         * Set the singleton flag for this bean.
         * A singleton is created in a scope which is global to a single container.
         *
         * @param singleton the value of the singleton flag
         * @return this builder (not {@code null})
         */
        public BeanBuilder setSingleton(final boolean singleton) {
            this.singleton = singleton;
            return this;
        }

        /**
         * Restrict the types of this bean.
         * The bean will only be able to be looked up using one of these types.
         *
         * @param types the restricted types (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public BeanBuilder addRestrictedTypes(Collection> types) {
            Assert.checkNotNullParam("types", types);
            if (restrictedTypes != null) {
                restrictedTypes.addAll(types);
            } else {
                restrictedTypes = new ArrayList<>(types);
            }
            return this;
        }

        /**
         * Commit this bean definition into the enclosing container builder.
         *
         * @return the container builder (not {@code null})
         */
        public Builder build() {
            builder.beanBuilders.add(this);
            return builder;
        }
    }

    /**
     * A builder for a bean supplier which constructs a bean using reflection.
     *
     * @param  the bean type
     */
    public static final class SupplierBuilder {
        private final BeanBuilder beanBuilder;
        private final List> argumentSuppliers = new ArrayList<>();
        private Constructor constructor;
        private List> injectors;

        SupplierBuilder(final BeanBuilder beanBuilder) {
            this.beanBuilder = beanBuilder;
        }

        /**
         * Set the constructor to use to instantiate the bean.
         *
         * @param constructor the constructor (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public SupplierBuilder setConstructor(final Constructor constructor) {
            Assert.checkNotNullParam("constructor", constructor);
            this.constructor = constructor;
            return this;
        }

        private List> getInjectorList() {
            final List> injectors = this.injectors;
            if (injectors == null) {
                return this.injectors = new ArrayList<>();
            }
            return injectors;
        }

        /**
         * Add a general field injection.
         *
         * @param field the field to inject into (must not be {@code null})
         * @param supplier the supplier of the field's value (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public SupplierBuilder injectField(Field field, BeanSupplier supplier) {
            getInjectorList().add(Injector.forField(Assert.checkNotNullParam("field", field),
                    Assert.checkNotNullParam("supplier", supplier)));
            return this;
        }

        /**
         * Add a bean dependency field injection.
         *
         * @param field the field to inject into (must not be {@code null})
         * @param injectType the bean type to inject (must not be {@code null})
         * @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
         * @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
         * @param filter the filter to apply to determine whether a given bean should be included (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public SupplierBuilder injectField(Field field, Class injectType, String beanName, boolean optional,
                DependencyFilter filter) {
            return injectField(
                    field,
                    BeanSupplier.resolving(
                            injectType,
                            beanName,
                            optional,
                            filter));
        }

        /**
         * Add a bean dependency field injection.
         * The type of the bean is derived from the field's type.
         *
         * @param field the field to inject into (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public SupplierBuilder injectField(Field field) {
            return injectField(field, "");
        }

        /**
         * Add a bean dependency field injection.
         * The type of the bean is derived from the field's type.
         *
         * @param field the field to inject into (must not be {@code null})
         * @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public SupplierBuilder injectField(Field field, String beanName) {
            return injectField(field, beanName, false);
        }

        /**
         * Add a bean dependency field injection.
         * The type of the bean is derived from the field's type.
         *
         * @param field the field to inject into (must not be {@code null})
         * @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
         * @return this builder (not {@code null})
         */
        public SupplierBuilder injectField(Field field, boolean optional) {
            return injectField(field, "", optional);
        }

        /**
         * Add a bean dependency field injection.
         * The type of the bean is derived from the field's type.
         *
         * @param field the field to inject into (must not be {@code null})
         * @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
         * @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
         * @return this builder (not {@code null})
         */
        public SupplierBuilder injectField(Field field, String beanName, boolean optional) {
            return injectField(field, beanName, optional, DependencyFilter.ACCEPT);
        }

        /**
         * Add a bean dependency field injection.
         * The type of the bean is derived from the field's type.
         *
         * @param field the field to inject into (must not be {@code null})
         * @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
         * @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
         * @param filter the filter to apply to determine whether a given bean should be included (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public SupplierBuilder injectField(Field field, String beanName, boolean optional, DependencyFilter filter) {
            return injectField(Assert.checkNotNullParam("field", field), field.getType(), beanName, optional, filter);
        }

        /**
         * Add a general method injection.
         *
         * @param method the method to inject into (must not be {@code null})
         * @param supplier the supplier of the method's value (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public SupplierBuilder injectMethod(Method method, BeanSupplier supplier) {
            getInjectorList().add(Injector.forSetterMethod(Assert.checkNotNullParam("method", method), supplier));
            return this;
        }

        /**
         * Add a bean dependency method injection.
         *
         * @param method the method to inject into (must not be {@code null})
         * @param injectType the bean type to inject (must not be {@code null})
         * @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
         * @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
         * @param filter the filter to apply to determine whether a given bean should be included (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public SupplierBuilder injectMethod(Method method, Class injectType, String beanName, boolean optional,
                DependencyFilter filter) {
            return injectMethod(
                    method,
                    BeanSupplier.resolving(
                            injectType,
                            beanName,
                            optional,
                            filter));
        }

        /**
         * Add a bean dependency method injection.
         * The type of the bean is derived from the method's sole argument type.
         *
         * @param method the method to inject into (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public SupplierBuilder injectMethod(Method method) {
            return injectMethod(method, "");
        }

        /**
         * Add a bean dependency method injection.
         * The type of the bean is derived from the method's sole argument type.
         *
         * @param method the method to inject into (must not be {@code null})
         * @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public SupplierBuilder injectMethod(Method method, String beanName) {
            return injectMethod(method, beanName, false);
        }

        /**
         * Add a bean dependency method injection.
         * The type of the bean is derived from the method's sole argument type.
         *
         * @param method the method to inject into (must not be {@code null})
         * @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
         * @return this builder (not {@code null})
         */
        public SupplierBuilder injectMethod(Method method, boolean optional) {
            return injectMethod(method, "", optional);
        }

        /**
         * Add a bean dependency method injection.
         * The type of the bean is derived from the method's sole argument type.
         *
         * @param method the method to inject into (must not be {@code null})
         * @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
         * @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
         * @return this builder (not {@code null})
         */

        public SupplierBuilder injectMethod(Method method, String beanName, boolean optional) {
            return injectMethod(Assert.checkNotNullParam("method", method), beanName, optional, DependencyFilter.ACCEPT);
        }

        /**
         * Add a bean dependency method injection.
         * The type of the bean is derived from the method's sole argument type.
         *
         * @param method the method to inject into (must not be {@code null})
         * @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
         * @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
         * @param filter the filter to apply to determine whether a given bean should be included (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public SupplierBuilder injectMethod(Method method, String beanName, boolean optional, DependencyFilter filter) {
            return injectMethod(Assert.checkNotNullParam("method", method), method.getParameterTypes()[0], beanName, optional,
                    filter);
        }

        /**
         * Add a general constructor argument injection.
         *
         * @param supplier the supplier of the argument's value (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public SupplierBuilder addConstructorArgument(BeanSupplier supplier) {
            argumentSuppliers.add(Assert.checkNotNullParam("supplier", supplier));
            return this;
        }

        /**
         * Add a bean dependency constructor argument injection.
         *
         * @param injectType the bean type to inject (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public SupplierBuilder addConstructorArgument(Class injectType) {
            return addConstructorArgument(injectType, false);
        }

        /**
         * Add a bean dependency constructor argument injection.
         *
         * @param injectType the bean type to inject (must not be {@code null})
         * @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
         * @return this builder (not {@code null})
         */
        public SupplierBuilder addConstructorArgument(Class injectType, boolean optional) {
            return addConstructorArgument(injectType, "", optional);
        }

        /**
         * Add a bean dependency constructor argument injection.
         *
         * @param injectType the bean type to inject (must not be {@code null})
         * @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public SupplierBuilder addConstructorArgument(Class injectType, String beanName) {
            return addConstructorArgument(injectType, beanName, false);
        }

        /**
         * Add a bean dependency constructor argument injection.
         *
         * @param injectType the bean type to inject (must not be {@code null})
         * @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
         * @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
         * @return this builder (not {@code null})
         */
        public SupplierBuilder addConstructorArgument(Class injectType, String beanName, boolean optional) {
            return addConstructorArgument(injectType, beanName, optional, DependencyFilter.ACCEPT);
        }

        /**
         * Add a bean dependency constructor argument injection.
         *
         * @param injectType the bean type to inject (must not be {@code null})
         * @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
         * @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
         * @param filter the filter to apply to determine whether a given bean should be included (must not be {@code null})
         * @return this builder (not {@code null})
         */
        public SupplierBuilder addConstructorArgument(Class injectType, String beanName, boolean optional,
                DependencyFilter filter) {
            return addConstructorArgument(
                    BeanSupplier.resolving(
                            injectType,
                            beanName,
                            optional,
                            filter));
        }

        /**
         * Commit this supplier definition into the enclosing bean builder.
         * Any supplier previously set on the bean builder is overwritten.
         *
         * @return the enclosing bean builder (not {@code null})
         */
        public BeanBuilder build() {
            BeanSupplier supplier = new ConstructorSupplier<>(Assert.checkNotNullParam("constructor", constructor),
                    argumentSuppliers);
            final List> injectors = this.injectors;
            if (injectors != null) {
                supplier = new InjectingSupplier<>(supplier, List.copyOf(injectors));
            }
            beanBuilder.setSupplier(supplier);
            return beanBuilder;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy