View Javadoc

1   /*
2    $Id: GroovyMain.java 4080 2006-09-26 20:36:00Z glaforge $
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  import java.math.BigInteger;
64  
65  import org.apache.commons.cli.CommandLine;
66  import org.apache.commons.cli.CommandLineParser;
67  import org.apache.commons.cli.HelpFormatter;
68  import org.apache.commons.cli.OptionBuilder;
69  import org.apache.commons.cli.Options;
70  import org.apache.commons.cli.ParseException;
71  import org.apache.commons.cli.PosixParser;
72  import org.codehaus.groovy.control.CompilationFailedException;
73  import org.codehaus.groovy.control.CompilerConfiguration;
74  import org.codehaus.groovy.runtime.InvokerHelper;
75  import org.codehaus.groovy.runtime.InvokerInvocationException;
76  
77  /***
78   * A Command line to execute groovy.
79   *
80   * @author Jeremy Rayner
81   * @author Yuri Schimke
82   * @version $Revision: 4080 $
83   */
84  public class GroovyMain {
85      // arguments to the script
86      private List args;
87  
88      // is this a file on disk
89      private boolean isScriptFile;
90  
91      // filename or content of script
92      private String script;
93  
94      // process args as input files
95      private boolean processFiles;
96  
97      // edit input files in place
98      private boolean editFiles;
99  
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 + " JVM: " + System.getProperty("java.vm.version"));
143             } else {
144                 // If we fail, then exit with an error so scripting frameworks can catch it
145                 if (!process(cmd)) {
146                     System.exit(1);
147                 }
148             }
149         } catch (ParseException pe) {
150             System.out.println("error: " + pe.getMessage());
151             HelpFormatter formatter = new HelpFormatter();
152             formatter.printHelp("groovy", options);
153         }
154     }
155 
156     /***
157      * Parse the command line.
158      *
159      * @param options the options parser.
160      * @param args    the command line args.
161      * @return parsed command line.
162      * @throws ParseException if there was a problem.
163      */
164     private static CommandLine parseCommandLine(Options options, String[] args) throws ParseException {
165         CommandLineParser parser = new PosixParser();
166         CommandLine cmd = parser.parse(options, args, true);
167         return cmd;
168     }
169 
170     /***
171      * Build the options parser.  Has to be synchronized because of the way Options are constructed.
172      *
173      * @return an options parser.
174      */
175     private static synchronized Options buildOptions() {
176         Options options = new Options();
177 
178         options.addOption(
179             OptionBuilder.hasArg(false)
180             .withDescription("usage information")
181             .withLongOpt("help")
182             .create('h'));
183         options.addOption(
184             OptionBuilder.hasArg(false)
185             .withDescription("debug mode will print out full stack traces")
186             .withLongOpt("debug")
187             .create('d'));
188         options.addOption(
189             OptionBuilder.hasArg(false)
190             .withDescription("display the Groovy and JVM versions")
191             .withLongOpt("version")
192             .create('v'));
193         options.addOption(
194             OptionBuilder.withArgName("charset")
195             .hasArg()
196             .withDescription("specify the encoding of the files")
197             .withLongOpt("encoding")
198             .create('c'));
199         options.addOption(
200             OptionBuilder.withArgName("script")
201             .hasArg()
202             .withDescription("specify a command line script")
203             .create('e'));
204         options.addOption(
205             OptionBuilder.withArgName("extension")
206             .hasOptionalArg()
207             .withDescription("modify files in place, create backup if extension is given (e.g. \'.bak\')")
208             .create('i'));
209         options.addOption(
210             OptionBuilder.hasArg(false)
211             .withDescription("process files line by line")
212             .create('n'));
213         options.addOption(
214             OptionBuilder.hasArg(false)
215             .withDescription("process files line by line and print result")
216             .create('p'));
217         options.addOption(
218             OptionBuilder.withArgName("port")
219             .hasOptionalArg()
220             .withDescription("listen on a port and process inbound lines")
221             .create('l'));
222         options.addOption(
223                 OptionBuilder.withArgName("splitPattern")
224                 .hasOptionalArg()
225                 .withDescription("automatically split current line (defaults to '//s'")
226                 .withLongOpt("autosplit")
227                 .create('a'));
228         return options;
229     }
230 
231     /***
232      * Process the users request.
233      *
234      * @param line the parsed command line.
235      * @throws ParseException if invalid options are chosen
236      */
237     private static boolean process(CommandLine line) throws ParseException {
238         GroovyMain main = new GroovyMain();
239 
240         List args = line.getArgList();
241 
242         // add the ability to parse scripts with a specified encoding
243         if (line.hasOption('c')) {
244             main.conf.setSourceEncoding(line.getOptionValue("encoding"));
245         }
246 
247         main.isScriptFile = !line.hasOption('e');
248         main.debug = line.hasOption('d');
249         main.conf.setDebug(main.debug);
250         main.processFiles = line.hasOption('p') || line.hasOption('n');
251         main.autoOutput = line.hasOption('p');
252         main.editFiles = line.hasOption('i');
253         if (main.editFiles) {
254             main.backupExtension = line.getOptionValue('i');
255         }
256         main.autoSplit = line.hasOption('a');
257         String sp = line.getOptionValue('a');
258         if (sp != null)
259             main.splitPattern = sp;
260 
261         if (main.isScriptFile) {
262             if (args.isEmpty())
263                 throw new ParseException("neither -e or filename provided");
264 
265             main.script = (String) args.remove(0);
266             if (main.script.endsWith(".java"))
267                 throw new ParseException("error: cannot compile file with .java extension: " + main.script);
268         } else {
269             main.script = line.getOptionValue('e');
270         }
271 
272         main.processSockets = line.hasOption('l');
273         if (main.processSockets) {
274             String p = line.getOptionValue('l', "1960"); // default port to listen to
275             main.port = new Integer(p).intValue();
276         }
277         main.args = args;
278 
279         return main.run();
280     }
281 
282 
283     /***
284      * Run the script.
285      */
286     private boolean run() {
287         try {
288             if (processSockets) {
289                 processSockets();
290             } else if (processFiles) {
291                 processFiles();
292             } else {
293                 processOnce();
294             }
295             return true;
296         } catch (CompilationFailedException e) {
297             System.err.println(e);
298             return false;
299         } catch (Throwable e) {
300             if (e instanceof InvokerInvocationException) {
301                 InvokerInvocationException iie = (InvokerInvocationException) e;
302                 e = iie.getCause();
303             }
304             System.err.println("Caught: " + e);
305             if (debug) {
306                 e.printStackTrace();
307             } else {
308                 StackTraceElement[] stackTrace = e.getStackTrace();
309                 for (int i = 0; i < stackTrace.length; i++) {
310                     StackTraceElement element = stackTrace[i];
311                     String fileName = element.getFileName();
312                     if (fileName!=null && !fileName.endsWith(".java")) {
313                         System.err.println("\tat " + element);
314                     }
315                 }
316             }
317             return false;
318         }
319     }
320 
321     /***
322      * Process Sockets.
323      */
324     private void processSockets() throws CompilationFailedException, IOException {
325         GroovyShell groovy = new GroovyShell(conf);
326         //check the script is currently valid before starting a server against the script
327         if (isScriptFile) {
328             groovy.parse(new FileInputStream(huntForTheScriptFile(script)));
329         } else {
330             groovy.parse(script);
331         }
332         new GroovySocketServer(groovy, isScriptFile, script, autoOutput, port);
333     }
334 
335     /***
336      * Hunt for the script file, doesn't bother if it is named precisely.
337      *
338      * Tries in this order:
339      * - actual supplied name
340      * - name.groovy
341      * - name.gvy
342      * - name.gy
343      * - name.gsh
344      */
345     public File huntForTheScriptFile(String scriptFileName) {
346         File scriptFile = new File(scriptFileName);
347         String[] standardExtensions = {".groovy",".gvy",".gy",".gsh"};
348         int i = 0;
349         while (i < standardExtensions.length && !scriptFile.exists()) {
350             scriptFile = new File(scriptFileName + standardExtensions[i]);
351             i++;
352         }
353         // if we still haven't found the file, point back to the originally specified filename
354         if (!scriptFile.exists()) {
355             scriptFile = new File(scriptFileName);
356         }
357         return scriptFile;
358     }
359 
360     /***
361      * Process the input files.
362      */
363     private void processFiles() throws CompilationFailedException, 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(new InputStreamReader(System.in));
376             PrintWriter writer = new PrintWriter(System.out);
377 
378             try {
379                 processReader(s, reader, writer);
380             } finally {
381                 reader.close();
382                 writer.close();
383             }
384 
385         } else {
386             Iterator i = args.iterator();
387             while (i.hasNext()) {
388                 String filename = (String) i.next();
389                 File file = huntForTheScriptFile(filename);
390                 processFile(s, file);
391             }
392         }
393     }
394 
395     /***
396      * Process a single input file.
397      *
398      * @param s    the script to execute.
399      * @param file the input file.
400      */
401     private void processFile(Script s, File file) throws IOException {
402         if (!file.exists())
403             throw new FileNotFoundException(file.getName());
404 
405         if (!editFiles) {
406             BufferedReader reader = new BufferedReader(new FileReader(file));
407             try {
408                 PrintWriter writer = new PrintWriter(System.out);
409                 processReader(s, reader, writer);
410                 writer.flush();
411             } finally {
412                 reader.close();
413             }
414         } else {
415             File backup = null;
416             if (backupExtension == null) {
417                 backup = File.createTempFile("groovy_", ".tmp");
418                 backup.deleteOnExit();
419             } else {
420                 backup = new File(file.getPath() + backupExtension);
421             }
422             backup.delete();
423             if (!file.renameTo(backup))
424                 throw new IOException("unable to rename " + file + " to " + backup);
425 
426             BufferedReader reader = new BufferedReader(new FileReader(backup));
427             try {
428                 PrintWriter writer = new PrintWriter(new FileWriter(file));
429                 try {
430                     processReader(s, reader, writer);
431                 } finally {
432                     writer.close();
433                 }
434             } finally {
435                 reader.close();
436             }
437         }
438     }
439 
440     /***
441      * Process a script against a single input file.
442      *
443      * @param s      script to execute.
444      * @param reader input file.
445      * @param pw     output sink.
446      */
447     private void processReader(Script s, BufferedReader reader, PrintWriter pw) throws IOException {
448         String line = null;
449         String lineCountName = "count";
450         s.setProperty(lineCountName, BigInteger.ZERO);
451         String autoSplitName = "split";
452         s.setProperty("out", pw);
453         while ((line = reader.readLine()) != null) {
454             s.setProperty("line", line);
455             s.setProperty(lineCountName,
456                     ((BigInteger)s.getProperty(lineCountName)).add(BigInteger.ONE));
457             if(autoSplit)
458                 s.setProperty(autoSplitName, line.split(splitPattern));
459             Object o = s.run();
460 
461             if (autoOutput) {
462                 pw.println(o);
463             }
464         }
465     }
466 
467     private static ClassLoader getLoader(ClassLoader cl) {
468         if (cl!=null) return cl;
469         cl = Thread.currentThread().getContextClassLoader();
470         if (cl!=null) return cl;
471         cl = GroovyMain.class.getClassLoader();
472         if (cl!=null) return cl;
473         return null;
474     }
475 
476     /***
477      * Process the standard, single script with args.
478      */
479     private void processOnce() throws CompilationFailedException, IOException {
480         GroovyShell groovy = new GroovyShell(conf);
481 
482         if (isScriptFile)
483             groovy.run(huntForTheScriptFile(script), args);
484         else
485             groovy.run(script, "script_from_command_line", args);
486     }
487 }