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

net.e6tech.elements.common.resources.ResourcesState Maven / Gradle / Ivy

There is a newer version: 2.7.9
Show newest version
/*
 * Copyright 2015-2019 Futeh Kao
 *
 * 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 net.e6tech.elements.common.resources;

import net.e6tech.elements.common.inject.Injector;
import net.e6tech.elements.common.inject.Module;
import net.e6tech.elements.common.inject.ModuleFactory;
import net.e6tech.elements.common.util.SystemException;

import java.util.*;
import java.util.concurrent.Callable;

/**
 * Created by futeh.
 */
@SuppressWarnings("unchecked")
class ResourcesState {

    private static final List emptyResourceProviders = Collections.unmodifiableList(new ArrayList<>());

    enum State {
        INITIAL,
        OPEN,
        COMMITTED,
        ABORTED,
    }

    private static final String CLASS_MSG = "Class ";
    private static final String BOUND_TO_MSG = " is already bound to ";
    private ModuleFactory factory;
    private Module module;
    protected Injector injector;
    protected Injector parentInjector;
    private State state = State.INITIAL;
    private List resourceProviders = new LinkedList<>();
    private LinkedList injectionList = new LinkedList<>();
    private List externalResourceProviders;
    private Map variables;

    ResourcesState(Resources resources) {
        factory = resources.getResourceManager().getModule().getFactory();
        module = factory.create();
    }

    protected void cleanup() {
        module = factory.create();
        resourceProviders.clear();
        state = State.INITIAL;
        injectionList.clear();
        injector = null;
        externalResourceProviders = null;
    }

    public Module getModule() {
        return module;
    }

    public State getState() {
        return state;
    }

    public void setState(State state) {
        this.state = state;
    }

    public List getResourceProviders() {
        return resourceProviders;
    }

    public void setResourceProviders(List resourceProviders) {
        this.resourceProviders = resourceProviders;
    }

    List getExternalResourceProviders() {
        if (externalResourceProviders == null)
            return emptyResourceProviders;
        return externalResourceProviders;
    }

    void setExternalResourceProviders(List externalResourceProviders) {
        this.externalResourceProviders = externalResourceProviders;
    }

    public void addModule(Module module) {
        this.module.add(module);
    }

    protected void onOpen(Resources resources) {
        if (!injectionList.isEmpty()) {
            createInjector(resources, true);
        }
    }

    protected Injector createInjector(Resources resources, boolean strict) {
        if (injector == null || !injectionList.isEmpty()) {
            injector = (resources.getResourceManager() != null) ?
                    getModule().build(resources.getResourceManager().getModule())
                    : getModule().build();

            // we need to inject here for objects awaiting to be injected because
            // resourceProviders may depend on these objects.
            while (!injectionList.isEmpty()) {
                // need to remove item because it may make resources dirty again calling bind or rebind.  In such a case
                // onOpen will be call again.
                Object obj = injectionList.remove();
                privateInject(resources, injector, obj, strict);
            }
        }
        return injector;
    }

    public  T inject(Resources resources, T object) {
        return inject(resources, object, true);
    }

    public  T inject(Resources resources, T object, boolean strict) {
        if (object == null)
            return object;

        if (state == State.INITIAL) {
            // to be injected when resources is opened.
            injectionList.add(object);
        } else {
            createInjector(resources, strict);
            privateInject(resources, injector, object, strict);
        }
        return object;
    }

    protected void privateInject(Resources resources, Injector injector, Object object, boolean strict) {
        if (object instanceof InjectionListener) {
            ((InjectionListener) object).preInject(resources);
        }

        boolean completed;
        try {
            completed = injector.inject(object, strict);
        } catch (Exception ex) {
            if (parentInjector != null) {
                completed = parentInjector.inject(object, strict);
            } else {
                throw ex;
            }
        }
        if (object instanceof InjectionListener) {
            if (strict || completed)
                ((InjectionListener) object).injected(resources);
        }
    }

    public  T tryBind(Class cls, Callable callable) {
        T o = getModule().getBoundInstance(cls);
        if (o != null)
            return o;

        T instance = null;
        try {
            instance = (T) getModule().bindInstance(cls, callable.call());
        } catch (Exception e) {
            throw new SystemException(e);
        }
        return instance;
    }

    public  T bind(Class cls, T resource) {
        Object o = getModule().getBoundInstance(cls);
        if (o != null)
            throw new AlreadyBoundException(CLASS_MSG + cls + BOUND_TO_MSG + o);

        return (T) getModule().bindInstance(cls, resource);
    }

    public  T rebind(Class cls, T resource) {
        T instance = null;

        try {
            instance = (T) getModule().rebindInstance(cls, resource);
        } catch (Exception e) {
            throw new SystemException(e);
        }
        return instance;
    }

    public  T unbind(Class cls) {
        T instance = null;
        try {
            instance = (T) getModule().unbindInstance(cls);
        } catch (Exception e) {
            throw new SystemException(e);
        }
        return instance;
    }

    public void bindClass(Class cls, Class service) {
        Class c = getModule().getBoundClass(cls);
        if (c != null)
            throw new AlreadyBoundException(CLASS_MSG + cls + BOUND_TO_MSG + c);
        if (service != null)
            getModule().bindClass(cls, service);
        else getModule().bindInstance(cls, null);
    }

    public  T bindNamedInstance(Class cls, String name, T resource) {
        Object o = getModule().getBoundNamedInstance(cls, name);
        if (o != null)
            throw new AlreadyBoundException(CLASS_MSG + cls + BOUND_TO_MSG + o);
        return rebindNamedInstance(cls, name, resource);
    }

    public  T rebindNamedInstance(Class cls, String name, T resource) {
        return (T) getModule().rebindNamedInstance(cls, name, resource);
    }

    public  T getNamedInstance(Resources resources, Class cls, String name) {
        T instance = null;
        if (state == State.INITIAL) {
            if (getModule().getBoundNamedInstance(cls, name) != null)
                instance = getModule().getBoundNamedInstance(cls, name);
            else if (resources.getResourceManager().getModule().getBoundNamedInstance(cls, name) != null)
                instance = resources.getResourceManager().getModule().getBoundNamedInstance(cls, name);
        } else {
            instance = createInjector(resources, true).getNamedInstance(cls, name);
        }
        if (instance == null) {
            throw new InstanceNotFoundException("No instance for class " + cls.getName() +
                    ". Use newInstance if you meant to create an instance.");
        }
        return instance;
    }

    public boolean hasInstance(Resources resources, Class cls) {
        if (cls.isAssignableFrom(Resources.class) || cls.isAssignableFrom(ResourceManager.class))
            return true;
        return getModule().getBoundInstance(cls) != null || resources.getResourceManager().hasInstance(cls);
    }

    public  T getInstance(Resources resources, Class cls) {
        if (cls.isAssignableFrom(Resources.class)) {
            return (T) resources;
        } else if (cls.isAssignableFrom(ResourceManager.class)) {
            return (T) resources.getResourceManager();
        }

        T instance = null;
        if (state == State.INITIAL) {
            if (getModule().getBoundInstance(cls) != null)
                instance = getModule().getBoundInstance(cls);
            else if (resources.getResourceManager().hasInstance(cls))
                instance = resources.getResourceManager().getInstance(cls);
        } else {
             instance = createInjector(resources, true).getInstance(cls);
        }
        if (instance == null) {
            throw new InstanceNotFoundException("No instance for class " + cls.getName() +
                    ". Use newInstance if you meant to create an instance.");
        }
        return instance;
    }

    @SuppressWarnings("unchecked")
    public  Optional getVariable(String key) {
        if (variables == null)
            return Optional.empty();
        T t = (T) variables.get(key);
        return Optional.ofNullable(t);
    }

    public void setVariable(String key, Object val) {
        if (variables == null)
            variables = new HashMap<>();
        variables.put(key, val);
    }

    public  Map computeMapIfAbsent(Class key) {
        if (variables == null)
            variables = new HashMap<>();
        return (Map) variables.computeIfAbsent(key.toString(), k -> new LinkedHashMap<>());
    }

    public Injector getParentInjector() {
        return parentInjector;
    }

    public void setParentInjector(Injector parentInjector) {
        this.parentInjector = parentInjector;
    }
}