1 /* 2 * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.tools.script.shell; 27 28 import java.io.*; 29 import java.net.*; 30 import java.text.*; 31 import java.util.*; 32 import javax.script.*; 33 34 /** 35 * This is the main class for Java script shell. 36 */ 37 public class Main { 38 /** 39 * main entry point to the command line tool 40 * @param args command line argument array 41 */ 42 public static void main(String[] args) { 43 // parse command line options 44 String[] scriptArgs = processOptions(args); 45 46 // process each script command 47 for (Command cmd : scripts) { 48 cmd.run(scriptArgs); 49 } 50 51 System.exit(EXIT_SUCCESS); 52 } 53 54 // Each -e or -f or interactive mode is represented 55 // by an instance of Command. 56 private static interface Command { 57 public void run(String[] arguments); 58 } 59 60 /** 61 * Parses and processes command line options. 62 * @param args command line argument array 63 */ 64 private static String[] processOptions(String[] args) { 65 // current scripting language selected 66 String currentLanguage = DEFAULT_LANGUAGE; 67 // current script file encoding selected 68 String currentEncoding = null; 69 70 // check for -classpath or -cp first 71 checkClassPath(args); 72 73 // have we seen -e or -f ? 74 boolean seenScript = false; 75 // have we seen -f - already? 76 boolean seenStdin = false; 77 for (int i=0; i < args.length; i++) { 78 String arg = args[i]; 79 if (arg.equals("-classpath") || 80 arg.equals("-cp")) { 81 // handled already, just continue 82 i++; 83 continue; 84 } 85 86 // collect non-option arguments and pass these as script arguments 87 if (!arg.startsWith("-")) { 88 int numScriptArgs; 89 int startScriptArg; 90 if (seenScript) { 91 // if we have seen -e or -f already all non-option arguments 92 // are passed as script arguments 93 numScriptArgs = args.length - i; 94 startScriptArg = i; 95 } else { 96 // if we have not seen -e or -f, first non-option argument 97 // is treated as script file name and rest of the non-option 98 // arguments are passed to script as script arguments 99 numScriptArgs = args.length - i - 1; 100 startScriptArg = i + 1; 101 ScriptEngine se = getScriptEngine(currentLanguage); 102 addFileSource(se, args[i], currentEncoding); 103 } 104 // collect script arguments and return to main 105 String[] result = new String[numScriptArgs]; 106 System.arraycopy(args, startScriptArg, result, 0, numScriptArgs); 107 return result; 108 } 109 110 if (arg.startsWith("-D")) { 111 String value = arg.substring(2); 112 int eq = value.indexOf('='); 113 if (eq != -1) { 114 System.setProperty(value.substring(0, eq), 115 value.substring(eq + 1)); 116 } else { 117 if (!value.equals("")) { 118 System.setProperty(value, ""); 119 } else { 120 // do not allow empty property name 121 usage(EXIT_CMD_NO_PROPNAME); 122 } 123 } 124 continue; 125 } else if (arg.equals("-?") || 126 arg.equals("-h") || 127 arg.equals("--help") || 128 // -help: legacy. 129 arg.equals("-help")) { 130 usage(EXIT_SUCCESS); 131 } else if (arg.equals("-e")) { 132 seenScript = true; 133 if (++i == args.length) 134 usage(EXIT_CMD_NO_SCRIPT); 135 136 ScriptEngine se = getScriptEngine(currentLanguage); 137 addStringSource(se, args[i]); 138 continue; 139 } else if (arg.equals("-encoding")) { 140 if (++i == args.length) 141 usage(EXIT_CMD_NO_ENCODING); 142 currentEncoding = args[i]; 143 continue; 144 } else if (arg.equals("-f")) { 145 seenScript = true; 146 if (++i == args.length) 147 usage(EXIT_CMD_NO_FILE); 148 ScriptEngine se = getScriptEngine(currentLanguage); 149 if (args[i].equals("-")) { 150 if (seenStdin) { 151 usage(EXIT_MULTIPLE_STDIN); 152 } else { 153 seenStdin = true; 154 } 155 addInteractiveMode(se); 156 } else { 157 addFileSource(se, args[i], currentEncoding); 158 } 159 continue; 160 } else if (arg.equals("-l")) { 161 if (++i == args.length) 162 usage(EXIT_CMD_NO_LANG); 163 currentLanguage = args[i]; 164 continue; 165 } else if (arg.equals("-q")) { 166 listScriptEngines(); 167 } 168 // some unknown option... 169 usage(EXIT_UNKNOWN_OPTION); 170 } 171 172 if (! seenScript) { 173 ScriptEngine se = getScriptEngine(currentLanguage); 174 addInteractiveMode(se); 175 } 176 return new String[0]; 177 } 178 179 /** 180 * Adds interactive mode Command 181 * @param se ScriptEngine to use in interactive mode. 182 */ 183 private static void addInteractiveMode(final ScriptEngine se) { 184 scripts.add(new Command() { 185 public void run(String[] args) { 186 setScriptArguments(se, args); 187 processSource(se, "-", null); 188 } 189 }); 190 } 191 192 /** 193 * Adds script source file Command 194 * @param se ScriptEngine used to evaluate the script file 195 * @param fileName script file name 196 * @param encoding script file encoding 197 */ 198 private static void addFileSource(final ScriptEngine se, 199 final String fileName, 200 final String encoding) { 201 scripts.add(new Command() { 202 public void run(String[] args) { 203 setScriptArguments(se, args); 204 processSource(se, fileName, encoding); 205 } 206 }); 207 } 208 209 /** 210 * Adds script string source Command 211 * @param se ScriptEngine to be used to evaluate the script string 212 * @param source Script source string 213 */ 214 private static void addStringSource(final ScriptEngine se, 215 final String source) { 216 scripts.add(new Command() { 217 public void run(String[] args) { 218 setScriptArguments(se, args); 219 String oldFile = setScriptFilename(se, "<string>"); 220 try { 221 evaluateString(se, source); 222 } finally { 223 setScriptFilename(se, oldFile); 224 } 225 } 226 }); 227 } 228 229 /** 230 * Prints list of script engines available and exits. 231 */ 232 private static void listScriptEngines() { 233 List<ScriptEngineFactory> factories = engineManager.getEngineFactories(); 234 for (ScriptEngineFactory factory: factories) { 235 getError().println(getMessage("engine.info", 236 new Object[] { factory.getLanguageName(), 237 factory.getLanguageVersion(), 238 factory.getEngineName(), 239 factory.getEngineVersion() 240 })); 241 } 242 System.exit(EXIT_SUCCESS); 243 } 244 245 /** 246 * Processes a given source file or standard input. 247 * @param se ScriptEngine to be used to evaluate 248 * @param filename file name, can be null 249 * @param encoding script file encoding, can be null 250 */ 251 private static void processSource(ScriptEngine se, String filename, 252 String encoding) { 253 if (filename.equals("-")) { 254 BufferedReader in = new BufferedReader 255 (new InputStreamReader(getIn())); 256 boolean hitEOF = false; 257 String prompt = getPrompt(se); 258 se.put(ScriptEngine.FILENAME, "<STDIN>"); 259 while (!hitEOF) { 260 getError().print(prompt); 261 String source = ""; 262 try { 263 source = in.readLine(); 264 } catch (IOException ioe) { 265 getError().println(ioe.toString()); 266 } 267 if (source == null) { 268 hitEOF = true; 269 break; 270 } 271 Object res = evaluateString(se, source, false); 272 if (res != null) { 273 res = res.toString(); 274 if (res == null) { 275 res = "null"; 276 } 277 getError().println(res); 278 } 279 } 280 } else { 281 FileInputStream fis = null; 282 try { 283 fis = new FileInputStream(filename); 284 } catch (FileNotFoundException fnfe) { 285 getError().println(getMessage("file.not.found", 286 new Object[] { filename })); 287 System.exit(EXIT_FILE_NOT_FOUND); 288 } 289 evaluateStream(se, fis, filename, encoding); 290 } 291 } 292 293 /** 294 * Evaluates given script source 295 * @param se ScriptEngine to evaluate the string 296 * @param script Script source string 297 * @param exitOnError whether to exit the process on script error 298 */ 299 private static Object evaluateString(ScriptEngine se, 300 String script, boolean exitOnError) { 301 try { 302 return se.eval(script); 303 } catch (ScriptException sexp) { 304 getError().println(getMessage("string.script.error", 305 new Object[] { sexp.getMessage() })); 306 if (exitOnError) 307 System.exit(EXIT_SCRIPT_ERROR); 308 } catch (Exception exp) { 309 exp.printStackTrace(getError()); 310 if (exitOnError) 311 System.exit(EXIT_SCRIPT_ERROR); 312 } 313 314 return null; 315 } 316 317 /** 318 * Evaluate script string source and exit on script error 319 * @param se ScriptEngine to evaluate the string 320 * @param script Script source string 321 */ 322 private static void evaluateString(ScriptEngine se, String script) { 323 evaluateString(se, script, true); 324 } 325 326 /** 327 * Evaluates script from given reader 328 * @param se ScriptEngine to evaluate the string 329 * @param reader Reader from which is script is read 330 * @param name file name to report in error. 331 */ 332 private static Object evaluateReader(ScriptEngine se, 333 Reader reader, String name) { 334 String oldFilename = setScriptFilename(se, name); 335 try { 336 return se.eval(reader); 337 } catch (ScriptException sexp) { 338 getError().println(getMessage("file.script.error", 339 new Object[] { name, sexp.getMessage() })); 340 System.exit(EXIT_SCRIPT_ERROR); 341 } catch (Exception exp) { 342 exp.printStackTrace(getError()); 343 System.exit(EXIT_SCRIPT_ERROR); 344 } finally { 345 setScriptFilename(se, oldFilename); 346 } 347 return null; 348 } 349 350 /** 351 * Evaluates given input stream 352 * @param se ScriptEngine to evaluate the string 353 * @param is InputStream from which script is read 354 * @param name file name to report in error 355 */ 356 private static Object evaluateStream(ScriptEngine se, 357 InputStream is, String name, 358 String encoding) { 359 BufferedReader reader = null; 360 if (encoding != null) { 361 try { 362 reader = new BufferedReader(new InputStreamReader(is, 363 encoding)); 364 } catch (UnsupportedEncodingException uee) { 365 getError().println(getMessage("encoding.unsupported", 366 new Object[] { encoding })); 367 System.exit(EXIT_NO_ENCODING_FOUND); 368 } 369 } else { 370 reader = new BufferedReader(new InputStreamReader(is)); 371 } 372 return evaluateReader(se, reader, name); 373 } 374 375 /** 376 * Prints usage message and exits 377 * @param exitCode process exit code 378 */ 379 private static void usage(int exitCode) { 380 getError().println(getMessage("main.usage", 381 new Object[] { PROGRAM_NAME })); 382 System.exit(exitCode); 383 } 384 385 /** 386 * Gets prompt for interactive mode 387 * @return prompt string to use 388 */ 389 private static String getPrompt(ScriptEngine se) { 390 List<String> names = se.getFactory().getNames(); 391 return names.get(0) + "> "; 392 } 393 394 /** 395 * Get formatted, localized error message 396 */ 397 private static String getMessage(String key, Object[] params) { 398 return MessageFormat.format(msgRes.getString(key), params); 399 } 400 401 // input stream from where we will read 402 private static InputStream getIn() { 403 return System.in; 404 } 405 406 // stream to print error messages 407 private static PrintStream getError() { 408 return System.err; 409 } 410 411 // get current script engine 412 private static ScriptEngine getScriptEngine(String lang) { 413 ScriptEngine se = engines.get(lang); 414 if (se == null) { 415 se = engineManager.getEngineByName(lang); 416 if (se == null) { 417 getError().println(getMessage("engine.not.found", 418 new Object[] { lang })); 419 System.exit(EXIT_ENGINE_NOT_FOUND); 420 } 421 422 // initialize the engine 423 initScriptEngine(se); 424 // to avoid re-initialization of engine, store it in a map 425 engines.put(lang, se); 426 } 427 return se; 428 } 429 430 // initialize a given script engine 431 private static void initScriptEngine(ScriptEngine se) { 432 // put engine global variable 433 se.put("engine", se); 434 435 // load init.<ext> file from resource 436 List<String> exts = se.getFactory().getExtensions(); 437 InputStream sysIn = null; 438 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 439 for (String ext : exts) { 440 try { 441 sysIn = Main.class.getModule().getResourceAsStream("com/sun/tools/script/shell/init." + ext); 442 } catch (IOException ioe) { 443 throw new RuntimeException(ioe); 444 } 445 if (sysIn != null) break; 446 } 447 if (sysIn != null) { 448 evaluateStream(se, sysIn, "<system-init>", null); 449 } 450 } 451 452 /** 453 * Checks for -classpath, -cp in command line args. Creates a ClassLoader 454 * and sets it as Thread context loader for current thread. 455 * 456 * @param args command line argument array 457 */ 458 private static void checkClassPath(String[] args) { 459 String classPath = null; 460 for (int i = 0; i < args.length; i++) { 461 if (args[i].equals("-classpath") || 462 args[i].equals("-cp")) { 463 if (++i == args.length) { 464 // just -classpath or -cp with no value 465 usage(EXIT_CMD_NO_CLASSPATH); 466 } else { 467 classPath = args[i]; 468 } 469 } 470 } 471 472 if (classPath != null) { 473 /* We create a class loader, configure it with specified 474 * classpath values and set the same as context loader. 475 * Note that ScriptEngineManager uses context loader to 476 * load script engines. So, this ensures that user defined 477 * script engines will be loaded. For classes referred 478 * from scripts, Rhino engine uses thread context loader 479 * but this is script engine dependent. We don't have 480 * script engine independent solution anyway. Unless we 481 * know the class loader used by a specific engine, we 482 * can't configure correct loader. 483 */ 484 URL[] urls = pathToURLs(classPath); 485 URLClassLoader loader = new URLClassLoader(urls); 486 Thread.currentThread().setContextClassLoader(loader); 487 } 488 489 // now initialize script engine manager. Note that this has to 490 // be done after setting the context loader so that manager 491 // will see script engines from user specified classpath 492 engineManager = new ScriptEngineManager(); 493 } 494 495 /** 496 * Utility method for converting a search path string to an array 497 * of directory and JAR file URLs. 498 * 499 * @param path the search path string 500 * @return the resulting array of directory and JAR file URLs 501 */ 502 private static URL[] pathToURLs(String path) { 503 String[] components = path.split(File.pathSeparator); 504 URL[] urls = new URL[components.length]; 505 int count = 0; 506 while(count < components.length) { 507 URL url = fileToURL(new File(components[count])); 508 if (url != null) { 509 urls[count++] = url; 510 } 511 } 512 if (urls.length != count) { 513 URL[] tmp = new URL[count]; 514 System.arraycopy(urls, 0, tmp, 0, count); 515 urls = tmp; 516 } 517 return urls; 518 } 519 520 /** 521 * Returns the directory or JAR file URL corresponding to the specified 522 * local file name. 523 * 524 * @param file the File object 525 * @return the resulting directory or JAR file URL, or null if unknown 526 */ 527 private static URL fileToURL(File file) { 528 String name; 529 try { 530 name = file.getCanonicalPath(); 531 } catch (IOException e) { 532 name = file.getAbsolutePath(); 533 } 534 name = name.replace(File.separatorChar, '/'); 535 if (!name.startsWith("/")) { 536 name = "/" + name; 537 } 538 // If the file does not exist, then assume that it's a directory 539 if (!file.isFile()) { 540 name = name + "/"; 541 } 542 try { 543 return new URL("file", "", name); 544 } catch (MalformedURLException e) { 545 throw new IllegalArgumentException("file"); 546 } 547 } 548 549 private static void setScriptArguments(ScriptEngine se, String[] args) { 550 se.put("arguments", args); 551 se.put(ScriptEngine.ARGV, args); 552 } 553 554 private static String setScriptFilename(ScriptEngine se, String name) { 555 String oldName = (String) se.get(ScriptEngine.FILENAME); 556 se.put(ScriptEngine.FILENAME, name); 557 return oldName; 558 } 559 560 // exit codes 561 private static final int EXIT_SUCCESS = 0; 562 private static final int EXIT_CMD_NO_CLASSPATH = 1; 563 private static final int EXIT_CMD_NO_FILE = 2; 564 private static final int EXIT_CMD_NO_SCRIPT = 3; 565 private static final int EXIT_CMD_NO_LANG = 4; 566 private static final int EXIT_CMD_NO_ENCODING = 5; 567 private static final int EXIT_CMD_NO_PROPNAME = 6; 568 private static final int EXIT_UNKNOWN_OPTION = 7; 569 private static final int EXIT_ENGINE_NOT_FOUND = 8; 570 private static final int EXIT_NO_ENCODING_FOUND = 9; 571 private static final int EXIT_SCRIPT_ERROR = 10; 572 private static final int EXIT_FILE_NOT_FOUND = 11; 573 private static final int EXIT_MULTIPLE_STDIN = 12; 574 575 // default scripting language 576 private static final String DEFAULT_LANGUAGE = "js"; 577 // list of scripts to process 578 private static List<Command> scripts; 579 // the script engine manager 580 private static ScriptEngineManager engineManager; 581 // map of engines we loaded 582 private static Map<String, ScriptEngine> engines; 583 // error messages resource 584 private static ResourceBundle msgRes; 585 private static String BUNDLE_NAME = "com.sun.tools.script.shell.messages"; 586 private static String PROGRAM_NAME = "jrunscript"; 587 588 static { 589 scripts = new ArrayList<Command>(); 590 engines = new HashMap<String, ScriptEngine>(); 591 msgRes = ResourceBundle.getBundle(BUNDLE_NAME, Locale.getDefault()); 592 } 593 }