com.threewks.thundr.module.Modules.java Source code

Java tutorial

Introduction

Here is the source code for com.threewks.thundr.module.Modules.java

Source

/*
 * This file is a component of thundr, a software library from 3wks.
 * Read more: http://3wks.github.io/thundr/
 * Copyright (C) 2014 3wks, <thundr@3wks.com.au>
 *
 * 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 com.threewks.thundr.module;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import com.atomicleopard.expressive.Cast;
import com.atomicleopard.expressive.EList;
import com.atomicleopard.expressive.ETransformer;
import com.atomicleopard.expressive.Expressive;
import com.atomicleopard.expressive.transform.CollectionTransformer;
import com.threewks.thundr.injection.InjectionContext;
import com.threewks.thundr.injection.Module;
import com.threewks.thundr.injection.UpdatableInjectionContext;
import com.threewks.thundr.logger.Logger;

public class Modules {
    private Map<Class<? extends Module>, Collection<Class<? extends Module>>> moduleDependencies = new LinkedHashMap<Class<? extends Module>, Collection<Class<? extends Module>>>();
    private Map<Class<? extends Module>, Module> instances = new LinkedHashMap<Class<? extends Module>, Module>();
    private Map<Module, ModuleStatus> status = new HashMap<Module, ModuleStatus>();
    private List<Module> orderedModules = null;

    public Modules() {
    }

    public void addModule(Class<? extends Module> module) {
        if (!hasModule(module)) {
            Module instance = loadModule(module);
            moduleDependencies.put(module, null);
            instances.put(module, instance);
            status.put(instance, ModuleStatus.Added);
            Logger.debug("Added module %s", Transformers.toModuleName.from(module));
        }
    }

    public <T extends Module> T getModule(Class<T> moduleClass) {
        return Cast.as(instances.get(moduleClass), moduleClass);
    }

    public List<? extends Module> getModules(Collection<Class<? extends Module>> moduleClasses) {
        List<Module> result = new ArrayList<Module>();
        for (Class<? extends Module> moduleClass : moduleClasses) {
            Module instance = instances.get(moduleClass);
            if (instance != null) {
                result.add(instance);
            }
        }
        return result;
    }

    public boolean hasModule(Class<? extends Module> moduleClass) {
        return instances.containsKey(moduleClass);
    }

    public List<Module> listModules() {
        return new ArrayList<Module>(instances.values());
    }

    public void runStartupLifecycle(UpdatableInjectionContext injectionContext) {
        Logger.debug("Loading modules...");
        List<Module> startupOrder = new ArrayList<Module>();
        while (!allModulesStarted()) {
            if (status.values().contains(ModuleStatus.Added)) {
                resolveDependencies();
                orderedModules = determineDependencyOrder();
            } else {
                boolean allInitialised = initialiseNext(injectionContext);
                boolean allConfigured = false;
                if (allInitialised) {
                    allConfigured = configureNext(injectionContext);
                }
                if (allConfigured) {
                    Module started = startNext(injectionContext);
                    if (started != null) {
                        startupOrder.add(started);
                    }
                }
            }
        }
        Logger.info("Modules loaded");
        if (Logger.willDebug()) {
            StringBuilder sb = new StringBuilder();
            for (Module injectionConfiguration : startupOrder) {
                sb.append("\n\t");
                sb.append(injectionConfiguration.getClass().getSimpleName());
            }
            Logger.debug("Modules started in this order:%s", sb.toString());

        }
    }

    /**
     * @param injectionContext
     * @return true if all modules are initialised
     */
    private boolean initialiseNext(UpdatableInjectionContext injectionContext) {
        Module initialise = getFirstModuleWithStatus(ModuleStatus.DependenciesResolved);
        if (initialise != null) {
            initialise.initialise(injectionContext);
            status.put(initialise, ModuleStatus.Initialised);
        }
        return initialise == null;
    }

    /**
     * @param injectionContext
     * @return true if all modules are already initialised
     */
    private boolean configureNext(UpdatableInjectionContext injectionContext) {
        Module configure = getFirstModuleWithStatus(ModuleStatus.Initialised);
        if (configure != null) {
            configure.configure(injectionContext);
            status.put(configure, ModuleStatus.Configured);
        }
        return configure == null;
    }

    private Module startNext(UpdatableInjectionContext injectionContext) {
        Module start = getFirstModuleWithStatus(ModuleStatus.Configured);
        if (start != null) {
            start.start(injectionContext);
            status.put(start, ModuleStatus.Started);
        }
        return start;
    }

    private Module getFirstModuleWithStatus(ModuleStatus status) {
        Module result = null;
        for (Module injectionConfiguration : orderedModules) {
            if (status.equals(this.status.get(injectionConfiguration))) {
                return injectionConfiguration;
            }
        }
        return result;
    }

    private boolean allModulesStarted() {
        Collection<ModuleStatus> values = status.values();
        return !values.contains(ModuleStatus.Added) && !values.contains(ModuleStatus.DependenciesResolved)
                && !values.contains(ModuleStatus.Initialised) && !values.contains(ModuleStatus.Configured);
    }

    public void runStopLifecycle(InjectionContext injectionContext) {
        List<Module> reverseOrder = new LinkedList<Module>(orderedModules);
        Collections.reverse(reverseOrder);
        for (Module injectionConfiguration : reverseOrder) {
            injectionConfiguration.stop(injectionContext);
            status.put(injectionConfiguration, ModuleStatus.Stopped);
        }
    }

    /**
     * Determines the dependency order of all modules.
     * 
     * @return
     */
    protected List<Module> determineDependencyOrder() {
        List<Module> orderedModules = new ArrayList<Module>();

        while (!orderedModules.containsAll(instances.values())) {
            boolean anyAdded = false;
            for (Map.Entry<Class<? extends Module>, Module> entry : instances.entrySet()) {
                Module instance = entry.getValue();

                if (!orderedModules.contains(instance)) {
                    Class<? extends Module> configurationClass = entry.getKey();

                    Collection<Class<? extends Module>> dependencies = moduleDependencies.get(configurationClass);
                    List<? extends Module> injectionConfigurations = getModules(dependencies);
                    if (orderedModules.containsAll(injectionConfigurations)) {
                        orderedModules.add(instance);
                        anyAdded = true;
                    }
                }
            }
            if (!anyAdded) {
                List<Module> unloaded = Expressive.list(instances.values()).removeItems(orderedModules);
                EList<String> moduleNames = Transformers.toModuleNamesFromInstance.from(unloaded);
                throw new ModuleLoadingException(
                        "Unable to load modules - there are unloaded modules whose dependencies cannot be satisfied. This probably indicates a cyclical dependency. The following modules have not been loaded: %s",
                        StringUtils.join(moduleNames, " "));
            }
        }
        return orderedModules;
    }

    /**
     * Causes the dependent modules for any modules already added to be added as well.
     */
    public void resolveDependencies() {
        while (hasMoreDependenciesToResolve()) {
            for (Module injectionConfiguration : getModulesWithUnresolvedDependencies()) {

                Class<? extends Module> moduleClass = injectionConfiguration.getClass();

                DependencyRegistry dependencyRegistry = new DependencyRegistry();
                injectionConfiguration.requires(dependencyRegistry);
                Collection<Class<? extends Module>> dependencies = dependencyRegistry.getDependencies();
                String moduleName = Transformers.toModuleName.from(moduleClass);
                for (Class<? extends Module> dependencyClass : dependencies) {
                    addModule(dependencyClass);
                    Logger.debug("Module %s depends on %s", moduleName,
                            Transformers.toModuleName.from(dependencyClass));
                }

                moduleDependencies.put(moduleClass, dependencies);
                status.put(injectionConfiguration, ModuleStatus.DependenciesResolved);
            }
        }
    }

    private Collection<Module> getModulesWithUnresolvedDependencies() {
        Collection<Module> result = new ArrayList<Module>();
        for (Map.Entry<Module, ModuleStatus> loaded : status.entrySet()) {
            if (ModuleStatus.Added.equals(loaded.getValue())) {
                result.add(loaded.getKey());
            }
        }
        return result;
    }

    private boolean hasMoreDependenciesToResolve() {
        return status.values().contains(ModuleStatus.Added);
    }

    protected static Module loadModule(Class<? extends Module> moduleClass) {
        try {
            Object newInstance = moduleClass.newInstance();
            Module configuration = Cast.as(newInstance, Module.class);
            if (configuration == null) {
                throw new ModuleLoadingException(
                        "Failed to load module '%s' - the configuration class %s does not implement '%s'",
                        moduleClass.getName(), Module.class.getName());
            }
            return configuration;
        } catch (InstantiationException e) {
            throw new ModuleLoadingException(e, moduleClass.getPackage().getName(),
                    "failed to instantiate configuration class %s: %s", moduleClass.getName(), e.getMessage());
        } catch (IllegalAccessException e) {
            throw new ModuleLoadingException(e, moduleClass.getPackage().getName(),
                    "cannot instantiate configuration class %s: %s", moduleClass.getName(), e.getMessage());
        }
    }

    public static class Transformers {
        public static final ETransformer<Class<?>, String> toModuleName = new ETransformer<Class<?>, String>() {
            @Override
            public String from(Class<?> from) {
                return from.getName();
            }
        };
        public static final CollectionTransformer<Class<?>, String> toModuleNames = Expressive.Transformers
                .transformAllUsing(toModuleName);
        public static final ETransformer<Module, String> toModuleNameFromInstance = new ETransformer<Module, String>() {
            @Override
            public String from(Module from) {
                return from.getClass().getName();
            }
        };
        public static final CollectionTransformer<Module, String> toModuleNamesFromInstance = Expressive.Transformers
                .transformAllUsing(toModuleNameFromInstance);
    }

    private enum ModuleStatus {
        Added, DependenciesResolved, Initialised, Configured, Started, Stopped;
    }
}