View Javadoc

    1   /*
    2    $Id: GroovyShell.java,v 1.38 2004/12/27 10:31:24 spullara 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.lang;
    47
    48  import groovy.ui.GroovyMain;
    49  import org.codehaus.groovy.control.CompilationFailedException;
    50  import org.codehaus.groovy.control.CompilerConfiguration;
    51  import org.codehaus.groovy.runtime.InvokerHelper;
    52
    53  import java.io.ByteArrayInputStream;
    54  import java.io.File;
    55  import java.io.IOException;
    56  import java.io.InputStream;
    57  import java.lang.reflect.Constructor;
    58  import java.security.AccessController;
    59  import java.security.PrivilegedAction;
    60  import java.security.PrivilegedActionException;
    61  import java.security.PrivilegedExceptionAction;
    62  import java.util.List;
    63
    64  /***
    65   * Represents a groovy shell capable of running arbitrary groovy scripts
    66   *
    67   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
    68   * @author Guillaume Laforge
    69   * @version $Revision: 1.38 $
    70   */
    71  public class GroovyShell extends GroovyObjectSupport {
    72      public static final String[] EMPTY_ARGS = {
    73      };
    74
    75      private GroovyClassLoader loader;
    76      private Binding context;
    77      private int counter;
    78
    79      public static void main(String[] args) {
    80          GroovyMain.main(args);
    81      }
    82
    83      public GroovyShell() {
    84          this(null, new Binding());
    85      }
    86
    87      public GroovyShell(Binding binding) {
    88          this(null, binding);
    89      }
    90
    91      public GroovyShell(CompilerConfiguration config) {
    92          this(new Binding(), config);
    93      }
    94
    95      public GroovyShell(Binding binding, CompilerConfiguration config) {
    96          this(null, binding, config);
    97      }
    98
    99      public GroovyShell(ClassLoader parent, Binding binding) {
    100         this(parent, binding, null);
    101     }
    102
    103     public GroovyShell(ClassLoader parent) {
    104         this(parent, new Binding(), null);
    105     }
    106
    107     public GroovyShell(final ClassLoader parent, Binding binding, final CompilerConfiguration config) {
    108         this.loader =
    109                 (GroovyClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
    110                     public Object run() {
    111                         ClassLoader pcl = parent;
    112                         if (pcl == null) {
    113                             pcl = Thread.currentThread().getContextClassLoader();
    114                             if (pcl == null) {
    115                                 pcl = GroovyShell.class.getClassLoader();
    116                             }
    117                         }
    118                         return new GroovyClassLoader(pcl, (config == null) ? new CompilerConfiguration() : config);
    119                     }
    120                 });
    121         this.context = binding;
    122     }
    123
    124     /***
    125      * Creates a child shell using a new ClassLoader which uses the parent shell's
    126      * class loader as its parent
    127      *
    128      * @param shell is the parent shell used for the variable bindings and the parent class loader
    129      */
    130     public GroovyShell(GroovyShell shell) {
    131         this(shell.loader, shell.context);
    132     }
    133
    134     public Binding getContext() {
    135         return context;
    136     }
    137
    138     public Object getProperty(String property) {
    139         Object answer = getVariable(property);
    140         if (answer == null) {
    141             answer = super.getProperty(property);
    142         }
    143         return answer;
    144     }
    145
    146     public void setProperty(String property, Object newValue) {
    147         setVariable(property, newValue);
    148         try {
    149             super.setProperty(property, newValue);
    150         } catch (GroovyRuntimeException e) {
    151             // ignore, was probably a dynamic property
    152         }
    153     }
    154
    155     /***
    156      * A helper method which runs the given script file with the given command line arguments
    157      *
    158      * @param scriptFile the file of the script to run
    159      * @param list       the command line arguments to pass in
    160      */
    161     public void run(File scriptFile, List list) throws CompilationFailedException, IOException {
    162         String[] args = new String[list.size()];
    163         run(scriptFile, (String[]) list.toArray(args));
    164     }
    165
    166     /***
    167      * A helper method which runs the given cl script with the given command line arguments
    168      *
    169      * @param scriptText is the text content of the script
    170      * @param fileName   is the logical file name of the script (which is used to create the class name of the script)
    171      * @param list       the command line arguments to pass in
    172      */
    173     public void run(String scriptText, String fileName, List list) throws CompilationFailedException, IOException {
    174         String[] args = new String[list.size()];
    175         list.toArray(args);
    176         run(scriptText, fileName, args);
    177     }
    178
    179     /***
    180      * Runs the given script file name with the given command line arguments
    181      *
    182      * @param scriptFile the file name of the script to run
    183      * @param args       the command line arguments to pass in
    184      */
    185     public void run(final File scriptFile, String[] args) throws CompilationFailedException, IOException {
    186         String scriptName = scriptFile.getName();
    187         int p = scriptName.lastIndexOf(".");
    188         if (p++ >= 0) {
    189             if (scriptName.substring(p).equals("java")) {
    190                 System.err.println("error: cannot compile file with .java extension: " + scriptName);
    191                 throw new CompilationFailedException(0, null);
    192             }
    193         }
    194
    195         // Get the current context classloader and save it on the stack
    196         final Thread thread = Thread.currentThread();
    197         ClassLoader currentClassLoader = thread.getContextClassLoader();
    198
    199         class DoSetContext implements PrivilegedAction {
    200             ClassLoader classLoader;
    201
    202             public DoSetContext(ClassLoader loader) {
    203                 classLoader = loader;
    204             }
    205
    206             public Object run() {
    207                 thread.setContextClassLoader(classLoader);
    208                 return null;
    209             }
    210         }
    211         ;
    212
    213         AccessController.doPrivileged(new DoSetContext(loader));
    214
    215         // Parse the script, generate the class, and invoke the main method.  This is a little looser than
    216         // if you are compiling the script because the JVM isn't executing the main method.
    217         Class scriptClass;
    218         try {
    219             scriptClass = (Class) AccessController.doPrivileged(new PrivilegedExceptionAction() {
    220                 public Object run() throws CompilationFailedException, IOException {
    221                     return loader.parseClass(scriptFile);
    222                 }
    223             });
    224         } catch (PrivilegedActionException pae) {
    225             Exception e = pae.getException();
    226             if (e instanceof CompilationFailedException) {
    227                 throw (CompilationFailedException) e;
    228             } else if (e instanceof IOException) {
    229                 throw (IOException) e;
    230             } else {
    231                 throw (RuntimeException) pae.getException();
    232             }
    233         }
    234
    235         runMainOrTestOrRunnable(scriptClass, args);
    236
    237         // Set the context classloader back to what it was.
    238         AccessController.doPrivileged(new DoSetContext(currentClassLoader));
    239     }
    240
    241     /***
    242      * if (theClass has a main method) {
    243      * run the main method
    244      * } else if (theClass instanceof GroovyTestCase) {
    245      * use the test runner to run it
    246      * } else if (theClass implements Runnable) {
    247      * if (theClass has a constructor with String[] params)
    248      * instanciate theClass with this constructor and run
    249      * else if (theClass has a no-args constructor)
    250      * instanciate theClass with the no-args constructor and run
    251      * }
    252      */
    253     private void runMainOrTestOrRunnable(Class scriptClass, String[] args) {
    254         try {
    255             // let's find a main method
    256             scriptClass.getMethod("main", new Class[]{String[].class});
    257         } catch (NoSuchMethodException e) {
    258             // As no main() method was found, let's see if it's a unit test
    259             // if it's a unit test extending GroovyTestCase, run it with JUnit's TextRunner
    260             if (isUnitTestCase(scriptClass)) {
    261                 runTest(scriptClass);
    262             }
    263             // no main() method, not a unit test,
    264             // if it implements Runnable, try to instanciate it
    265             else if (Runnable.class.isAssignableFrom(scriptClass)) {
    266                 Constructor constructor = null;
    267                 Runnable runnable = null;
    268                 Throwable reason = null;
    269                 try {
    270                     // first, fetch the constructor taking String[] as parameter
    271                     constructor = scriptClass.getConstructor(new Class[]{(new String[]{}).getClass()});
    272                     try {
    273                         // instanciate a runnable and run it
    274                         runnable = (Runnable) constructor.newInstance(new Object[]{args});
    275                     } catch (Throwable t) {
    276                         reason = t;
    277                     }
    278                 } catch (NoSuchMethodException e1) {
    279                     try {
    280                         // otherwise, find the default constructor
    281                         constructor = scriptClass.getConstructor(new Class[]{});
    282                         try {
    283                             // instanciate a runnable and run it
    284                             runnable = (Runnable) constructor.newInstance(new Object[]{});
    285                         } catch (Throwable t) {
    286                             reason = t;
    287                         }
    288                     } catch (NoSuchMethodException nsme) {
    289                         reason = nsme;
    290                     }
    291                 }
    292                 if (constructor != null && runnable != null) {
    293                     runnable.run();
    294                 } else {
    295                     throw new GroovyRuntimeException("This script or class could not be run. ", reason);
    296                 }
    297             } else {
    298                 throw new GroovyRuntimeException("This script or class could not be run. \n" +
    299                         "It should either: \n" +
    300                         "- have a main method, \n" +
    301                         "- be a class extending GroovyTestCase, \n" +
    302                         "- or implement the Runnable interface.");
    303             }
    304             return;
    305         }
    306         // if that main method exist, invoke it
    307         InvokerHelper.invokeMethod(scriptClass, "main", new Object[]{args});
    308     }
    309
    310     /***
    311      * Run the specified class extending GroovyTestCase as a unit test.
    312      * This is done through reflection, to avoid adding a dependency to the JUnit framework.
    313      * Otherwise, developers embedding Groovy and using GroovyShell to load/parse/compile
    314      * groovy scripts and classes would have to add another dependency on their classpath.
    315      *
    316      * @param scriptClass the class to be run as a unit test
    317      */
    318     private void runTest(Class scriptClass) {
    319         try {
    320             InvokerHelper.invokeStaticMethod("junit.textui.TestRunner", "run", new Object[]{scriptClass});
    321         } catch (Exception e) {
    322             throw new GroovyRuntimeException("Failed to run the unit test. JUnit is not on the Classpath.");
    323         }
    324     }
    325
    326     /***
    327      * Utility method to check through reflection if the parsed class extends GroovyTestCase.
    328      *
    329      * @param scriptClass the class we want to know if it extends GroovyTestCase
    330      * @return true if the class extends groovy.util.GroovyTestCase
    331      */
    332     private boolean isUnitTestCase(Class scriptClass) {
    333         // check if the parsed class is a GroovyTestCase,
    334         // so that it is possible to run it as a JUnit test
    335         boolean isUnitTestCase = false;
    336         try {
    337             try {
    338                 Class testCaseClass = this.loader.loadClass("groovy.util.GroovyTestCase");
    339                 // if scriptClass extends testCaseClass
    340                 if (testCaseClass.isAssignableFrom(scriptClass)) {
    341                     isUnitTestCase = true;
    342                 }
    343             } catch (ClassNotFoundException e) {
    344                 // fall through
    345             }
    346         } catch (Throwable e) {
    347             // fall through
    348         }
    349         return isUnitTestCase;
    350     }
    351
    352     /***
    353      * Runs the given script text with command line arguments
    354      *
    355      * @param scriptText is the text content of the script
    356      * @param fileName   is the logical file name of the script (which is used to create the class name of the script)
    357      * @param args       the command line arguments to pass in
    358      */
    359     public void run(String scriptText, String fileName, String[] args) throws CompilationFailedException, IOException {
    360         run(new ByteArrayInputStream(scriptText.getBytes()), fileName, args);
    361     }
    362
    363     /***
    364      * Runs the given script with command line arguments
    365      *
    366      * @param in       the stream reading the script
    367      * @param fileName is the logical file name of the script (which is used to create the class name of the script)
    368      * @param args     the command line arguments to pass in
    369      */
    370     public Object run(final InputStream in, final String fileName, String[] args) throws CompilationFailedException, IOException {
    371         GroovyCodeSource gcs = (GroovyCodeSource) AccessController.doPrivileged(new PrivilegedAction() {
    372             public Object run() {
    373                 return new GroovyCodeSource(in, fileName, "/groovy/shell");
    374             }
    375         });
    376         Class scriptClass = parseClass(gcs);
    377         runMainOrTestOrRunnable(scriptClass, args);
    378         return null;
    379     }
    380
    381     public Object getVariable(String name) {
    382         return context.getVariables().get(name);
    383     }
    384
    385     public void setVariable(String name, Object value) {
    386         context.setVariable(name, value);
    387     }
    388
    389     /***
    390      * Evaluates some script against the current Binding and returns the result
    391      *
    392      * @param codeSource
    393      * @return
    394      * @throws CompilationFailedException
    395      * @throws IOException
    396      */
    397     public Object evaluate(GroovyCodeSource codeSource) throws CompilationFailedException, IOException {
    398         Script script = parse(codeSource);
    399         return script.run();
    400     }
    401
    402     /***
    403      * Evaluates some script against the current Binding and returns the result
    404      *
    405      * @param scriptText the text of the script
    406      * @param fileName   is the logical file name of the script (which is used to create the class name of the script)
    407      */
    408     public Object evaluate(String scriptText, String fileName) throws CompilationFailedException, ClassNotFoundException, IOException {
    409         return evaluate(new ByteArrayInputStream(scriptText.getBytes()), fileName);
    410     }
    411
    412     /***
    413      * Evaluates some script against the current Binding and returns the result.
    414      * The .class file created from the script is given the supplied codeBase
    415      */
    416     public Object evaluate(String scriptText, String fileName, String codeBase) throws CompilationFailedException, IOException {
    417         return evaluate(new GroovyCodeSource(new ByteArrayInputStream(scriptText.getBytes()), fileName, codeBase));
    418     }
    419
    420     /***
    421      * Evaluates some script against the current Binding and returns the result
    422      *
    423      * @param file is the file of the script (which is used to create the class name of the script)
    424      */
    425     public Object evaluate(File file) throws CompilationFailedException, IOException {
    426         return evaluate(new GroovyCodeSource(file));
    427     }
    428
    429     /***
    430      * Evaluates some script against the current Binding and returns the result
    431      *
    432      * @param scriptText the text of the script
    433      */
    434     public Object evaluate(String scriptText) throws CompilationFailedException, IOException {
    435         return evaluate(new ByteArrayInputStream(scriptText.getBytes()), generateScriptName());
    436     }
    437
    438     /***
    439      * Evaluates some script against the current Binding and returns the result
    440      *
    441      * @param in the stream reading the script
    442      */
    443     public Object evaluate(InputStream in) throws CompilationFailedException, IOException {
    444         return evaluate(in, generateScriptName());
    445     }
    446
    447     /***
    448      * Evaluates some script against the current Binding and returns the result
    449      *
    450      * @param in       the stream reading the script
    451      * @param fileName is the logical file name of the script (which is used to create the class name of the script)
    452      */
    453     public Object evaluate(InputStream in, String fileName) throws CompilationFailedException, IOException {
    454         Script script = null;
    455         try {
    456             script = parse(in, fileName);
    457             return script.run();
    458         } finally {
    459             if (script != null) {
    460                 InvokerHelper.removeClass(script.getClass());
    461             }
    462         }
    463     }
    464
    465     /***
    466      * Parses the given script and returns it ready to be run
    467      *
    468      * @param in       the stream reading the script
    469      * @param fileName is the logical file name of the script (which is used to create the class name of the script)
    470      * @return the parsed script which is ready to be run via @link Script.run()
    471      */
    472     public Script parse(final InputStream in, final String fileName) throws CompilationFailedException, IOException {
    473         GroovyCodeSource gcs = (GroovyCodeSource) AccessController.doPrivileged(new PrivilegedAction() {
    474             public Object run() {
    475                 return new GroovyCodeSource(in, fileName, "/groovy/shell");
    476             }
    477         });
    478         return parse(gcs);
    479     }
    480
    481     /***
    482      * Parses the groovy code contained in codeSource and returns a java class.
    483      */
    484     private Class parseClass(final GroovyCodeSource codeSource) throws CompilationFailedException, IOException {
    485         // Don't cache scripts
    486         return loader.parseClass(codeSource, false);
    487     }
    488
    489     /***
    490      * Parses the given script and returns it ready to be run.  When running in a secure environment
    491      * (-Djava.security.manager) codeSource.getCodeSource() determines what policy grants should be
    492      * given to the script.
    493      *
    494      * @param codeSource
    495      * @return
    496      */
    497     public Script parse(final GroovyCodeSource codeSource) throws CompilationFailedException, IOException {
    498         return InvokerHelper.createScript(parseClass(codeSource), context);
    499     }
    500
    501     /***
    502      * Parses the given script and returns it ready to be run
    503      *
    504      * @param file is the file of the script (which is used to create the class name of the script)
    505      */
    506     public Script parse(File file) throws CompilationFailedException, IOException {
    507         return parse(new GroovyCodeSource(file));
    508     }
    509
    510     /***
    511      * Parses the given script and returns it ready to be run
    512      *
    513      * @param scriptText the text of the script
    514      */
    515     public Script parse(String scriptText) throws CompilationFailedException, IOException {
    516         return parse(new ByteArrayInputStream(scriptText.getBytes()), generateScriptName());
    517     }
    518
    519     public Script parse(String scriptText, String fileName) throws CompilationFailedException, IOException {
    520         return parse(new ByteArrayInputStream(scriptText.getBytes()), fileName);
    521     }
    522
    523     /***
    524      * Parses the given script and returns it ready to be run
    525      *
    526      * @param in the stream reading the script
    527      */
    528     public Script parse(InputStream in) throws CompilationFailedException, IOException {
    529         return parse(in, generateScriptName());
    530     }
    531
    532     protected synchronized String generateScriptName() {
    533         return "Script" + (++counter) + ".groovy";
    534     }
    535 }