001 /*
002 * $Id: GroovyScriptEngine.java,v 1.6 2005/04/24 05:55:00 spullara Exp $version Jan 9, 2004 12:19:58 PM $user Exp $
003 *
004 * Copyright 2003 (C) Sam Pullara. All Rights Reserved.
005 *
006 * Redistribution and use of this software and associated documentation
007 * ("Software"), with or without modification, are permitted provided that the
008 * following conditions are met: 1. Redistributions of source code must retain
009 * copyright statements and notices. Redistributions must also contain a copy
010 * of this document. 2. Redistributions in binary form must reproduce the above
011 * copyright notice, this list of conditions and the following disclaimer in
012 * the documentation and/or other materials provided with the distribution. 3.
013 * The name "groovy" must not be used to endorse or promote products derived
014 * from this Software without prior written permission of The Codehaus. For
015 * written permission, please contact info@codehaus.org. 4. Products derived
016 * from this Software may not be called "groovy" nor may "groovy" appear in
017 * their names without prior written permission of The Codehaus. "groovy" is a
018 * registered trademark of The Codehaus. 5. Due credit should be given to The
019 * Codehaus - http://groovy.codehaus.org/
020 *
021 * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
022 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
023 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
024 * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
025 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
026 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
027 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
028 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
029 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
030 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
031 * DAMAGE.
032 *
033 */
034 package groovy.util;
035
036 import groovy.lang.Binding;
037 import groovy.lang.GroovyClassLoader;
038 import groovy.lang.Script;
039
040 import java.io.BufferedReader;
041 import java.io.File;
042 import java.io.IOException;
043 import java.io.InputStreamReader;
044 import java.net.MalformedURLException;
045 import java.net.URL;
046 import java.net.URLConnection;
047 import java.security.AccessController;
048 import java.security.PrivilegedAction;
049 import java.util.Collections;
050 import java.util.HashMap;
051 import java.util.Iterator;
052 import java.util.Map;
053
054 import org.codehaus.groovy.control.CompilationFailedException;
055 import org.codehaus.groovy.runtime.InvokerHelper;
056
057 /**
058 * @author sam
059 *
060 * To change the template for this generated type comment go to Window -
061 * Preferences - Java - Code Generation - Code and Comments
062 */
063 public class GroovyScriptEngine implements ResourceConnector {
064
065 /**
066 * Simple testing harness for the GSE. Enter script roots as arguments and
067 * then input script names to run them.
068 *
069 * @param args
070 * @throws Exception
071 */
072 public static void main(String[] args) throws Exception {
073 URL[] roots = new URL[args.length];
074 for (int i = 0; i < roots.length; i++) {
075 roots[i] = new File(args[i]).toURL();
076 }
077 GroovyScriptEngine gse = new GroovyScriptEngine(roots);
078 BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
079 String line;
080 while (true) {
081 System.out.print("groovy> ");
082 if ((line = br.readLine()) == null || line.equals("quit"))
083 break;
084 try {
085 System.out.println(gse.run(line, new Binding()));
086 } catch (Exception e) {
087 e.printStackTrace();
088 }
089 }
090 }
091
092 private URL[] roots;
093 private Map scriptCache = Collections.synchronizedMap(new HashMap());
094 private ResourceConnector rc;
095
096 private static class ScriptCacheEntry {
097 private Class scriptClass;
098 private long lastModified;
099 private Map dependencies = new HashMap();
100 }
101
102 public URLConnection getResourceConnection(String resourceName) throws ResourceException {
103 // Get the URLConnection
104 URLConnection groovyScriptConn = null;
105
106 ResourceException se = null;
107 for (int i = 0; i < roots.length; i++) {
108 URL scriptURL = null;
109 try {
110 scriptURL = new URL(roots[i], resourceName);
111 groovyScriptConn = scriptURL.openConnection();
112 } catch (MalformedURLException e) {
113 String message = "Malformed URL: " + roots[i] + ", " + resourceName;
114 if (se == null) {
115 se = new ResourceException(message);
116 } else {
117 se = new ResourceException(message, se);
118 }
119 } catch (IOException e1) {
120 String message = "Cannot open URL: " + scriptURL;
121 if (se == null) {
122 se = new ResourceException(message);
123 } else {
124 se = new ResourceException(message, se);
125 }
126 }
127
128 }
129
130 // If we didn't find anything, report on all the exceptions that
131 // occurred.
132 if (groovyScriptConn == null) {
133 throw se;
134 }
135
136 return groovyScriptConn;
137 }
138
139 /**
140 * The groovy script engine will run groovy scripts and reload them and
141 * their dependencies when they are modified. This is useful for embedding
142 * groovy in other containers like games and application servers.
143 *
144 * @param roots This an array of URLs where Groovy scripts will be stored. They should
145 * be layed out using their package structure like Java classes
146 */
147 public GroovyScriptEngine(URL[] roots) {
148 this.roots = roots;
149 this.rc = this;
150 }
151
152 public GroovyScriptEngine(String[] args) throws IOException {
153 roots = new URL[args.length];
154 for (int i = 0; i < roots.length; i++) {
155 roots[i] = new File(args[i]).toURL();
156 }
157 this.rc = this;
158 }
159
160 public GroovyScriptEngine(String arg) throws IOException {
161 roots = new URL[1];
162 roots[0] = new File(arg).toURL();
163 this.rc = this;
164 }
165
166 public GroovyScriptEngine(ResourceConnector rc) {
167 this.rc = rc;
168 }
169
170 public String run(String script, String argument) throws ResourceException, ScriptException {
171 Binding binding = new Binding();
172 binding.setVariable("arg", argument);
173 Object result = run(script, binding);
174 return result == null ? "" : result.toString();
175 }
176
177 public Object run(String script, Binding binding) throws ResourceException, ScriptException {
178
179 ScriptCacheEntry entry;
180
181 script = script.intern();
182 synchronized (script) {
183
184 URLConnection groovyScriptConn = rc.getResourceConnection(script);
185
186 // URL last modified
187 long lastModified = groovyScriptConn.getLastModified();
188 // Check the cache for the script
189 entry = (ScriptCacheEntry) scriptCache.get(script);
190 // If the entry isn't null check all the dependencies
191 boolean dependencyOutOfDate = false;
192 if (entry != null) {
193 for (Iterator i = entry.dependencies.keySet().iterator(); i.hasNext();) {
194 URLConnection urlc = null;
195 URL url = (URL) i.next();
196 try {
197 urlc = url.openConnection();
198 urlc.setDoInput(false);
199 urlc.setDoOutput(false);
200 long dependentLastModified = urlc.getLastModified();
201 if (dependentLastModified > ((Long) entry.dependencies.get(url)).longValue()) {
202 dependencyOutOfDate = true;
203 break;
204 }
205 } catch (IOException ioe) {
206 dependencyOutOfDate = true;
207 break;
208 }
209 }
210 }
211
212 if (entry == null || entry.lastModified < lastModified || dependencyOutOfDate) {
213 // Make a new entry
214 entry = new ScriptCacheEntry();
215
216 // Closure variable
217 final ScriptCacheEntry finalEntry = entry;
218
219 // Compile the script into an object
220 GroovyClassLoader groovyLoader =
221 (GroovyClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
222 public Object run() {
223 return new GroovyClassLoader(getClass().getClassLoader()) {
224 protected Class findClass(String className) throws ClassNotFoundException {
225 String filename = className.replace('.', File.separatorChar) + ".groovy";
226 URLConnection dependentScriptConn = null;
227 try {
228 dependentScriptConn = rc.getResourceConnection(filename);
229 finalEntry.dependencies.put(
230 dependentScriptConn.getURL(),
231 new Long(dependentScriptConn.getLastModified()));
232 } catch (ResourceException e1) {
233 throw new ClassNotFoundException("Could not read " + className + ": " + e1);
234 }
235 try {
236 return parseClass(dependentScriptConn.getInputStream(), filename);
237 } catch (CompilationFailedException e2) {
238 throw new ClassNotFoundException("Syntax error in " + className + ": " + e2);
239 } catch (IOException e2) {
240 throw new ClassNotFoundException("Problem reading " + className + ": " + e2);
241 }
242 }
243 };
244 }
245 });
246
247 try {
248 entry.scriptClass = groovyLoader.parseClass(groovyScriptConn.getInputStream(), script);
249 } catch (Exception e) {
250 throw new ScriptException("Could not parse script: " + script, e);
251 }
252 entry.lastModified = lastModified;
253 scriptCache.put(script, entry);
254 }
255 }
256 Script scriptObject = InvokerHelper.createScript(entry.scriptClass, binding);
257 return scriptObject.run();
258 }
259 }