L2ScriptEngineManager.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. /*
  2. * Copyright (C) 2004-2015 L2J Server
  3. *
  4. * This file is part of L2J Server.
  5. *
  6. * L2J Server is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * L2J Server is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. package com.l2jserver.gameserver.scripting;
  20. import java.io.BufferedReader;
  21. import java.io.File;
  22. import java.io.FileInputStream;
  23. import java.io.FileOutputStream;
  24. import java.io.IOException;
  25. import java.io.InputStreamReader;
  26. import java.io.LineNumberReader;
  27. import java.util.HashMap;
  28. import java.util.LinkedList;
  29. import java.util.List;
  30. import java.util.Map;
  31. import java.util.logging.Level;
  32. import java.util.logging.Logger;
  33. import javax.script.Compilable;
  34. import javax.script.CompiledScript;
  35. import javax.script.ScriptContext;
  36. import javax.script.ScriptEngine;
  37. import javax.script.ScriptEngineFactory;
  38. import javax.script.ScriptEngineManager;
  39. import javax.script.ScriptException;
  40. import javax.script.SimpleScriptContext;
  41. import com.l2jserver.Config;
  42. import com.l2jserver.script.jython.JythonScriptEngine;
  43. /**
  44. * Caches script engines and provides functionality for executing and managing scripts.
  45. * @author KenM
  46. */
  47. public final class L2ScriptEngineManager
  48. {
  49. private static final Logger _log = Logger.getLogger(L2ScriptEngineManager.class.getName());
  50. public static final File SCRIPT_FOLDER = new File(Config.DATAPACK_ROOT.getAbsolutePath(), "data/scripts");
  51. public static L2ScriptEngineManager getInstance()
  52. {
  53. return SingletonHolder._instance;
  54. }
  55. private final Map<String, ScriptEngine> _nameEngines = new HashMap<>();
  56. private final Map<String, ScriptEngine> _extEngines = new HashMap<>();
  57. private final List<ScriptManager<?>> _scriptManagers = new LinkedList<>();
  58. private File _currentLoadingScript;
  59. // Configs
  60. // TODO move to config file
  61. /**
  62. * Informs(logs) the scripts being loaded.<BR>
  63. * Apply only when executing script from files.<BR>
  64. */
  65. private static final boolean VERBOSE_LOADING = false;
  66. /**
  67. * If the script engine supports compilation the script is compiled before execution.<BR>
  68. */
  69. private static final boolean ATTEMPT_COMPILATION = true;
  70. /**
  71. * Clean an previous error log(if such exists) for the script being loaded before trying to load.<BR>
  72. * Apply only when executing script from files.<BR>
  73. */
  74. private static final boolean PURGE_ERROR_LOG = true;
  75. protected L2ScriptEngineManager()
  76. {
  77. ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
  78. List<ScriptEngineFactory> factories = scriptEngineManager.getEngineFactories();
  79. for (ScriptEngineFactory factory : factories)
  80. {
  81. try
  82. {
  83. ScriptEngine engine = factory.getScriptEngine();
  84. boolean reg = false;
  85. for (String name : factory.getNames())
  86. {
  87. ScriptEngine existentEngine = _nameEngines.get(name);
  88. if (existentEngine != null)
  89. {
  90. double engineVer = Double.parseDouble(factory.getEngineVersion());
  91. double existentEngVer = Double.parseDouble(existentEngine.getFactory().getEngineVersion());
  92. if (engineVer <= existentEngVer)
  93. {
  94. continue;
  95. }
  96. }
  97. reg = true;
  98. _nameEngines.put(name, engine);
  99. }
  100. if (reg)
  101. {
  102. _log.info("Script Engine: " + factory.getEngineName() + " " + factory.getEngineVersion() + " - Language: " + factory.getLanguageName() + " - Language Version: " + factory.getLanguageVersion());
  103. }
  104. for (String ext : factory.getExtensions())
  105. {
  106. if (!ext.equals("java") || factory.getLanguageName().equals("java"))
  107. {
  108. _extEngines.put(ext, engine);
  109. }
  110. }
  111. }
  112. catch (Exception e)
  113. {
  114. _log.log(Level.WARNING, "Failed initializing factory: " + e.getMessage(), e);
  115. }
  116. }
  117. preConfigure();
  118. }
  119. private void preConfigure()
  120. {
  121. // Jython sys.path
  122. String dataPackDirForwardSlashes = SCRIPT_FOLDER.getPath().replaceAll("\\\\", "/");
  123. String configScript = "import sys;sys.path.insert(0,'" + dataPackDirForwardSlashes + "');";
  124. try
  125. {
  126. eval("jython", configScript);
  127. }
  128. catch (ScriptException e)
  129. {
  130. _log.severe("Failed preconfiguring jython: " + e.getMessage());
  131. }
  132. }
  133. private ScriptEngine getEngineByName(String name)
  134. {
  135. return _nameEngines.get(name);
  136. }
  137. private ScriptEngine getEngineByExtension(String ext)
  138. {
  139. return _extEngines.get(ext);
  140. }
  141. public void executeScriptList(File list) throws IOException
  142. {
  143. if (Config.ALT_DEV_NO_QUESTS)
  144. {
  145. if (!Config.ALT_DEV_NO_HANDLERS)
  146. {
  147. try
  148. {
  149. executeScript(new File(SCRIPT_FOLDER, "handlers/MasterHandler.java"));
  150. _log.info("Handlers loaded, all other scripts skipped");
  151. }
  152. catch (ScriptException se)
  153. {
  154. _log.log(Level.WARNING, "", se);
  155. }
  156. }
  157. return;
  158. }
  159. if (list.isFile())
  160. {
  161. try (FileInputStream fis = new FileInputStream(list);
  162. InputStreamReader isr = new InputStreamReader(fis);
  163. LineNumberReader lnr = new LineNumberReader(isr))
  164. {
  165. String line;
  166. while ((line = lnr.readLine()) != null)
  167. {
  168. if (Config.ALT_DEV_NO_HANDLERS && line.contains("MasterHandler.java"))
  169. {
  170. continue;
  171. }
  172. String[] parts = line.trim().split("#");
  173. if ((parts.length > 0) && !parts[0].isEmpty() && (parts[0].charAt(0) != '#'))
  174. {
  175. line = parts[0];
  176. if (line.endsWith("/**"))
  177. {
  178. line = line.substring(0, line.length() - 3);
  179. }
  180. else if (line.endsWith("/*"))
  181. {
  182. line = line.substring(0, line.length() - 2);
  183. }
  184. File file = new File(SCRIPT_FOLDER, line);
  185. if (file.isDirectory() && parts[0].endsWith("/**"))
  186. {
  187. executeAllScriptsInDirectory(file, true, 32);
  188. }
  189. else if (file.isDirectory() && parts[0].endsWith("/*"))
  190. {
  191. executeAllScriptsInDirectory(file);
  192. }
  193. else if (file.isFile())
  194. {
  195. try
  196. {
  197. executeScript(file);
  198. }
  199. catch (ScriptException e)
  200. {
  201. reportScriptFileError(file, e);
  202. }
  203. }
  204. else
  205. {
  206. _log.warning("Failed loading: (" + file.getCanonicalPath() + ") @ " + list.getName() + ":" + lnr.getLineNumber() + " - Reason: doesnt exists or is not a file.");
  207. }
  208. }
  209. }
  210. }
  211. }
  212. else
  213. {
  214. throw new IllegalArgumentException("Argument must be an file containing a list of scripts to be loaded");
  215. }
  216. }
  217. public void executeAllScriptsInDirectory(File dir)
  218. {
  219. executeAllScriptsInDirectory(dir, false, 0);
  220. }
  221. public void executeAllScriptsInDirectory(File dir, boolean recurseDown, int maxDepth)
  222. {
  223. executeAllScriptsInDirectory(dir, recurseDown, maxDepth, 0);
  224. }
  225. private void executeAllScriptsInDirectory(File dir, boolean recurseDown, int maxDepth, int currentDepth)
  226. {
  227. if (dir.isDirectory())
  228. {
  229. final File[] files = dir.listFiles();
  230. if (files == null)
  231. {
  232. return;
  233. }
  234. for (File file : files)
  235. {
  236. if (file.isDirectory() && recurseDown && (maxDepth > currentDepth))
  237. {
  238. if (VERBOSE_LOADING)
  239. {
  240. _log.info("Entering folder: " + file.getName());
  241. }
  242. executeAllScriptsInDirectory(file, recurseDown, maxDepth, currentDepth + 1);
  243. }
  244. else if (file.isFile())
  245. {
  246. try
  247. {
  248. String name = file.getName();
  249. int lastIndex = name.lastIndexOf('.');
  250. String extension;
  251. if (lastIndex != -1)
  252. {
  253. extension = name.substring(lastIndex + 1);
  254. ScriptEngine engine = getEngineByExtension(extension);
  255. if (engine != null)
  256. {
  257. executeScript(engine, file);
  258. }
  259. }
  260. }
  261. catch (ScriptException e)
  262. {
  263. reportScriptFileError(file, e);
  264. }
  265. }
  266. }
  267. }
  268. else
  269. {
  270. throw new IllegalArgumentException("The argument directory either doesnt exists or is not an directory.");
  271. }
  272. }
  273. public void executeScript(File file) throws ScriptException
  274. {
  275. String name = file.getName();
  276. int lastIndex = name.lastIndexOf('.');
  277. String extension;
  278. if (lastIndex != -1)
  279. {
  280. extension = name.substring(lastIndex + 1);
  281. }
  282. else
  283. {
  284. throw new ScriptException("Script file (" + name + ") doesnt has an extension that identifies the ScriptEngine to be used.");
  285. }
  286. ScriptEngine engine = getEngineByExtension(extension);
  287. if (engine == null)
  288. {
  289. throw new ScriptException("No engine registered for extension (" + extension + ")");
  290. }
  291. executeScript(engine, file);
  292. }
  293. public void executeScript(String engineName, File file) throws ScriptException
  294. {
  295. ScriptEngine engine = getEngineByName(engineName);
  296. if (engine == null)
  297. {
  298. throw new ScriptException("No engine registered with name (" + engineName + ")");
  299. }
  300. executeScript(engine, file);
  301. }
  302. public void executeScript(ScriptEngine engine, File file) throws ScriptException
  303. {
  304. if (VERBOSE_LOADING)
  305. {
  306. _log.info("Loading Script: " + file.getAbsolutePath());
  307. }
  308. if (PURGE_ERROR_LOG)
  309. {
  310. String name = file.getAbsolutePath() + ".error.log";
  311. File errorLog = new File(name);
  312. if (errorLog.isFile())
  313. {
  314. errorLog.delete();
  315. }
  316. }
  317. final String relativeName = file.getAbsolutePath().substring(SCRIPT_FOLDER.getAbsolutePath().length() + 1).replace('\\', '/');
  318. try (FileInputStream fis = new FileInputStream(file);
  319. InputStreamReader isr = new InputStreamReader(fis);
  320. BufferedReader reader = new BufferedReader(isr))
  321. {
  322. if ((engine instanceof Compilable) && ATTEMPT_COMPILATION)
  323. {
  324. ScriptContext context = new SimpleScriptContext();
  325. context.setAttribute("mainClass", getClassForFile(file).replace('/', '.').replace('\\', '.'), ScriptContext.ENGINE_SCOPE);
  326. context.setAttribute(ScriptEngine.FILENAME, relativeName, ScriptContext.ENGINE_SCOPE);
  327. context.setAttribute("classpath", SCRIPT_FOLDER.getAbsolutePath(), ScriptContext.ENGINE_SCOPE);
  328. context.setAttribute("sourcepath", SCRIPT_FOLDER.getAbsolutePath(), ScriptContext.ENGINE_SCOPE);
  329. context.setAttribute(JythonScriptEngine.JYTHON_ENGINE_INSTANCE, engine, ScriptContext.ENGINE_SCOPE);
  330. setCurrentLoadingScript(file);
  331. ScriptContext ctx = engine.getContext();
  332. try
  333. {
  334. engine.setContext(context);
  335. Compilable eng = (Compilable) engine;
  336. CompiledScript cs = eng.compile(reader);
  337. cs.eval(context);
  338. }
  339. finally
  340. {
  341. engine.setContext(ctx);
  342. setCurrentLoadingScript(null);
  343. context.removeAttribute(ScriptEngine.FILENAME, ScriptContext.ENGINE_SCOPE);
  344. context.removeAttribute("mainClass", ScriptContext.ENGINE_SCOPE);
  345. }
  346. }
  347. else
  348. {
  349. ScriptContext context = new SimpleScriptContext();
  350. context.setAttribute("mainClass", getClassForFile(file).replace('/', '.').replace('\\', '.'), ScriptContext.ENGINE_SCOPE);
  351. context.setAttribute(ScriptEngine.FILENAME, relativeName, ScriptContext.ENGINE_SCOPE);
  352. context.setAttribute("classpath", SCRIPT_FOLDER.getAbsolutePath(), ScriptContext.ENGINE_SCOPE);
  353. context.setAttribute("sourcepath", SCRIPT_FOLDER.getAbsolutePath(), ScriptContext.ENGINE_SCOPE);
  354. setCurrentLoadingScript(file);
  355. try
  356. {
  357. engine.eval(reader, context);
  358. }
  359. finally
  360. {
  361. setCurrentLoadingScript(null);
  362. engine.getContext().removeAttribute(ScriptEngine.FILENAME, ScriptContext.ENGINE_SCOPE);
  363. engine.getContext().removeAttribute("mainClass", ScriptContext.ENGINE_SCOPE);
  364. }
  365. }
  366. }
  367. catch (IOException e)
  368. {
  369. _log.log(Level.WARNING, "Error executing script!", e);
  370. }
  371. }
  372. public static String getClassForFile(File script)
  373. {
  374. String path = script.getAbsolutePath();
  375. String scpPath = SCRIPT_FOLDER.getAbsolutePath();
  376. if (path.startsWith(scpPath))
  377. {
  378. int idx = path.lastIndexOf('.');
  379. return path.substring(scpPath.length() + 1, idx);
  380. }
  381. return null;
  382. }
  383. public ScriptContext getScriptContext(ScriptEngine engine)
  384. {
  385. return engine.getContext();
  386. }
  387. public ScriptContext getScriptContext(String engineName)
  388. {
  389. ScriptEngine engine = getEngineByName(engineName);
  390. if (engine == null)
  391. {
  392. throw new IllegalStateException("No engine registered with name (" + engineName + ")");
  393. }
  394. return getScriptContext(engine);
  395. }
  396. public Object eval(ScriptEngine engine, String script, ScriptContext context) throws ScriptException
  397. {
  398. if ((engine instanceof Compilable) && ATTEMPT_COMPILATION)
  399. {
  400. Compilable eng = (Compilable) engine;
  401. CompiledScript cs = eng.compile(script);
  402. return context != null ? cs.eval(context) : cs.eval();
  403. }
  404. return context != null ? engine.eval(script, context) : engine.eval(script);
  405. }
  406. public Object eval(String engineName, String script) throws ScriptException
  407. {
  408. return eval(engineName, script, null);
  409. }
  410. public Object eval(String engineName, String script, ScriptContext context) throws ScriptException
  411. {
  412. ScriptEngine engine = getEngineByName(engineName);
  413. if (engine == null)
  414. {
  415. throw new ScriptException("No engine registered with name (" + engineName + ")");
  416. }
  417. return eval(engine, script, context);
  418. }
  419. public Object eval(ScriptEngine engine, String script) throws ScriptException
  420. {
  421. return eval(engine, script, null);
  422. }
  423. public void reportScriptFileError(File script, ScriptException e)
  424. {
  425. String dir = script.getParent();
  426. String name = script.getName() + ".error.log";
  427. if (dir != null)
  428. {
  429. final File file = new File(dir + "/" + name);
  430. try (FileOutputStream fos = new FileOutputStream(file))
  431. {
  432. String errorHeader = "Error on: " + file.getCanonicalPath() + Config.EOL + "Line: " + e.getLineNumber() + " - Column: " + e.getColumnNumber() + Config.EOL + Config.EOL;
  433. fos.write(errorHeader.getBytes());
  434. fos.write(e.getMessage().getBytes());
  435. _log.warning("Failed executing script: " + script.getAbsolutePath() + ". See " + file.getName() + " for details.");
  436. }
  437. catch (IOException ioe)
  438. {
  439. _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);
  440. }
  441. }
  442. else
  443. {
  444. _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);
  445. }
  446. }
  447. public void registerScriptManager(ScriptManager<?> manager)
  448. {
  449. _scriptManagers.add(manager);
  450. }
  451. public void removeScriptManager(ScriptManager<?> manager)
  452. {
  453. _scriptManagers.remove(manager);
  454. }
  455. public List<ScriptManager<?>> getScriptManagers()
  456. {
  457. return _scriptManagers;
  458. }
  459. /**
  460. * @param currentLoadingScript The currentLoadingScript to set.
  461. */
  462. protected void setCurrentLoadingScript(File currentLoadingScript)
  463. {
  464. _currentLoadingScript = currentLoadingScript;
  465. }
  466. /**
  467. * @return Returns the currentLoadingScript.
  468. */
  469. public File getCurrentLoadingScript()
  470. {
  471. return _currentLoadingScript;
  472. }
  473. private static class SingletonHolder
  474. {
  475. protected static final L2ScriptEngineManager _instance = new L2ScriptEngineManager();
  476. }
  477. }