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 }