/*******************************************************************************
* Copyright (c) 2001, 2010 Mathew A. Nelson and Robocode contributors
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://robocode.sourceforge.net/license/epl-v10.html
*
* Contributors:
* Pavel Savara
* - Initial implementation
*******************************************************************************/
package net.sf.robocode.core;
import net.sf.robocode.io.Logger;
import org.picocontainer.Characteristics;
import org.picocontainer.MutablePicoContainer;
import org.picocontainer.behaviors.Caching;
import org.picocontainer.behaviors.OptInCaching;
import org.picocontainer.classname.DefaultClassLoadingPicoContainer;
import java.awt.*;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.net.URL;
import java.net.MalformedURLException;
/**
* Root of loaders.
*
* we have three types of classLoaders.
* 1) System class loader. Is loaded by default and is parent of loaders below. It contains this container class. All content of robot.api module. All general java stuff.
* 2) EngineClassLoader. Is used as isolation of all other robocode modules from system classloader. Anything loaded by engine is loaded there. We use single instance of this loader.
* 3) RobotClassLoader. Is used by robots. It will load every class on robot's private classPath. It blocks malicious attempts of references to robocode engine classes. We use multiple instances of this loader.
* - communication between classes from different classloaders must be done using interfaces or data types from system classLoader
* - this class must not reference any class of EngineClassLoader scope
*
* Dependency injection
* We use PicoContainer as IoC vehicle. We configure it by loading Module class in every .jar or classpath we can find on system classPath
* 1) Container.cache is containing singletons
* 2) Container.factory will create always new instance of component
*
* @author Pavel Savara (original)
*/
public final class Container extends ContainerBase {
public static final boolean isSecutityOn = !System.getProperty("NOSECURITY", "false").equals("true");
private static final String classPath = System.getProperties().getProperty("robocode.class.path", null);
public static final MutablePicoContainer cache;
public static final MutablePicoContainer factory;
public static final ClassLoader systemLoader;
public static final ClassLoader engineLoader;
private static Set<String> known = new HashSet<String>();
public static final List<IModule> modules = new ArrayList<IModule>();
static {
instance = new Container();
systemLoader = Container.class.getClassLoader();
engineLoader = new EngineClassLoader(systemLoader);
final Thread currentThread = Thread.currentThread();
currentThread.setContextClassLoader(engineLoader);
currentThread.setName("Application Thread");
// make sure we have AWT before we init security
Toolkit.getDefaultToolkit();
cache = new DefaultClassLoadingPicoContainer(engineLoader, new Caching(), null);
factory = new DefaultClassLoadingPicoContainer(engineLoader, new OptInCaching(), cache);
loadModule("net.sf.robocode.api", systemLoader);
final String[] cp = classPath.split(File.pathSeparator);
// load core first
for (String path : cp) {
if (path.toLowerCase().contains("robocode.core")) {
loadFromPath(path);
}
}
for (String path : cp) {
loadFromPath(path);
}
if (known.size() < 2) {
Logger.logError("Main modules not loaded, something went wrong. We have only " + known.size() + " modules");
Logger.logError("Class path: " + classPath);
throw new Error("Main modules not loaded");
}
for (IModule module : modules) {
module.afterLoaded(modules);
}
}
public static void init() {}
private static void loadFromPath(String path) {
try {
File pathf = new File(path).getCanonicalFile();
path = pathf.toString();
final String test = path.toLowerCase();
if (pathf.isDirectory()) {
String name = getModuleName(path);
if (name != null) {
loadModule(name, engineLoader);
}
} else if (test.contains(File.separator + "robocode.") && test.endsWith(".jar")) {
final int i = test.lastIndexOf("robocode.jar");
if (i > 0) {
// load other .jar files in location
final File dir = new File(path.substring(0, i));
Logger.logMessage("Loading plugins from " + dir.toString());
loadJars(dir);
} else {
String name = getModuleName(path);
if (name != null) {
loadModule(name, engineLoader);
}
}
}
} catch (IOException e) {
Logger.logError(e);
}
}
private static void loadJars(File pathf) {
final File[] files = pathf.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.toLowerCase().startsWith("robocode") && name.toLowerCase().endsWith(".jar");
}
});
for (File file : files) {
loadModule(getModuleName(file.toString()), engineLoader);
}
}
private static boolean loadModule(String module, ClassLoader loader) {
try {
if (known.contains(module)) {
// Logger.logMessage("already loaded " + module);
return false;
}
Class<?> modClass = loader.loadClass(module + ".Module");
final Object moduleInstance = modClass.newInstance();
if (moduleInstance instanceof IModule) {
modules.add((IModule) moduleInstance);
}
Logger.logMessage("Loaded " + module);
known.add(module);
return true;
} catch (ClassNotFoundException ignore) {// it is not our module ?
// Logger.logMessage("Can't load " + module);
} catch (IllegalAccessException e) {
Logger.logError(e);
} catch (InstantiationException e) {
Logger.logError(e);
}
return false;
}
private static String getModuleName(String path) {
final String test = path.toLowerCase();
if (test.endsWith("robocode.jar") || test.contains("robocode.api")) {
return null;
}
int i = test.lastIndexOf("robocode.");
if (i > 0) {
String name = path.substring(i);
i = name.indexOf(File.separator);
if (i > 0) {
return "net.sf." + name.substring(0, i);
}
i = name.indexOf("-");
if (i > 0) {
return "net.sf." + name.substring(0, i);
}
return "net.sf." + name;
}
return null;
}
public static void loadJars(String allowed) {
final URL[] jars = findJars(allowed);
for (URL jar : jars) {
((EngineClassLoader) engineLoader).addURL(jar);
}
}
public static URL[] findJars(String allowed) {
java.util.List<String> urls = new ArrayList<String>();
final String classPath = System.getProperty("robocode.class.path", null);
for (String path : classPath.split(File.pathSeparator)) {
String test = path.toLowerCase();
if (test.contains(allowed)) {
if (!test.contains("robocode.jar") && !test.contains("robocode.api")
) {
urls.add(path);
}
}
}
return convertUrls(urls);
}
private static URL[] convertUrls(java.util.List<String> surls) {
final URL[] urls = new URL[surls.size()];
for (int i = 0; i < surls.size(); i++) {
String url = surls.get(i);
File f = new File(url);
try {
urls[i] = f.getCanonicalFile().toURI().toURL();
} catch (MalformedURLException e) {
Logger.logError(e);
} catch (IOException e) {
Logger.logError(e);
}
}
return urls;
}
protected <T> T getBaseComponent(final Class<T> tClass) {
return cache.getComponent(tClass);
}
public static <T> T getComponent(java.lang.Class<T> tClass) {
return cache.getComponent(tClass);
}
@SuppressWarnings("unchecked")
public static <T> T getComponent(String name) {
return (T) cache.getComponent(name);
}
public static <T> T getComponent(java.lang.Class<T> tClass, String className) {
final List<T> list = cache.getComponents(tClass);
for (T component : list) {
if (component.getClass().getName().endsWith(className)) {
return component;
}
}
return null;
}
public static <T> java.util.List<T> getComponents(java.lang.Class<T> tClass) {
return cache.getComponents(tClass);
}
public static <T> T createComponent(java.lang.Class<T> tClass) {
return factory.as(Characteristics.NO_CACHE).getComponent(tClass);
}
}