001 /* 002 $Id: Groovy.java 4077 2006-09-26 19:51:42Z glaforge $ 003 004 Copyright 2005 (C) Jeremy Rayner. All Rights Reserved. 005 006 Redistribution and use of this software and associated documentation 007 ("Software"), with or without modification, are permitted provided 008 that the following conditions are met: 009 010 1. Redistributions of source code must retain copyright 011 statements and notices. Redistributions must also contain a 012 copy of this document. 013 014 2. Redistributions in binary form must reproduce the 015 above copyright notice, this list of conditions and the 016 following disclaimer in the documentation and/or other 017 materials provided with the distribution. 018 019 3. The name "groovy" must not be used to endorse or promote 020 products derived from this Software without prior written 021 permission of The Codehaus. For written permission, 022 please contact info@codehaus.org. 023 024 4. Products derived from this Software may not be called "groovy" 025 nor may "groovy" appear in their names without prior written 026 permission of The Codehaus. "groovy" is a registered 027 trademark of The Codehaus. 028 029 5. Due credit should be given to The Codehaus - 030 http://groovy.codehaus.org/ 031 032 THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS 033 ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 034 NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 035 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 036 THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 037 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 038 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 039 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 040 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 041 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 042 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 043 OF THE POSSIBILITY OF SUCH DAMAGE. 044 045 */ 046 047 package org.codehaus.groovy.ant; 048 049 import groovy.lang.Binding; 050 import groovy.lang.GroovyClassLoader; 051 import groovy.lang.GroovyShell; 052 import groovy.lang.Script; 053 import groovy.util.AntBuilder; 054 055 import java.io.BufferedOutputStream; 056 import java.io.BufferedReader; 057 import java.io.File; 058 import java.io.FileOutputStream; 059 import java.io.FileReader; 060 import java.io.IOException; 061 import java.io.PrintStream; 062 import java.io.PrintWriter; 063 import java.io.Reader; 064 import java.io.StringWriter; 065 import java.lang.reflect.Field; 066 import java.util.Vector; 067 068 import org.apache.tools.ant.BuildException; 069 import org.apache.tools.ant.DirectoryScanner; 070 import org.apache.tools.ant.Project; 071 import org.apache.tools.ant.Task; 072 import org.apache.tools.ant.types.FileSet; 073 import org.apache.tools.ant.types.Path; 074 import org.apache.tools.ant.types.Reference; 075 import org.codehaus.groovy.control.CompilationFailedException; 076 import org.codehaus.groovy.control.CompilerConfiguration; 077 import org.codehaus.groovy.runtime.InvokerHelper; 078 import org.codehaus.groovy.tools.ErrorReporter; 079 080 /** 081 * Executes a series of Groovy statements. 082 * 083 * <p>Statements can 084 * either be read in from a text file using the <i>src</i> attribute or from 085 * between the enclosing groovy tags.</p> 086 */ 087 public class Groovy extends Task { 088 /** 089 * files to load 090 */ 091 private Vector filesets = new Vector(); 092 093 /** 094 * input file 095 */ 096 private File srcFile = null; 097 098 /** 099 * input command 100 */ 101 private String command = ""; 102 103 /** 104 * Results Output file. 105 */ 106 private File output = null; 107 108 /** 109 * Append to an existing file or overwrite it? 110 */ 111 private boolean append = false; 112 113 private Path classpath; 114 115 /** 116 * Compiler configuration. 117 * 118 * Used to specify the debug output to print stacktraces in case something fails. 119 * TODO: Could probably be reused to specify the encoding of the files to load or other properties. 120 */ 121 private CompilerConfiguration configuration = new CompilerConfiguration(); 122 123 /** 124 * Enable compiler to report stack trace information if a problem occurs 125 * during compilation. 126 * @param stacktrace 127 */ 128 public void setStacktrace(boolean stacktrace) { 129 configuration.setDebug(stacktrace); 130 } 131 132 133 /** 134 * Set the name of the file to be run. The folder of the file is automatically added to the classpath. 135 * Required unless statements are enclosed in the build file 136 */ 137 public void setSrc(final File srcFile) { 138 this.srcFile = srcFile; 139 } 140 141 /** 142 * Set an inline command to execute. 143 * NB: Properties are not expanded in this text. 144 */ 145 public void addText(String txt) { 146 log("addText('"+txt+"')", Project.MSG_VERBOSE); 147 this.command += txt; 148 } 149 150 /** 151 * Adds a set of files (nested fileset attribute). 152 */ 153 public void addFileset(FileSet set) { 154 filesets.addElement(set); 155 } 156 157 /** 158 * Set the output file; 159 * optional, defaults to the Ant log. 160 */ 161 public void setOutput(File output) { 162 this.output = output; 163 } 164 165 /** 166 * whether output should be appended to or overwrite 167 * an existing file. Defaults to false. 168 * 169 * @since Ant 1.5 170 */ 171 public void setAppend(boolean append) { 172 this.append = append; 173 } 174 175 176 /** 177 * Sets the classpath for loading. 178 * @param classpath The classpath to set 179 */ 180 public void setClasspath(final Path classpath) { 181 this.classpath = classpath; 182 } 183 184 /** 185 * Returns a new path element that can be configured. 186 * Gets called for instance by Ant when it encounters a nested <classpath> element. 187 */ 188 public Path createClasspath() { 189 if (this.classpath == null) { 190 this.classpath = new Path(getProject()); 191 } 192 return this.classpath.createPath(); 193 } 194 195 /** 196 * Set the classpath for loading 197 * using the classpath reference. 198 */ 199 public void setClasspathRef(final Reference r) { 200 createClasspath().setRefid(r); 201 } 202 203 /** 204 * Gets the classpath. 205 * @return Returns a Path 206 */ 207 public Path getClasspath() { 208 return classpath; 209 } 210 211 /** 212 * Load the file and then execute it 213 */ 214 public void execute() throws BuildException { 215 log("execute()", Project.MSG_VERBOSE); 216 217 command = command.trim(); 218 219 if (srcFile == null && command.length() == 0 220 && filesets.isEmpty()) { 221 throw new BuildException("Source file does not exist!", getLocation()); 222 } 223 224 if (srcFile != null && !srcFile.exists()) { 225 throw new BuildException("Source file does not exist!", getLocation()); 226 } 227 228 // deal with the filesets 229 for (int i = 0; i < filesets.size(); i++) { 230 FileSet fs = (FileSet) filesets.elementAt(i); 231 DirectoryScanner ds = fs.getDirectoryScanner(getProject()); 232 File srcDir = fs.getDir(getProject()); 233 234 String[] srcFiles = ds.getIncludedFiles(); 235 } 236 237 try { 238 PrintStream out = System.out; 239 try { 240 if (output != null) { 241 log("Opening PrintStream to output file " + output, 242 Project.MSG_VERBOSE); 243 out = new PrintStream( 244 new BufferedOutputStream( 245 new FileOutputStream(output 246 .getAbsolutePath(), 247 append))); 248 } 249 250 // if there are no groovy statements between the enclosing Groovy tags 251 // then read groovy statements in from a text file using the src attribute 252 if (command == null || command.trim().length() == 0) { 253 createClasspath().add(new Path(getProject(), srcFile.getParentFile().getCanonicalPath())); 254 command = getText(new BufferedReader(new FileReader(srcFile))); 255 } 256 257 258 if (command != null) { 259 execGroovy(command,out); 260 } else { 261 throw new BuildException("Source file does not exist!", getLocation()); 262 } 263 264 } finally { 265 if (out != null && out != System.out) { 266 out.close(); 267 } 268 } 269 } catch (IOException e) { 270 throw new BuildException(e, getLocation()); 271 } 272 273 log("statements executed successfully", Project.MSG_VERBOSE); 274 } 275 276 277 private static String getText(BufferedReader reader) throws IOException { 278 StringBuffer answer = new StringBuffer(); 279 // reading the content of the file within a char buffer allow to keep the correct line endings 280 char[] charBuffer = new char[4096]; 281 int nbCharRead = 0; 282 while ((nbCharRead = reader.read(charBuffer)) != -1) { 283 // appends buffer 284 answer.append(charBuffer, 0, nbCharRead); 285 } 286 reader.close(); 287 return answer.toString(); 288 } 289 290 291 /** 292 * read in lines and execute them 293 */ 294 protected void runStatements(Reader reader, PrintStream out) 295 throws IOException { 296 log("runStatements()", Project.MSG_VERBOSE); 297 298 StringBuffer txt = new StringBuffer(); 299 String line = ""; 300 301 BufferedReader in = new BufferedReader(reader); 302 303 while ((line = in.readLine()) != null) { 304 line = getProject().replaceProperties(line); 305 306 if (line.indexOf("--") >= 0) { 307 txt.append("\n"); 308 } 309 } 310 // Catch any statements not followed by ; 311 if (!txt.equals("")) { 312 execGroovy(txt.toString(), out); 313 } 314 } 315 316 317 /** 318 * Exec the statement. 319 */ 320 protected void execGroovy(final String txt, final PrintStream out) { 321 log("execGroovy()", Project.MSG_VERBOSE); 322 323 // Check and ignore empty statements 324 if ("".equals(txt.trim())) { 325 return; 326 } 327 328 log("Groovy: " + txt, Project.MSG_VERBOSE); 329 330 //log(getClasspath().toString(),Project.MSG_VERBOSE); 331 Object mavenPom = null; 332 final Project project = getProject(); 333 final ClassLoader baseClassLoader; 334 // treat the case Ant is run through Maven, and 335 if ("org.apache.commons.grant.GrantProject".equals(project.getClass().getName())) { 336 try { 337 final Object propsHandler = project.getClass().getMethod("getPropsHandler", new Class[0]).invoke(project, new Object[0]); 338 final Field contextField = propsHandler.getClass().getDeclaredField("context"); 339 contextField.setAccessible(true); 340 final Object context = contextField.get(propsHandler); 341 mavenPom = InvokerHelper.invokeMethod(context, "getProject", new Object[0]); 342 } 343 catch (Exception e) { 344 throw new BuildException("Impossible to retrieve Maven's Ant project: " + e.getMessage(), getLocation()); 345 } 346 // let ASM lookup "root" classloader 347 Thread.currentThread().setContextClassLoader(GroovyShell.class.getClassLoader()); 348 // load groovy into "root.maven" classloader instead of "root" so that 349 // groovy script can access Maven classes 350 baseClassLoader = mavenPom.getClass().getClassLoader(); 351 } else { 352 baseClassLoader = GroovyShell.class.getClassLoader(); 353 } 354 355 final GroovyClassLoader classLoader = new GroovyClassLoader(baseClassLoader); 356 addClassPathes(classLoader); 357 358 final GroovyShell groovy = new GroovyShell(classLoader, new Binding(), configuration); 359 try { 360 final Script script = groovy.parse(txt); 361 script.setProperty("ant", new AntBuilder(project, getOwningTarget())); 362 script.setProperty("project", project); 363 script.setProperty("properties", new AntProjectPropertiesDelegate(project)); 364 script.setProperty("target", getOwningTarget()); 365 script.setProperty("task", this); 366 if (mavenPom != null) { 367 script.setProperty("pom", mavenPom); 368 } 369 script.run(); 370 } catch (CompilationFailedException e) { 371 StringWriter writer = new StringWriter(); 372 new ErrorReporter( e, false ).write( new PrintWriter(writer) ); 373 String message = writer.toString(); 374 throw new BuildException("Script Failed: "+ message, getLocation()); 375 } 376 } 377 378 379 /** 380 * Adds the class pathes (if any) 381 * @param classLoader the classloader to configure 382 */ 383 protected void addClassPathes(final GroovyClassLoader classLoader) 384 { 385 if (classpath != null) 386 { 387 for (int i = 0; i < classpath.list().length; i++) 388 { 389 classLoader.addClasspath(classpath.list()[i]); 390 } 391 } 392 } 393 394 /** 395 * print any results in the statement. 396 */ 397 protected void printResults(PrintStream out) { 398 log("printResults()", Project.MSG_VERBOSE); 399 StringBuffer line = new StringBuffer(); 400 out.println(line); 401 line = new StringBuffer(); 402 out.println(); 403 } 404 }