001: /*
002: $Id: GroovyMain.java 4080 2006-09-26 20:36:00Z glaforge $
003:
004: Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005:
006: Redistribution and use of this software and associated documentation
007: ("Software"), with or without modification, are permitted provided
008: that the following conditions are met:
009:
010: 1. Redistributions of source code must retain copyright
011: statements and notices. Redistributions must also contain a
012: copy of this document.
013:
014: 2. Redistributions in binary form must reproduce the
015: above copyright notice, this list of conditions and the
016: following disclaimer in the documentation and/or other
017: materials provided with the distribution.
018:
019: 3. The name "groovy" must not be used to endorse or promote
020: products derived from this Software without prior written
021: permission of The Codehaus. For written permission,
022: please contact info@codehaus.org.
023:
024: 4. Products derived from this Software may not be called "groovy"
025: nor may "groovy" appear in their names without prior written
026: permission of The Codehaus. "groovy" is a registered
027: trademark of The Codehaus.
028:
029: 5. Due credit should be given to The Codehaus -
030: http://groovy.codehaus.org/
031:
032: THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033: ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034: NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
036: THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037: INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039: SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041: STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042: ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043: OF THE POSSIBILITY OF SUCH DAMAGE.
044:
045: */
046: package groovy.ui;
047:
048: import groovy.lang.GroovyShell;
049: import groovy.lang.MetaClass;
050: import groovy.lang.Script;
051:
052: import java.io.BufferedReader;
053: import java.io.File;
054: import java.io.FileInputStream;
055: import java.io.FileNotFoundException;
056: import java.io.FileReader;
057: import java.io.FileWriter;
058: import java.io.IOException;
059: import java.io.InputStreamReader;
060: import java.io.PrintWriter;
061: import java.util.Iterator;
062: import java.util.List;
063: import java.math.BigInteger;
064:
065: import org.apache.commons.cli.CommandLine;
066: import org.apache.commons.cli.CommandLineParser;
067: import org.apache.commons.cli.HelpFormatter;
068: import org.apache.commons.cli.OptionBuilder;
069: import org.apache.commons.cli.Options;
070: import org.apache.commons.cli.ParseException;
071: import org.apache.commons.cli.PosixParser;
072: import org.codehaus.groovy.control.CompilationFailedException;
073: import org.codehaus.groovy.control.CompilerConfiguration;
074: import org.codehaus.groovy.runtime.InvokerHelper;
075: import org.codehaus.groovy.runtime.InvokerInvocationException;
076:
077: /**
078: * A Command line to execute groovy.
079: *
080: * @author Jeremy Rayner
081: * @author Yuri Schimke
082: * @version $Revision: 4080 $
083: */
084: public class GroovyMain {
085: // arguments to the script
086: private List args;
087:
088: // is this a file on disk
089: private boolean isScriptFile;
090:
091: // filename or content of script
092: private String script;
093:
094: // process args as input files
095: private boolean processFiles;
096:
097: // edit input files in place
098: private boolean editFiles;
099:
100: // automatically output the result of each script
101: private boolean autoOutput;
102:
103: // automatically split each line using the splitpattern
104: private boolean autoSplit;
105:
106: // The pattern used to split the current line
107: private String splitPattern = " ";
108:
109: // process sockets
110: private boolean processSockets;
111:
112: // port to listen on when processing sockets
113: private int port;
114:
115: // backup input files with extension
116: private String backupExtension;
117:
118: // do you want full stack traces in script exceptions?
119: private boolean debug = false;
120:
121: // Compiler configuration, used to set the encodings of the scripts/classes
122: private CompilerConfiguration conf = new CompilerConfiguration();
123:
124: /**
125: * Main CLI interface.
126: *
127: * @param args all command line args.
128: */
129: public static void main(String args[]) {
130: MetaClass.setUseReflection(true);
131:
132: Options options = buildOptions();
133:
134: try {
135: CommandLine cmd = parseCommandLine(options, args);
136:
137: if (cmd.hasOption('h')) {
138: HelpFormatter formatter = new HelpFormatter();
139: formatter.printHelp("groovy", options);
140: } else if (cmd.hasOption('v')) {
141: String version = InvokerHelper.getVersion();
142: System.out.println("Groovy Version: " + version
143: + " JVM: "
144: + System.getProperty("java.vm.version"));
145: } else {
146: // If we fail, then exit with an error so scripting frameworks can catch it
147: if (!process(cmd)) {
148: System.exit(1);
149: }
150: }
151: } catch (ParseException pe) {
152: System.out.println("error: " + pe.getMessage());
153: HelpFormatter formatter = new HelpFormatter();
154: formatter.printHelp("groovy", options);
155: }
156: }
157:
158: /**
159: * Parse the command line.
160: *
161: * @param options the options parser.
162: * @param args the command line args.
163: * @return parsed command line.
164: * @throws ParseException if there was a problem.
165: */
166: private static CommandLine parseCommandLine(Options options,
167: String[] args) throws ParseException {
168: CommandLineParser parser = new PosixParser();
169: CommandLine cmd = parser.parse(options, args, true);
170: return cmd;
171: }
172:
173: /**
174: * Build the options parser. Has to be synchronized because of the way Options are constructed.
175: *
176: * @return an options parser.
177: */
178: private static synchronized Options buildOptions() {
179: Options options = new Options();
180:
181: options.addOption(OptionBuilder.hasArg(false).withDescription(
182: "usage information").withLongOpt("help").create('h'));
183: options.addOption(OptionBuilder.hasArg(false).withDescription(
184: "debug mode will print out full stack traces")
185: .withLongOpt("debug").create('d'));
186: options.addOption(OptionBuilder.hasArg(false).withDescription(
187: "display the Groovy and JVM versions").withLongOpt(
188: "version").create('v'));
189: options.addOption(OptionBuilder.withArgName("charset").hasArg()
190: .withDescription("specify the encoding of the files")
191: .withLongOpt("encoding").create('c'));
192: options.addOption(OptionBuilder.withArgName("script").hasArg()
193: .withDescription("specify a command line script")
194: .create('e'));
195: options
196: .addOption(OptionBuilder
197: .withArgName("extension")
198: .hasOptionalArg()
199: .withDescription(
200: "modify files in place, create backup if extension is given (e.g. \'.bak\')")
201: .create('i'));
202: options.addOption(OptionBuilder.hasArg(false).withDescription(
203: "process files line by line").create('n'));
204: options.addOption(OptionBuilder.hasArg(false).withDescription(
205: "process files line by line and print result").create(
206: 'p'));
207: options.addOption(OptionBuilder.withArgName("port")
208: .hasOptionalArg().withDescription(
209: "listen on a port and process inbound lines")
210: .create('l'));
211: options
212: .addOption(OptionBuilder
213: .withArgName("splitPattern")
214: .hasOptionalArg()
215: .withDescription(
216: "automatically split current line (defaults to '\\s'")
217: .withLongOpt("autosplit").create('a'));
218: return options;
219: }
220:
221: /**
222: * Process the users request.
223: *
224: * @param line the parsed command line.
225: * @throws ParseException if invalid options are chosen
226: */
227: private static boolean process(CommandLine line)
228: throws ParseException {
229: GroovyMain main = new GroovyMain();
230:
231: List args = line.getArgList();
232:
233: // add the ability to parse scripts with a specified encoding
234: if (line.hasOption('c')) {
235: main.conf
236: .setSourceEncoding(line.getOptionValue("encoding"));
237: }
238:
239: main.isScriptFile = !line.hasOption('e');
240: main.debug = line.hasOption('d');
241: main.conf.setDebug(main.debug);
242: main.processFiles = line.hasOption('p') || line.hasOption('n');
243: main.autoOutput = line.hasOption('p');
244: main.editFiles = line.hasOption('i');
245: if (main.editFiles) {
246: main.backupExtension = line.getOptionValue('i');
247: }
248: main.autoSplit = line.hasOption('a');
249: String sp = line.getOptionValue('a');
250: if (sp != null)
251: main.splitPattern = sp;
252:
253: if (main.isScriptFile) {
254: if (args.isEmpty())
255: throw new ParseException(
256: "neither -e or filename provided");
257:
258: main.script = (String) args.remove(0);
259: if (main.script.endsWith(".java"))
260: throw new ParseException(
261: "error: cannot compile file with .java extension: "
262: + main.script);
263: } else {
264: main.script = line.getOptionValue('e');
265: }
266:
267: main.processSockets = line.hasOption('l');
268: if (main.processSockets) {
269: String p = line.getOptionValue('l', "1960"); // default port to listen to
270: main.port = new Integer(p).intValue();
271: }
272: main.args = args;
273:
274: return main.run();
275: }
276:
277: /**
278: * Run the script.
279: */
280: private boolean run() {
281: try {
282: if (processSockets) {
283: processSockets();
284: } else if (processFiles) {
285: processFiles();
286: } else {
287: processOnce();
288: }
289: return true;
290: } catch (CompilationFailedException e) {
291: System.err.println(e);
292: return false;
293: } catch (Throwable e) {
294: if (e instanceof InvokerInvocationException) {
295: InvokerInvocationException iie = (InvokerInvocationException) e;
296: e = iie.getCause();
297: }
298: System.err.println("Caught: " + e);
299: if (debug) {
300: e.printStackTrace();
301: } else {
302: StackTraceElement[] stackTrace = e.getStackTrace();
303: for (int i = 0; i < stackTrace.length; i++) {
304: StackTraceElement element = stackTrace[i];
305: String fileName = element.getFileName();
306: if (fileName != null && !fileName.endsWith(".java")) {
307: System.err.println("\tat " + element);
308: }
309: }
310: }
311: return false;
312: }
313: }
314:
315: /**
316: * Process Sockets.
317: */
318: private void processSockets() throws CompilationFailedException,
319: IOException {
320: GroovyShell groovy = new GroovyShell(conf);
321: //check the script is currently valid before starting a server against the script
322: if (isScriptFile) {
323: groovy.parse(new FileInputStream(
324: huntForTheScriptFile(script)));
325: } else {
326: groovy.parse(script);
327: }
328: new GroovySocketServer(groovy, isScriptFile, script,
329: autoOutput, port);
330: }
331:
332: /**
333: * Hunt for the script file, doesn't bother if it is named precisely.
334: *
335: * Tries in this order:
336: * - actual supplied name
337: * - name.groovy
338: * - name.gvy
339: * - name.gy
340: * - name.gsh
341: */
342: public File huntForTheScriptFile(String scriptFileName) {
343: File scriptFile = new File(scriptFileName);
344: String[] standardExtensions = { ".groovy", ".gvy", ".gy",
345: ".gsh" };
346: int i = 0;
347: while (i < standardExtensions.length && !scriptFile.exists()) {
348: scriptFile = new File(scriptFileName
349: + standardExtensions[i]);
350: i++;
351: }
352: // if we still haven't found the file, point back to the originally specified filename
353: if (!scriptFile.exists()) {
354: scriptFile = new File(scriptFileName);
355: }
356: return scriptFile;
357: }
358:
359: /**
360: * Process the input files.
361: */
362: private void processFiles() throws CompilationFailedException,
363: IOException {
364: GroovyShell groovy = new GroovyShell(conf);
365:
366: Script s = null;
367:
368: if (isScriptFile) {
369: s = groovy.parse(huntForTheScriptFile(script));
370: } else {
371: s = groovy.parse(script, "main");
372: }
373:
374: if (args.isEmpty()) {
375: BufferedReader reader = new BufferedReader(
376: new InputStreamReader(System.in));
377: PrintWriter writer = new PrintWriter(System.out);
378:
379: try {
380: processReader(s, reader, writer);
381: } finally {
382: reader.close();
383: writer.close();
384: }
385:
386: } else {
387: Iterator i = args.iterator();
388: while (i.hasNext()) {
389: String filename = (String) i.next();
390: File file = huntForTheScriptFile(filename);
391: processFile(s, file);
392: }
393: }
394: }
395:
396: /**
397: * Process a single input file.
398: *
399: * @param s the script to execute.
400: * @param file the input file.
401: */
402: private void processFile(Script s, File file) throws IOException {
403: if (!file.exists())
404: throw new FileNotFoundException(file.getName());
405:
406: if (!editFiles) {
407: BufferedReader reader = new BufferedReader(new FileReader(
408: file));
409: try {
410: PrintWriter writer = new PrintWriter(System.out);
411: processReader(s, reader, writer);
412: writer.flush();
413: } finally {
414: reader.close();
415: }
416: } else {
417: File backup = null;
418: if (backupExtension == null) {
419: backup = File.createTempFile("groovy_", ".tmp");
420: backup.deleteOnExit();
421: } else {
422: backup = new File(file.getPath() + backupExtension);
423: }
424: backup.delete();
425: if (!file.renameTo(backup))
426: throw new IOException("unable to rename " + file
427: + " to " + backup);
428:
429: BufferedReader reader = new BufferedReader(new FileReader(
430: backup));
431: try {
432: PrintWriter writer = new PrintWriter(new FileWriter(
433: file));
434: try {
435: processReader(s, reader, writer);
436: } finally {
437: writer.close();
438: }
439: } finally {
440: reader.close();
441: }
442: }
443: }
444:
445: /**
446: * Process a script against a single input file.
447: *
448: * @param s script to execute.
449: * @param reader input file.
450: * @param pw output sink.
451: */
452: private void processReader(Script s, BufferedReader reader,
453: PrintWriter pw) throws IOException {
454: String line = null;
455: String lineCountName = "count";
456: s.setProperty(lineCountName, BigInteger.ZERO);
457: String autoSplitName = "split";
458: s.setProperty("out", pw);
459: while ((line = reader.readLine()) != null) {
460: s.setProperty("line", line);
461: s.setProperty(lineCountName, ((BigInteger) s
462: .getProperty(lineCountName)).add(BigInteger.ONE));
463: if (autoSplit)
464: s.setProperty(autoSplitName, line.split(splitPattern));
465: Object o = s.run();
466:
467: if (autoOutput) {
468: pw.println(o);
469: }
470: }
471: }
472:
473: private static ClassLoader getLoader(ClassLoader cl) {
474: if (cl != null)
475: return cl;
476: cl = Thread.currentThread().getContextClassLoader();
477: if (cl != null)
478: return cl;
479: cl = GroovyMain.class.getClassLoader();
480: if (cl != null)
481: return cl;
482: return null;
483: }
484:
485: /**
486: * Process the standard, single script with args.
487: */
488: private void processOnce() throws CompilationFailedException,
489: IOException {
490: GroovyShell groovy = new GroovyShell(conf);
491:
492: if (isScriptFile)
493: groovy.run(huntForTheScriptFile(script), args);
494: else
495: groovy.run(script, "script_from_command_line", args);
496: }
497: }
|