lineage2.gameserver.scripts.Scripts.java Source code

Java tutorial

Introduction

Here is the source code for lineage2.gameserver.scripts.Scripts.java

Source

/*
 * 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;
        }
    }
}