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 }