Java tutorial
/* * 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 3 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package lineage2.gameserver.scripts; import java.io.File; import java.io.FileInputStream; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import lineage2.commons.compiler.Compiler; import lineage2.commons.compiler.MemoryClassLoader; import lineage2.gameserver.Config; import lineage2.gameserver.model.Player; import lineage2.gameserver.model.quest.Quest; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.commons.lang3.reflect.MethodUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Mobius * @version $Revision: 1.0 $ */ public class Scripts { /** * Field _log. */ private static final Logger _log = LoggerFactory.getLogger(Scripts.class); /** * Field _instance. */ private static final Scripts _instance = new Scripts(); /** * Method getInstance. * @return Scripts */ public static final Scripts getInstance() { return _instance; } /** * Field dialogAppends. */ public static final Map<Integer, List<ScriptClassAndMethod>> dialogAppends = new HashMap<>(); /** * Field onAction. */ public static final Map<String, ScriptClassAndMethod> onAction = new HashMap<>(); /** * Field onActionShift. */ public static final Map<String, ScriptClassAndMethod> onActionShift = new HashMap<>(); /** * Field compiler. */ private final Compiler compiler = new Compiler(); /** * Field _classes. */ private final Map<String, Class<?>> _classes = new TreeMap<>(); /** * Constructor for Scripts. */ private Scripts() { load(); } /** * Method load. */ private void load() { _log.info("Scripts: Loading..."); List<Class<?>> classes = new ArrayList<>(); boolean result = false; File f = new File("../libs/lineage2-scripts.jar"); if (f.exists()) { JarInputStream stream = null; try { stream = new JarInputStream(new FileInputStream(f)); JarEntry entry = null; while ((entry = stream.getNextJarEntry()) != null) { if (entry.getName().contains(ClassUtils.INNER_CLASS_SEPARATOR) || !entry.getName().endsWith(".class")) { continue; } String name = entry.getName().replace(".class", "").replace("/", "."); Class<?> clazz = Class.forName(name); if (Modifier.isAbstract(clazz.getModifiers())) { continue; } classes.add(clazz); } result = true; } catch (Exception e) { _log.error("Fail to load scripts.jar!", e); classes.clear(); } finally { IOUtils.closeQuietly(stream); } } if (!result) { result = load(classes, ""); } if (!result) { _log.error("Scripts: Failed loading scripts!"); Runtime.getRuntime().exit(0); return; } _log.info("Scripts: Loaded " + classes.size() + " classes."); Class<?> clazz; for (int i = 0; i < classes.size(); i++) { clazz = classes.get(i); _classes.put(clazz.getName(), clazz); } } /** * Method init. */ public void init() { for (Class<?> clazz : _classes.values()) { addHandlers(clazz); if (Config.DONTLOADQUEST) { if (ClassUtils.isAssignable(clazz, Quest.class)) { continue; } } if (ClassUtils.isAssignable(clazz, ScriptFile.class)) { try { ((ScriptFile) clazz.newInstance()).onLoad(); } catch (Exception e) { _log.error("Scripts: Failed running " + clazz.getName() + ".onLoad()", e); } } } } /** * Method reload. * @return boolean */ public boolean reload() { _log.info("Scripts: Reloading..."); return reload(""); } /** * Method reload. * @param target String * @return boolean */ public boolean reload(String target) { List<Class<?>> classes = new ArrayList<>(); if (load(classes, target)) { _log.info("Scripts: Reloaded " + classes.size() + " classes."); } else { _log.error("Scripts: Failed reloading script(s): " + target + "!"); return false; } Class<?> clazz, prevClazz; for (int i = 0; i < classes.size(); i++) { clazz = classes.get(i); prevClazz = _classes.put(clazz.getName(), clazz); if (prevClazz != null) { if (ClassUtils.isAssignable(prevClazz, ScriptFile.class)) { try { ((ScriptFile) prevClazz.newInstance()).onReload(); } catch (Exception e) { _log.error("Scripts: Failed running " + prevClazz.getName() + ".onReload()", e); } } removeHandlers(prevClazz); } if (Config.DONTLOADQUEST) { if (ClassUtils.isAssignable(clazz, Quest.class)) { continue; } } if (ClassUtils.isAssignable(clazz, ScriptFile.class)) { try { ((ScriptFile) clazz.newInstance()).onLoad(); } catch (Exception e) { _log.error("Scripts: Failed running " + clazz.getName() + ".onLoad()", e); } } addHandlers(clazz); } return true; } /** * Method shutdown. */ public void shutdown() { for (Class<?> clazz : _classes.values()) { if (ClassUtils.isAssignable(clazz, Quest.class)) { continue; } if (ClassUtils.isAssignable(clazz, ScriptFile.class)) { try { ((ScriptFile) clazz.newInstance()).onShutdown(); } catch (Exception e) { _log.error("Scripts: Failed running " + clazz.getName() + ".onShutdown()", e); } } } } /** * Method load. * @param classes List<Class<?>> * @param target String * @return boolean */ private boolean load(List<Class<?>> classes, String target) { Collection<File> scriptFiles = Collections.emptyList(); File file = new File(Config.DATAPACK_ROOT, "data/scripts/" + target.replace(".", "/") + ".java"); if (file.isFile()) { scriptFiles = new ArrayList<>(1); scriptFiles.add(file); } else { file = new File(Config.DATAPACK_ROOT, "data/scripts/" + target); if (file.isDirectory()) { scriptFiles = FileUtils.listFiles(file, FileFilterUtils.suffixFileFilter(".java"), FileFilterUtils.directoryFileFilter()); } } if (scriptFiles.isEmpty()) { return false; } Class<?> clazz; boolean success = compiler.compile(scriptFiles); if (success) { MemoryClassLoader classLoader = compiler.getClassLoader(); for (String name : classLoader.getLoadedClasses()) { if (name.contains(ClassUtils.INNER_CLASS_SEPARATOR)) { continue; } try { clazz = classLoader.loadClass(name); if (Modifier.isAbstract(clazz.getModifiers())) { continue; } classes.add(clazz); } catch (ClassNotFoundException e) { success = false; _log.error("Scripts: Can't load script class: " + name, e); } } classLoader.clear(); } return success; } /** * Method addHandlers. * @param clazz Class<?> */ private void addHandlers(Class<?> clazz) { try { for (Method method : clazz.getMethods()) { if (method.getName().contains("DialogAppend_")) { Integer id = Integer.parseInt(method.getName().substring(13)); List<ScriptClassAndMethod> handlers = dialogAppends.get(id); if (handlers == null) { handlers = new ArrayList<>(); dialogAppends.put(id, handlers); } handlers.add(new ScriptClassAndMethod(clazz.getName(), method.getName())); } else if (method.getName().contains("OnAction_")) { String name = method.getName().substring(9); onAction.put(name, new ScriptClassAndMethod(clazz.getName(), method.getName())); } else if (method.getName().contains("OnActionShift_")) { String name = method.getName().substring(14); onActionShift.put(name, new ScriptClassAndMethod(clazz.getName(), method.getName())); } } } catch (Exception e) { _log.error("", e); } } /** * Method removeHandlers. * @param script Class<?> */ private void removeHandlers(Class<?> script) { try { for (List<ScriptClassAndMethod> entry : dialogAppends.values()) { List<ScriptClassAndMethod> toRemove = new ArrayList<>(); for (ScriptClassAndMethod sc : entry) { if (sc.className.equals(script.getName())) { toRemove.add(sc); } } for (ScriptClassAndMethod sc : toRemove) { entry.remove(sc); } } List<String> toRemove = new ArrayList<>(); for (Map.Entry<String, ScriptClassAndMethod> entry : onAction.entrySet()) { if (entry.getValue().className.equals(script.getName())) { toRemove.add(entry.getKey()); } } for (String key : toRemove) { onAction.remove(key); } toRemove = new ArrayList<>(); for (Map.Entry<String, ScriptClassAndMethod> entry : onActionShift.entrySet()) { if (entry.getValue().className.equals(script.getName())) { toRemove.add(entry.getKey()); } } for (String key : toRemove) { onActionShift.remove(key); } } catch (Exception e) { _log.error("", e); } } /** * Method callScripts. * @param className String * @param methodName String * @return Object */ public Object callScripts(String className, String methodName) { return callScripts(null, className, methodName, null, null); } /** * Method callScripts. * @param className String * @param methodName String * @param args Object[] * @return Object */ public Object callScripts(String className, String methodName, Object[] args) { return callScripts(null, className, methodName, args, null); } /** * Method callScripts. * @param className String * @param methodName String * @param variables Map<String,Object> * @return Object */ public Object callScripts(String className, String methodName, Map<String, Object> variables) { return callScripts(null, className, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, variables); } /** * Method callScripts. * @param className String * @param methodName String * @param args Object[] * @param variables Map<String,Object> * @return Object */ public Object callScripts(String className, String methodName, Object[] args, Map<String, Object> variables) { return callScripts(null, className, methodName, args, variables); } /** * Method callScripts. * @param caller Player * @param className String * @param methodName String * @return Object */ public Object callScripts(Player caller, String className, String methodName) { return callScripts(caller, className, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null); } /** * Method callScripts. * @param caller Player * @param className String * @param methodName String * @param args Object[] * @return Object */ public Object callScripts(Player caller, String className, String methodName, Object[] args) { return callScripts(caller, className, methodName, args, null); } /** * Method callScripts. * @param caller Player * @param className String * @param methodName String * @param variables Map<String,Object> * @return Object */ public Object callScripts(Player caller, String className, String methodName, Map<String, Object> variables) { return callScripts(caller, className, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, variables); } /** * Method callScripts. * @param caller Player * @param className String * @param methodName String * @param args Object[] * @param variables Map<String,Object> * @return Object */ public Object callScripts(Player caller, String className, String methodName, Object[] args, Map<String, Object> variables) { Object o; Class<?> clazz; clazz = _classes.get(className); if (clazz == null) { _log.error("Script class " + className + " not found!"); return null; } try { o = clazz.newInstance(); } catch (Exception e) { _log.error("Scripts: Failed creating instance of " + clazz.getName(), e); return null; } if ((variables != null) && !variables.isEmpty()) { for (Map.Entry<String, Object> param : variables.entrySet()) { try { FieldUtils.writeField(o, param.getKey(), param.getValue()); } catch (Exception e) { _log.error("Scripts: Failed setting fields for " + clazz.getName(), e); } } } if (caller != null) { try { Field field = null; if ((field = FieldUtils.getField(clazz, "self")) != null) { FieldUtils.writeField(field, o, caller.getRef()); } } catch (Exception e) { _log.error("Scripts: Failed setting field for " + clazz.getName(), e); } } Object ret = null; try { Class<?>[] parameterTypes = new Class<?>[args.length]; for (int i = 0; i < args.length; i++) { parameterTypes[i] = args[i] != null ? args[i].getClass() : null; } ret = MethodUtils.invokeMethod(o, methodName, args, parameterTypes); } catch (NoSuchMethodException nsme) { _log.error("Scripts: No such method " + clazz.getName() + "." + methodName + "()!"); } catch (InvocationTargetException ite) { _log.error("Scripts: Error while calling " + clazz.getName() + "." + methodName + "()", ite.getTargetException()); } catch (Exception e) { _log.error("Scripts: Failed calling " + clazz.getName() + "." + methodName + "()", e); } return ret; } /** * Method getClasses. * @return Map<String,Class<?>> */ public Map<String, Class<?>> getClasses() { return _classes; } /** * @author Mobius */ public static class ScriptClassAndMethod { /** * Field className. */ public final String className; /** * Field methodName. */ public final String methodName; /** * Constructor for ScriptClassAndMethod. * @param className String * @param methodName String */ public ScriptClassAndMethod(String className, String methodName) { this.className = className; this.methodName = methodName; } } }