package de.skuzzle.polly.core.moduleloader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.log4j.Logger;
import de.skuzzle.polly.core.moduleloader.annotations.None;
import de.skuzzle.polly.core.moduleloader.annotations.Provide;
import de.skuzzle.polly.core.moduleloader.annotations.Require;
public class DefaultModuleLoader implements ModuleLoader {
private static Logger logger = Logger.getLogger(DefaultModuleLoader.class
.getName());
private Map<Class<?>, Provider> setupProvides;
private Map<Integer, Provider> providedStates;;
private Map<Provider, Set<Class<?>>> beforeSetupReq;
private Map<Provider, Set<Integer>> requiredStates;
private Map<Class<?>, Object> provides;
private Set<Provider> providers;
private Set<Integer> state;
private Provider startUp;
public DefaultModuleLoader() {
this.setupProvides = new HashMap<Class<?>, Provider>();
this.providedStates = new HashMap<Integer, Provider>();
this.beforeSetupReq = new HashMap<Provider, Set<Class<?>>>();
this.requiredStates = new HashMap<Provider, Set<Integer>>();
this.provides = new HashMap<Class<?>, Object>();
this.providers = new HashSet<Provider>();
this.state = new HashSet<Integer>();
}
public void exportToDot(File output) throws IOException {
PrintWriter w = null;
try {
w = new PrintWriter(new FileWriter(output));
w.println("digraph modules {");
if (this.startUp != null) {
w.println(" node [shape=ellipse fillcolor=cadetblue color=black style=\"filled,solid\"] " +
this.startUp.getName());
}
w.println(" node [shape=ellipse fillcolor=lightblue2 color=black style=\"filled,solid\"]");
for (Provider provider : this.providers) {
if (provider != this.startUp) {
w.println(" " + provider.getName() + ";");
}
}
w.println();
w.println(" node [shape=ellipse fillcolor=azure2 color=azure4 style=\"dashed,filled\"];");
for (Entry<Class<?>, Provider> e : this.setupProvides.entrySet()) {
w.println(" " + e.getKey().getSimpleName());
}
for (Entry<Provider, Set<Class<?>>> e : this.beforeSetupReq.entrySet()) {
for (Class<?> cls : e.getValue()) {
w.println(" " + cls.getSimpleName() + "->" + e.getKey().getName());
}
}
for (Entry<Class<?>, Provider> e : this.setupProvides.entrySet()) {
w.println(" " + e.getValue().getName() + "->" +
e.getKey().getSimpleName());
}
w.println("}");
w.flush();
} finally {
if (w != null) {
w.close();
}
}
}
private void processModule(Provider provider) {
Class<?> cls = provider.getClass();
de.skuzzle.polly.core.moduleloader.annotations.Module an = cls.getAnnotation(
de.skuzzle.polly.core.moduleloader.annotations.Module.class);
if (an == null) {
throw new ModuleDependencyException("module " + provider
+ " is not annotated");
}
if (an.startUp()) {
if (this.startUp != null) {
throw new ModuleDependencyException("there is already a startup module: "
+ this.startUp);
}
this.startUp = provider;
}
for (Provide p : an.provides()) {
if (p.component() != None.class) {
this.willProvideDuringSetup(p.component(), provider);
}
if (p.state() >= 0) {
this.willSetState(p.state(), provider);
}
}
for (Require r : an.requires()) {
if (r.component() != None.class) {
this.requireBeforeSetup(r.component(), provider);
}
if (r.state() >= 0) {
this.requireState(r.state(), provider);
}
}
}
@Override
public <T> void willProvideDuringSetup(Class<T> component, Provider provider) {
Provider m = this.setupProvides.get(component);
if (m != null) {
throw new ModuleDependencyException("Module '" + provider
+ "' cannot provide component '" + component
+ "' because it is already provided by module '" + m + "'");
}
Set<Class<?>> requires = this.beforeSetupReq.get(provider);
if (requires != null && requires.contains(component)) {
throw new ModuleDependencyException("Module '" + provider + ""
+ "' cannot provide '" + component
+ "' because it already requires it.");
}
this.setupProvides.put(component, provider);
}
@Override
public <T> void requireBeforeSetup(Class<?> component, Provider provider) {
Provider mod = this.setupProvides.get(component);
if (mod == provider) {
throw new ModuleDependencyException("Module '" + provider
+ "' cannot require '" + component
+ "' because it already provides it.");
}
// TODO: check cyclic dependency
Set<Class<?>> set = this.beforeSetupReq.get(provider);
if (set == null) {
set = new HashSet<Class<?>>();
this.beforeSetupReq.put(provider, set);
}
set.add(component);
}
@Override
public boolean checkRequires(Class<?> component, Provider provider) {
return this.beforeSetupReq.get(provider).contains(component);
}
@Override
public void provideComponent(Object component) {
this.provideComponentAs(component.getClass(), component);
}
@Override
public void provideComponentAs(Class<?> type, Object component) {
if (type == null) {
throw new ModuleDependencyException("Provided type cannot be null");
} else if (component == null) {
throw new ModuleDependencyException("Provided component for '"
+ type + "' cannot be null");
}
this.provides.put(type, component);
}
@Override
public void registerModule(Provider provider) {
this.processModule(provider);
this.providers.add(provider);
}
@Override
public <T> T requireNow(Class<T> component) {
Object comp = this.provides.get(component);
if (comp != null) {
return component.cast(comp);
} else {
throw new IllegalArgumentException("component '" + component
+ "' not provided");
}
}
private boolean isProvided(Class<?> component) {
return this.provides.get(component) != null;
}
@Override
public void runSetup() throws SetupException {
if (this.startUp != null) {
this.runModuleSetup(this.startUp, new HashSet<Provider>());
}
for (Provider provider : this.providers) {
this.runModuleSetup(provider, new HashSet<Provider>());
}
}
private void runModuleSetup(Provider provider, Set<Provider> callSet)
throws SetupException {
if (provider.isSetup()) {
return;
}
callSet.add(provider);
Set<Class<?>> required = this.beforeSetupReq.get(provider);
if (required != null) {
logger.trace("Resolving " + required.size()
+ " requirements for module '" + provider + "': ");
for (Class<?> component : required) {
if (this.isProvided(component)) {
continue;
}
// find module that provides this component
Provider mod = this.setupProvides.get(component);
if (mod == null) {
throw new ModuleDependencyException(
"invalid dependency. no module provides '" + component
+ "' during setup");
} else if (callSet.contains(mod)) {
throw new ModuleDependencyException(
"invalid cyclic dependency between module '" + mod
+ "' and '" + provider + "'");
} else if (mod != provider) {
this.runModuleSetup(mod, callSet);
}
}
}
logger.info("Running setup for '" + provider + "'...");
provider.setupModule();
logger.trace("Success");
callSet.remove(provider);
// check if all components that 'module' claimed to provide are
// actually provided now
for (Entry<Class<?>, Provider> entry : this.setupProvides.entrySet()) {
if (entry.getValue() == provider && !this.isProvided(entry.getKey())) {
throw new ModuleDependencyException("Module '" + provider
+ "' claimed to provide '" + entry.getKey()
+ "' but did not");
}
}
}
@Override
public boolean isStateSet(int state) {
return this.state.contains(state);
}
@Override
public void addState(int state) {
this.state.add(state);
}
@Override
public void requireState(int state, Provider provider) {
Provider mod = this.providedStates.get(state);
if (mod == provider) {
throw new ModuleDependencyException("Module '" + provider
+ "' cannot require state '" + state
+ "' because it already provides it.");
}
Set<Integer> set = this.requiredStates.get(provider);
if (set == null) {
set = new HashSet<Integer>();
this.requiredStates.put(provider, set);
}
set.add(state);
}
@Override
public void willSetState(int state, Provider provider) {
Provider m = this.providedStates.get(state);
if (m != null) {
throw new ModuleDependencyException("State '" + state
+ "' already provided by module '" + m + "'");
}
Set<Integer> requires = this.requiredStates.get(provider);
if (requires != null && requires.contains(state)) {
throw new ModuleDependencyException("Module '" + provider + ""
+ "' cannot provide state '" + state
+ "' because it already requires it.");
}
this.providedStates.put(state, provider);
}
@Override
public void runModules() throws Exception {
for (Provider provider : this.providers) {
this.runModule(provider, new HashSet<Provider>());
}
}
private void runModule(Provider provider, Set<Provider> callSet) throws Exception {
if (provider.isRun()) {
return;
}
callSet.add(provider);
Set<Integer> required = this.requiredStates.get(provider);
if (required != null) {
logger.trace("Requiring " + required.size()
+ " states for module '" + provider + "': ");
for (Integer state : required) {
if (this.isStateSet(state)) {
continue;
}
// find module that provides this component
Provider mod = this.providedStates.get(state);
if (mod == null) {
throw new ModuleDependencyException(
"invalid dependency. no module provides state '"
+ state + "' during run");
} else if (callSet.contains(mod)) {
throw new ModuleDependencyException(
"invalid cyclic dependency between module '" + mod
+ "' and '" + provider + "'");
} else if (mod != provider) {
this.runModule(mod, callSet);
}
}
}
logger.info("Running Module for '" + provider + "'");
provider.runModule();
logger.trace("Success");
callSet.remove(provider);
// check if all states that 'module' claimed to provide are
// actually provided now
for (Entry<Integer, Provider> entry : this.providedStates.entrySet()) {
if (entry.getValue() == provider && !this.isStateSet(entry.getKey())) {
throw new ModuleDependencyException("Module '" + provider
+ "' claimed to provide state '" + entry.getKey()
+ "' but did not");
}
}
}
public void dispose() {
for (Provider mod : this.providers) {
mod.dispose();
}
if (this.startUp != null) {
this.startUp.dispose();
}
this.beforeSetupReq.clear();
this.providers.clear();
this.providedStates.clear();
this.provides.clear();
this.requiredStates.clear();
this.setupProvides.clear();
this.state.clear();
}
}