/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package groovy.lang; import groovy.ui.GroovyMain; import groovy.security.GroovyCodeSourcePermission; import org.codehaus.groovy.control.CompilationFailedException; import org.codehaus.groovy.control.CompilerConfiguration; import org.codehaus.groovy.plugin.GroovyRunner; import org.codehaus.groovy.runtime.InvokerHelper; import org.codehaus.groovy.runtime.InvokerInvocationException; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.List; import java.util.Map; /** * Represents a groovy shell capable of running arbitrary groovy scripts * * @author <a href="mailto:james@coredevelopers.net">James Strachan</a> * @author Guillaume Laforge * @author Paul King */ public class GroovyShell extends GroovyObjectSupport { public static final String DEFAULT_CODE_BASE = "/groovy/shell"; private final Binding context; private int counter; private final CompilerConfiguration config; private GroovyClassLoader loader; public static void main(String[] args) { GroovyMain.main(args); } public GroovyShell() { this(null, new Binding()); } public GroovyShell(Binding binding) { this(null, binding); } public GroovyShell(ClassLoader parent, CompilerConfiguration config) { this(parent, new Binding(), config); } public GroovyShell(CompilerConfiguration config) { this(new Binding(), config); } public GroovyShell(Binding binding, CompilerConfiguration config) { this(null, binding, config); } public GroovyShell(ClassLoader parent, Binding binding) { this(parent, binding, CompilerConfiguration.DEFAULT); } public GroovyShell(ClassLoader parent) { this(parent, new Binding(), CompilerConfiguration.DEFAULT); } public GroovyShell(ClassLoader parent, Binding binding, final CompilerConfiguration config) { if (binding == null) { throw new IllegalArgumentException("Binding must not be null."); } if (config == null) { throw new IllegalArgumentException("Compiler configuration must not be null."); } final ClassLoader parentLoader = (parent!=null)?parent:GroovyShell.class.getClassLoader(); this.loader = AccessController.doPrivileged(new PrivilegedAction<GroovyClassLoader>() { public GroovyClassLoader run() { return new GroovyClassLoader(parentLoader,config); } }); this.context = binding; this.config = config; } public void resetLoadedClasses() { loader.clearCache(); } /** * Creates a child shell using a new ClassLoader which uses the parent shell's * class loader as its parent * * @param shell is the parent shell used for the variable bindings and the parent class loader */ public GroovyShell(GroovyShell shell) { this(shell.loader, shell.context); } public Binding getContext() { return context; } public GroovyClassLoader getClassLoader() { return loader; } public Object getProperty(String property) { Object answer = getVariable(property); if (answer == null) { answer = super.getProperty(property); } return answer; } public void setProperty(String property, Object newValue) { setVariable(property, newValue); try { super.setProperty(property, newValue); } catch (GroovyRuntimeException e) { // ignore, was probably a dynamic property } } // // FIXME: Use List<String> here, current version is not safe // /** * A helper method which runs the given script file with the given command line arguments * * @param scriptFile the file of the script to run * @param list the command line arguments to pass in */ public Object run(File scriptFile, List list) throws CompilationFailedException, IOException { String[] args = new String[list.size()]; return run(scriptFile, (String[]) list.toArray(args)); } /** * A helper method which runs the given cl script with the given command line arguments * * @param scriptText is the text content of the script * @param fileName is the logical file name of the script (which is used to create the class name of the script) * @param list the command line arguments to pass in */ public Object run(String scriptText, String fileName, List list) throws CompilationFailedException { String[] args = new String[list.size()]; list.toArray(args); return run(scriptText, fileName, args); } /** * Runs the given script file name with the given command line arguments * * @param scriptFile the file name of the script to run * @param args the command line arguments to pass in */ public Object run(final File scriptFile, String[] args) throws CompilationFailedException, IOException { String scriptName = scriptFile.getName(); int p = scriptName.lastIndexOf("."); if (p++ >= 0) { if (scriptName.substring(p).equals("java")) { throw new CompilationFailedException(0, null); } } // Get the current context classloader and save it on the stack final Thread thread = Thread.currentThread(); //ClassLoader currentClassLoader = thread.getContextClassLoader(); class DoSetContext implements PrivilegedAction { ClassLoader classLoader; public DoSetContext(ClassLoader loader) { classLoader = loader; } public Object run() { thread.setContextClassLoader(classLoader); return null; } } AccessController.doPrivileged(new DoSetContext(loader)); // Parse the script, generate the class, and invoke the main method. This is a little looser than // if you are compiling the script because the JVM isn't executing the main method. Class scriptClass; try { scriptClass = AccessController.doPrivileged(new PrivilegedExceptionAction<Class>() { public Class run() throws CompilationFailedException, IOException { return loader.parseClass(scriptFile); } }); } catch (PrivilegedActionException pae) { Exception e = pae.getException(); if (e instanceof CompilationFailedException) { throw (CompilationFailedException) e; } else if (e instanceof IOException) { throw (IOException) e; } else { throw (RuntimeException) pae.getException(); } } return runScriptOrMainOrTestOrRunnable(scriptClass, args); // Set the context classloader back to what it was. //AccessController.doPrivileged(new DoSetContext(currentClassLoader)); } /** * if (theClass is a Script) { * run it like a script * } else if (theClass has a main method) { * run the main method * } else if (theClass instanceof GroovyTestCase) { * use the test runner to run it * } else if (theClass implements Runnable) { * if (theClass has a constructor with String[] params) * instantiate theClass with this constructor and run * else if (theClass has a no-args constructor) * instantiate theClass with the no-args constructor and run * } */ private Object runScriptOrMainOrTestOrRunnable(Class scriptClass, String[] args) { // Always set the "args" property, regardless of what path we take in the code. // Bad enough to have side effects but worse if their behavior is wonky. context.setProperty("args", args); if (scriptClass == null) { return null; } //TODO: This logic mostly duplicates InvokerHelper.createScript. They should probably be unified. if (Script.class.isAssignableFrom(scriptClass)) { // treat it just like a script if it is one try { Constructor constructor = scriptClass.getConstructor(Binding.class); Script script = (Script) constructor.newInstance(context); return script.run(); } catch (InstantiationException e) { // ignore instantiation errors,, try to do main } catch (IllegalAccessException e) { // ignore instantiation errors, try to do main } catch (NoSuchMethodException e) { try { // Fallback for non-standard "Scripts" that don't have contextual constructor. Script script = (Script) scriptClass.newInstance(); script.setBinding(context); return script.run(); } catch (InstantiationException e1) { // ignore instantiation errors, try to do main } catch (IllegalAccessException e1) { // ignore instantiation errors, try to do main } } catch (InvocationTargetException e) { // ignore instantiation errors, try to do main } } try { // let's find a main method scriptClass.getMethod("main", new Class[]{String[].class}); // if that main method exist, invoke it return InvokerHelper.invokeMethod(scriptClass, "main", new Object[]{args}); } catch (NoSuchMethodException e) { // if it implements Runnable, try to instantiate it if (Runnable.class.isAssignableFrom(scriptClass)) { return runRunnable(scriptClass, args); } // if it's a JUnit 3.8.x test, run it with an appropriate runner if (isJUnit3Test(scriptClass)) { return runJUnit3Test(scriptClass); } // if it's a JUnit 3.8.x test suite, run it with an appropriate runner if (isJUnit3TestSuite(scriptClass)) { return runJUnit3TestSuite(scriptClass); } // if it's a JUnit 4.x test, run it with an appropriate runner if (isJUnit4Test(scriptClass)) { return runJUnit4Test(scriptClass); } for (Map.Entry<String, GroovyRunner> entry : GroovySystem.RUNNER_REGISTRY.entrySet()) { GroovyRunner runner = entry.getValue(); if (runner != null && runner.canRun(scriptClass, this.loader)) { return runner.run(scriptClass, this.loader); } } String message = "This script or class could not be run.\n" + "It should either:\n" + "- have a main method,\n" + "- be a JUnit test or extend GroovyTestCase,\n" + "- implement the Runnable interface,\n" + "- or be compatible with a registered script runner. Known runners:\n"; if (GroovySystem.RUNNER_REGISTRY.isEmpty()) { message += " * <none>"; } for (Map.Entry<String, GroovyRunner> entry : GroovySystem.RUNNER_REGISTRY.entrySet()) { message += " * " + entry.getKey() + "\n"; } throw new GroovyRuntimeException(message); } } private static Object runRunnable(Class scriptClass, String[] args) { Constructor constructor = null; Runnable runnable = null; Throwable reason = null; try { // first, fetch the constructor taking String[] as parameter constructor = scriptClass.getConstructor(new Class[]{(new String[]{}).getClass()}); try { // instantiate a runnable and run it runnable = (Runnable) constructor.newInstance(new Object[]{args}); } catch (Throwable t) { reason = t; } } catch (NoSuchMethodException e1) { try { // otherwise, find the default constructor constructor = scriptClass.getConstructor(new Class[]{}); try { // instantiate a runnable and run it runnable = (Runnable) constructor.newInstance(); } catch (InvocationTargetException ite) { throw new InvokerInvocationException(ite.getTargetException()); } catch (Throwable t) { reason = t; } } catch (NoSuchMethodException nsme) { reason = nsme; } } if (constructor != null && runnable != null) { runnable.run(); } else { throw new GroovyRuntimeException("This script or class was runnable but could not be run. ", reason); } return null; } /** * Run the specified class extending TestCase as a unit test. * This is done through reflection, to avoid adding a dependency to the JUnit framework. * Otherwise, developers embedding Groovy and using GroovyShell to load/parse/compile * groovy scripts and classes would have to add another dependency on their classpath. * * @param scriptClass the class to be run as a unit test */ private static Object runJUnit3Test(Class scriptClass) { try { Object testSuite = InvokerHelper.invokeConstructorOf("junit.framework.TestSuite", new Object[]{scriptClass}); return InvokerHelper.invokeStaticMethod("junit.textui.TestRunner", "run", new Object[]{testSuite}); } catch (ClassNotFoundException e) { throw new GroovyRuntimeException("Failed to run the unit test. JUnit is not on the Classpath.", e); } } /** * Run the specified class extending TestSuite as a unit test. * This is done through reflection, to avoid adding a dependency to the JUnit framework. * Otherwise, developers embedding Groovy and using GroovyShell to load/parse/compile * groovy scripts and classes would have to add another dependency on their classpath. * * @param scriptClass the class to be run as a unit test */ private static Object runJUnit3TestSuite(Class scriptClass) { try { Object testSuite = InvokerHelper.invokeStaticMethod(scriptClass, "suite", new Object[]{}); return InvokerHelper.invokeStaticMethod("junit.textui.TestRunner", "run", new Object[]{testSuite}); } catch (ClassNotFoundException e) { throw new GroovyRuntimeException("Failed to run the unit test. JUnit is not on the Classpath.", e); } } private Object runJUnit4Test(Class scriptClass) { try { return InvokerHelper.invokeStaticMethod("org.codehaus.groovy.vmplugin.v5.JUnit4Utils", "realRunJUnit4Test", new Object[]{scriptClass, this.loader}); } catch (ClassNotFoundException e) { throw new GroovyRuntimeException("Failed to run the JUnit 4 test.", e); } } /** * Utility method to check through reflection if the class appears to be a * JUnit 3.8.x test, i.e. checks if it extends JUnit 3.8.x's TestCase. * * @param scriptClass the class we want to check * @return true if the class appears to be a test */ private boolean isJUnit3Test(Class scriptClass) { // check if the parsed class is a GroovyTestCase, // so that it is possible to run it as a JUnit test boolean isUnitTestCase = false; try { try { Class testCaseClass = this.loader.loadClass("junit.framework.TestCase"); // if scriptClass extends testCaseClass if (testCaseClass.isAssignableFrom(scriptClass)) { isUnitTestCase = true; } } catch (ClassNotFoundException e) { // fall through } } catch (Throwable e) { // fall through } return isUnitTestCase; } /** * Utility method to check through reflection if the class appears to be a * JUnit 3.8.x test suite, i.e. checks if it extends JUnit 3.8.x's TestSuite. * * @param scriptClass the class we want to check * @return true if the class appears to be a test */ private boolean isJUnit3TestSuite(Class scriptClass) { // check if the parsed class is a TestSuite, // so that it is possible to run it as a JUnit test boolean isUnitTestSuite = false; try { try { Class testSuiteClass = this.loader.loadClass("junit.framework.TestSuite"); // if scriptClass extends TestSuiteClass if (testSuiteClass.isAssignableFrom(scriptClass)) { isUnitTestSuite = true; } } catch (ClassNotFoundException e) { // fall through } } catch (Throwable e) { // fall through } return isUnitTestSuite; } /** * Utility method to check via reflection if the parsed class appears to be a JUnit4 * test, i.e. checks whether it appears to be using the relevant JUnit 4 annotations. * * @param scriptClass the class we want to check * @return true if the class appears to be a test */ private boolean isJUnit4Test(Class scriptClass) { // check if there are appropriate class or method annotations // that suggest we have a JUnit 4 test boolean isTest = false; try { if (InvokerHelper.invokeStaticMethod("org.codehaus.groovy.vmplugin.v5.JUnit4Utils", "realIsJUnit4Test", new Object[]{scriptClass, this.loader}) == Boolean.TRUE) { isTest = true; } } catch (ClassNotFoundException e) { throw new GroovyRuntimeException("Failed to invoke the JUnit 4 helper class.", e); } return isTest; } /** * Runs the given script text with command line arguments * * @param scriptText is the text content of the script * @param fileName is the logical file name of the script (which is used to create the class name of the script) * @param args the command line arguments to pass in */ public Object run(final String scriptText, final String fileName, String[] args) throws CompilationFailedException { GroovyCodeSource gcs = AccessController.doPrivileged(new PrivilegedAction<GroovyCodeSource>() { public GroovyCodeSource run() { return new GroovyCodeSource(scriptText, fileName, DEFAULT_CODE_BASE); } }); return run(gcs, args); } /** * Runs the given script source with command line arguments * * @param source is the source content of the script * @param args the command line arguments to pass in */ public Object run(GroovyCodeSource source, List args) throws CompilationFailedException { return run(source, ((String[]) args.toArray(new String[args.size()]))); } /** * Runs the given script source with command line arguments * * @param source is the source content of the script * @param args the command line arguments to pass in */ public Object run(GroovyCodeSource source, String[] args) throws CompilationFailedException { Class scriptClass = parseClass(source); return runScriptOrMainOrTestOrRunnable(scriptClass, args); } /** * Runs the given script source with command line arguments * * @param source is the source content of the script * @param args the command line arguments to pass in */ public Object run(URI source, List args) throws CompilationFailedException, IOException { return run(new GroovyCodeSource(source), ((String[]) args.toArray(new String[args.size()]))); } /** * Runs the given script source with command line arguments * * @param source is the source content of the script * @param args the command line arguments to pass in */ public Object run(URI source, String[] args) throws CompilationFailedException, IOException { return run(new GroovyCodeSource(source), args); } /** * Runs the given script with command line arguments * * @param in the stream reading the script * @param fileName is the logical file name of the script (which is used to create the class name of the script) * @param list the command line arguments to pass in */ public Object run(final Reader in, final String fileName, List list) throws CompilationFailedException { return run(in, fileName, (String[]) list.toArray(new String[list.size()])); } /** * Runs the given script with command line arguments * * @param in the stream reading the script * @param fileName is the logical file name of the script (which is used to create the class name of the script) * @param args the command line arguments to pass in */ public Object run(final Reader in, final String fileName, String[] args) throws CompilationFailedException { GroovyCodeSource gcs = AccessController.doPrivileged(new PrivilegedAction<GroovyCodeSource>() { public GroovyCodeSource run() { return new GroovyCodeSource(in, fileName, DEFAULT_CODE_BASE); } }); Class scriptClass = parseClass(gcs); return runScriptOrMainOrTestOrRunnable(scriptClass, args); } public Object getVariable(String name) { return context.getVariables().get(name); } public void setVariable(String name, Object value) { context.setVariable(name, value); } /** * Evaluates some script against the current Binding and returns the result * * @param codeSource * @throws CompilationFailedException */ public Object evaluate(GroovyCodeSource codeSource) throws CompilationFailedException { Script script = parse(codeSource); return script.run(); } /** * Evaluates some script against the current Binding and returns the result * * @param scriptText the text of the script */ public Object evaluate(final String scriptText) throws CompilationFailedException { return evaluate(scriptText, generateScriptName(), DEFAULT_CODE_BASE); } /** * Evaluates some script against the current Binding and returns the result * * @param scriptText the text of the script * @param fileName is the logical file name of the script (which is used to create the class name of the script) */ public Object evaluate(String scriptText, String fileName) throws CompilationFailedException { return evaluate(scriptText, fileName, DEFAULT_CODE_BASE); } /** * Evaluates some script against the current Binding and returns the result. * The .class file created from the script is given the supplied codeBase */ public Object evaluate(final String scriptText, final String fileName, final String codeBase) throws CompilationFailedException { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new GroovyCodeSourcePermission(codeBase)); } GroovyCodeSource gcs = AccessController.doPrivileged(new PrivilegedAction<GroovyCodeSource>() { public GroovyCodeSource run() { return new GroovyCodeSource(scriptText, fileName, codeBase); } }); return evaluate(gcs); } /** * Evaluates some script against the current Binding and returns the result * * @param file is the file of the script (which is used to create the class name of the script) */ public Object evaluate(File file) throws CompilationFailedException, IOException { return evaluate(new GroovyCodeSource(file, config.getSourceEncoding())); } /** * Evaluates some script against the current Binding and returns the result * * @param uri is the URI of the script (which is used to create the class name of the script) */ public Object evaluate(URI uri) throws CompilationFailedException, IOException { return evaluate(new GroovyCodeSource(uri)); } /** * Evaluates some script against the current Binding and returns the result * * @param in the stream reading the script */ public Object evaluate(Reader in) throws CompilationFailedException { return evaluate(in, generateScriptName()); } /** * Evaluates some script against the current Binding and returns the result * * @param in the stream reading the script * @param fileName is the logical file name of the script (which is used to create the class name of the script) */ public Object evaluate(Reader in, String fileName) throws CompilationFailedException { Script script = null; try { script = parse(in, fileName); return script.run(); } finally { if (script != null) { InvokerHelper.removeClass(script.getClass()); } } } /** * Parses the given script and returns it ready to be run * * @param reader the stream reading the script * @param fileName is the logical file name of the script (which is used to create the class name of the script) * @return the parsed script which is ready to be run via {@link Script#run()} */ public Script parse(final Reader reader, final String fileName) throws CompilationFailedException { return parse(new GroovyCodeSource(reader, fileName, DEFAULT_CODE_BASE)); } /** * Parses the groovy code contained in codeSource and returns a java class. */ private Class parseClass(final GroovyCodeSource codeSource) throws CompilationFailedException { // Don't cache scripts return loader.parseClass(codeSource, false); } /** * Parses the given script and returns it ready to be run. When running in a secure environment * (-Djava.security.manager) codeSource.getCodeSource() determines what policy grants should be * given to the script. * * @param codeSource * @return ready to run script */ public Script parse(final GroovyCodeSource codeSource) throws CompilationFailedException { return InvokerHelper.createScript(parseClass(codeSource), context); } /** * Parses the given script and returns it ready to be run * * @param file is the file of the script (which is used to create the class name of the script) */ public Script parse(File file) throws CompilationFailedException, IOException { return parse(new GroovyCodeSource(file, config.getSourceEncoding())); } /** * Parses the given script and returns it ready to be run * * @param uri is the URI of the script (which is used to create the class name of the script) */ public Script parse(URI uri) throws CompilationFailedException, IOException { return parse(new GroovyCodeSource(uri)); } /** * Parses the given script and returns it ready to be run * * @param scriptText the text of the script */ public Script parse(String scriptText) throws CompilationFailedException { return parse(scriptText, generateScriptName()); } public Script parse(final String scriptText, final String fileName) throws CompilationFailedException { GroovyCodeSource gcs = AccessController.doPrivileged(new PrivilegedAction<GroovyCodeSource>() { public GroovyCodeSource run() { return new GroovyCodeSource(scriptText, fileName, DEFAULT_CODE_BASE); } }); return parse(gcs); } /** * Parses the given script and returns it ready to be run * * @param in the stream reading the script */ public Script parse(Reader in) throws CompilationFailedException { return parse(in, generateScriptName()); } protected synchronized String generateScriptName() { return "Script" + (++counter) + ".groovy"; } }