View Javadoc

1   /*
2    $Id: GroovyMain.java,v 1.22 2005/06/14 21:15:20 blackdrag Exp $
3   
4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11   statements and notices.  Redistributions must also contain a
12   copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15   above copyright notice, this list of conditions and the
16   following disclaimer in the documentation and/or other
17   materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20   products derived from this Software without prior written
21   permission of The Codehaus.  For written permission,
22   please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25   nor may "groovy" appear in their names without prior written
26   permission of The Codehaus. "groovy" is a registered
27   trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30   http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
44  
45   */
46  package groovy.ui;
47  
48  import groovy.lang.GroovyShell;
49  import groovy.lang.MetaClass;
50  import groovy.lang.Script;
51  
52  import java.io.BufferedReader;
53  import java.io.File;
54  import java.io.FileInputStream;
55  import java.io.FileNotFoundException;
56  import java.io.FileReader;
57  import java.io.FileWriter;
58  import java.io.IOException;
59  import java.io.InputStreamReader;
60  import java.io.PrintWriter;
61  import java.util.Iterator;
62  import java.util.List;
63  
64  import org.apache.commons.cli.CommandLine;
65  import org.apache.commons.cli.CommandLineParser;
66  import org.apache.commons.cli.HelpFormatter;
67  import org.apache.commons.cli.OptionBuilder;
68  import org.apache.commons.cli.Options;
69  import org.apache.commons.cli.ParseException;
70  import org.apache.commons.cli.PosixParser;
71  import org.codehaus.groovy.control.CompilationFailedException;
72  import org.codehaus.groovy.control.CompilerConfiguration;
73  import org.codehaus.groovy.runtime.InvokerHelper;
74  import org.codehaus.groovy.runtime.InvokerInvocationException;
75  
76  /***
77   * A Command line to execute groovy.
78   *
79   * @author Jeremy Rayner
80   * @author Yuri Schimke
81   * @version $Revision: 1.22 $
82   */
83  public class GroovyMain {
84      // arguments to the script
85      private List args;
86  
87      // is this a file on disk
88      private boolean isScriptFile;
89  
90      // filename or content of script
91      private String script;
92  
93      // process args as input files
94      private boolean processFiles;
95  
96      // edit input files in place
97      private boolean editFiles;
98  
99      // automatically output the result of each script
100     private boolean autoOutput;
101 
102     // process sockets
103     private boolean processSockets;
104 
105     // port to listen on when processing sockets
106     private int port;
107 
108     // backup input files with extension
109     private String backupExtension;
110 
111     // do you want full stack traces in script exceptions?
112     private boolean debug = false;
113 
114     // Compiler configuration, used to set the encodings of the scripts/classes
115     private CompilerConfiguration conf = new CompilerConfiguration();
116 
117     /***
118      * Main CLI interface.
119      *
120      * @param args all command line args.
121      */
122     public static void main(String args[]) {
123         MetaClass.setUseReflection(true);
124 
125         Options options = buildOptions();
126 
127         try {
128             CommandLine cmd = parseCommandLine(options, args);
129 
130             if (cmd.hasOption('h')) {
131                 HelpFormatter formatter = new HelpFormatter();
132                 formatter.printHelp("groovy", options);
133             } else if (cmd.hasOption('v')) {
134                 String version = InvokerHelper.getVersion();
135                 System.out.println("Groovy Version: " + version + " JVM: " + System.getProperty("java.vm.version"));
136             } else {
137                 // If we fail, then exit with an error so scripting frameworks can catch it
138                 if (!process(cmd)) {
139                     System.exit(1);
140                 }
141             }
142         } catch (ParseException pe) {
143             System.out.println("error: " + pe.getMessage());
144             HelpFormatter formatter = new HelpFormatter();
145             formatter.printHelp("groovy", options);
146         }
147     }
148 
149     /***
150      * Parse the command line.
151      *
152      * @param options the options parser.
153      * @param args    the command line args.
154      * @return parsed command line.
155      * @throws ParseException if there was a problem.
156      */
157     private static CommandLine parseCommandLine(Options options, String[] args) throws ParseException {
158         CommandLineParser parser = new PosixParser();
159         CommandLine cmd = parser.parse(options, args, true);
160         return cmd;
161     }
162 
163     /***
164      * Build the options parser.  Has to be synchronized because of the way Options are constructed.
165      *
166      * @return an options parser.
167      */
168     private static synchronized Options buildOptions() {
169         Options options = new Options();
170 
171         options.addOption(OptionBuilder.hasArg(false).withDescription("usage information").withLongOpt("help").create('h'));
172 
173         options.addOption(OptionBuilder.hasArg(false).withDescription("debug mode will print out full stack traces").withLongOpt("debug").create('d'));
174 
175         options.addOption(OptionBuilder.hasArg(false).withDescription("display the Groovy and JVM versions").withLongOpt("version").create('v'));
176 
177         options.addOption(OptionBuilder.withArgName("charset").hasArg().withDescription("specify the encoding of the files").withLongOpt("encoding").create('c'));
178 
179         options.addOption(OptionBuilder.withArgName("script").hasArg().withDescription("specify a command line script").create('e'));
180 
181         options.addOption(OptionBuilder.withArgName("extension").hasOptionalArg().withDescription("modify files in place").create('i'));
182 
183         options.addOption(OptionBuilder.hasArg(false).withDescription("process files line by line").create('n'));
184 
185         options.addOption(OptionBuilder.hasArg(false).withDescription("process files line by line and print result").create('p'));
186 
187         options.addOption(OptionBuilder.withArgName("port").hasOptionalArg().withDescription("listen on a port and process inbound lines").create('l'));
188         return options;
189     }
190 
191     /***
192      * Process the users request.
193      *
194      * @param line the parsed command line.
195      * @throws ParseException if invalid options are chosen
196      */
197     private static boolean process(CommandLine line) throws ParseException {
198         GroovyMain main = new GroovyMain();
199 
200         List args = line.getArgList();
201 
202         // add the ability to parse scripts with a specified encoding
203         if (line.hasOption('c')) {
204             main.conf.setSourceEncoding(line.getOptionValue("encoding"));
205         }
206 
207         main.isScriptFile = !line.hasOption('e');
208         main.debug = line.hasOption('d');
209         main.conf.setDebug(main.debug);
210         main.processFiles = line.hasOption('p') || line.hasOption('n');
211         main.autoOutput = line.hasOption('p');
212         main.editFiles = line.hasOption('i');
213         if (main.editFiles) {
214             main.backupExtension = line.getOptionValue('i');
215         }
216 
217         if (main.isScriptFile) {
218             if (args.isEmpty())
219                 throw new ParseException("neither -e or filename provided");
220 
221             main.script = (String) args.remove(0);
222             if (main.script.endsWith(".java"))
223                 throw new ParseException("error: cannot compile file with .java extension: " + main.script);
224         } else {
225             main.script = line.getOptionValue('e');
226         }
227 
228         main.processSockets = line.hasOption('l');
229         if (main.processSockets) {
230             String p = line.getOptionValue('l', "1960"); // default port to listen to
231             main.port = new Integer(p).intValue();
232         }
233         main.args = args;
234 
235         return main.run();
236     }
237 
238 
239     /***
240      * Run the script.
241      */
242     private boolean run() {
243         try {
244             if (processSockets) {
245                 processSockets();
246             } else if (processFiles) {
247                 processFiles();
248             } else {
249                 processOnce();
250             }
251             return true;
252         } catch (CompilationFailedException e) {
253             System.err.println(e);
254             return false;
255         } catch (Throwable e) {
256             if (e instanceof InvokerInvocationException) {
257                 InvokerInvocationException iie = (InvokerInvocationException) e;
258                 e = iie.getCause();
259             }
260             System.err.println("Caught: " + e);
261             if (debug) {
262                 e.printStackTrace();
263             } else {
264                 StackTraceElement[] stackTrace = e.getStackTrace();
265                 for (int i = 0; i < stackTrace.length; i++) {
266                     StackTraceElement element = stackTrace[i];
267                     String fileName = element.getFileName();
268                     if (fileName!=null && !fileName.endsWith(".java")) {
269                         System.err.println("\tat " + element);
270                     }
271                 }
272             }
273             return false;
274         }
275     }
276 
277     /***
278      * Process Sockets.
279      */
280     private void processSockets() throws CompilationFailedException, IOException {
281         GroovyShell groovy = new GroovyShell(conf);
282         //check the script is currently valid before starting a server against the script
283         if (isScriptFile) {
284             groovy.parse(new FileInputStream(huntForTheScriptFile(script)));
285         } else {
286             groovy.parse(script);
287         }
288         new GroovySocketServer(groovy, isScriptFile, script, autoOutput, port);
289     }
290 
291     /***
292      * Hunt for the script file, doesn't bother if it is named precisely.
293      *
294      * Tries in this order:
295      * - actual supplied name
296      * - name.groovy
297      * - name.gvy
298      * - name.gy
299      * - name.gsh
300      */
301     public File huntForTheScriptFile(String scriptFileName) {
302         File scriptFile = new File(scriptFileName);
303         String[] standardExtensions = {".groovy",".gvy",".gy",".gsh"};
304         int i = 0;
305         while (i < standardExtensions.length && !scriptFile.exists()) {
306             scriptFile = new File(scriptFileName + standardExtensions[i]);
307             i++;
308         }
309         // if we still haven't found the file, point back to the originally specified filename
310         if (!scriptFile.exists()) {
311             scriptFile = new File(scriptFileName);
312         }
313         return scriptFile;
314     }
315 
316     /***
317      * Process the input files.
318      */
319     private void processFiles() throws CompilationFailedException, IOException {
320         GroovyShell groovy = new GroovyShell(conf);
321 
322         Script s = null;
323 
324         if (isScriptFile) {
325             s = groovy.parse(huntForTheScriptFile(script));
326         } else {
327             s = groovy.parse(script, "main");
328         }
329 
330         if (args.isEmpty()) {
331             BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
332             PrintWriter writer = new PrintWriter(System.out);
333 
334             processReader(s, reader, writer);
335         } else {
336             Iterator i = args.iterator();
337             while (i.hasNext()) {
338                 String filename = (String) i.next();
339                 File file = huntForTheScriptFile(filename);
340                 processFile(s, file);
341             }
342         }
343     }
344 
345     /***
346      * Process a single input file.
347      *
348      * @param s    the script to execute.
349      * @param file the input file.
350      */
351     private void processFile(Script s, File file) throws IOException {
352         if (!file.exists())
353             throw new FileNotFoundException(file.getName());
354 
355         if (!editFiles) {
356             BufferedReader reader = new BufferedReader(new FileReader(file));
357             try {
358                 PrintWriter writer = new PrintWriter(System.out);
359                 processReader(s, reader, writer);
360                 writer.flush();
361             } finally {
362                 reader.close();
363             }
364         } else {
365             File backup = null;
366             if (backupExtension == null) {
367                 backup = File.createTempFile("groovy_", ".tmp");
368                 backup.deleteOnExit();
369             } else {
370                 backup = new File(file.getPath() + backupExtension);
371                 backup.delete();
372             }
373             if (!file.renameTo(backup))
374                 throw new IOException("unable to rename " + file + " to " + backup);
375 
376             BufferedReader reader = new BufferedReader(new FileReader(backup));
377             try {
378                 PrintWriter writer = new PrintWriter(new FileWriter(file));
379                 try {
380                     processReader(s, reader, writer);
381                 } finally {
382                     writer.close();
383                 }
384             } finally {
385                 reader.close();
386             }
387         }
388     }
389 
390     /***
391      * Process a script against a single input file.
392      *
393      * @param s      script to execute.
394      * @param reader input file.
395      * @param pw     output sink.
396      */
397     private void processReader(Script s, BufferedReader reader, PrintWriter pw) throws IOException {
398         String line = null;
399         s.setProperty("out", pw);
400         while ((line = reader.readLine()) != null) {
401             s.setProperty("line", line);
402             Object o = s.run();
403 
404             if (autoOutput) {
405                 pw.println(o);
406             }
407         }
408     }
409 
410     /***
411      * Process the standard, single script with args.
412      */
413     private void processOnce() throws CompilationFailedException, IOException {
414         GroovyShell groovy = new GroovyShell(conf);
415 
416         if (isScriptFile)
417             groovy.run(huntForTheScriptFile(script), args);
418         else
419             groovy.run(script, "script_from_command_line", args);
420     }
421 }