View Javadoc

1   /*
2    $Id: GroovyMain.java,v 1.16 2005/02/23 20:53:45 jez 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  import org.apache.commons.cli.*;
52  import org.codehaus.groovy.control.CompilationFailedException;
53  import org.codehaus.groovy.control.CompilerConfiguration;
54  import org.codehaus.groovy.runtime.InvokerHelper;
55  import org.codehaus.groovy.runtime.InvokerInvocationException;
56  
57  import java.io.*;
58  import java.util.Iterator;
59  import java.util.List;
60  
61  /***
62   * A Command line to execute groovy.
63   *
64   * @author Jeremy Rayner
65   * @author Yuri Schimke
66   * @version $Revision: 1.16 $
67   */
68  public class GroovyMain {
69      // arguments to the script
70      private List args;
71  
72      // is this a file on disk
73      private boolean isScriptFile;
74  
75      // filename or content of script
76      private String script;
77  
78      // process args as input files
79      private boolean processFiles;
80  
81      // edit input files in place
82      private boolean editFiles;
83  
84      // automatically output the result of each script
85      private boolean autoOutput;
86  
87      // process sockets
88      private boolean processSockets;
89  
90      // port to listen on when processing sockets
91      private int port;
92  
93      // backup input files with extension
94      private String backupExtension;
95  
96      // do you want full stack traces in script exceptions?
97      private boolean debug = false;
98  
99      // Compiler configuration, used to set the encodings of the scripts/classes
100     private CompilerConfiguration conf = new CompilerConfiguration();
101 
102     /***
103      * Main CLI interface.
104      *
105      * @param args all command line args.
106      */
107     public static void main(String args[]) {
108         MetaClass.setUseReflection(true);
109 
110         Options options = buildOptions();
111 
112         try {
113             CommandLine cmd = parseCommandLine(options, args);
114 
115             if (cmd.hasOption('h')) {
116                 HelpFormatter formatter = new HelpFormatter();
117                 formatter.printHelp("groovy", options);
118             } else if (cmd.hasOption('v')) {
119                 String version = InvokerHelper.getVersion();
120                 System.out.println("Groovy Version: " + version + " JVM: " + System.getProperty("java.vm.version"));
121             } else {
122                 // If we fail, then exit with an error so scripting frameworks can catch it
123                 if (!process(cmd)) {
124                     System.exit(1);
125                 }
126             }
127         } catch (ParseException pe) {
128             System.out.println("error: " + pe.getMessage());
129             HelpFormatter formatter = new HelpFormatter();
130             formatter.printHelp("groovy", options);
131         }
132     }
133 
134     /***
135      * Parse the command line.
136      *
137      * @param options the options parser.
138      * @param args    the command line args.
139      * @return parsed command line.
140      * @throws ParseException if there was a problem.
141      */
142     private static CommandLine parseCommandLine(Options options, String[] args) throws ParseException {
143         CommandLineParser parser = new PosixParser();
144         CommandLine cmd = parser.parse(options, args, true);
145         return cmd;
146     }
147 
148     /***
149      * Build the options parser.  Has to be synchronized because of the way Options are constructed.
150      *
151      * @return an options parser.
152      */
153     private static synchronized Options buildOptions() {
154         Options options = new Options();
155 
156         options.addOption(OptionBuilder.hasArg(false).withDescription("usage information").withLongOpt("help").create('h'));
157 
158         options.addOption(OptionBuilder.hasArg(false).withDescription("debug mode will print out full stack traces").withLongOpt("debug").create('d'));
159 
160         options.addOption(OptionBuilder.hasArg(false).withDescription("display the Groovy and JVM versions").withLongOpt("version").create('v'));
161 
162         options.addOption(OptionBuilder.withArgName("charset").hasArg().withDescription("specify the encoding of the files").withLongOpt("encoding").create('c'));
163 
164         options.addOption(OptionBuilder.withArgName("script").hasArg().withDescription("specify a command line script").create('e'));
165 
166         options.addOption(OptionBuilder.withArgName("extension").hasOptionalArg().withDescription("modify files in place").create('i'));
167 
168         options.addOption(OptionBuilder.hasArg(false).withDescription("process files line by line").create('n'));
169 
170         options.addOption(OptionBuilder.hasArg(false).withDescription("process files line by line and print result").create('p'));
171 
172         options.addOption(OptionBuilder.withArgName("port").hasOptionalArg().withDescription("listen on a port and process inbound lines").create('l'));
173         return options;
174     }
175 
176     /***
177      * Process the users request.
178      *
179      * @param line the parsed command line.
180      * @throws ParseException if invalid options are chosen
181      */
182     private static boolean process(CommandLine line) throws ParseException {
183         GroovyMain main = new GroovyMain();
184 
185         List args = line.getArgList();
186 
187         // add the ability to parse scripts with a specified encoding
188         if (line.hasOption('c')) {
189             main.conf.setSourceEncoding(line.getOptionValue("encoding"));
190         }
191 
192         main.isScriptFile = !line.hasOption('e');
193         main.debug = line.hasOption('d');
194         main.processFiles = line.hasOption('p') || line.hasOption('n');
195         main.autoOutput = line.hasOption('p');
196         main.editFiles = line.hasOption('i');
197         if (main.editFiles) {
198             main.backupExtension = line.getOptionValue('i');
199         }
200 
201         if (main.isScriptFile) {
202             if (args.isEmpty())
203                 throw new ParseException("neither -e or filename provided");
204 
205             main.script = (String) args.remove(0);
206             if (main.script.endsWith(".java"))
207                 throw new ParseException("error: cannot compile file with .java extension: " + main.script);
208         } else {
209             main.script = line.getOptionValue('e');
210         }
211 
212         main.processSockets = line.hasOption('l');
213         if (main.processSockets) {
214             String p = line.getOptionValue('l', "1960"); // default port to listen to
215             main.port = new Integer(p).intValue();
216         }
217         main.args = args;
218 
219         return main.run();
220     }
221 
222 
223     /***
224      * Run the script.
225      */
226     private boolean run() {
227         try {
228             if (processSockets) {
229                 processSockets();
230             } else if (processFiles) {
231                 processFiles();
232             } else {
233                 processOnce();
234             }
235             return true;
236         } catch (Throwable e) {
237             if (e instanceof InvokerInvocationException) {
238                 InvokerInvocationException iie = (InvokerInvocationException) e;
239                 e = iie.getCause();
240             }
241             System.err.println("Caught: " + e);
242             if (debug) {
243                 e.printStackTrace();
244             } else {
245                 StackTraceElement[] stackTrace = e.getStackTrace();
246                 for (int i = 0; i < stackTrace.length; i++) {
247                     StackTraceElement element = stackTrace[i];
248                     if (!element.getFileName().endsWith(".java")) {
249                         System.err.println("\tat " + element);
250                     }
251                 }
252             }
253             return false;
254         }
255     }
256 
257     /***
258      * Process Sockets.
259      */
260     private void processSockets() throws CompilationFailedException, IOException {
261         GroovyShell groovy = new GroovyShell(conf);
262         //check the script is currently valid before starting a server against the script
263         if (isScriptFile) {
264             groovy.parse(new FileInputStream(script));
265         } else {
266             groovy.parse(script);
267         }
268         new GroovySocketServer(groovy, isScriptFile, script, autoOutput, port);
269     }
270 
271     /***
272      * Hunt for the script file, doesn't bother if it is named precisely.
273      *
274      * Tries in this order:
275      * - actual supplied name
276      * - name.groovy
277      * - name.gvy
278      * - name.gy
279      * - name.gsh
280      */
281     private File huntForTheScriptFile(String scriptFileName) {
282         File scriptFile = new File(scriptFileName);
283         String[] standardExtensions = {".groovy",".gvy",".gy",".gsh"};
284         int i = 0;
285         while (i < standardExtensions.length && !scriptFile.exists()) {
286             scriptFile = new File(scriptFileName + standardExtensions[i]);
287             i++;
288         }
289         // if we still haven't found the file, point back to the originally specified filename
290         if (!scriptFile.exists()) {
291             scriptFile = new File(scriptFileName);
292         }
293         return scriptFile;
294     }
295 
296     /***
297      * Process the input files.
298      */
299     private void processFiles() throws CompilationFailedException, IOException {
300         GroovyShell groovy = new GroovyShell(conf);
301 
302         Script s = null;
303 
304         if (isScriptFile) {
305             s = groovy.parse(huntForTheScriptFile(script));
306         } else {
307             s = groovy.parse(script, "main");
308         }
309 
310         if (args.isEmpty()) {
311             BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
312             PrintWriter writer = new PrintWriter(System.out);
313 
314             processReader(s, reader, writer);
315         } else {
316             Iterator i = args.iterator();
317             while (i.hasNext()) {
318                 String filename = (String) i.next();
319                 File file = huntForTheScriptFile(filename);
320                 processFile(s, file);
321             }
322         }
323     }
324 
325     /***
326      * Process a single input file.
327      *
328      * @param s    the script to execute.
329      * @param file the input file.
330      */
331     private void processFile(Script s, File file) throws IOException {
332         if (!file.exists())
333             throw new FileNotFoundException(file.getName());
334 
335         if (!editFiles) {
336             BufferedReader reader = new BufferedReader(new FileReader(file));
337             try {
338                 PrintWriter writer = new PrintWriter(System.out);
339                 processReader(s, reader, writer);
340                 writer.flush();
341             } finally {
342                 reader.close();
343             }
344         } else {
345             File backup = null;
346             if (backupExtension == null) {
347                 backup = File.createTempFile("groovy_", ".tmp");
348                 backup.deleteOnExit();
349             } else {
350                 backup = new File(file.getPath() + backupExtension);
351                 backup.delete();
352             }
353             if (!file.renameTo(backup))
354                 throw new IOException("unable to rename " + file + " to " + backup);
355 
356             BufferedReader reader = new BufferedReader(new FileReader(backup));
357             try {
358                 PrintWriter writer = new PrintWriter(new FileWriter(file));
359                 try {
360                     processReader(s, reader, writer);
361                 } finally {
362                     writer.close();
363                 }
364             } finally {
365                 reader.close();
366             }
367         }
368     }
369 
370     /***
371      * Process a script against a single input file.
372      *
373      * @param s      script to execute.
374      * @param reader input file.
375      * @param pw     output sink.
376      */
377     private void processReader(Script s, BufferedReader reader, PrintWriter pw) throws IOException {
378         String line = null;
379         s.setProperty("out", pw);
380         while ((line = reader.readLine()) != null) {
381             s.setProperty("line", line);
382             Object o = s.run();
383 
384             if (autoOutput) {
385                 pw.println(o);
386             }
387         }
388     }
389 
390     /***
391      * Process the standard, single script with args.
392      */
393     private void processOnce() throws CompilationFailedException, IOException {
394         GroovyShell groovy = new GroovyShell(conf);
395 
396         if (isScriptFile)
397             groovy.run(huntForTheScriptFile(script), args);
398         else
399             groovy.run(script, "main", args);
400     }
401 }