001/* 002 * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. 003 * Use is subject to license terms. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, are 006 * permitted provided that the following conditions are met: Redistributions of source code 007 * must retain the above copyright notice, this list of conditions and the following disclaimer. 008 * Redistributions in binary form must reproduce the above copyright notice, this list of 009 * conditions and the following disclaimer in the documentation and/or other materials 010 * provided with the distribution. Neither the name of the Sun Microsystems nor the names of 011 * is contributors may be used to endorse or promote products derived from this software 012 * without specific prior written permission. 013 014 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 015 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 016 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 017 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 018 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 019 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 020 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 021 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 022 * POSSIBILITY OF SUCH DAMAGE. 023 */ 024 025/* 026 * JavaScriptEngine.java 027 * @author A. Sundararajan 028 */ 029 030package org.kepler.scriptengine.java; 031 032import java.io.IOException; 033import java.io.Reader; 034import java.lang.reflect.Method; 035import java.lang.reflect.Modifier; 036import java.util.Iterator; 037import java.util.Map; 038 039import javax.script.AbstractScriptEngine; 040import javax.script.Bindings; 041import javax.script.Compilable; 042import javax.script.CompiledScript; 043import javax.script.Invocable; 044import javax.script.ScriptContext; 045import javax.script.ScriptEngine; 046import javax.script.ScriptEngineFactory; 047import javax.script.ScriptException; 048import javax.script.SimpleBindings; 049 050/** 051 * This is script engine for Java programming language. 052 */ 053public class JavaScriptEngine extends AbstractScriptEngine 054 implements Compilable, Invocable { 055 056 private Class<?> _clazz; 057 058 // Java compiler 059 private JavaCompiler compiler; 060 061 public JavaScriptEngine() { 062 compiler = new JavaCompiler(); 063 } 064 065 // my factory, may be null 066 private ScriptEngineFactory factory; 067 068 // my implementation for CompiledScript 069 private class JavaCompiledScript extends CompiledScript { 070 private Class<?> clazz; 071 072 JavaCompiledScript (Class<?> clazz) { 073 this.clazz = clazz; 074 } 075 076 @Override 077 public ScriptEngine getEngine() { 078 return JavaScriptEngine.this; 079 } 080 081 @Override 082 public Object eval(ScriptContext ctx) throws ScriptException { 083 return evalClass(clazz, ctx); 084 } 085 } 086 087 @Override 088 public CompiledScript compile(String script) throws ScriptException { 089 Class<?> clazz = parse(script, context); 090 return new JavaCompiledScript(clazz); 091 } 092 093 @Override 094 public CompiledScript compile(Reader reader) throws ScriptException { 095 return compile(readFully(reader)); 096 } 097 098 @Override 099 public Object eval(String str, ScriptContext ctx) 100 throws ScriptException { 101 _clazz = parse(str, ctx); 102 return _clazz; 103 //return evalClass(clazz, ctx); 104 } 105 106 @Override 107 public Object eval(Reader reader, ScriptContext ctx) 108 throws ScriptException { 109 return eval(readFully(reader), ctx); 110 } 111 112 @Override 113 public ScriptEngineFactory getFactory() { 114 synchronized (this) { 115 if (factory == null) { 116 factory = new JavaScriptEngineFactory(); 117 } 118 } 119 return factory; 120 } 121 122 @Override 123 public Bindings createBindings() { 124 return new SimpleBindings(); 125 } 126 127 void setFactory(ScriptEngineFactory factory) { 128 this.factory = factory; 129 } 130 131 // Internals only below this point 132 133 private Class<?> parse(String str, ScriptContext ctx) throws ScriptException { 134 String fileName = getFileName(ctx); 135 String sourcePath = getSourcePath(ctx); 136 String classPath = getClassPath(ctx); 137 138 Map<String, byte[]> classBytes = compiler.compile(fileName, str, 139 ctx.getErrorWriter(), sourcePath, classPath); 140 141 if (classBytes == null) { 142 throw new ScriptException("compilation failed"); 143 } 144 145 // create a ClassLoader to load classes from MemoryJavaFileManager 146 try(MemoryClassLoader loader = new MemoryClassLoader(classBytes, classPath, 147 getParentLoader(ctx))) { 148 149 String mainClassName = getMainClassName(ctx); 150 if (mainClassName != null) { 151 try { 152 Class<?> clazz = loader.load(mainClassName); 153 Method mainMethod = findMainMethod(clazz); 154 if (mainMethod == null) { 155 throw new ScriptException("no main method in " + mainClassName); 156 } 157 return clazz; 158 } catch (ClassNotFoundException cnfe) { 159 throw new ScriptException(cnfe); 160 } 161 } 162 163 // no main class configured - load all compiled classes 164 Iterable<Class<?>> classes; 165 try { 166 classes = loader.loadAll(); 167 } catch (ClassNotFoundException exp) { 168 throw new ScriptException(exp); 169 } 170 171 // search for class with main method 172 Class<?> c = findMainClass(classes); 173 if (c != null) { 174 return c; 175 } else { 176 // if class with "main" method, then 177 // return first class 178 Iterator<Class<?>> itr = classes.iterator(); 179 if (itr.hasNext()) { 180 return itr.next(); 181 } else { 182 return null; 183 } 184 } 185 186 } catch (IOException e) { 187 throw new ScriptException("Error closing class loader: " + e.getMessage()); 188 } 189 } 190 191 private static Class<?> findMainClass(Iterable<Class<?>> classes) { 192 // find a public class with public static main method 193 for (Class<?> clazz : classes) { 194 int modifiers = clazz.getModifiers(); 195 if (Modifier.isPublic(modifiers)) { 196 Method mainMethod = findMainMethod(clazz); 197 if (mainMethod != null) { 198 return clazz; 199 } 200 } 201 } 202 203 // okay, try to find package private class that 204 // has public static main method 205 for (Class<?> clazz : classes) { 206 Method mainMethod = findMainMethod(clazz); 207 if (mainMethod != null) { 208 return clazz; 209 } 210 } 211 212 // no main class found! 213 return null; 214 } 215 216 // find public static void main(String[]) method, if any 217 private static Method findMainMethod(Class<?> clazz) { 218 try { 219 Method mainMethod = clazz.getMethod("main", new Class[] { String[].class }); 220 int modifiers = mainMethod.getModifiers(); 221 if (Modifier.isPublic(modifiers) && 222 Modifier.isStatic(modifiers)) { 223 return mainMethod; 224 } 225 } catch (NoSuchMethodException nsme) { 226 } 227 return null; 228 } 229 230 // find public static void setScriptContext(ScriptContext) method, if any 231 private static Method findSetScriptContextMethod(Class<?> clazz) { 232 try { 233 Method setCtxMethod = clazz.getMethod("setScriptContext", 234 new Class<?>[] { ScriptContext.class }); 235 int modifiers = setCtxMethod.getModifiers(); 236 if (Modifier.isPublic(modifiers) && 237 Modifier.isStatic(modifiers)) { 238 return setCtxMethod; 239 } 240 } catch (NoSuchMethodException nsme) { 241 } 242 return null; 243 } 244 245 private static String getFileName(ScriptContext ctx) { 246 int scope = ctx.getAttributesScope(ScriptEngine.FILENAME); 247 if (scope != -1) { 248 return ctx.getAttribute(ScriptEngine.FILENAME, scope).toString(); 249 } else { 250 return "$unnamed.java"; 251 } 252 } 253 254 255 // for certain variables, we look for System properties. This is 256 // the prefix used for such System properties 257 private static final String SYSPROP_PREFIX = "com.sun.script.java."; 258 259 private static final String[] EMPTY_STRING_ARRAY = new String[0]; 260 private static final String ARGUMENTS = "arguments"; 261 private static String[] getArguments(ScriptContext ctx) { 262 int scope = ctx.getAttributesScope(ARGUMENTS); 263 if (scope != -1) { 264 Object obj = ctx.getAttribute(ARGUMENTS, scope); 265 if (obj instanceof String[]) { 266 return (String[])obj; 267 } 268 } 269 // return zero length array 270 return EMPTY_STRING_ARRAY; 271 } 272 273 private static final String SOURCEPATH = "sourcepath"; 274 private static String getSourcePath(ScriptContext ctx) { 275 int scope = ctx.getAttributesScope(SOURCEPATH); 276 if (scope != -1) { 277 return ctx.getAttribute(SOURCEPATH).toString(); 278 } else { 279 // look for "com.sun.script.java.sourcepath" 280 return System.getProperty(SYSPROP_PREFIX + SOURCEPATH); 281 } 282 } 283 284 private static final String CLASSPATH = "classpath"; 285 private static String getClassPath(ScriptContext ctx) { 286 int scope = ctx.getAttributesScope(CLASSPATH); 287 if (scope != -1) { 288 return ctx.getAttribute(CLASSPATH).toString(); 289 } else { 290 // look for "com.sun.script.java.classpath" 291 String res = System.getProperty(SYSPROP_PREFIX + CLASSPATH); 292 if (res == null) { 293 res = System.getProperty("java.class.path"); 294 } 295 return res; 296 } 297 } 298 299 private static final String MAINCLASS = "mainClass"; 300 private static String getMainClassName(ScriptContext ctx) { 301 int scope = ctx.getAttributesScope(MAINCLASS); 302 if (scope != -1) { 303 return ctx.getAttribute(MAINCLASS).toString(); 304 } else { 305 // look for "com.sun.script.java.mainClass" 306 return System.getProperty(SYSPROP_PREFIX + MAINCLASS); 307 } 308 } 309 310 public static final String PARENTLOADER = "parentLoader"; 311 private static ClassLoader getParentLoader(ScriptContext ctx) { 312 int scope = ctx.getAttributesScope(PARENTLOADER); 313 if (scope != -1) { 314 Object loader = ctx.getAttribute(PARENTLOADER); 315 if (loader instanceof ClassLoader) { 316 return (ClassLoader) loader; 317 } // else fall through.. 318 } 319 return null; 320 } 321 322 private static Object evalClass(Class<?> clazz, ScriptContext ctx) 323 throws ScriptException { 324 // JSR-223 requirement 325 ctx.setAttribute("context", ctx, ScriptContext.ENGINE_SCOPE); 326 if (clazz == null) { 327 return null; 328 } 329 try { 330 boolean isPublicClazz = Modifier.isPublic(clazz.getModifiers()); 331 332 // find the setScriptContext method 333 Method setCtxMethod = findSetScriptContextMethod(clazz); 334 // call setScriptContext and pass current ctx variable 335 if (setCtxMethod != null) { 336 if (! isPublicClazz) { 337 // try to relax access 338 setCtxMethod.setAccessible(true); 339 } 340 setCtxMethod.invoke(null, new Object[] { ctx }); 341 } 342 343 // find the main method 344 Method mainMethod = findMainMethod(clazz); 345 if (mainMethod != null) { 346 if (! isPublicClazz) { 347 // try to relax access 348 mainMethod.setAccessible(true); 349 } 350 351 // get "command line" args for the main method 352 String[] args = getArguments(ctx); 353 354 // call main method 355 mainMethod.invoke(null, new Object[] { args }); 356 } 357 358 // return main class as eval's result 359 return clazz; 360 } catch (Exception exp) { 361 throw new ScriptException(exp); 362 } 363 } 364 365 // read a Reader fully and return the content as string 366 private String readFully(Reader reader) throws ScriptException { 367 char[] arr = new char[8*1024]; // 8K at a time 368 StringBuilder buf = new StringBuilder(); 369 int numChars; 370 try { 371 while ((numChars = reader.read(arr, 0, arr.length)) > 0) { 372 buf.append(arr, 0, numChars); 373 } 374 } catch (IOException exp) { 375 throw new ScriptException(exp); 376 } 377 return buf.toString(); 378 } 379 380 @Override 381 public <T> T getInterface(Class<T> arg0) { 382 // TODO Auto-generated method stub 383 return null; 384 } 385 386 @Override 387 public <T> T getInterface(Object arg0, Class<T> arg1) { 388 // TODO Auto-generated method stub 389 return null; 390 } 391 392 @Override 393 public Object invokeFunction(String arg0, Object... arg1) 394 throws ScriptException, NoSuchMethodException { 395 // TODO Auto-generated method stub 396 return null; 397 } 398 399 @Override 400 public Object invokeMethod(Object thiz, String name, Object... args) 401 throws ScriptException, NoSuchMethodException { 402 403 Class<?>[] classes = new Class<?>[args.length]; 404 Method method = thiz.getClass().getMethod(name, classes); 405 try { 406 return method.invoke(thiz, args); 407 } catch (Exception e) { 408 throw new ScriptException(e); 409 } 410 } 411}