/* * Copyright 2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package org.visage.tools.script; import java.io.*; import java.net.*; import java.text.*; import java.util.*; import javax.tools.*; /** * This is the main class for Visage shell. */ public class ScriptShell implements DiagnosticListener<JavaFileObject> { VisageScriptContext context; /** * main entry point to the command line tool * @param args command line argument array */ public static void main(String[] args) { ScriptShell shell = new ScriptShell(Thread.currentThread().getContextClassLoader()); shell.initScriptEngine(); /// parse command line options String[] scriptArgs = shell.processOptions(args); // process each script command for (Command cmd : shell.scripts) { cmd.run(scriptArgs); } shell.close(); } public ScriptShell(ClassLoader parentClassLoader) { context = new VisageScriptContext(parentClassLoader); } // Each -e or -f or interactive mode is represented // by an instance of Command. private static interface Command { public void run(String[] arguments); } /** * Parses and processes command line options. * @param args command line argument array */ protected String[] processOptions(String[] args) { // current script file encoding selected String currentEncoding = null; // check for -classpath or -cp first checkClassPath(args); // have we seen -e or -f ? boolean seenScript = false; // have we seen -f - already? boolean seenStdin = false; for (int i=0; i < args.length; i++) { String arg = args[i]; if (arg.equals("-classpath") || arg.equals("-cp")) { // handled already, just continue i++; continue; } // collect non-option arguments and pass these as script arguments if (!arg.startsWith("-")) { int numScriptArgs; int startScriptArg; if (seenScript) { // if we have seen -e or -f already all non-option arguments // are passed as script arguments numScriptArgs = args.length - i; startScriptArg = i; } else { // if we have not seen -e or -f, first non-option argument // is treated as script file name and rest of the non-option // arguments are passed to script as script arguments numScriptArgs = args.length - i - 1; startScriptArg = i + 1; addFileSource(args[i], currentEncoding); } // collect script arguments and return to main String[] result = new String[numScriptArgs]; System.arraycopy(args, startScriptArg, result, 0, numScriptArgs); return result; } if (arg.startsWith("-D")) { String value = arg.substring(2); int eq = value.indexOf('='); if (eq != -1) { System.setProperty(value.substring(0, eq), value.substring(eq + 1)); } else { if (!value.equals("")) { System.setProperty(value, ""); } else { // do not allow empty property name usage(EXIT_CMD_NO_PROPNAME); } } continue; } else if (arg.equals("-?") || arg.equals("-help")) { usage(EXIT_SUCCESS); } else if (arg.equals("-e")) { seenScript = true; if (++i == args.length) usage(EXIT_CMD_NO_SCRIPT); addStringSource(args[i]); continue; } else if (arg.equals("-encoding")) { if (++i == args.length) usage(EXIT_CMD_NO_ENCODING); currentEncoding = args[i]; continue; } else if (arg.equals("-f")) { seenScript = true; if (++i == args.length) usage(EXIT_CMD_NO_FILE); if (args[i].equals("-")) { if (seenStdin) { usage(EXIT_MULTIPLE_STDIN); } else { seenStdin = true; } addInteractiveMode(); } else { addFileSource(args[i], currentEncoding); } continue; } // some unknown option... usage(EXIT_UNKNOWN_OPTION); } if (! seenScript) { addInteractiveMode(); } return new String[0]; } /** * Adds interactive mode Command * @param se ScriptEngine to use in interactive mode. */ protected void addInteractiveMode() { scripts.add(new Command() { public void run(String[] args) { setScriptArguments(args); processSource("-", null); } }); } /** * Adds script source file Command * @param se ScriptEngine used to evaluate the script file * @param fileName script file name * @param encoding script file encoding */ protected void addFileSource(final String fileName, final String encoding) { scripts.add(new Command() { public void run(String[] args) { setScriptArguments(args); processSource(fileName, encoding); } }); } /** * Adds script string source Command * @param se ScriptEngine to be used to evaluate the script string * @param source Script source string */ private void addStringSource(final String source) { scripts.add(new Command() { public void run(String[] args) { setScriptArguments(args); String oldFile = setScriptFilename("<string>"); try { evaluateString(source); } finally { setScriptFilename(oldFile); } } }); } /** * Processes a given source file or standard input. * @param se ScriptEngine to be used to evaluate * @param filename file name, can be null * @param encoding script file encoding, can be null */ protected void processSource(String filename, String encoding) { if (filename.equals("-")) { BufferedReader in = getReader(); boolean hitEOF = false; while (!hitEOF) { ++counter; String prompt = getPrompt(); getError().print(prompt); String source = ""; try { source = in.readLine(); } catch (IOException ioe) { getError().println(ioe.toString()); } if (source == null) { hitEOF = true; break; } evaluateString(source, false); } } else { FileInputStream fis = null; try { fis = new FileInputStream(filename); } catch (FileNotFoundException fnfe) { getError().println(getMessage("file.not.found", new Object[] { filename })); System.exit(EXIT_FILE_NOT_FOUND); } evaluateStream(fis, filename, encoding); } } public int counter; DiagnosticListener<JavaFileObject> diagnosticListener; public void report(Diagnostic<? extends JavaFileObject> diagnostic) { System.err.println(formatDiagnostic(diagnostic)); } protected String formatDiagnostic(Diagnostic<? extends JavaFileObject> diagnostic) { return "visage"+counter+":"+diagnostic.getLineNumber()+": "+diagnostic.getMessage(null); } public void setDiagnosticListener(DiagnosticListener<JavaFileObject> diagnosticListener) { this.diagnosticListener = diagnosticListener; } protected DiagnosticListener<JavaFileObject> getDiagnosticListener() { if (diagnosticListener == null) diagnosticListener = this; return diagnosticListener; } protected void reportException (Throwable ex) { // FIXME we should filter out StackElementElement entries from // this clas, and from the scripting framework, but it's non-trivial, // since we also have to deal with getCause(). ex.printStackTrace(); } protected void reportResult (Object value) { if (value != null) { String str = value.toString(); if (str == null) str = "null"; getError().println(str); } } /** * Evaluates given script source * @param se ScriptEngine to evaluate the string * @param script Script source string * @param exitOnError whether to exit the process on script error */ private void evaluateString(String script, boolean exitOnError) { String sourcePath = null; // FIXME String classPath = System.getProperty("java.class.path"); // FIXME Writer err = null; // FIXME String fileName = "visage" + counter; VisageCompiledScript compiled = context.compiler.compile(fileName, script, err, sourcePath, classPath, getDiagnosticListener()); if (compiled == null) return; evaluate(compiled); } /** * Evaluate script string source and exit on script error * @param se ScriptEngine to evaluate the string * @param script Script source string */ protected void evaluateString(String script) { evaluateString(script, true); } /** * Evaluates script from given reader * @param se ScriptEngine to evaluate the string * @param reader Reader from which is script is read * @param name file name to report in error. */ protected void evaluateReader(Reader reader, String fileName) { String sourcePath = null; // FIXME String classPath = System.getProperty("java.class.path"); // FIXME Writer err = null; String script; try { script = VisageScriptCompiler.readFully(reader); } catch (java.io.IOException ex) { reportException(ex); return; } VisageCompiledScript compiled = context.compiler.compile(fileName, script, err, sourcePath, classPath, getDiagnosticListener()); if (compiled == null) return; evaluate(compiled); } protected void evaluate (VisageCompiledScript compiled) { try { reportResult(compiled.eval(context)); } catch (Throwable ex) { reportException(ex); } } /** * Evaluates given input stream * @param is InputStream from which script is read * @param name file name to report in error */ protected void evaluateStream(InputStream is, String name, String encoding) { BufferedReader reader = null; if (encoding != null) { try { reader = new BufferedReader(new InputStreamReader(is, encoding)); } catch (UnsupportedEncodingException uee) { getError().println(getMessage("encoding.unsupported", new Object[] { encoding })); System.exit(EXIT_NO_ENCODING_FOUND); } } else { reader = new BufferedReader(new InputStreamReader(is)); } evaluateReader(reader, name); } /** * Prints usage message and exits * @param exitCode process exit code */ private void usage(int exitCode) { getError().println(getMessage("main.usage", new Object[] { PROGRAM_NAME })); System.exit(exitCode); } /** * Gets prompt for interactive mode * @return prompt string to use */ private String getPrompt() { return "/*visage"+counter+"*/ "; } /** * Get formatted, localized error message */ private static String getMessage(String key, Object[] params) { return MessageFormat.format(key, params); } protected BufferedReader shellReader; protected BufferedReader getReader() { if (shellReader == null) shellReader = new BufferedReader (new InputStreamReader(System.in)); return shellReader; } public void close() { if (shellReader == null) { try { shellReader.close(); } catch (Throwable ex) { // do nothing } } shellReader = null; } // stream to print error messages protected PrintStream getError() { return System.err; } // initialize a given script engine // FIXME - unused private void initScriptEngine() { // load init.visage file from resource ClassLoader cl = Thread.currentThread().getContextClassLoader(); InputStream sysIn = cl.getResourceAsStream("org/visage/tools/script/shell/init.visage"); if (sysIn != null) { evaluateStream(sysIn, "<system-init>", null); } } /** * Checks for -classpath, -cp in command line args. Creates a ClassLoader * and sets it as Thread context loader for current thread. * * @param args command line argument array */ private void checkClassPath(String[] args) { String classPath = null; for (int i = 0; i < args.length; i++) { if (args[i].equals("-classpath") || args[i].equals("-cp")) { if (++i == args.length) { // just -classpath or -cp with no value usage(EXIT_CMD_NO_CLASSPATH); } else { classPath = args[i]; } } } if (classPath != null) { /* We create a class loader, configure it with specified * classpath values and set the same as context loader. * Note that ScriptEngineManager uses context loader to * load script engines. So, this ensures that user defined * script engines will be loaded. For classes referred * from scripts, Rhino engine uses thread context loader * but this is script engine dependent. We don't have * script engine independent solution anyway. Unless we * know the class loader used by a specific engine, we * can't configure correct loader. */ ClassLoader parent = ScriptShell.class.getClassLoader(); URL[] urls = pathToURLs(classPath); URLClassLoader loader = new URLClassLoader(urls, parent); Thread.currentThread().setContextClassLoader(loader); } } /** * Utility method for converting a search path string to an array * of directory and JAR file URLs. * * @param path the search path string * @return the resulting array of directory and JAR file URLs */ private static URL[] pathToURLs(String path) { String[] components = path.split(File.pathSeparator); URL[] urls = new URL[components.length]; int count = 0; while(count < components.length) { URL url = fileToURL(new File(components[count])); if (url != null) { urls[count++] = url; } } if (urls.length != count) { URL[] tmp = new URL[count]; System.arraycopy(urls, 0, tmp, 0, count); urls = tmp; } return urls; } /** * Returns the directory or JAR file URL corresponding to the specified * local file name. * * @param file the File object * @return the resulting directory or JAR file URL, or null if unknown */ private static URL fileToURL(File file) { String name; try { name = file.getCanonicalPath(); } catch (IOException e) { name = file.getAbsolutePath(); } name = name.replace(File.separatorChar, '/'); if (!name.startsWith("/")) { name = "/" + name; } // If the file does not exist, then assume that it's a directory if (!file.isFile()) { name = name + "/"; } try { return new URL("file", "", name); } catch (MalformedURLException e) { throw new IllegalArgumentException("file"); } } protected void setScriptArguments(String[] args) { /* FIXME put("arguments", args); put(ScriptEngine.ARGV, args); */ } protected String setScriptFilename(String name) { return null; /* FIXME String oldName = (String) se.get(ScriptEngine.FILENAME); se.put(ScriptEngine.FILENAME, name); return oldName; */ } // exit codes private static final int EXIT_SUCCESS = 0; private static final int EXIT_CMD_NO_CLASSPATH = 1; private static final int EXIT_CMD_NO_FILE = 2; private static final int EXIT_CMD_NO_SCRIPT = 3; private static final int EXIT_CMD_NO_LANG = 4; private static final int EXIT_CMD_NO_ENCODING = 5; private static final int EXIT_CMD_NO_PROPNAME = 6; private static final int EXIT_UNKNOWN_OPTION = 7; private static final int EXIT_ENGINE_NOT_FOUND = 8; private static final int EXIT_NO_ENCODING_FOUND = 9; private static final int EXIT_SCRIPT_ERROR = 10; private static final int EXIT_FILE_NOT_FOUND = 11; private static final int EXIT_MULTIPLE_STDIN = 12; // list of scripts to process private List<Command> scripts = new ArrayList<Command>(); // error messages resource private static ResourceBundle msgRes; private static String BUNDLE_NAME = "org.visage.tools.script.shell.messages"; private static String PROGRAM_NAME = "visagerunscript"; static { // msgRes = ResourceBundle.getBundle(BUNDLE_NAME, Locale.getDefault()); } }