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

io.github.pustike.inject.impl.DefaultInjector 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.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import javax.inject.Provider;

import io.github.pustike.inject.BindingKey;
import io.github.pustike.inject.Injector;
import io.github.pustike.inject.NoSuchBindingException;
import io.github.pustike.inject.bind.InjectionListener;
import io.github.pustike.inject.bind.InjectionPoint;
import io.github.pustike.inject.bind.InjectionPointLoader;
import io.github.pustike.inject.bind.Module;

/**
 * Default implementation of an {@link Injector injector}.
 */
public final class DefaultInjector implements Injector {
    private final InjectionPointLoader injectionPointLoader;
    private final Map, Binding> keyBindingMap;
    private final Map>> injectionListenerMatcherMap;
    private DefaultInjector parentInjector;
    private boolean configured;

    /**
     * Create an instance of the Injector with bindings provided by modules and using an internal cache.
     * @param modules a collection of modules
     * @return the instance of default injector
     */
    public static Injector create(Iterable modules) {
        return create(null, modules);
    }

    /**
     * Create an instance of the Injector with bindings provided by modules and the function to apply for injection
     * point cache.
     * @param injectionPointLoader injection point cache/mapping function
     * @param modules a collection of modules
     * @return the instance of default injector
     */
    public static DefaultInjector create(InjectionPointLoader injectionPointLoader, Iterable modules) {
        if (modules == null) {
            throw new NullPointerException("The module list must not be null.");
        }
        if (modules instanceof Collection ? ((Collection) modules).isEmpty()
                : !modules.iterator().hasNext()) {
            throw new IllegalArgumentException("The module list must not be empty.");
        }
        DefaultInjector injector = new DefaultInjector(injectionPointLoader);
        DefaultBinder binder = new DefaultBinder(injector).configure(modules);
        // add injector itself as a binding to the registry
        BindingKey bindingKey = BindingKey.of(Injector.class);
        Provider injectorProvider = () -> injector;
        injector.register(bindingKey, new Binding<>(bindingKey, injectorProvider,
                binder.getScope(Scopes.EAGER_SINGLETON)));
        // do not allow any further modifications to keyBindingMap
        injector.postConfiguration();
        binder.clear();// clear them all
        return injector;
    }

    private DefaultInjector(InjectionPointLoader injectionPointLoader) {
        this.injectionPointLoader = injectionPointLoader == null //
                ? new DefaultInjectionPointLoader() : injectionPointLoader;
        this.keyBindingMap = new ConcurrentHashMap<>();
        this.injectionListenerMatcherMap = new LinkedHashMap<>();
    }

    @Override
    public  T getInstance(Class type) throws NoSuchBindingException {
        return getInstance(BindingKey.of(type));
    }

    @Override
    @SuppressWarnings("unchecked")
    public  T getInstance(BindingKey key) throws NoSuchBindingException {
        Binding binding = getBinding(key);
        if (binding == null) {
            throw new NoSuchBindingException("No binding registered for key: " + key);
        }
        return (T) binding.getInstance(key);
    }

    @Override
    public  Provider getProvider(Class type) throws NoSuchBindingException {
        return getInstance(BindingKey.of(type).toProviderType());
    }

    @Override
    @SuppressWarnings("unchecked")
    public  Provider getProvider(BindingKey key) throws NoSuchBindingException {
        return getInstance(key.toProviderType());
    }

    @Override
    public void injectMembers(Object instance) {
        if (instance == null) {
            throw new NullPointerException("The instance must not be null.");
        }
        @SuppressWarnings("unchecked")
        Class instanceClass = (Class) instance.getClass();
        injectMembers(BindingKey.of(instanceClass), instance);
    }

    @Override
    public Injector getParent() {
        return parentInjector;
    }

    @Override
    public Injector createChildInjector(Module... modules) {
        return createChildInjector(Arrays.asList(modules));
    }

    @Override
    public Injector createChildInjector(Iterable modules) {
        DefaultInjector injector = create(injectionPointLoader, modules);
        injector.parentInjector = this;
        return injector;
    }

    @SuppressWarnings("unchecked")
    private  Binding getBinding(BindingKey bindingKey) {
        Binding binding = bindingKey != null ? (Binding) keyBindingMap.get(bindingKey) : null;
        if (binding == null && parentInjector != null) {
            binding = parentInjector.getBinding(bindingKey);
        }
        return binding;
    }

     void register(BindingKey bindingKey, Binding binding) {
        if (configured) {
            throw new IllegalStateException("Bindings can not be registered after the Injector is configured!");
        }
        @SuppressWarnings("unchecked")
        Binding previousBinding = (Binding) keyBindingMap.putIfAbsent(bindingKey, binding);
        if (previousBinding != null) {
            if(!previousBinding.addBinding(binding)) {// if multiBinder add this binding to it!
                throw new IllegalStateException("A binding is already registered for this key: " + bindingKey);
            }
        }
    }

    void bindInjectionListener(Predicate> typeMatcher, InjectionListener injectionListener) {
        injectionListenerMatcherMap.put(injectionListener, typeMatcher);
    }

    private void postConfiguration() {
        this.configured = true;
        for (Binding binding : keyBindingMap.values()) {
            binding.postConfiguration(this);
        }
    }

     void injectMembers(BindingKey bindingKey, T instance) {
        // first inject based on all known bindings
        List> injectionPointList = injectionPointLoader.getInjectionPoints(instance.getClass());
        for (InjectionPoint injectionPoint : injectionPointList) {
            injectionPoint.injectTo(instance, this);
        }
        // call matching Injection Listeners for this instance type
        Class instanceType = instance.getClass();
        for (Map.Entry>> mapEntry : injectionListenerMatcherMap.entrySet()) {
            Predicate> typeMatcher = mapEntry.getValue();
            if (typeMatcher.test(instanceType)) {
                mapEntry.getKey().afterInjection(bindingKey, instance);
            }
        }
    }

    public void dispose() {
        keyBindingMap.clear();
        injectionPointLoader.invalidateAll();
        injectionListenerMatcherMap.clear();
    }
}