001package com.randomnoun.common.jessop;
002
003/* (c) 2016 randomnoun. All Rights Reserved. This work is licensed under a
004 * BSD Simplified License. ( http://www.randomnoun.com/bsd-simplified.html ) 
005 */
006
007import java.io.ByteArrayOutputStream;
008import java.io.IOException;
009import java.io.PrintWriter;
010import java.io.Reader;
011import java.io.StringReader;
012
013import javax.script.AbstractScriptEngine;
014import javax.script.Bindings;
015import javax.script.Compilable;
016import javax.script.CompiledScript;
017import javax.script.ScriptContext;
018import javax.script.ScriptEngine;
019import javax.script.ScriptEngineFactory;
020import javax.script.ScriptEngineManager;
021import javax.script.ScriptException;
022import javax.script.SimpleBindings;
023
024import org.apache.log4j.Logger;
025
026import com.randomnoun.common.jessop.lang.JavascriptJessopScriptBuilder;
027
028/** The jessop ScriptEngine class.
029 * 
030 * @author knoxg
031 */
032public class JessopScriptEngine extends AbstractScriptEngine implements Compilable {
033
034        /** Logger instance for this class */
035        Logger logger = Logger.getLogger(JessopScriptEngine.class);
036
037        /** ScriptEngineFactory that created this class */
038        ScriptEngineFactory factory;
039        
040    /** Reserved key for a named value that identifies the initial language used for jessop scripts.
041     * If not set, will default to 'javascript'
042     */
043        public static final String JESSOP_LANGUAGE = "com.randommoun.common.jessop.language";
044        
045        /** Default value for the JESSOP_LANGUAGE key; has the value "javascript" */
046        public static final String JESSOP_DEFAULT_LANGUAGE = "javascript";
047        
048    /** Reserved key for a named value that identifies the initial ScriptEngine used for jessop scripts.
049     * If not set, will use the default engine for the default language.
050     */
051        public static final String JESSOP_ENGINE = "com.randommoun.common.jessop.engine";
052
053        /** Default value for the JESSOP_ENGINE key; has the value "rhino" */
054        public static final String JESSOP_DEFAULT_ENGINE = "rhino";
055        
056        
057    /** Reserved key for a named value that sets the initial exception converter.
058     * If not set, will use the default converter for the default language
059     */
060        public static final String JESSOP_EXCEPTION_CONVERTER = "com.randommoun.common.jessop.exceptionConverter";
061
062        /** Default value for the JESSOP_EXCEPTION_CONVERTER key; has the value null */
063        public static final String JESSOP_DEFAULT_EXCEPTION_CONVERTER = null;
064
065    /** Reserved key for a named value that sets the initial bindings converter.
066     * If not set, will use the default converter for the default language
067     */
068        public static final String JESSOP_BINDINGS_CONVERTER = "com.randommoun.common.jessop.bindingsConverter";
069
070        /** Default value for the JESSOP_EXCEPTION_CONVERTER key; has the value null */
071        // so this isn't final any more, since it might change depending on what's on the classpath
072        public static String JESSOP_DEFAULT_BINDINGS_CONVERTER; //  = "com.randomnoun.common.jessop.engine.jvmRhino.JvmRhinoBindingsConverter";
073
074        static {
075                JavascriptJessopScriptBuilder jjsb = new JavascriptJessopScriptBuilder(); 
076                JESSOP_DEFAULT_BINDINGS_CONVERTER = jjsb.getDefaultBindingsConverterClassName();
077        }
078        
079        
080    /** Reserved key for a named value that controls whether the target script is compiled
081     * (providing the target engine allows it).
082     * If not set, will default to 'false'.
083     */
084        public static final String JESSOP_COMPILE_TARGET = "com.randommoun.common.jessop.compileTarget";
085
086        /** Default value for the JESSOP_COMPILE_TARGET key; has the value "false" */
087        public static final String JESSOP_DEFAULT_COMPILE_TARGET = "false";
088
089        
090    /** Reserved key for a named value that controls whether the target script 
091     * will have EOLs suppressed after scriptlets that appear at the end of a line.
092     * If not set, will default to 'false'.
093     */
094        public static final String JESSOP_SUPPRESS_EOL = "com.randommoun.common.jessop.suppressEol";
095
096        /** Default value for the JESSOP_SUPPRESS_EOL key; has the value "false" */
097        public static final String JESSOP_DEFAULT_SUPPRESS_EOL = "false";
098
099        
100        // so I guess we implement this twice then
101        // let's always compile it if we can
102        
103        /** {@inheritDoc} */
104        @Override
105        public Object eval(String script, ScriptContext context) throws ScriptException {
106                CompiledScript cscript = compile(script);
107                return cscript.eval(context);
108        }
109
110        /** {@inheritDoc} */
111        @Override
112        public Object eval(Reader reader, ScriptContext context) throws ScriptException {
113                CompiledScript cscript = compile(reader);
114                return cscript.eval(context);
115        }
116
117        // if the user doesn't supply a context, we evaluate it with a null context (not the default context)
118        // the JessopCompiledScript will then use the appropriate language's default context instead
119        
120        /** {@inheritDoc} */
121        @Override
122        public Object eval(String script) throws ScriptException {
123        return eval(script, (ScriptContext) null);
124    }
125
126        /** {@inheritDoc} */
127        @Override
128        public Object eval(Reader reader) throws ScriptException {
129        return eval(reader, (ScriptContext) null);
130    }
131
132        /** {@inheritDoc} */
133        @Override
134        public Bindings createBindings() {
135                return new SimpleBindings();
136        }
137
138        /** {@inheritDoc} */
139        @Override
140        public ScriptEngineFactory getFactory() {
141                if (factory != null) {
142                        return factory;
143                } else {
144                        return new JessopScriptEngineFactory();
145                }
146        }
147        
148        // package private; called by ScriptEngineFactory only
149        void setEngineFactory(ScriptEngineFactory fac) {
150                factory = fac;
151        }
152
153        @Override
154        /** {@inheritDoc} */
155        public CompiledScript compile(String script) throws ScriptException {
156                return compile(new StringReader(script));
157        }
158
159        /** {@inheritDoc} */
160        @Override
161        public CompiledScript compile(Reader script) throws ScriptException {
162                try {
163                        // if (initialLanguage == null) { initialLanguage = "javascript"; }
164
165                        JessopDeclarations declarations = new JessopDeclarations();
166                        // jessop defaults
167                        declarations.setEngine(JESSOP_DEFAULT_ENGINE);
168                        declarations.setExceptionConverter(JESSOP_DEFAULT_EXCEPTION_CONVERTER);
169                        declarations.setBindingsConverter(JESSOP_DEFAULT_BINDINGS_CONVERTER);
170                        declarations.setCompileTarget(Boolean.valueOf(JESSOP_DEFAULT_COMPILE_TARGET));
171                        declarations.setSuppressEol(Boolean.valueOf(JESSOP_DEFAULT_SUPPRESS_EOL));
172
173                        // ScriptEngine defaults
174                        String filename = (String) get(ScriptEngine.FILENAME);
175                        String initialLanguage = (String) get(JessopScriptEngine.JESSOP_LANGUAGE);
176                        String initialEngine = (String) get(JessopScriptEngine.JESSOP_ENGINE);
177                        String initialExceptionConverter = (String) get(JessopScriptEngine.JESSOP_EXCEPTION_CONVERTER);
178                        String initialBindingsConverter = (String) get(JessopScriptEngine.JESSOP_BINDINGS_CONVERTER);
179                        String initialCompileTarget = (String) get(JessopScriptEngine.JESSOP_COMPILE_TARGET);
180                        String initialSuppressEol = (String) get(JessopScriptEngine.JESSOP_SUPPRESS_EOL);
181                        if (initialLanguage==null) { initialLanguage = JESSOP_DEFAULT_LANGUAGE; }
182                        if (filename!=null) { declarations.setFilename(filename); }
183                        if (initialEngine!=null) { declarations.setEngine(initialEngine); }
184                        if (initialExceptionConverter!=null) { declarations.setExceptionConverter(initialExceptionConverter); }
185                        if (initialBindingsConverter!=null) { declarations.setBindingsConverter(initialBindingsConverter); }
186                        if (initialCompileTarget!=null) { declarations.setCompileTarget(Boolean.valueOf(initialCompileTarget)); }
187                        if (initialSuppressEol!=null) { declarations.setSuppressEol(Boolean.valueOf(initialSuppressEol)); }
188                        
189                        ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
190                        PrintWriter pw = new PrintWriter(baos);
191                    // new JavascriptJessopScriptBuilder(); // default for now
192                        JessopScriptBuilder jsb = ((JessopScriptEngineFactory) getFactory()).getJessopScriptBuilderForLanguage(initialLanguage);
193                        jsb.setPrintWriter(pw);
194                        Tokeniser t = new Tokeniser(this, jsb);
195                        jsb.setTokeniserAndDeclarations(t, declarations);
196                        
197                        // tokenise the script
198                        int ch = script.read();
199                        while (ch!=-1) {
200                                t.parseChar((char) ch);
201                                ch = script.read();
202                        }
203                        t.parseEndOfFile();
204                        pw.flush();
205                        
206                        // get the output from the PrintWriter
207                        String newScript = baos.toString();
208                        
209                        // the final JSB contains the final declarations that were in effect 
210                        declarations = t.jsb.getDeclarations();
211                        
212                        // get this from the jessop declaration eventally, but for now:
213                        // if the underlying engine supports compilation, then compile that here, otherwise just store the source
214                        ScriptEngine engine = new ScriptEngineManager().getEngineByName(declarations.engine);  // nashorn in JDK9
215                        if (engine==null) {
216                                throw new ScriptException("java.scriptx engine '" + declarations.engine + "' not found");
217                        }
218                        
219                        JessopBindingsConverter jbc = null;
220                        if (declarations.bindingsConverter !=null && !declarations.bindingsConverter.equals("")) {
221                                try {
222                                        jbc = (JessopBindingsConverter) 
223                                                Class.forName(declarations.bindingsConverter).newInstance();
224                                } catch (Exception e) {
225                                        throw (ScriptException) new ScriptException(
226                                          "bindingsConverter '" + declarations.bindingsConverter + "' not loaded").initCause(e);
227                                }
228                        }
229                        
230                        JessopExceptionConverter jec = null;
231                        if (declarations.exceptionConverter!=null && !declarations.exceptionConverter.equals("")) {
232                                try {
233                                        jec = (JessopExceptionConverter) 
234                                                Class.forName(declarations.exceptionConverter).newInstance();
235                                } catch (Exception e) {
236                                        throw (ScriptException) new ScriptException(
237                                          "exceptionConverter '" + declarations.exceptionConverter + "' not loaded").initCause(e);
238                                }
239                        }
240                         
241                        // com.sun.script.javascript.RhinoScriptEngine m = (com.sun.script.javascript.RhinoScriptEngine) engine;
242                        // the newScript is compiled here, if the engine supports it
243                        return new JessopCompiledScript(engine, declarations.isCompileTarget(), 
244                                declarations.getFilename(), newScript, jec, jbc);
245                        
246                } catch (IOException ioe) {
247                        throw new ScriptException(ioe);
248                }
249                
250                
251                
252        }
253}