blob: 843470910406041031ba5790e1281ba6621e6843 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2005 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any 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 ClassLoader parent = Main.class.getClassLoader();
478 URL[] urls = pathToURLs(classPath);
479 URLClassLoader loader = new URLClassLoader(urls, parent);
480 Thread.currentThread().setContextClassLoader(loader);
481 }
482
483 // now initialize script engine manager. Note that this has to
484 // be done after setting the context loader so that manager
485 // will see script engines from user specified classpath
486 engineManager = new ScriptEngineManager();
487 }
488
489 /**
490 * Utility method for converting a search path string to an array
491 * of directory and JAR file URLs.
492 *
493 * @param path the search path string
494 * @return the resulting array of directory and JAR file URLs
495 */
496 private static URL[] pathToURLs(String path) {
497 String[] components = path.split(File.pathSeparator);
498 URL[] urls = new URL[components.length];
499 int count = 0;
500 while(count < components.length) {
501 URL url = fileToURL(new File(components[count]));
502 if (url != null) {
503 urls[count++] = url;
504 }
505 }
506 if (urls.length != count) {
507 URL[] tmp = new URL[count];
508 System.arraycopy(urls, 0, tmp, 0, count);
509 urls = tmp;
510 }
511 return urls;
512 }
513
514 /**
515 * Returns the directory or JAR file URL corresponding to the specified
516 * local file name.
517 *
518 * @param file the File object
519 * @return the resulting directory or JAR file URL, or null if unknown
520 */
521 private static URL fileToURL(File file) {
522 String name;
523 try {
524 name = file.getCanonicalPath();
525 } catch (IOException e) {
526 name = file.getAbsolutePath();
527 }
528 name = name.replace(File.separatorChar, '/');
529 if (!name.startsWith("/")) {
530 name = "/" + name;
531 }
532 // If the file does not exist, then assume that it's a directory
533 if (!file.isFile()) {
534 name = name + "/";
535 }
536 try {
537 return new URL("file", "", name);
538 } catch (MalformedURLException e) {
539 throw new IllegalArgumentException("file");
540 }
541 }
542
543 private static void setScriptArguments(ScriptEngine se, String[] args) {
544 se.put("arguments", args);
545 se.put(ScriptEngine.ARGV, args);
546 }
547
548 private static String setScriptFilename(ScriptEngine se, String name) {
549 String oldName = (String) se.get(ScriptEngine.FILENAME);
550 se.put(ScriptEngine.FILENAME, name);
551 return oldName;
552 }
553
554 // exit codes
555 private static final int EXIT_SUCCESS = 0;
556 private static final int EXIT_CMD_NO_CLASSPATH = 1;
557 private static final int EXIT_CMD_NO_FILE = 2;
558 private static final int EXIT_CMD_NO_SCRIPT = 3;
559 private static final int EXIT_CMD_NO_LANG = 4;
560 private static final int EXIT_CMD_NO_ENCODING = 5;
561 private static final int EXIT_CMD_NO_PROPNAME = 6;
562 private static final int EXIT_UNKNOWN_OPTION = 7;
563 private static final int EXIT_ENGINE_NOT_FOUND = 8;
564 private static final int EXIT_NO_ENCODING_FOUND = 9;
565 private static final int EXIT_SCRIPT_ERROR = 10;
566 private static final int EXIT_FILE_NOT_FOUND = 11;
567 private static final int EXIT_MULTIPLE_STDIN = 12;
568
569 // default scripting language
570 private static final String DEFAULT_LANGUAGE = "js";
571 // list of scripts to process
572 private static List<Command> scripts;
573 // the script engine manager
574 private static ScriptEngineManager engineManager;
575 // map of engines we loaded
576 private static Map<String, ScriptEngine> engines;
577 // error messages resource
578 private static ResourceBundle msgRes;
579 private static String BUNDLE_NAME = "com.sun.tools.script.shell.messages";
580 private static String PROGRAM_NAME = "jrunscript";
581
582 static {
583 scripts = new ArrayList<Command>();
584 engines = new HashMap<String, ScriptEngine>();
585 msgRes = ResourceBundle.getBundle(BUNDLE_NAME, Locale.getDefault());
586 }
587}