398 lines
12 KiB
Java
398 lines
12 KiB
Java
/*
|
|
* Copyright (C) 2004-2015 L2J Server
|
|
*
|
|
* This file is part of L2J Server.
|
|
*
|
|
* L2J Server 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.
|
|
*
|
|
* L2J Server 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 com.l2jserver.gameserver.scripting;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStreamReader;
|
|
import java.io.LineNumberReader;
|
|
import java.util.HashMap;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
import javax.script.Compilable;
|
|
import javax.script.CompiledScript;
|
|
import javax.script.ScriptContext;
|
|
import javax.script.ScriptEngine;
|
|
import javax.script.ScriptEngineFactory;
|
|
import javax.script.ScriptEngineManager;
|
|
import javax.script.ScriptException;
|
|
import javax.script.SimpleScriptContext;
|
|
|
|
import com.l2jserver.Config;
|
|
import com.l2jserver.commons.javaengine.JavaScriptEngineFactory;
|
|
|
|
/**
|
|
* Caches script engines and provides functionality for executing and managing scripts.
|
|
* @author KenM
|
|
*/
|
|
public final class L2ScriptEngineManager
|
|
{
|
|
private static final Logger _log = Logger.getLogger(L2ScriptEngineManager.class.getName());
|
|
|
|
public static final File SCRIPT_FOLDER = new File(Config.DATAPACK_ROOT.getAbsolutePath(), "data/scripts");
|
|
|
|
public static L2ScriptEngineManager getInstance()
|
|
{
|
|
return SingletonHolder._instance;
|
|
}
|
|
|
|
private final Map<String, ScriptEngine> _extEngines = new HashMap<>();
|
|
private final List<ScriptManager<?>> _scriptManagers = new LinkedList<>();
|
|
|
|
private File _currentLoadingScript;
|
|
|
|
// Configs
|
|
// TODO move to config file
|
|
/**
|
|
* Informs(logs) the scripts being loaded.<BR>
|
|
* Apply only when executing script from files.<BR>
|
|
*/
|
|
private static final boolean VERBOSE_LOADING = false;
|
|
|
|
/**
|
|
* If the script engine supports compilation the script is compiled before execution.<BR>
|
|
*/
|
|
private static final boolean ATTEMPT_COMPILATION = true;
|
|
|
|
/**
|
|
* Clean an previous error log(if such exists) for the script being loaded before trying to load.<BR>
|
|
* Apply only when executing script from files.<BR>
|
|
*/
|
|
private static final boolean PURGE_ERROR_LOG = true;
|
|
|
|
protected L2ScriptEngineManager()
|
|
{
|
|
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
|
|
ScriptEngineFactory factory = new JavaScriptEngineFactory();
|
|
scriptEngineManager.registerEngineExtension("java", factory);
|
|
_extEngines.put("java", factory.getScriptEngine());
|
|
_log.info("Script Engine: " + factory.getEngineName() + " " + factory.getEngineVersion() + " - Language: " + factory.getLanguageName() + " - Language Version: " + factory.getLanguageVersion());
|
|
}
|
|
|
|
private ScriptEngine getEngineByExtension(String ext)
|
|
{
|
|
return _extEngines.get(ext);
|
|
}
|
|
|
|
public void executeScriptList(File list) throws IOException
|
|
{
|
|
if (Config.ALT_DEV_NO_QUESTS)
|
|
{
|
|
if (!Config.ALT_DEV_NO_HANDLERS)
|
|
{
|
|
try
|
|
{
|
|
executeScript(new File(SCRIPT_FOLDER, "handlers/MasterHandler.java"));
|
|
_log.info("Handlers loaded, all other scripts skipped");
|
|
}
|
|
catch (ScriptException se)
|
|
{
|
|
_log.log(Level.WARNING, "", se);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (list.isFile())
|
|
{
|
|
try (FileInputStream fis = new FileInputStream(list);
|
|
InputStreamReader isr = new InputStreamReader(fis);
|
|
LineNumberReader lnr = new LineNumberReader(isr))
|
|
{
|
|
String line;
|
|
while ((line = lnr.readLine()) != null)
|
|
{
|
|
if (Config.ALT_DEV_NO_HANDLERS && line.contains("MasterHandler.java"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
String[] parts = line.trim().split("#");
|
|
|
|
if ((parts.length > 0) && !parts[0].isEmpty() && (parts[0].charAt(0) != '#'))
|
|
{
|
|
line = parts[0];
|
|
|
|
if (line.endsWith("/**"))
|
|
{
|
|
line = line.substring(0, line.length() - 3);
|
|
}
|
|
else if (line.endsWith("/*"))
|
|
{
|
|
line = line.substring(0, line.length() - 2);
|
|
}
|
|
|
|
File file = new File(SCRIPT_FOLDER, line);
|
|
|
|
if (file.isDirectory() && parts[0].endsWith("/**"))
|
|
{
|
|
executeAllScriptsInDirectory(file, true, 32);
|
|
}
|
|
else if (file.isDirectory() && parts[0].endsWith("/*"))
|
|
{
|
|
executeAllScriptsInDirectory(file);
|
|
}
|
|
else if (file.isFile())
|
|
{
|
|
try
|
|
{
|
|
executeScript(file);
|
|
}
|
|
catch (ScriptException e)
|
|
{
|
|
reportScriptFileError(file, e);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_log.warning("Failed loading: (" + file.getCanonicalPath() + ") @ " + list.getName() + ":" + lnr.getLineNumber() + " - Reason: doesnt exists or is not a file.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new IllegalArgumentException("Argument must be an file containing a list of scripts to be loaded");
|
|
}
|
|
}
|
|
|
|
public void executeAllScriptsInDirectory(File dir)
|
|
{
|
|
executeAllScriptsInDirectory(dir, false, 0);
|
|
}
|
|
|
|
public void executeAllScriptsInDirectory(File dir, boolean recurseDown, int maxDepth)
|
|
{
|
|
executeAllScriptsInDirectory(dir, recurseDown, maxDepth, 0);
|
|
}
|
|
|
|
private void executeAllScriptsInDirectory(File dir, boolean recurseDown, int maxDepth, int currentDepth)
|
|
{
|
|
if (dir.isDirectory())
|
|
{
|
|
for (File file : dir.listFiles())
|
|
{
|
|
if (file.isDirectory() && recurseDown && (maxDepth > currentDepth))
|
|
{
|
|
if (VERBOSE_LOADING)
|
|
{
|
|
_log.info("Entering folder: " + file.getName());
|
|
}
|
|
executeAllScriptsInDirectory(file, recurseDown, maxDepth, currentDepth + 1);
|
|
}
|
|
else if (file.isFile())
|
|
{
|
|
try
|
|
{
|
|
String name = file.getName();
|
|
int lastIndex = name.lastIndexOf('.');
|
|
String extension;
|
|
if (lastIndex != -1)
|
|
{
|
|
extension = name.substring(lastIndex + 1);
|
|
ScriptEngine engine = getEngineByExtension(extension);
|
|
if (engine != null)
|
|
{
|
|
executeScript(engine, file);
|
|
}
|
|
}
|
|
}
|
|
catch (ScriptException e)
|
|
{
|
|
reportScriptFileError(file, e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new IllegalArgumentException("The argument directory either doesnt exists or is not an directory.");
|
|
}
|
|
}
|
|
|
|
public void executeScript(File file) throws ScriptException
|
|
{
|
|
String name = file.getName();
|
|
int lastIndex = name.lastIndexOf('.');
|
|
String extension;
|
|
if (lastIndex != -1)
|
|
{
|
|
extension = name.substring(lastIndex + 1);
|
|
}
|
|
else
|
|
{
|
|
throw new ScriptException("Script file (" + name + ") doesnt has an extension that identifies the ScriptEngine to be used.");
|
|
}
|
|
|
|
ScriptEngine engine = getEngineByExtension(extension);
|
|
if (engine == null)
|
|
{
|
|
throw new ScriptException("No engine registered for extension (" + extension + ")");
|
|
}
|
|
executeScript(engine, file);
|
|
}
|
|
|
|
public void executeScript(ScriptEngine engine, File file) throws ScriptException
|
|
{
|
|
if (VERBOSE_LOADING)
|
|
{
|
|
_log.info("Loading Script: " + file.getAbsolutePath());
|
|
}
|
|
|
|
if (PURGE_ERROR_LOG)
|
|
{
|
|
String name = file.getAbsolutePath() + ".error.log";
|
|
File errorLog = new File(name);
|
|
if (errorLog.isFile())
|
|
{
|
|
errorLog.delete();
|
|
}
|
|
}
|
|
|
|
final String relativeName = file.getAbsolutePath().substring(SCRIPT_FOLDER.getAbsolutePath().length() + 1).replace('\\', '/');
|
|
try (FileInputStream fis = new FileInputStream(file);
|
|
InputStreamReader isr = new InputStreamReader(fis);
|
|
BufferedReader reader = new BufferedReader(isr))
|
|
{
|
|
ScriptContext context = new SimpleScriptContext();
|
|
context.setAttribute("mainClass", getClassForFile(file).replace('/', '.').replace('\\', '.'), ScriptContext.ENGINE_SCOPE);
|
|
context.setAttribute(ScriptEngine.FILENAME, relativeName, ScriptContext.ENGINE_SCOPE);
|
|
context.setAttribute("classpath", SCRIPT_FOLDER.getAbsolutePath(), ScriptContext.ENGINE_SCOPE);
|
|
context.setAttribute("sourcepath", SCRIPT_FOLDER.getAbsolutePath(), ScriptContext.ENGINE_SCOPE);
|
|
setCurrentLoadingScript(file);
|
|
try
|
|
{
|
|
engine.eval(reader, context);
|
|
}
|
|
finally
|
|
{
|
|
setCurrentLoadingScript(null);
|
|
engine.getContext().removeAttribute(ScriptEngine.FILENAME, ScriptContext.ENGINE_SCOPE);
|
|
engine.getContext().removeAttribute("mainClass", ScriptContext.ENGINE_SCOPE);
|
|
}
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
_log.log(Level.WARNING, "Error executing script!", e);
|
|
}
|
|
}
|
|
|
|
public static String getClassForFile(File script)
|
|
{
|
|
String path = script.getAbsolutePath();
|
|
String scpPath = SCRIPT_FOLDER.getAbsolutePath();
|
|
if (path.startsWith(scpPath))
|
|
{
|
|
int idx = path.lastIndexOf('.');
|
|
return path.substring(scpPath.length() + 1, idx);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public ScriptContext getScriptContext(ScriptEngine engine)
|
|
{
|
|
return engine.getContext();
|
|
}
|
|
|
|
public Object eval(ScriptEngine engine, String script, ScriptContext context) throws ScriptException
|
|
{
|
|
if ((engine instanceof Compilable) && ATTEMPT_COMPILATION)
|
|
{
|
|
Compilable eng = (Compilable) engine;
|
|
CompiledScript cs = eng.compile(script);
|
|
return context != null ? cs.eval(context) : cs.eval();
|
|
}
|
|
return context != null ? engine.eval(script, context) : engine.eval(script);
|
|
}
|
|
|
|
public Object eval(ScriptEngine engine, String script) throws ScriptException
|
|
{
|
|
return eval(engine, script, null);
|
|
}
|
|
|
|
public void reportScriptFileError(File script, ScriptException e)
|
|
{
|
|
String dir = script.getParent();
|
|
String name = script.getName() + ".error.log";
|
|
if (dir != null)
|
|
{
|
|
final File file = new File(dir + "/" + name);
|
|
try (FileOutputStream fos = new FileOutputStream(file))
|
|
{
|
|
String errorHeader = "Error on: " + file.getCanonicalPath() + Config.EOL + "Line: " + e.getLineNumber() + " - Column: " + e.getColumnNumber() + Config.EOL + Config.EOL;
|
|
fos.write(errorHeader.getBytes());
|
|
fos.write(e.getMessage().getBytes());
|
|
_log.warning("Failed executing script: " + script.getAbsolutePath() + ". See " + file.getName() + " for details.");
|
|
}
|
|
catch (IOException ioe)
|
|
{
|
|
_log.log(Level.WARNING, "Failed executing script: " + script.getAbsolutePath() + Config.EOL + e.getMessage() + "Additionally failed when trying to write an error report on script directory. Reason: " + ioe.getMessage(), ioe);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_log.log(Level.WARNING, "Failed executing script: " + script.getAbsolutePath() + Config.EOL + e.getMessage() + "Additionally failed when trying to write an error report on script directory.", e);
|
|
}
|
|
}
|
|
|
|
public void registerScriptManager(ScriptManager<?> manager)
|
|
{
|
|
_scriptManagers.add(manager);
|
|
}
|
|
|
|
public void removeScriptManager(ScriptManager<?> manager)
|
|
{
|
|
_scriptManagers.remove(manager);
|
|
}
|
|
|
|
public List<ScriptManager<?>> getScriptManagers()
|
|
{
|
|
return _scriptManagers;
|
|
}
|
|
|
|
/**
|
|
* @param currentLoadingScript The currentLoadingScript to set.
|
|
*/
|
|
protected void setCurrentLoadingScript(File currentLoadingScript)
|
|
{
|
|
_currentLoadingScript = currentLoadingScript;
|
|
}
|
|
|
|
/**
|
|
* @return Returns the currentLoadingScript.
|
|
*/
|
|
protected File getCurrentLoadingScript()
|
|
{
|
|
return _currentLoadingScript;
|
|
}
|
|
|
|
private static class SingletonHolder
|
|
{
|
|
protected static final L2ScriptEngineManager _instance = new L2ScriptEngineManager();
|
|
}
|
|
}
|