// Copyright 2013 Michel Kraemer // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package de.undercouch.citeproc.script; import java.io.Reader; import java.util.Collection; import java.util.List; import java.util.Map; import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import de.undercouch.citeproc.VariableWrapper; import de.undercouch.citeproc.VariableWrapperParams; import de.undercouch.citeproc.helper.json.JsonBuilder; import de.undercouch.citeproc.helper.json.JsonObject; import de.undercouch.citeproc.helper.json.StringJsonBuilder; /** * Executes JavaScript scripts through the Java Scripting API * @author Michel Kraemer */ public class JREScriptRunner extends AbstractScriptRunner { private final ScriptEngine engine; /** * Default constructor */ public JREScriptRunner() { engine = new ScriptEngineManager().getEngineByName("javascript"); } @Override public String getName() { return engine.getFactory().getEngineName(); } @Override public String getVersion() { return engine.getFactory().getEngineVersion(); } @Override public void eval(Reader reader) throws ScriptRunnerException { try { engine.eval(reader); } catch (ScriptException e) { throw new ScriptRunnerException("Could not evaluate code", e); } } @Override public JsonBuilder createJsonBuilder() { return new StringJsonBuilder(this); } @Override public <T> T callMethod(String name, Class<T> resultType, Object... args) throws ScriptRunnerException { Invocable i = (Invocable)engine; try { return convert(i.invokeFunction(name, convertArguments(args)), resultType); } catch (NoSuchMethodException e) { throw new ScriptRunnerException("Could not call method", e); } catch (ScriptException e) { throw new ScriptRunnerException("Could not call method", e); } } @Override public void callMethod(String name, Object... args) throws ScriptRunnerException { Invocable i = (Invocable)engine; try { i.invokeFunction(name, convertArguments(args)); } catch (NoSuchMethodException e) { throw new ScriptRunnerException("Could not call method", e); } catch (ScriptException e) { throw new ScriptRunnerException("Could not call method", e); } } @Override public <T> T callMethod(Object obj, String name, Class<T> resultType, Object... args) throws ScriptRunnerException { Invocable i = (Invocable)engine; try { return convert(i.invokeMethod(obj, name, convertArguments(args)), resultType); } catch (NoSuchMethodException e) { throw new ScriptRunnerException("Could not call method", e); } catch (ScriptException e) { throw new ScriptRunnerException("Could not call method", e); } } @Override public void callMethod(Object obj, String name, Object... args) throws ScriptRunnerException { Invocable i = (Invocable)engine; try { i.invokeMethod(obj, name, convertArguments(args)); } catch (NoSuchMethodException e) { throw new ScriptRunnerException("Could not call method", e); } catch (ScriptException e) { throw new ScriptRunnerException("Could not call method", e); } } @Override @SuppressWarnings("unchecked") public <T> T convert(Object r, Class<T> resultType) { if (List.class.isAssignableFrom(resultType) && r instanceof Map) { r = ((Map<?, ?>)r).values(); } return (T)r; } private Object[] convertArguments(Object[] args) throws ScriptException { Object[] result = new Object[args.length]; for (int i = 0; i < args.length; ++i) { Object o = args[i]; //convert JSON objects, collections, arrays, and maps, but do //not convert script objects (such as Bindings) if (o == null) { result[i] = null; } else if (o instanceof JsonObject || o instanceof Collection || o.getClass().isArray() || (o instanceof Map && o.getClass().getPackage().getName().startsWith("java."))) { result[i] = engine.eval("(" + createJsonBuilder().toJson(o).toString() + ")"); } else if (o instanceof VariableWrapper) { o = new VariableWrapperWrapper((VariableWrapper)o); result[i] = o; } else { result[i] = o; } } return result; } /** * <p>Wraps around {@link VariableWrapper} and converts * {@link VariableWrapperParams} objects to JSON objects</p> * <p>Note: this class must be public so Nashorn can inspect it and * find the <code>wrap()</code> method.</p> * @author Michel Kraemer */ public static class VariableWrapperWrapper { private final VariableWrapper wrapper; /** * Creates a new wrapper * @param wrapper the variable wrapper to wrap around */ public VariableWrapperWrapper(VariableWrapper wrapper) { this.wrapper = wrapper; } /** * Call the {@link VariableWrapper} with the given parameters * @param params the context in which an item should be rendered * @param prePunct the text that precedes the item to render * @param str the item to render * @param postPunct the text that follows the item to render * @return the string to be rendered */ public String wrap(Map<String, Object> params, String prePunct, String str, String postPunct) { VariableWrapperParams p = VariableWrapperParams.fromJson(params); return wrapper.wrap(p, prePunct, str, postPunct); } } }