Save This Page
Home » groovy-src-1.6.3 » groovy » lang » [javadoc | source]
    1   /*
    2    * Copyright 2003-2007 the original author or authors.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *     http://www.apache.org/licenses/LICENSE-2.0
    9    *
   10    * Unless required by applicable law or agreed to in writing, software
   11    * distributed under the License is distributed on an "AS IS" BASIS,
   12    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13    * See the License for the specific language governing permissions and
   14    * limitations under the License.
   15    */
   16   package groovy.lang;
   17   
   18   import groovy.ui.GroovyMain;
   19   
   20   import org.codehaus.groovy.control.CompilationFailedException;
   21   import org.codehaus.groovy.control.CompilerConfiguration;
   22   import org.codehaus.groovy.runtime.InvokerHelper;
   23   
   24   import java.io;
   25   import java.lang.reflect.Constructor;
   26   import java.security.AccessController;
   27   import java.security.PrivilegedAction;
   28   import java.security.PrivilegedActionException;
   29   import java.security.PrivilegedExceptionAction;
   30   import java.util.List;
   31   import java.util.Map;
   32   
   33   /**
   34    * Represents a groovy shell capable of running arbitrary groovy scripts
   35    *
   36    * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
   37    * @author Guillaume Laforge
   38    * @version $Revision: 13745 $
   39    */
   40   public class GroovyShell extends GroovyObjectSupport {
   41          
   42       public static final String[] EMPTY_ARGS = {};
   43   
   44       
   45       private Binding context;
   46       private int counter;
   47       private CompilerConfiguration config;
   48       private GroovyClassLoader loader;
   49   
   50       public static void main(String[] args) {
   51           GroovyMain.main(args);
   52       }
   53   
   54       public GroovyShell() {
   55           this(null, new Binding());
   56       }
   57   
   58       public GroovyShell(Binding binding) {
   59           this(null, binding);
   60       }
   61   
   62       public GroovyShell(CompilerConfiguration config) {
   63           this(new Binding(), config);
   64       }
   65   
   66       public GroovyShell(Binding binding, CompilerConfiguration config) {
   67           this(null, binding, config);
   68       }
   69   
   70       public GroovyShell(ClassLoader parent, Binding binding) {
   71           this(parent, binding, CompilerConfiguration.DEFAULT);
   72       }
   73   
   74       public GroovyShell(ClassLoader parent) {
   75           this(parent, new Binding(), CompilerConfiguration.DEFAULT);
   76       }
   77       
   78       public GroovyShell(ClassLoader parent, Binding binding, final CompilerConfiguration config) {
   79           if (binding == null) {
   80               throw new IllegalArgumentException("Binding must not be null.");
   81           }
   82           if (config == null) {
   83               throw new IllegalArgumentException("Compiler configuration must not be null.");
   84           }
   85           final ClassLoader parentLoader = (parent!=null)?parent:GroovyShell.class.getClassLoader();
   86           this.loader = (GroovyClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
   87               public Object run() {
   88                   return new GroovyClassLoader(parentLoader,config);
   89               }
   90           });
   91           this.context = binding;        
   92           this.config = config;
   93       }
   94       
   95       public void initializeBinding() {
   96           Map map = context.getVariables();
   97           if (map.get("shell")==null) map.put("shell",this);
   98       }
   99       
  100       public void resetLoadedClasses() {
  101           loader.clearCache();
  102       }
  103   
  104       /**
  105        * Creates a child shell using a new ClassLoader which uses the parent shell's
  106        * class loader as its parent
  107        *
  108        * @param shell is the parent shell used for the variable bindings and the parent class loader
  109        */
  110       public GroovyShell(GroovyShell shell) {
  111           this(shell.loader, shell.context);
  112       }
  113   
  114       public Binding getContext() {
  115           return context;
  116       }
  117   
  118       public GroovyClassLoader getClassLoader() {
  119           return loader;
  120       }
  121   
  122       public Object getProperty(String property) {
  123           Object answer = getVariable(property);
  124           if (answer == null) {
  125               answer = super.getProperty(property);
  126           }
  127           return answer;
  128       }
  129   
  130       public void setProperty(String property, Object newValue) {
  131           setVariable(property, newValue);
  132           try {
  133               super.setProperty(property, newValue);
  134           } catch (GroovyRuntimeException e) {
  135               // ignore, was probably a dynamic property
  136           }
  137       }
  138   
  139       /**
  140        * A helper method which runs the given script file with the given command line arguments
  141        *
  142        * @param scriptFile the file of the script to run
  143        * @param list       the command line arguments to pass in
  144        */
  145       public Object run(File scriptFile, List list) throws CompilationFailedException, IOException {
  146           String[] args = new String[list.size()];
  147           return run(scriptFile, (String[]) list.toArray(args));
  148       }
  149   
  150       /**
  151        * A helper method which runs the given cl script with the given command line arguments
  152        *
  153        * @param scriptText is the text content of the script
  154        * @param fileName   is the logical file name of the script (which is used to create the class name of the script)
  155        * @param list       the command line arguments to pass in
  156        */
  157       public Object run(String scriptText, String fileName, List list) throws CompilationFailedException {
  158           String[] args = new String[list.size()];
  159           list.toArray(args);
  160           return run(scriptText, fileName, args);
  161       }
  162   
  163       /**
  164        * Runs the given script file name with the given command line arguments
  165        *
  166        * @param scriptFile the file name of the script to run
  167        * @param args       the command line arguments to pass in
  168        */
  169       public Object run(final File scriptFile, String[] args) throws CompilationFailedException, IOException {
  170           String scriptName = scriptFile.getName();
  171           int p = scriptName.lastIndexOf(".");
  172           if (p++ >= 0) {
  173               if (scriptName.substring(p).equals("java")) {
  174                   System.err.println("error: cannot compile file with .java extension: " + scriptName);
  175                   throw new CompilationFailedException(0, null);
  176               }
  177           }
  178   
  179           // Get the current context classloader and save it on the stack
  180           final Thread thread = Thread.currentThread();
  181           //ClassLoader currentClassLoader = thread.getContextClassLoader();
  182   
  183           class DoSetContext implements PrivilegedAction {
  184               ClassLoader classLoader;
  185   
  186               public DoSetContext(ClassLoader loader) {
  187                   classLoader = loader;
  188               }
  189   
  190               public Object run() {
  191                   thread.setContextClassLoader(classLoader);
  192                   return null;
  193               }
  194           }
  195   
  196           AccessController.doPrivileged(new DoSetContext(loader));
  197   
  198           // Parse the script, generate the class, and invoke the main method.  This is a little looser than
  199           // if you are compiling the script because the JVM isn't executing the main method.
  200           Class scriptClass;
  201           try {
  202               scriptClass = (Class) AccessController.doPrivileged(new PrivilegedExceptionAction() {
  203                   public Object run() throws CompilationFailedException, IOException {
  204                       return loader.parseClass(scriptFile);
  205                   }
  206               });
  207           } catch (PrivilegedActionException pae) {
  208               Exception e = pae.getException();
  209               if (e instanceof CompilationFailedException) {
  210                   throw (CompilationFailedException) e;
  211               } else if (e instanceof IOException) {
  212                   throw (IOException) e;
  213               } else {
  214                   throw (RuntimeException) pae.getException();
  215               }
  216           }
  217   
  218           return runScriptOrMainOrTestOrRunnable(scriptClass, args);
  219   
  220           // Set the context classloader back to what it was.
  221           //AccessController.doPrivileged(new DoSetContext(currentClassLoader));
  222       }
  223   
  224       /**
  225        * if (theClass is a Script) {
  226        * run it like a script
  227        * } else if (theClass has a main method) {
  228        * run the main method
  229        * } else if (theClass instanceof GroovyTestCase) {
  230        * use the test runner to run it
  231        * } else if (theClass implements Runnable) {
  232        * if (theClass has a constructor with String[] params)
  233        * instanciate theClass with this constructor and run
  234        * else if (theClass has a no-args constructor)
  235        * instanciate theClass with the no-args constructor and run
  236        * }
  237        */
  238       private Object runScriptOrMainOrTestOrRunnable(Class scriptClass, String[] args) {
  239           if (scriptClass == null) {
  240               return null;
  241           }
  242           try {
  243               if (Script.class.isAssignableFrom(scriptClass)) {
  244                   // treat it just like a script if it is one
  245                   Script script  = null;
  246                   try {
  247                       script = (Script) scriptClass.newInstance();
  248                   } catch (InstantiationException e) {
  249                       // ignore instaintiations errors,, try to do main
  250                   } catch (IllegalAccessException e) {
  251                      // ignore instaintiations errors, try to do main
  252                   }
  253                   if (script != null) {
  254                       script.setBinding(context);
  255                       script.setProperty("args", args);
  256                       return script.run();
  257                   }
  258               }
  259               // let's find a main method
  260               scriptClass.getMethod("main", new Class[]{String[].class});
  261               // if that main method exist, invoke it
  262               return InvokerHelper.invokeMethod(scriptClass, "main", new Object[]{args});
  263           } catch (NoSuchMethodException e) {
  264               // if it implements Runnable, try to instantiate it
  265               if (Runnable.class.isAssignableFrom(scriptClass)) {
  266                   return runRunnable(scriptClass, args);
  267               }
  268               // if it's a JUnit 3.8.x test, run it with an appropriate runner
  269               if (isJUnit3Test(scriptClass)) {
  270                   return runJUnit3Test(scriptClass);
  271               }
  272               // if it's a JUnit 4.x test, run it with an appropriate runner
  273               if (isJUnit4Test(scriptClass)) {
  274                   return runJUnit4Test(scriptClass);
  275               }
  276               // if it's a TestNG tst, run it with an appropriate runner
  277               if (isTestNgTest(scriptClass)) {
  278                   return runTestNgTest(scriptClass);
  279               }
  280               throw new GroovyRuntimeException("This script or class could not be run.\n" +
  281                       "It should either: \n" +
  282                       "- have a main method, \n" +
  283                       "- be a JUnit test, TestNG test or extend GroovyTestCase, \n" +
  284                       "- or implement the Runnable interface.");
  285           }
  286       }
  287   
  288       private Object runRunnable(Class scriptClass, String[] args) {
  289           Constructor constructor = null;
  290           Runnable runnable = null;
  291           Throwable reason = null;
  292           try {
  293               // first, fetch the constructor taking String[] as parameter
  294               constructor = scriptClass.getConstructor(new Class[]{(new String[]{}).getClass()});
  295               try {
  296                   // instantiate a runnable and run it
  297                   runnable = (Runnable) constructor.newInstance(new Object[]{args});
  298               } catch (Throwable t) {
  299                   reason = t;
  300               }
  301           } catch (NoSuchMethodException e1) {
  302               try {
  303                   // otherwise, find the default constructor
  304                   constructor = scriptClass.getConstructor(new Class[]{});
  305                   try {
  306                       // instantiate a runnable and run it
  307                       runnable = (Runnable) constructor.newInstance(new Object[]{});
  308                   } catch (Throwable t) {
  309                       reason = t;
  310                   }
  311               } catch (NoSuchMethodException nsme) {
  312                   reason = nsme;
  313               }
  314           }
  315           if (constructor != null && runnable != null) {
  316               runnable.run();
  317           } else {
  318               throw new GroovyRuntimeException("This script or class was runnable but could not be run. ", reason);
  319           }
  320           return null;
  321       }
  322   
  323       /**
  324        * Run the specified class extending TestCase as a unit test.
  325        * This is done through reflection, to avoid adding a dependency to the JUnit framework.
  326        * Otherwise, developers embedding Groovy and using GroovyShell to load/parse/compile
  327        * groovy scripts and classes would have to add another dependency on their classpath.
  328        *
  329        * @param scriptClass the class to be run as a unit test
  330        */
  331       private Object runJUnit3Test(Class scriptClass) {
  332           try {
  333               Object testSuite = InvokerHelper.invokeConstructorOf("junit.framework.TestSuite",new Object[]{scriptClass});
  334               return InvokerHelper.invokeStaticMethod("junit.textui.TestRunner", "run", new Object[]{testSuite});
  335           } catch (ClassNotFoundException e) {
  336               throw new GroovyRuntimeException("Failed to run the unit test. JUnit is not on the Classpath.");
  337           }
  338       }
  339   
  340       private Object runJUnit4Test(Class scriptClass) {
  341           try {
  342               return InvokerHelper.invokeStaticMethod("org.codehaus.groovy.vmplugin.v5.JUnit4Utils",
  343                       "realRunJUnit4Test", new Object[]{scriptClass});
  344           } catch (ClassNotFoundException e) {
  345               throw new GroovyRuntimeException("Failed to run the JUnit 4 test.");
  346           }
  347       }
  348   
  349       private Object runTestNgTest(Class scriptClass) {
  350           try {
  351               return InvokerHelper.invokeStaticMethod("org.codehaus.groovy.vmplugin.v5.TestNgUtils",
  352                       "realRunTestNgTest", new Object[]{scriptClass});
  353           } catch (ClassNotFoundException e) {
  354               throw new GroovyRuntimeException("Failed to run the TestNG test.");
  355           }
  356       }
  357   
  358       /**
  359        * Utility method to check through reflection if the class appears to be a
  360        * JUnit 3.8.x test, i.e.&nsbp;checks if it extends JUnit 3.8.x's TestCase.
  361        *
  362        * @param scriptClass the class we want to check
  363        * @return true if the class appears to be a test
  364        */
  365       private boolean isJUnit3Test(Class scriptClass) {
  366           // check if the parsed class is a GroovyTestCase,
  367           // so that it is possible to run it as a JUnit test
  368           boolean isUnitTestCase = false;
  369           try {
  370               try {
  371                   Class testCaseClass = this.loader.loadClass("junit.framework.TestCase");
  372                   // if scriptClass extends testCaseClass
  373                   if (testCaseClass.isAssignableFrom(scriptClass)) {
  374                       isUnitTestCase = true;
  375                   }
  376               } catch (ClassNotFoundException e) {
  377                   // fall through
  378               }
  379           } catch (Throwable e) {
  380               // fall through
  381           }
  382           return isUnitTestCase;
  383       }
  384   
  385       /**
  386        * Utility method to check via reflection if the parsed class appears to be a JUnit4
  387        * test, i.e.&nsbp;checks whether it appears to be using the relevant JUnit 4 annotations.
  388        *
  389        * @param scriptClass the class we want to check
  390        * @return true if the class appears to be a test
  391        */
  392       private boolean isJUnit4Test(Class scriptClass) {
  393           // if we are running under Java 1.4 don't bother trying to check
  394           char version = System.getProperty("java.version").charAt(2);
  395           if (version < '5') {
  396               return false;
  397           }
  398   
  399           // check if there are appropriate class or method annotations
  400           // that suggest we have a JUnit 4 test
  401           boolean isTest = false;
  402   
  403           try {
  404               if (InvokerHelper.invokeStaticMethod("org.codehaus.groovy.vmplugin.v5.JUnit4Utils",
  405                       "realIsJUnit4Test", new Object[]{scriptClass, this.loader}) == Boolean.TRUE) {
  406                   isTest = true;
  407               };
  408           } catch (ClassNotFoundException e) {
  409               throw new GroovyRuntimeException("Failed to invoke the JUnit 4 helper class.");
  410           }
  411           return isTest;
  412       }
  413   
  414       /**
  415        * Utility method to check via reflection if the parsed class appears to be a TestNG
  416        * test, i.e.&nsbp;checks whether it appears to be using the relevant TestNG annotations.
  417        *
  418        * @param scriptClass the class we want to check
  419        * @return true if the class appears to be a test
  420        */
  421       private boolean isTestNgTest(Class scriptClass) {
  422           char version = System.getProperty("java.version").charAt(2);
  423           if (version < '5') {
  424               return false;
  425           }
  426   
  427           // check if there are appropriate class or method annotations
  428           // that suggest we have a TestNG test
  429           boolean isTest = false;
  430   
  431           try {
  432               if (InvokerHelper.invokeStaticMethod("org.codehaus.groovy.vmplugin.v5.TestNgUtils",
  433                       "realIsTestNgTest", new Object[]{scriptClass, this.loader}) == Boolean.TRUE) {
  434                   isTest = true;
  435               };
  436           } catch (ClassNotFoundException e) {
  437               throw new GroovyRuntimeException("Failed to invoke the TestNG helper class.");
  438           }
  439           return isTest;
  440       }
  441   
  442       /**
  443        * Runs the given script text with command line arguments
  444        *
  445        * @param scriptText is the text content of the script
  446        * @param fileName   is the logical file name of the script (which is used to create the class name of the script)
  447        * @param args       the command line arguments to pass in
  448        */
  449       public Object run(String scriptText, String fileName, String[] args) throws CompilationFailedException {
  450           try {
  451               return run(new ByteArrayInputStream(scriptText.getBytes(config.getSourceEncoding())), fileName, args);
  452           } catch (UnsupportedEncodingException e) {
  453               throw new CompilationFailedException(0, null, e);
  454           }
  455       }
  456   
  457       /**
  458        * Runs the given script with command line arguments
  459        *
  460        * @param in       the stream reading the script
  461        * @param fileName is the logical file name of the script (which is used to create the class name of the script)
  462        * @param args     the command line arguments to pass in
  463        */
  464       public Object run(final InputStream in, final String fileName, String[] args) throws CompilationFailedException {
  465           GroovyCodeSource gcs = (GroovyCodeSource) AccessController.doPrivileged(new PrivilegedAction() {
  466               public Object run() {
  467                   return new GroovyCodeSource(in, fileName, "/groovy/shell");
  468               }
  469           });
  470           Class scriptClass = parseClass(gcs);
  471           return runScriptOrMainOrTestOrRunnable(scriptClass, args);
  472       }
  473   
  474       public Object getVariable(String name) {
  475           return context.getVariables().get(name);
  476       }
  477   
  478       public void setVariable(String name, Object value) {
  479           context.setVariable(name, value);
  480       }
  481   
  482       /**
  483        * Evaluates some script against the current Binding and returns the result
  484        *
  485        * @param codeSource
  486        * @throws CompilationFailedException
  487        * @throws CompilationFailedException
  488        */
  489       public Object evaluate(GroovyCodeSource codeSource) throws CompilationFailedException {
  490           Script script = parse(codeSource);
  491           return script.run();
  492       }
  493   
  494       /**
  495        * Evaluates some script against the current Binding and returns the result
  496        *
  497        * @param scriptText the text of the script
  498        * @param fileName   is the logical file name of the script (which is used to create the class name of the script)
  499        */
  500       public Object evaluate(String scriptText, String fileName) throws CompilationFailedException {
  501           try {
  502               return evaluate(new ByteArrayInputStream(scriptText.getBytes(config.getSourceEncoding())), fileName);
  503           } catch (UnsupportedEncodingException e) {
  504               throw new CompilationFailedException(0, null, e);
  505           }
  506       }
  507   
  508       /**
  509        * Evaluates some script against the current Binding and returns the result.
  510        * The .class file created from the script is given the supplied codeBase
  511        */
  512       public Object evaluate(String scriptText, String fileName, String codeBase) throws CompilationFailedException {
  513           try {
  514               return evaluate(new GroovyCodeSource(new ByteArrayInputStream(scriptText.getBytes(config.getSourceEncoding())), fileName, codeBase));
  515           } catch (UnsupportedEncodingException e) {
  516               throw new CompilationFailedException(0, null, e);
  517           }
  518       }
  519   
  520       /**
  521        * Evaluates some script against the current Binding and returns the result
  522        *
  523        * @param file is the file of the script (which is used to create the class name of the script)
  524        */
  525       public Object evaluate(File file) throws CompilationFailedException, IOException {
  526           return evaluate(new GroovyCodeSource(file));
  527       }
  528   
  529       /**
  530        * Evaluates some script against the current Binding and returns the result
  531        *
  532        * @param scriptText the text of the script
  533        */
  534       public Object evaluate(String scriptText) throws CompilationFailedException {
  535           try {
  536               return evaluate(new ByteArrayInputStream(scriptText.getBytes(config.getSourceEncoding())), generateScriptName());
  537           } catch (UnsupportedEncodingException e) {
  538               throw new CompilationFailedException(0, null, e);
  539           }
  540       }
  541   
  542       /**
  543        * Evaluates some script against the current Binding and returns the result
  544        *
  545        * @param in the stream reading the script
  546        */
  547       public Object evaluate(InputStream in) throws CompilationFailedException {
  548           return evaluate(in, generateScriptName());
  549       }
  550   
  551       /**
  552        * Evaluates some script against the current Binding and returns the result
  553        *
  554        * @param in       the stream reading the script
  555        * @param fileName is the logical file name of the script (which is used to create the class name of the script)
  556        */
  557       public Object evaluate(InputStream in, String fileName) throws CompilationFailedException {
  558           Script script = null;
  559           try {
  560               script = parse(in, fileName);
  561               return script.run();
  562           } finally {
  563               if (script != null) {
  564                   InvokerHelper.removeClass(script.getClass());
  565               }
  566           }
  567       }
  568   
  569       /**
  570        * Parses the given script and returns it ready to be run
  571        *
  572        * @param in       the stream reading the script
  573        * @param fileName is the logical file name of the script (which is used to create the class name of the script)
  574        * @return the parsed script which is ready to be run via @link Script.run()
  575        */
  576       public Script parse(final InputStream in, final String fileName) throws CompilationFailedException {
  577           GroovyCodeSource gcs = (GroovyCodeSource) AccessController.doPrivileged(new PrivilegedAction() {
  578               public Object run() {
  579                   return new GroovyCodeSource(in, fileName, "/groovy/shell");
  580               }
  581           });
  582           return parse(gcs);
  583       }
  584   
  585       /**
  586        * Parses the groovy code contained in codeSource and returns a java class.
  587        */
  588       private Class parseClass(final GroovyCodeSource codeSource) throws CompilationFailedException {
  589           // Don't cache scripts
  590           return loader.parseClass(codeSource, false);
  591       }
  592   
  593       /**
  594        * Parses the given script and returns it ready to be run.  When running in a secure environment
  595        * (-Djava.security.manager) codeSource.getCodeSource() determines what policy grants should be
  596        * given to the script.
  597        *
  598        * @param codeSource
  599        * @return ready to run script
  600        */
  601       public Script parse(final GroovyCodeSource codeSource) throws CompilationFailedException {
  602           return InvokerHelper.createScript(parseClass(codeSource), context);
  603       }
  604   
  605       /**
  606        * Parses the given script and returns it ready to be run
  607        *
  608        * @param file is the file of the script (which is used to create the class name of the script)
  609        */
  610       public Script parse(File file) throws CompilationFailedException, IOException {
  611           return parse(new GroovyCodeSource(file));
  612       }
  613   
  614       /**
  615        * Parses the given script and returns it ready to be run
  616        *
  617        * @param scriptText the text of the script
  618        */
  619       public Script parse(String scriptText) throws CompilationFailedException {
  620           try {
  621               return parse(new ByteArrayInputStream(scriptText.getBytes(config.getSourceEncoding())), generateScriptName());
  622           } catch (UnsupportedEncodingException e) {
  623               throw new CompilationFailedException(0, null, e);
  624           }
  625       }
  626   
  627       public Script parse(String scriptText, String fileName) throws CompilationFailedException {
  628           try {
  629               return parse(new ByteArrayInputStream(scriptText.getBytes(config.getSourceEncoding())), fileName);
  630           } catch (UnsupportedEncodingException e) {
  631               throw new CompilationFailedException(0, null, e);
  632           }
  633       }
  634   
  635       /**
  636        * Parses the given script and returns it ready to be run
  637        *
  638        * @param in the stream reading the script
  639        */
  640       public Script parse(InputStream in) throws CompilationFailedException {
  641           return parse(in, generateScriptName());
  642       }
  643   
  644       protected synchronized String generateScriptName() {
  645           return "Script" + (++counter) + ".groovy";
  646       }
  647   }

Save This Page
Home » groovy-src-1.6.3 » groovy » lang » [javadoc | source]