package it.paspiz85.nanobot.scripting;
import it.paspiz85.nanobot.logic.StateMainMenu;
import it.paspiz85.nanobot.platform.Platform;
import it.paspiz85.nanobot.util.BuildInfo;
import it.paspiz85.nanobot.util.Utils;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.SimpleScriptContext;
/**
* Script Manager.
*
* @author paspiz85
*
*/
public final class ScriptManager {
public static ScriptManager instance() {
return Utils.singleton(ScriptManager.class, () -> new ScriptManager());
}
private Consumer<String> alert;
private Function<String, Boolean> confirm;
private final Map<String, Path> embeddedScripts;
private final ScriptEngineManager factory;
protected final Logger logger = Logger.getLogger(getClass().getName());
private PromptFunction prompt;
private SelectFunction select;
private ScriptManager() {
factory = new ScriptEngineManager();
embeddedScripts = new TreeMap<>();
try {
Utils.withClasspathFolder(Utils.getParentResource(getClass(), "scripts").toURI(), (path) -> {
ScriptManager.this.embeddedScripts.putAll(scanPath(path));
});
} catch (final URISyntaxException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
}
protected ScriptContext buildContext(final Map<String, Object> params) {
final SimpleScriptContext context = new SimpleScriptContext();
for (final Entry<String, Object> e : params.entrySet()) {
context.setAttribute(e.getKey(), e.getValue(), ScriptContext.ENGINE_SCOPE);
}
context.setAttribute("alert", alert, ScriptContext.ENGINE_SCOPE);
context.setAttribute("confirm", confirm, ScriptContext.ENGINE_SCOPE);
context.setAttribute("prompt", prompt, ScriptContext.ENGINE_SCOPE);
context.setAttribute("select", select, ScriptContext.ENGINE_SCOPE);
context.setAttribute("logger", logger, ScriptContext.ENGINE_SCOPE);
context.setAttribute("postMessage", new BiConsumer<String, Boolean>() {
@Override
public void accept(final String message, final Boolean clan) {
try {
StateMainMenu.instance().postMessage(message, clan);
} catch (final InterruptedException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
}
}, ScriptContext.ENGINE_SCOPE);
context.setAttribute("buildInfo", BuildInfo.instance(), ScriptContext.ENGINE_SCOPE);
context.setAttribute("platform", Platform.instance(), ScriptContext.ENGINE_SCOPE);
return context;
}
protected void closeContext(final ScriptContext context) {
for (final Object obj : context.getBindings(ScriptContext.ENGINE_SCOPE).values()) {
if (obj instanceof Closeable) {
try {
((Closeable) obj).close();
} catch (final IOException e) {
logger.log(Level.WARNING, e.getMessage(), e);
}
}
}
}
public Set<String> getScripts() {
return getScriptsMap().keySet().stream().filter((s) -> !s.startsWith("_")).collect(Collectors.toSet());
}
private Map<String, Path> getScriptsMap() {
Map<String, Path> scripts = new TreeMap<>();
final Path extScripts = Paths.get("scripts");
if (extScripts.toFile().exists()) {
scripts = scanPath(extScripts);
scripts.putAll(embeddedScripts);
} else {
scripts = Collections.unmodifiableMap(embeddedScripts);
}
return scripts;
}
public void run(final String script) throws InterruptedException {
run(script, Collections.emptyMap());
}
public void run(final String script, final Map<String, Object> params) throws InterruptedException {
final String msg = "Running script " + script;
final Path path = getScriptsMap().get(script);
if (path == null) {
throw new IllegalArgumentException("Script not found");
}
logger.log(Level.CONFIG, msg);
final ScriptEngine engine = factory.getEngineByName("nashorn");
final ScriptContext context = buildContext(params);
try (InputStream in = path.toUri().toURL().openStream()) {
engine.eval(new InputStreamReader(in), context);
logger.log(Level.CONFIG, msg + " completed");
} catch (final RuntimeException ex) {
final Throwable cause = ex.getCause();
if (cause != null && cause instanceof InterruptedException) {
throw (InterruptedException) cause;
}
logger.log(Level.WARNING, msg + " failed: " + ex.getMessage(), ex);
} catch (final Exception ex) {
logger.log(Level.WARNING, msg + " failed: " + ex.getMessage(), ex);
} finally {
closeContext(context);
}
}
private Map<String, Path> scanPath(final Path path) {
final Map<String, Path> scripts = new TreeMap<>();
try (Stream<Path> walk = Files.walk(path, 1)) {
for (final Iterator<Path> it = walk.iterator(); it.hasNext();) {
final Path next = it.next();
if (Files.isDirectory(next)) {
continue;
}
scripts.put(next.getFileName().toString(), next);
}
} catch (final IOException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
return scripts;
}
public void setAlert(final Consumer<String> alert) {
this.alert = alert;
}
public void setConfirm(final Function<String, Boolean> confirm) {
this.confirm = confirm;
}
public void setPrompt(final PromptFunction prompt) {
this.prompt = prompt;
}
public void setSelect(final SelectFunction select) {
this.select = select;
}
}