/*
* Copyright 2011 Tyler Blair. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and contributors and should not be interpreted as representing official policies,
* either expressed or implied, of anybody else.
*/
package com.griefcraft.scripting;
import com.griefcraft.lwc.LWC;
import com.griefcraft.lwc.LWCInfo;
import com.griefcraft.scripting.event.LWCAccessEvent;
import com.griefcraft.scripting.event.LWCBlockInteractEvent;
import com.griefcraft.scripting.event.LWCCommandEvent;
import com.griefcraft.scripting.event.LWCDropItemEvent;
import com.griefcraft.scripting.event.LWCEvent;
import com.griefcraft.scripting.event.LWCProtectionDestroyEvent;
import com.griefcraft.scripting.event.LWCProtectionInteractEvent;
import com.griefcraft.scripting.event.LWCProtectionRegisterEvent;
import com.griefcraft.scripting.event.LWCProtectionRegistrationPostEvent;
import com.griefcraft.scripting.event.LWCProtectionRemovePostEvent;
import com.griefcraft.scripting.event.LWCRedstoneEvent;
import com.griefcraft.scripting.event.LWCReloadEvent;
import com.griefcraft.scripting.event.LWCSendLocaleEvent;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
public class ModuleLoader {
public enum Event {
/**
* Called when a module is loaded
*/
LOAD(0),
/**
* Called when a module is unloaded
*/
UNLOAD(0),
/**
* Called when a console or player issues a command
*/
COMMAND(3),
/**
* Called when redstone is passed to a protection
*/
REDSTONE(3),
/**
* Called when a protection is destroyed
*/
DESTROY_PROTECTION(5),
/**
* Called when a valid protection is left clicked
*/
INTERACT_PROTECTION(5),
/**
* Called when a block is left clicked
*/
INTERACT_BLOCK(3),
/**
* Called before a protection is registered
*/
REGISTER_PROTECTION(2),
/**
* Called when a protection needs to be checked if a player can access it
*/
ACCESS_PROTECTION(2),
/**
* Called when a protection needs to be checked if a player can admin it
*/
ADMIN_PROTECTION(2),
/**
* Called when a player drops an item
*/
DROP_ITEM(3),
/**
* Called after a protection is registered
*/
POST_REGISTRATION(1),
/**
* Called after a protection is confirmed to be set to be removed (cannot be cancelled here.)
*/
POST_REMOVAL(1),
/**
* Called when a localized message is sent to a player (e.g lwc.accessdenied)
*/
SEND_LOCALE(2),
//
ACCESS_REQUEST(),
/**
* Called when LWC's config is reloaded
*/
RELOAD_EVENT;
Event() {
}
Event(int arguments) {
this.arguments = arguments;
}
public int getExpectedArguments() {
return arguments;
}
/**
* Expected amount of arguments (not counting the lwc object!)
*/
int arguments;
}
private static Logger logger = Logger.getLogger("Loader");
/**
* The LWC instance this object belongs to
*/
private LWC lwc;
/**
* Path to the root of scripts
*/
public final static String ROOT_PATH = "plugins/LWC/";
/**
* Map of loaded modules
*/
private final Map<Plugin, List<MetaData>> pluginModules = Collections.synchronizedMap(new LinkedHashMap<Plugin, List<MetaData>>());
/**
* A cache used to get a list of modules for any given event. Reflection is used to find which events modules implement.
* This was mainly added for backwards compatibility reasons (vs events to be individually registered). This still
* achieves the same effect by using reflection.
*/
private final Map<Event, List<Module>> fastModuleCache = new HashMap<Event, List<Module>>();
/**
* Toasty caches for doesObjectOverrideMethod
*/
private final Map<String, Boolean> overrideCache = new HashMap<String, Boolean>();
public ModuleLoader(LWC lwc) {
this.lwc = lwc;
populateFastModuleCache();
}
/**
* Populate the fast module cache
*/
private void populateFastModuleCache() {
for (Event event : Event.values()) {
fastModuleCache.put(event, new ArrayList<Module>(10));
}
}
/**
* Register a module into the fast cache
* @param module
*/
private void registerFastCache(Module module) {
// get all of the methods from the module's superclass
Class<?> superclass = module.getClass().getSuperclass();
// Impossible
if (superclass == null) {
throw new IllegalArgumentException("Method cannot be its own superclass (?)");
}
if (superclass == Object.class) {
// then it implements the Module interface but doesn't extend any class
// also means getDeclaredMethods() below wouldn't show the interface methods when acting on Object.class
// modules would then fail to register if they just implement the Module interface instead of extending JavaModule
superclass = Module.class;// the easy way
}
// The methods that are possible to implement
Method[] methods = superclass.getDeclaredMethods();
// Now check each method to see if the module implements it
for (Method method : methods) {
boolean doesOverride = doesObjectOverrideMethod(module, method);
// It does override it! Add it to the fast cache o/
if (doesOverride) {
// But adding it to the fast cache isn't so easy, we need the event object
Class<?>[] parameters = method.getParameterTypes();
// If it's not 1 we have a method we do not want
if (parameters.length != 1) {
continue;
}
// Start comparing on the parameter to find the event type
Event event = null;
Class<?> parameter = parameters[0];
if (parameter == LWCAccessEvent.class) {
event = Event.ACCESS_REQUEST;
} else if (parameter == LWCBlockInteractEvent.class) {
event = Event.INTERACT_BLOCK;
} else if (parameter == LWCCommandEvent.class) {
event = Event.COMMAND;
} else if (parameter == LWCDropItemEvent.class) {
event = Event.DROP_ITEM;
} else if (parameter == LWCProtectionDestroyEvent.class) {
event = Event.DESTROY_PROTECTION;
} else if (parameter == LWCProtectionInteractEvent.class) {
event = Event.INTERACT_PROTECTION;
} else if (parameter == LWCProtectionRegisterEvent.class) {
event = Event.REGISTER_PROTECTION;
} else if (parameter == LWCProtectionRemovePostEvent.class) {
event = Event.POST_REMOVAL;
} else if (parameter == LWCProtectionRegistrationPostEvent.class) {
event = Event.POST_REGISTRATION;
} else if (parameter == LWCSendLocaleEvent.class) {
event = Event.SEND_LOCALE;
} else if (parameter == LWCRedstoneEvent.class) {
event = Event.REDSTONE;
} else if (parameter == LWCReloadEvent.class) {
event = Event.RELOAD_EVENT;
}
// ok!
if (event != null) {
List<Module> modules = fastModuleCache.get(event);
modules.add(module);
}
}
}
}
/**
* Check if a method overrides a method using reflection. This method uses a cache for constant access after
* the caches are warm and toasty.
* <p/>
* This assumes the object is overriding JavaModule
*
* @param object
* @param method
* @return
*/
public boolean doesObjectOverrideMethod(Object object, Method method) {
if (object == null) {
throw new IllegalArgumentException("Object cannot be null");
}
if (method == null) {
throw new IllegalArgumentException("Method cannot be null");
}
String methodName = method.getName();
String cacheKey = object.getClass().getSimpleName() + methodName;
// Check the cache
if (overrideCache.containsKey(cacheKey)) {
return overrideCache.get(cacheKey);
}
// The class to compare to; the assumed superclass
Class<?> compare = JavaModule.class;
// The result; does it actually override the method?
boolean result = false;
// Compare the methods the object declares
for (Method declaredMethod : object.getClass().getDeclaredMethods()) {
if (declaredMethod.getName().equals(methodName)) {
result = true;
break;
}
}
overrideCache.put(cacheKey, result);
return result;
}
/**
* Dispatch an event
*
* @param event
*/
public void dispatchEvent(LWCEvent event) {
if (event == null) {
return;
}
try {
List<Module> modules = fastModuleCache.get(event.getEventType());
Event type = event.getEventType();
for (Module module : modules) {
if (type == Event.INTERACT_PROTECTION) {
module.onProtectionInteract((LWCProtectionInteractEvent) event);
} else if (type == Event.INTERACT_BLOCK) {
module.onBlockInteract((LWCBlockInteractEvent) event);
} else if (type == Event.SEND_LOCALE) {
module.onSendLocale((LWCSendLocaleEvent) event);
} else if (type == Event.ACCESS_REQUEST) {
module.onAccessRequest((LWCAccessEvent) event);
} else if (type == Event.COMMAND) {
module.onCommand((LWCCommandEvent) event);
} else if (type == Event.DROP_ITEM) {
module.onDropItem((LWCDropItemEvent) event);
} else if (type == Event.DESTROY_PROTECTION) {
module.onDestroyProtection((LWCProtectionDestroyEvent) event);
}else if (type == Event.REGISTER_PROTECTION) {
module.onRegisterProtection((LWCProtectionRegisterEvent) event);
} else if (type == Event.POST_REMOVAL) {
module.onPostRemoval((LWCProtectionRemovePostEvent) event);
} else if (type == Event.POST_REGISTRATION) {
module.onPostRegistration((LWCProtectionRegistrationPostEvent) event);
} else if (type == Event.REDSTONE) {
module.onRedstone((LWCRedstoneEvent) event);
} else if (type == Event.RELOAD_EVENT) {
module.onReload((LWCReloadEvent) event);
}
}
} catch (Throwable throwable) {
throw new ModuleException("LWC Module threw an uncaught exception! LWC version: " + LWCInfo.FULL_VERSION, throwable);
}
}
/**
* Shutdown the plugin loader
*
* @todo broadcast UNLOAD
*/
public void shutdown() {
pluginModules.clear();
}
/**
* Load all of the modules not marked as loaded
*/
public void loadAll() {
// Ensure LWC is at the head of the list
synchronized (pluginModules) {
Map<Plugin, List<MetaData>> newMap = new LinkedHashMap<Plugin, List<MetaData>>();
// Add LWC
newMap.put(lwc.getPlugin(), pluginModules.get(lwc.getPlugin()));
// Add the rest
newMap.putAll(pluginModules);
// Clear the old map
pluginModules.clear();
// Add the new values in
pluginModules.putAll(newMap);
}
for (List<MetaData> modules : pluginModules.values()) {
for (MetaData metaData : modules) {
if (!metaData.isLoaded()) {
metaData.getModule().load(lwc);
metaData.trigger();
}
}
}
}
/**
* Get the first module represented by a class
*
* @param clazz
* @return
*/
public Module getModule(Class<? extends Module> clazz) {
for (List<MetaData> modules : pluginModules.values()) {
for (MetaData metaData : modules) {
Module module = metaData.getModule();
if (module.getClass() == clazz) {
return module;
}
}
}
return null;
}
/**
* Not intended to be used a lot -- use sparingly!
* The map returned is NOT modifiable.
*
* @return the registered modules
*/
public Map<Plugin, List<MetaData>> getRegisteredModules() {
return Collections.unmodifiableMap(pluginModules);
}
/**
* @return the number of registered modules
*/
public int getModuleCount() {
int count = 0;
for (List<MetaData> modules : pluginModules.values()) {
count += modules.size();
}
return count;
}
/**
* Register a module for a plugin
*
* @param plugin
* @param module
*/
public void registerModule(Plugin plugin, Module module) {
List<MetaData> modules = null;
if (plugin != null) {
modules = pluginModules.get(plugin);
}
if (modules == null) {
modules = new ArrayList<MetaData>();
}
MetaData metaData = new MetaData(module);
modules.add(metaData);
pluginModules.put(plugin, modules);
// Populate the fast cache
registerFastCache(module);
}
/**
* Remove the modules for a plugin
*
* @param plugin
*/
public void removeModules(Plugin plugin) {
pluginModules.remove(plugin);
}
}