/*
* Cero Project - Copyright 2006 The Cero Developement Team
* (Michael Laguerre, Camille Roux, Matthieu Segret, Mathieu Sivade)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
package cero.plugin;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import cero.UnsupportedPluginException;
import cero.games.AIPlayer;
import cero.games.Game;
import cero.games.GameInitializer;
import cero.games.Player;
import cero.games.Rule;
import cero.games.base.PlayerBase;
import cero.ui.InterfaceModule;
import cero.ui.UserInterface;
/**
* This class gives you acces to all the plugins you might want to use. It is a
* singleton, and you must *NOT* try to instanciate it, as it will throw an
* Exception if you do. Use getInstance instead to get a reference on the
* PluginManager.
*
* @author Telem
*/
public class PluginManager {
/** The collection of user interfaces */
private Collection<InterfacePlugin> uiPlugins = new ArrayList<InterfacePlugin>();
/** The collection of modules designed for user interfaces */
private Collection<InterfaceModulePlugin> uiPluginModules = new ArrayList<InterfaceModulePlugin>();
/** The collection of games mapped by their name */
private Map<String, GamePlugin> gamePlugins = new HashMap<String, GamePlugin>();
// FIXME : à faire disparaitre
/**
* The collection of the UI instances used, currently needed by PlayerBase
* to return a ChoiceMaker
*/
private Collection<UserInterface> uiInstances = new ArrayList<UserInterface>();
private static PluginManager instance = null;
public PluginManager() throws InstantiationException {
if (instance != null)
throw new InstantiationException(
"PluginManager is a Singleton, you can't create more than one instance");
instance = this;
}
public static PluginManager getInstance() {
if (instance == null) {
try {
new PluginManager();
} catch (InstantiationException e) {
// we're sure we'll never get there as long as we don't use
// multithreading
}
}
return instance;
}
public void loadPath(String path) {
loadPath(path, 0);
}
public void loadPath(String path, int recDeepness) {
loadPath(path, recDeepness, ClassLoader.getSystemClassLoader());
}
private void loadPath(String path, int recDeepness, ClassLoader parentcl) {
if (recDeepness < 0)
recDeepness = -1;
File file = new File(path);
DynamicURLClassLoader durlcl = new DynamicURLClassLoader(new URL[0],
parentcl);
String[] filesToLoad = null;
if (file.isDirectory())
filesToLoad = file.list();
else {
filesToLoad = new String[1];
filesToLoad[0] = file.getAbsolutePath();
}
if (filesToLoad != null) {
Collection<File> directories = new ArrayList<File>();
File tmpfile;
for (int i = 0; i < filesToLoad.length; i++) {
tmpfile = new File(path + "/" + filesToLoad[i]);
if (tmpfile.isDirectory())
directories.add(tmpfile);
else if (filesToLoad[i].endsWith(".jar")) {
// if it is a file, loads each plugin contained in it
try {
durlcl.addURL(new URL("file://"
+ file.getAbsolutePath()));
Set<String> loadedClassesNames = listClassesInJar(tmpfile);
Set<Class<?>> loadedClasses = new HashSet<Class<?>>();
for (String name : loadedClassesNames)
loadedClasses.add(durlcl.loadClass(name));
loadGamePlugin(loadedClasses, durlcl);
loadInterfacePlugins(loadedClasses);
loadInterfaceModulePlugins(loadedClasses);
} catch (IOException e1) {
// the file should be accessible, theorically. else, it
// will rely on the error system not yet implemented
e1.printStackTrace();
} catch (BrokenPluginException e) {
// we should inform the user
e.printStackTrace();
} catch (ClassNotFoundException e) {
// there is a .class entry in the jar that is not a clas
e.printStackTrace();
}
}
}
// once the files of the folder are loaded, we load the subfolders
// if needed
if (recDeepness != 0)
for (File f : directories)
loadPath(f.getAbsolutePath(), recDeepness - 1, durlcl);
}
}
private Set<String> listClassesInJar(File jarfile) throws IOException {
Set<String> classes = new HashSet<String>();
JarFile jf = new JarFile(jarfile);
Enumeration<JarEntry> jarEntries = jf.entries();
while (jarEntries.hasMoreElements()) {
String entryname = jarEntries.nextElement().getName();
if (entryname.endsWith(".class"))
classes.add(entryname.replace("/", ".").substring(0,
entryname.length() - 6));
}
return classes;
}
private static boolean canBeInstanciable(Class<?> c) {
final int notInstantiableClassMask = Modifier.ABSTRACT
| Modifier.INTERFACE | Modifier.PRIVATE | Modifier.PROTECTED;
if ((c.getModifiers() & notInstantiableClassMask) != 0)
return false;
if ((c.getModifiers() & Modifier.PUBLIC) == 0)
return false;
if (c.isLocalClass())
return false;
if (c.isMemberClass())
return false;
return true;
}
private void loadGamePlugin(Set<Class<?>> classes, ClassLoader myLoader)
throws BrokenPluginException {
Class<? extends Game> gameClass = null;
Class<? extends Player> playerClass = null;
Collection<Class<? extends Rule>> rulesClass = new ArrayList<Class<? extends Rule>>();
Collection<Class<? extends GameInitializer>> initClass = new ArrayList<Class<? extends GameInitializer>>();
Collection<Class<? extends AIPlayer>> aisClass = new ArrayList<Class<? extends AIPlayer>>();
for (Class<?> c : classes) {
if (!canBeInstanciable(c))
continue;
if (Game.class.isAssignableFrom(c))
gameClass = c.asSubclass(Game.class);
else if (AIPlayer.class.isAssignableFrom(c))
aisClass.add(c.asSubclass(AIPlayer.class));
else if (Player.class.isAssignableFrom(c))
playerClass = c.asSubclass(Player.class);
else if (Rule.class.isAssignableFrom(c))
rulesClass.add(c.asSubclass(Rule.class));
else if (GameInitializer.class.isAssignableFrom(c))
initClass.add(c.asSubclass(GameInitializer.class));
}
try {
if (gameClass == null)
throw new BrokenPluginException(
"Plugin is missing an instantiable Game class");
if (playerClass == null)
playerClass = myLoader.loadClass("cero.games.base.PlayerBase")
.asSubclass(PlayerBase.class);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("Couldn't find PlayerBase");
}
// GamePlugin construction
GamePlugin gp;
try {
gp = new GamePlugin(gameClass);
gp.setPlayerType(playerClass);
} catch (NotInstantiableException e) {
// if any of these aren't instantiable, the game can't be played
throw new BrokenPluginException(e.getMessage());
}
try {
for (Class<? extends AIPlayer> c : aisClass)
gp.addAIType(c);
for (Class<? extends Rule> c : rulesClass)
gp.addRule(c);
for (Class<? extends GameInitializer> c : initClass)
gp.addInitializer(c);
} catch (NotInstantiableException e) {
// if any of these aren't instantiable, the user should be warned
// yet the game can be played
e.printStackTrace();
}
gamePlugins.put(gp.getPluginName(), gp);
}
private void loadInterfacePlugins(Set<Class<?>> classes) {
for (Class<?> c : classes) {
if (canBeInstanciable(c) && UserInterface.class.isAssignableFrom(c)) {
try {
uiPlugins.add(new InterfacePlugin(c
.asSubclass(UserInterface.class)));
} catch (Exception e) {
// we should inform the user
e.printStackTrace();
}
}
}
}
private void loadInterfaceModulePlugins(Set<Class<?>> classes) {
for (Class<?> c : classes) {
if (canBeInstanciable(c)
&& InterfaceModule.class.isAssignableFrom(c)) {
try {
uiPluginModules.add(new InterfaceModulePlugin(c
.asSubclass(InterfaceModule.class)));
} catch (NotInstantiableException e) {
// TODO print down the modules which couldn't be found
e.printStackTrace();
}
}
}
}
public Collection<String> getGamesName() {
return new ArrayList<String>(gamePlugins.keySet());
}
public Collection<String> getAIsName(String gameName) {
GamePlugin gp = gamePlugins.get(gameName);
return (gp == null)?null:gp.getAINames();
}
public Collection<String> getInterfacesName() {
Collection<String> ui = new ArrayList<String>();
for (InterfacePlugin uip : uiPlugins) {
ui.add(uip.getPluginName());
}
return ui;
}
public Game getNewGame(String gameName) {
GamePlugin gp = gamePlugins.get(gameName);
if (gp == null)
return null;
Game game = gp.newGameInstance();
// adds the interface to the game, so that they become aware of it
for (UserInterface ui : uiInstances)
game.addGameListener(ui);
return game;
}
public Player getNewPlayer(String gameName) {
GamePlugin gp = gamePlugins.get(gameName);
return (gp == null)?null:gp.newPlayer();
}
public AIPlayer getNewAI(String gameName, String AIName) {
GamePlugin gp = gamePlugins.get(gameName);
return (gp == null)?null:gp.newAIPlayer(AIName);
}
public Collection<GameInitializer> getGameInitializers(String gameName) {
GamePlugin gp = gamePlugins.get(gameName);
return (gp == null)?null:gp.getInitializers();
}
public Collection<Rule> getGameRules(String gameName) {
GamePlugin gp = gamePlugins.get(gameName);
return (gp == null)?null:gp.getRules();
}
public UserInterface getNewUserInterface(String uiName) {
InterfacePlugin founduip = null;
for (InterfacePlugin uip : uiPlugins) {
if (uip.getPluginName().equals(uiName))
founduip = uip;
}
if (founduip == null)
return null;
UserInterface uiinst = founduip.newInterfaceInstance();
for (InterfaceModulePlugin imp : uiPluginModules) {
if (imp.getInterfaceType().isInstance(uiinst))
try {
uiinst.addInterfaceModule(imp.newModuleInstance());
} catch (UnsupportedPluginException e) {
// TODO we should inform the user the plugin is
// incoherent with the interface
}
}
uiInstances.add(uiinst);
return uiinst;
}
public Collection<UserInterface> getInterfaceInstances() {
return new ArrayList<UserInterface>(uiInstances);
}
}