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 + " 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    }