Main.java revision 10967:e336cbd8b15e
1/* 2 * Copyright (c) 2005, 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 26package com.sun.tools.script.shell; 27 28import java.io.*; 29import java.net.*; 30import java.text.*; 31import java.util.*; 32import javax.script.*; 33 34/** 35 * This is the main class for Java script shell. 36 */ 37public 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("-?") || arg.equals("-help")) { 126 usage(EXIT_SUCCESS); 127 } else if (arg.equals("-e")) { 128 seenScript = true; 129 if (++i == args.length) 130 usage(EXIT_CMD_NO_SCRIPT); 131 132 ScriptEngine se = getScriptEngine(currentLanguage); 133 addStringSource(se, args[i]); 134 continue; 135 } else if (arg.equals("-encoding")) { 136 if (++i == args.length) 137 usage(EXIT_CMD_NO_ENCODING); 138 currentEncoding = args[i]; 139 continue; 140 } else if (arg.equals("-f")) { 141 seenScript = true; 142 if (++i == args.length) 143 usage(EXIT_CMD_NO_FILE); 144 ScriptEngine se = getScriptEngine(currentLanguage); 145 if (args[i].equals("-")) { 146 if (seenStdin) { 147 usage(EXIT_MULTIPLE_STDIN); 148 } else { 149 seenStdin = true; 150 } 151 addInteractiveMode(se); 152 } else { 153 addFileSource(se, args[i], currentEncoding); 154 } 155 continue; 156 } else if (arg.equals("-l")) { 157 if (++i == args.length) 158 usage(EXIT_CMD_NO_LANG); 159 currentLanguage = args[i]; 160 continue; 161 } else if (arg.equals("-q")) { 162 listScriptEngines(); 163 } 164 // some unknown option... 165 usage(EXIT_UNKNOWN_OPTION); 166 } 167 168 if (! seenScript) { 169 ScriptEngine se = getScriptEngine(currentLanguage); 170 addInteractiveMode(se); 171 } 172 return new String[0]; 173 } 174 175 /** 176 * Adds interactive mode Command 177 * @param se ScriptEngine to use in interactive mode. 178 */ 179 private static void addInteractiveMode(final ScriptEngine se) { 180 scripts.add(new Command() { 181 public void run(String[] args) { 182 setScriptArguments(se, args); 183 processSource(se, "-", null); 184 } 185 }); 186 } 187 188 /** 189 * Adds script source file Command 190 * @param se ScriptEngine used to evaluate the script file 191 * @param fileName script file name 192 * @param encoding script file encoding 193 */ 194 private static void addFileSource(final ScriptEngine se, 195 final String fileName, 196 final String encoding) { 197 scripts.add(new Command() { 198 public void run(String[] args) { 199 setScriptArguments(se, args); 200 processSource(se, fileName, encoding); 201 } 202 }); 203 } 204 205 /** 206 * Adds script string source Command 207 * @param se ScriptEngine to be used to evaluate the script string 208 * @param source Script source string 209 */ 210 private static void addStringSource(final ScriptEngine se, 211 final String source) { 212 scripts.add(new Command() { 213 public void run(String[] args) { 214 setScriptArguments(se, args); 215 String oldFile = setScriptFilename(se, "<string>"); 216 try { 217 evaluateString(se, source); 218 } finally { 219 setScriptFilename(se, oldFile); 220 } 221 } 222 }); 223 } 224 225 /** 226 * Prints list of script engines available and exits. 227 */ 228 private static void listScriptEngines() { 229 List<ScriptEngineFactory> factories = engineManager.getEngineFactories(); 230 for (ScriptEngineFactory factory: factories) { 231 getError().println(getMessage("engine.info", 232 new Object[] { factory.getLanguageName(), 233 factory.getLanguageVersion(), 234 factory.getEngineName(), 235 factory.getEngineVersion() 236 })); 237 } 238 System.exit(EXIT_SUCCESS); 239 } 240 241 /** 242 * Processes a given source file or standard input. 243 * @param se ScriptEngine to be used to evaluate 244 * @param filename file name, can be null 245 * @param encoding script file encoding, can be null 246 */ 247 private static void processSource(ScriptEngine se, String filename, 248 String encoding) { 249 if (filename.equals("-")) { 250 BufferedReader in = new BufferedReader 251 (new InputStreamReader(getIn())); 252 boolean hitEOF = false; 253 String prompt = getPrompt(se); 254 se.put(ScriptEngine.FILENAME, "<STDIN>"); 255 while (!hitEOF) { 256 getError().print(prompt); 257 String source = ""; 258 try { 259 source = in.readLine(); 260 } catch (IOException ioe) { 261 getError().println(ioe.toString()); 262 } 263 if (source == null) { 264 hitEOF = true; 265 break; 266 } 267 Object res = evaluateString(se, source, false); 268 if (res != null) { 269 res = res.toString(); 270 if (res == null) { 271 res = "null"; 272 } 273 getError().println(res); 274 } 275 } 276 } else { 277 FileInputStream fis = null; 278 try { 279 fis = new FileInputStream(filename); 280 } catch (FileNotFoundException fnfe) { 281 getError().println(getMessage("file.not.found", 282 new Object[] { filename })); 283 System.exit(EXIT_FILE_NOT_FOUND); 284 } 285 evaluateStream(se, fis, filename, encoding); 286 } 287 } 288 289 /** 290 * Evaluates given script source 291 * @param se ScriptEngine to evaluate the string 292 * @param script Script source string 293 * @param exitOnError whether to exit the process on script error 294 */ 295 private static Object evaluateString(ScriptEngine se, 296 String script, boolean exitOnError) { 297 try { 298 return se.eval(script); 299 } catch (ScriptException sexp) { 300 getError().println(getMessage("string.script.error", 301 new Object[] { sexp.getMessage() })); 302 if (exitOnError) 303 System.exit(EXIT_SCRIPT_ERROR); 304 } catch (Exception exp) { 305 exp.printStackTrace(getError()); 306 if (exitOnError) 307 System.exit(EXIT_SCRIPT_ERROR); 308 } 309 310 return null; 311 } 312 313 /** 314 * Evaluate script string source and exit on script error 315 * @param se ScriptEngine to evaluate the string 316 * @param script Script source string 317 */ 318 private static void evaluateString(ScriptEngine se, String script) { 319 evaluateString(se, script, true); 320 } 321 322 /** 323 * Evaluates script from given reader 324 * @param se ScriptEngine to evaluate the string 325 * @param reader Reader from which is script is read 326 * @param name file name to report in error. 327 */ 328 private static Object evaluateReader(ScriptEngine se, 329 Reader reader, String name) { 330 String oldFilename = setScriptFilename(se, name); 331 try { 332 return se.eval(reader); 333 } catch (ScriptException sexp) { 334 getError().println(getMessage("file.script.error", 335 new Object[] { name, sexp.getMessage() })); 336 System.exit(EXIT_SCRIPT_ERROR); 337 } catch (Exception exp) { 338 exp.printStackTrace(getError()); 339 System.exit(EXIT_SCRIPT_ERROR); 340 } finally { 341 setScriptFilename(se, oldFilename); 342 } 343 return null; 344 } 345 346 /** 347 * Evaluates given input stream 348 * @param se ScriptEngine to evaluate the string 349 * @param is InputStream from which script is read 350 * @param name file name to report in error 351 */ 352 private static Object evaluateStream(ScriptEngine se, 353 InputStream is, String name, 354 String encoding) { 355 BufferedReader reader = null; 356 if (encoding != null) { 357 try { 358 reader = new BufferedReader(new InputStreamReader(is, 359 encoding)); 360 } catch (UnsupportedEncodingException uee) { 361 getError().println(getMessage("encoding.unsupported", 362 new Object[] { encoding })); 363 System.exit(EXIT_NO_ENCODING_FOUND); 364 } 365 } else { 366 reader = new BufferedReader(new InputStreamReader(is)); 367 } 368 return evaluateReader(se, reader, name); 369 } 370 371 /** 372 * Prints usage message and exits 373 * @param exitCode process exit code 374 */ 375 private static void usage(int exitCode) { 376 getError().println(getMessage("main.usage", 377 new Object[] { PROGRAM_NAME })); 378 System.exit(exitCode); 379 } 380 381 /** 382 * Gets prompt for interactive mode 383 * @return prompt string to use 384 */ 385 private static String getPrompt(ScriptEngine se) { 386 List<String> names = se.getFactory().getNames(); 387 return names.get(0) + "> "; 388 } 389 390 /** 391 * Get formatted, localized error message 392 */ 393 private static String getMessage(String key, Object[] params) { 394 return MessageFormat.format(msgRes.getString(key), params); 395 } 396 397 // input stream from where we will read 398 private static InputStream getIn() { 399 return System.in; 400 } 401 402 // stream to print error messages 403 private static PrintStream getError() { 404 return System.err; 405 } 406 407 // get current script engine 408 private static ScriptEngine getScriptEngine(String lang) { 409 ScriptEngine se = engines.get(lang); 410 if (se == null) { 411 se = engineManager.getEngineByName(lang); 412 if (se == null) { 413 getError().println(getMessage("engine.not.found", 414 new Object[] { lang })); 415 System.exit(EXIT_ENGINE_NOT_FOUND); 416 } 417 418 // initialize the engine 419 initScriptEngine(se); 420 // to avoid re-initialization of engine, store it in a map 421 engines.put(lang, se); 422 } 423 return se; 424 } 425 426 // initialize a given script engine 427 private static void initScriptEngine(ScriptEngine se) { 428 // put engine global variable 429 se.put("engine", se); 430 431 // load init.<ext> file from resource 432 List<String> exts = se.getFactory().getExtensions(); 433 InputStream sysIn = null; 434 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 435 for (String ext : exts) { 436 sysIn = cl.getResourceAsStream("com/sun/tools/script/shell/init." + 437 ext); 438 if (sysIn != null) break; 439 } 440 if (sysIn != null) { 441 evaluateStream(se, sysIn, "<system-init>", null); 442 } 443 } 444 445 /** 446 * Checks for -classpath, -cp in command line args. Creates a ClassLoader 447 * and sets it as Thread context loader for current thread. 448 * 449 * @param args command line argument array 450 */ 451 private static void checkClassPath(String[] args) { 452 String classPath = null; 453 for (int i = 0; i < args.length; i++) { 454 if (args[i].equals("-classpath") || 455 args[i].equals("-cp")) { 456 if (++i == args.length) { 457 // just -classpath or -cp with no value 458 usage(EXIT_CMD_NO_CLASSPATH); 459 } else { 460 classPath = args[i]; 461 } 462 } 463 } 464 465 if (classPath != null) { 466 /* We create a class loader, configure it with specified 467 * classpath values and set the same as context loader. 468 * Note that ScriptEngineManager uses context loader to 469 * load script engines. So, this ensures that user defined 470 * script engines will be loaded. For classes referred 471 * from scripts, Rhino engine uses thread context loader 472 * but this is script engine dependent. We don't have 473 * script engine independent solution anyway. Unless we 474 * know the class loader used by a specific engine, we 475 * can't configure correct loader. 476 */ 477 URL[] urls = pathToURLs(classPath); 478 URLClassLoader loader = new URLClassLoader(urls); 479 Thread.currentThread().setContextClassLoader(loader); 480 } 481 482 // now initialize script engine manager. Note that this has to 483 // be done after setting the context loader so that manager 484 // will see script engines from user specified classpath 485 engineManager = new ScriptEngineManager(); 486 } 487 488 /** 489 * Utility method for converting a search path string to an array 490 * of directory and JAR file URLs. 491 * 492 * @param path the search path string 493 * @return the resulting array of directory and JAR file URLs 494 */ 495 private static URL[] pathToURLs(String path) { 496 String[] components = path.split(File.pathSeparator); 497 URL[] urls = new URL[components.length]; 498 int count = 0; 499 while(count < components.length) { 500 URL url = fileToURL(new File(components[count])); 501 if (url != null) { 502 urls[count++] = url; 503 } 504 } 505 if (urls.length != count) { 506 URL[] tmp = new URL[count]; 507 System.arraycopy(urls, 0, tmp, 0, count); 508 urls = tmp; 509 } 510 return urls; 511 } 512 513 /** 514 * Returns the directory or JAR file URL corresponding to the specified 515 * local file name. 516 * 517 * @param file the File object 518 * @return the resulting directory or JAR file URL, or null if unknown 519 */ 520 private static URL fileToURL(File file) { 521 String name; 522 try { 523 name = file.getCanonicalPath(); 524 } catch (IOException e) { 525 name = file.getAbsolutePath(); 526 } 527 name = name.replace(File.separatorChar, '/'); 528 if (!name.startsWith("/")) { 529 name = "/" + name; 530 } 531 // If the file does not exist, then assume that it's a directory 532 if (!file.isFile()) { 533 name = name + "/"; 534 } 535 try { 536 return new URL("file", "", name); 537 } catch (MalformedURLException e) { 538 throw new IllegalArgumentException("file"); 539 } 540 } 541 542 private static void setScriptArguments(ScriptEngine se, String[] args) { 543 se.put("arguments", args); 544 se.put(ScriptEngine.ARGV, args); 545 } 546 547 private static String setScriptFilename(ScriptEngine se, String name) { 548 String oldName = (String) se.get(ScriptEngine.FILENAME); 549 se.put(ScriptEngine.FILENAME, name); 550 return oldName; 551 } 552 553 // exit codes 554 private static final int EXIT_SUCCESS = 0; 555 private static final int EXIT_CMD_NO_CLASSPATH = 1; 556 private static final int EXIT_CMD_NO_FILE = 2; 557 private static final int EXIT_CMD_NO_SCRIPT = 3; 558 private static final int EXIT_CMD_NO_LANG = 4; 559 private static final int EXIT_CMD_NO_ENCODING = 5; 560 private static final int EXIT_CMD_NO_PROPNAME = 6; 561 private static final int EXIT_UNKNOWN_OPTION = 7; 562 private static final int EXIT_ENGINE_NOT_FOUND = 8; 563 private static final int EXIT_NO_ENCODING_FOUND = 9; 564 private static final int EXIT_SCRIPT_ERROR = 10; 565 private static final int EXIT_FILE_NOT_FOUND = 11; 566 private static final int EXIT_MULTIPLE_STDIN = 12; 567 568 // default scripting language 569 private static final String DEFAULT_LANGUAGE = "js"; 570 // list of scripts to process 571 private static List<Command> scripts; 572 // the script engine manager 573 private static ScriptEngineManager engineManager; 574 // map of engines we loaded 575 private static Map<String, ScriptEngine> engines; 576 // error messages resource 577 private static ResourceBundle msgRes; 578 private static String BUNDLE_NAME = "com.sun.tools.script.shell.messages"; 579 private static String PROGRAM_NAME = "jrunscript"; 580 581 static { 582 scripts = new ArrayList<Command>(); 583 engines = new HashMap<String, ScriptEngine>(); 584 msgRes = ResourceBundle.getBundle(BUNDLE_NAME, Locale.getDefault()); 585 } 586} 587