/*
* This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
*
* Copyright (c) JCThePants (www.jcwhatever.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.jcwhatever.nucleus.internal.managed.scripting;
import com.jcwhatever.nucleus.Nucleus;
import com.jcwhatever.nucleus.internal.NucMsg;
import com.jcwhatever.nucleus.mixins.IDisposable;
import com.jcwhatever.nucleus.managed.scripting.IEvaluatedScript;
import com.jcwhatever.nucleus.managed.scripting.IScript;
import com.jcwhatever.nucleus.managed.scripting.IScriptApi;
import com.jcwhatever.nucleus.utils.PreCon;
import com.jcwhatever.nucleus.utils.ScriptUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;
/**
* NucleusFramework's default {@link com.jcwhatever.nucleus.managed.scripting.IEvaluatedScript} implementation.
*
* <p>An evaluated script</p>
*/
class EvaluatedScript implements IEvaluatedScript {
private final IScript _parentScript;
private final ScriptEngine _engine;
private ScriptContext _context;
private final Map<String, IScriptApi> _scriptApis;
private final List<IDisposable> _apiObjects = new ArrayList<>(25);
private boolean _isDisposed;
/**
* Constructor.
*
* @param parentScript The script that was evaluated.
* @param engine The engine that evaluated the script.
* @param scriptApis The api that was included.
*/
public EvaluatedScript(IScript parentScript, ScriptEngine engine,
@Nullable Collection<? extends IScriptApi> scriptApis) {
PreCon.notNull(parentScript);
PreCon.notNull(engine);
_parentScript = parentScript;
_engine = engine;
_scriptApis = new HashMap<>(scriptApis == null ? 10 : scriptApis.size() + 10);
if (scriptApis != null) {
for (IScriptApi api : scriptApis) {
addScriptApi(api, api.getName());
}
}
}
/**
* Get the script that was evaluated.
*/
@Override
public IScript getParentScript() {
return _parentScript;
}
/**
* Get the script engine that evaluated the script.
*/
@Override
public ScriptEngine getScriptEngine() {
return _engine;
}
/**
* Get the script api included during evaluation.
*/
@Override
public List<IScriptApi> getScriptApi() {
return new ArrayList<>(_scriptApis.values());
}
/**
* Get the script context.
*/
@Override
public ScriptContext getContext() {
if (_context == null) {
_context = createContext();
}
return _context;
}
/**
* Add a script api.
*/
@Override
public void addScriptApi(IScriptApi scriptApi, String variableName) {
PreCon.notNull(scriptApi);
PreCon.notNullOrEmpty(variableName);
if (_scriptApis.containsKey(scriptApi.getName())) {
NucMsg.debug("A script api named '{0}' is already added.", scriptApi.getName());
return;
}
IDisposable apiObject = scriptApi.createApi(Nucleus.getPlugin(), this);
if (apiObject == null) {
NucMsg.debug("Failed to retrieve IScriptApiObject from script api '{0}' " +
"for use by plugin named '{1}' assigned to variable name '{2}'.",
scriptApi.getName(),
scriptApi.getPlugin().getName(),
variableName);
return;
}
_scriptApis.put(scriptApi.getName(), scriptApi);
_engine.put(variableName, apiObject);
getContext().setAttribute(variableName, apiObject, ScriptContext.ENGINE_SCOPE);
_apiObjects.add(apiObject);
}
/**
* Determine if the script engine allows script functions
* to be invoked via {@link javax.script.Invocable} interface.
*/
@Override
public boolean canInvoke() {
return _engine instanceof Invocable;
}
/**
* Invoke a function in the evaluated script using the
* {@link javax.script.Invocable} interface.
*
* @param functionName The name of the function.
* @param parameters Function parameters.
*/
@Override
@Nullable
public Object invokeFunction(String functionName, Object... parameters)
throws NoSuchMethodException {
if (!(_engine instanceof Invocable))
return null;
Invocable inv = (Invocable)_engine;
try {
return inv.invokeFunction(functionName, parameters);
}
catch (ScriptException e) {
e.printStackTrace();
return null;
}
}
/**
* Evaluate another script into the scripts engine.
*
* @param script The script to evaluated.
*/
@Override
@Nullable
public Object evaluate(IScript script) {
return ScriptUtils.eval(_engine, getContext(), script).getResult();
}
@Override
public boolean isDisposed() {
return _isDisposed;
}
@Override
public void dispose() {
try {
invokeFunction("onScriptDispose");
} catch (NoSuchMethodException ignore) {
// do nothing
}
for (IDisposable api : _apiObjects) {
try {
api.dispose();
}
catch (Throwable e) {
e.printStackTrace();
}
}
_isDisposed = true;
_scriptApis.clear();
}
/**
* Invoked to get a context for the script.
*/
protected ScriptContext createContext() {
Class<?> contextClazz = getScriptEngine().getContext().getClass();
try {
// some engines require their own script context implementation,
// try instantiating a new script context using the type from the engine.
Constructor<?> constructor = contextClazz.getDeclaredConstructor();
return (ScriptContext)constructor.newInstance();
} catch (NoSuchMethodException | InvocationTargetException |
InstantiationException | IllegalAccessException e) {
NucMsg.debug(Nucleus.getPlugin(), "Failed to create new script context using current context type." +
"Using SimpleScriptContext instead.");
// if failed, use a SimpleScriptContext
return new SimpleScriptContext();
}
}
}