001package com.randomnoun.common.jessop.lang;
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 javax.script.ScriptEngine;
008import javax.script.ScriptEngineFactory;
009import javax.script.ScriptEngineManager;
010
011import org.apache.log4j.Logger;
012
013import com.randomnoun.common.jessop.AbstractJessopScriptBuilder;
014import com.randomnoun.common.jessop.JessopScriptBuilder;
015
016public class JavascriptJessopScriptBuilder extends AbstractJessopScriptBuilder implements JessopScriptBuilder {
017
018        Logger logger = Logger.getLogger(JavascriptJessopScriptBuilder.class);
019        int outputLine = 1;        // current line number in the target script;
020        int lastScriptletLine = 1; // the last line number of the last scriptlet (used for suppressEol)
021
022        public JavascriptJessopScriptBuilder() {
023        }
024        private void skipToLine(int line) {
025                while (outputLine < line) { print("\n"); }
026        }
027        private void print(String s) {
028                // logger.info("** PRINT " + s);
029                pw.print(s);
030                for (int i=0; i<s.length(); i++) {
031                        if (s.charAt(i)=='\n') { outputLine++; } 
032                }
033        }
034        private static String escapeJavascript(String string) {
035        StringBuilder sb = new StringBuilder(string.length());
036                for (int i = 0; i<string.length(); i++) {
037                        char ch = string.charAt(i);
038                        if (ch=='\n') {
039                            sb.append("\\n");
040                        } else if (ch=='\\' || ch=='"' || ch=='\'' || ch<32 || ch>126) {
041                                String hex = Integer.toString(ch, 16);
042                                sb.append("\\u" + "0000".substring(0, 4-hex.length()) + hex);
043                        } else {
044                                sb.append(ch);
045                        }
046                }
047        return sb.toString();
048    }
049        
050        @Override
051        public void emitText(int line, String s) {
052                skipToLine(line);
053                s = suppressEol(s, declarations.isSuppressEol() && lastScriptletLine == line);
054                print("out.write(\"" + escapeJavascript(s) + "\");");
055                lastScriptletLine = 0; // don't suppress eols on this line
056        }
057        @Override
058        public void emitExpression(int line, String s) {
059                skipToLine(line);
060                print("out.write('' + (" + s + "));"); // coerce to String
061                lastScriptletLine = 0; // don't suppress eols on this line
062        }
063        @Override
064        public void emitScriptlet(int line, String s) {
065                skipToLine(line);
066                print(s);
067                lastScriptletLine = line;
068                for (int i=0; i<s.length(); i++) { if (s.charAt(i)=='\n') { lastScriptletLine++; } }
069        }
070        @Override
071        public String getLanguage() {
072                return "javascript";
073        }
074        @Override
075        public String getDefaultScriptEngineName() {
076                // the phobos jsr223 wrapper calls itself 'rhino-nonjdk', as well as 'rhino'
077                
078                // if we have graalvm, then use that otherwise nashorn, otherwise rhino
079                ScriptEngine engine = new ScriptEngineManager().getEngineByName("graal-js");
080                if (engine!=null) {
081                        return "graal-js";
082                } else {
083                        engine = new ScriptEngineManager().getEngineByName("nashorn");
084                        if (engine!=null) {
085                                return "nashorn";
086                        } else {
087                                return "rhino";
088                        }
089                }
090        }
091        
092        
093        // this method should probably take an engine name parameter which is the engine in effect
094        
095        @Override
096        public String getDefaultBindingsConverterClassName() {
097
098                boolean isComSunRhino = false; // rhino engine is under the com.sun package
099                
100                ScriptEngine engine = new ScriptEngineManager().getEngineByName("graal-js");
101                if (engine!=null) {
102                        //  com.oracle.truffle.js.scriptengine.GraalJSScriptEngine
103                        return "com.randomnoun.common.jessop.engine.graaljs.GraalJsBindingsConverter";
104                }
105                
106                engine = new ScriptEngineManager().getEngineByName("nashorn");
107                if (engine!=null) {
108                        // jdk.nashorn.api.scripting.NashornScriptEngine
109                        return null; // nashorn doesn't need a bindingsconverter
110                }
111                
112                // let's see what class we get if we try to load the 'rhino' engine, then work from there
113                engine = new ScriptEngineManager().getEngineByName("rhino");  // nashorn in JDK9
114                if (engine!=null && engine.getClass().getName().equals("com.sun.script.javascript.RhinoScriptEngine")) {
115                        // it's either oracle or openjdk
116                        isComSunRhino = true;
117                }
118                String result = null;
119                if (!isComSunRhino) {
120                        // maybe we've got com.sun.phobos:phobos-rhino 
121                        // or org.rhq:rhq-scripting-javascript 
122                        // or de.christophkraemer:rhino-script-engine 
123                        // or any of the other JSR223 wrappers for rhino in central 
124                        // at http://search.maven.org/#search%7Cga%7C1%7Cc%3A%22RhinoScriptEngine%22
125                        try {
126                                /*Class c =*/ Class.forName("org.mozilla.javascript.NativeObject");
127                                // this exists, so use the mozilla rhino binding converter
128                                result = "com.randomnoun.common.jessop.engine.rhino.RhinoBindingsConverter";
129                        } catch (ClassNotFoundException cnfe) { }
130                }
131                
132                // ok, it's probably oracle or openjdk at this stage
133                if (result == null) {
134                        try {
135                                /*Class c =*/ Class.forName("sun.org.mozilla.javascript.internal.NativeObject");
136                                // this exists, so use the oracle binding converter
137                                result = "com.randomnoun.common.jessop.engine.rhinoOracle.RhinoOracleBindingsConverter";
138                        } catch (ClassNotFoundException cnfe2) { }
139                }
140                
141                if (result == null) {
142                        try {
143                                /*Class c =*/ Class.forName("sun.org.mozilla.javascript.NativeObject");
144                                // this exists, so use the openjdk binding converter
145                                result = "com.randomnoun.common.jessop.engine.rhinoOpenjdk.RhinoOpenjdkBindingsConverter";
146                        } catch (ClassNotFoundException cnfe3) {
147                                // logger.warn("No known rhino implementation on classpath; setting JessopBindingsConverter to null");
148                                result = null;
149                        }
150                }
151                
152                return result;
153        }
154        
155        @Override
156        public String getDefaultExceptionConverterClassName() {
157                ScriptEngine engine = new ScriptEngineManager().getEngineByName("graal-js");
158                if (engine!=null) {
159                        return "com.randomnoun.common.jessop.engine.graaljs.GraalJsExceptionConverter";
160                }
161                return null; // graal doesn't need a bindingsconverter
162        }
163
164        
165        
166        // going to use this for debugging only
167        public void testEngine() {
168                // this should match the engine in use, so let's see what 'rhino' gives us
169                ScriptEngine engine = new ScriptEngineManager().getEngineByName("rhino");  // nashorn in JDK9
170                if (engine!=null) {
171                        ScriptEngineFactory factory = engine.getFactory();
172                        logger.info("default rhino ScriptEngine is " + engine.getClass().getName());
173                        logger.info("ENGINE=" + factory.getParameter(ScriptEngine.ENGINE));
174                        logger.info("ENGINE_VERSION=" + factory.getParameter(ScriptEngine.ENGINE_VERSION));
175                        logger.info("LANGUAGE=" + factory.getParameter(ScriptEngine.LANGUAGE_VERSION));
176                        logger.info("LANGUAGE_VERSION=" + factory.getParameter(ScriptEngine.LANGUAGE_VERSION));
177                } else {
178                        logger.warn("default 'rhino' ScriptEngine not found");
179                }
180        }
181        
182}