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

io.github.pustike.inject.impl.DefaultBindingBuilder Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * Copyright (C) 2016-2017 the original author or authors.
 *
 * Licensed 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 io.github.pustike.inject.impl;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.inject.Provider;
import javax.inject.Singleton;

import io.github.pustike.inject.BindingKey;
import io.github.pustike.inject.Names;
import io.github.pustike.inject.Scope;
import io.github.pustike.inject.bind.AnnotatedBindingBuilder;
import io.github.pustike.inject.bind.LinkedBindingBuilder;
import io.github.pustike.inject.bind.MultiBinder;
import io.github.pustike.inject.bind.ScopedBindingBuilder;

/**
 * Default implementation of a binding builder. Implements {@link AnnotatedBindingBuilder},
 * thus {@link ScopedBindingBuilder}, and {@link LinkedBindingBuilder} as well. In other words:
 * Under the hood, you are always using this one and only binding builder.
 */
final class DefaultBindingBuilder implements AnnotatedBindingBuilder, MultiBinder {
    private static final String SCOPE_METHOD_LIST = "toInstance(Object), scope(Scopes)," +
            " asEagerSingleton(), and asLazySingleton()";
    private static final String TARGET_METHOD_LIST = "toInstance(Object), to(Class),"
            + " to(Constructor), to(Method), to(Provider, Class)";
    private final BindingKey sourceKey;
    private final DefaultBinder binder;
    private final Scope defaultScope;
    private Annotation sourceAnnotation;
    private Class sourceAnnotationType;
    private Class targetType;
    private Provider targetProvider;
    private Scope scope;
    // for multi-binding
    private final boolean multiBinder;
    private List> bindingList;
    private boolean addingBinding;

    DefaultBindingBuilder(BindingKey key, DefaultBinder binder, Scope defaultScope, boolean multiBinder) {
        this.sourceKey = key;
        this.binder = binder;
        this.defaultScope = defaultScope;
        this.multiBinder = multiBinder;
        this.bindingList = new ArrayList<>();
    }

    @Override
    public void toInstance(T instance) {
        if (instance == null) {
            throw new NullPointerException("The target instance must not be null.");
        }
        checkNoTarget();
        @SuppressWarnings("unchecked")
        Class instanceClass = (Class) instance.getClass();
        targetType = instanceClass;
        targetProvider = () -> instance;
        asEagerSingleton();
    }

    @Override
    public ScopedBindingBuilder to(Class implementation) {
        checkNoTarget();
        targetType = Objects.requireNonNull(implementation);
        return this;
    }

    @Override
    public ScopedBindingBuilder to(Constructor constructor) {
        if (constructor == null) {
            throw new NullPointerException("The target constructor must not be null.");
        }
        checkNoTarget();
        targetProvider = InstanceProvider.from(constructor);
        return this;
    }

    @Override
    public ScopedBindingBuilder to(Method factoryMethod) {
        if (factoryMethod == null) {
            throw new NullPointerException("The target constructor must not be null.");
        } else if (!Modifier.isStatic(factoryMethod.getModifiers())) {
            throw new IllegalStateException("The target method must be static.");
        } else if (factoryMethod.getReturnType().isPrimitive()) {
            throw new IllegalStateException("The target method must return a non-primitive result.");
        } else if (factoryMethod.getReturnType().isArray()) {
            throw new IllegalStateException("The target method must return a single object, and not an array.");
        } else if (Void.TYPE == factoryMethod.getReturnType()) {
            throw new IllegalStateException("The target method must return a non-void result.");
        }
        checkNoTarget();
        targetProvider = InstanceProvider.from(factoryMethod, null);
        return this;
    }

    @Override
    public ScopedBindingBuilder toProvider(Provider provider) {
        checkNoTarget();
        targetProvider = Objects.requireNonNull(provider);
        targetType = sourceKey.getType();
        return this;
    }

    @Override
    public ScopedBindingBuilder toProvider(Class> providerType) {
        checkNoTarget();
        Objects.requireNonNull(providerType);
        targetProvider = InstanceProvider.fromProvider(providerType);
        targetType = sourceKey.getType();
        return this;
    }

    @Override
    public void in(Class scopeAnnotation) {
        in(binder.getScope(scopeAnnotation.getName()));
    }

    @Override
    public void in(Scope scope) {
        if (this.scope != null) {
            throw new IllegalStateException("The methods " + SCOPE_METHOD_LIST
                    + " are mutually exclusive, and may be invoked only once.");
        }
        this.scope = Objects.requireNonNull(scope);
    }

    @Override
    public void asEagerSingleton() {
        in(binder.getScope(Scopes.EAGER_SINGLETON));
    }

    @Override
    public void asLazySingleton() {
        in(binder.getScope(Singleton.class.getName()));
    }

    @Override
    public LinkedBindingBuilder named(String name) {
        return annotatedWith(Names.named(name));
    }

    @Override
    public LinkedBindingBuilder annotatedWith(Annotation annotation) {
        if (sourceAnnotation != null) {
            throw new IllegalStateException("The method annotatedWith(Annotation) must not be invoked twice.");
        }
        sourceAnnotation = Objects.requireNonNull(annotation);
        return this;
    }

    @Override
    public LinkedBindingBuilder annotatedWith(Class annotationType) {
        if (sourceAnnotationType != null) {
            throw new IllegalStateException("The method annotatedWith(Class) must not be invoked twice.");
        }
        sourceAnnotationType = Objects.requireNonNull(annotationType);
        return this;
    }

    @Override
    public LinkedBindingBuilder addBinding() {
        if (addingBinding) {
            doAddBinding();
        }
        this.addingBinding = true;
        return this;
    }

    private void doAddBinding() {
        if (targetProvider == null && targetType == null) {
            throw new IllegalStateException("The target instance or a provider should be configured!");
        }
        @SuppressWarnings("unchecked")
        Provider provider = (Provider) getInstanceProvider();
        bindingList.add(new Binding<>(sourceKey, provider, getScope()));
        targetType = null;
        targetProvider = null;
        scope = null;
    }

    @SuppressWarnings("unchecked")
    void build(DefaultInjector injector) {
        if (addingBinding) {
            doAddBinding();
        }
        BindingKey bindingKey = sourceAnnotation != null ? BindingKey.of(sourceKey.getType(), sourceAnnotation)
                : BindingKey.of(sourceKey.getType(), sourceAnnotationType);
        bindingKey = multiBinder ? (BindingKey) bindingKey.toListType() : bindingKey;
        @SuppressWarnings("unchecked")
        Binding binding = multiBinder ? new Binding<>(bindingKey, bindingList, getScope())
                : new Binding<>(bindingKey, (Provider) getInstanceProvider(), getScope());
        injector.register(bindingKey, binding);
        // call matching TypeBindingListeners for this binding targetType
        Class instanceType = targetType == null ? sourceKey.getType() : targetType;
        binder.visitTypeBindingListeners(instanceType);
    }

    private Scope getScope() {
        if (scope == null) {
            Class instanceType = targetType != null ? targetType : sourceKey.getType();
            Class scopeAnnotation = getScopeAnnotation(instanceType.getDeclaredAnnotations());
            scope = scopeAnnotation != null ? binder.getScope(scopeAnnotation.getName()) : defaultScope;
        }
        if (scope == null) {
            throw new IllegalStateException("Neither of the methods " + SCOPE_METHOD_LIST
                    + " has been invoked on this binding builder.");
        }
        return scope;
    }

    static Class getScopeAnnotation(Annotation[] annotations) {
        for (Annotation annotation : annotations) {
            if (annotation.annotationType().isAnnotationPresent(javax.inject.Scope.class)) {
                return annotation.annotationType();
            }
        }
        return null;
    }

    private Provider getInstanceProvider() {
        if (targetProvider != null) {
            return targetProvider;
        } else if (targetType != null) {
            return InstanceProvider.from(targetType);
        }
        Class sourceType = sourceKey.getType();
        if (sourceType != null) {
            if (sourceType.isInterface() || Modifier.isAbstract(sourceType.getModifiers())) {
                throw new IllegalStateException("Neither of the methods " + TARGET_METHOD_LIST
                        + " have been invoked on this binding builder, but cannot bind " + sourceType.getName()
                        + " as target type, because it is an interface, or an abstract class.");
            }
            return InstanceProvider.from(sourceType);
        }
        throw new IllegalStateException("Neither of the methods " + TARGET_METHOD_LIST
                + " have been invoked on this binding builder.");
    }

    private void checkNoTarget() {
        if (targetType != null || targetProvider != null) {
            throw new IllegalStateException("The methods " + TARGET_METHOD_LIST
                    + " are mutually exclusive, and may be invoked only once.");
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy