jdk/src/jdk.dev/share/classes/com/sun/tools/script/shell/Main.java
changeset 27565 729f9700483a
parent 27564 eaaa79b68cd5
child 27566 fc4c57a0a8e2
equal deleted inserted replaced
27564:eaaa79b68cd5 27565:729f9700483a
     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 
       
    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("-?") || 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 }