001 /* 002 $Id: Groovy.java,v 1.9 2005/11/21 00:18:54 glaforge Exp $ 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.GroovyShell; 050 import groovy.lang.Script; 051 import groovy.lang.Binding; 052 import groovy.util.AntBuilder; 053 054 import java.io.BufferedOutputStream; 055 import java.io.BufferedReader; 056 import java.io.File; 057 import java.io.FileOutputStream; 058 import java.io.FileReader; 059 import java.io.IOException; 060 import java.io.PrintStream; 061 import java.io.Reader; 062 import java.io.StringWriter; 063 import java.io.PrintWriter; 064 import java.lang.reflect.Field; 065 import java.util.Hashtable; 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 * 088 * Based heavily on SQLExec.java which is part of apache-ant 089 * http://cvs.apache.org/viewcvs.cgi/ant/src/main/org/apache/tools/ant/taskdefs/SQLExec.java?rev=MAIN 090 * 091 * Copyright 2000-2005 The Apache Software Foundation 092 * 093 * Licensed under the Apache License, Version 2.0 (the "License"); 094 * you may not use this file except in compliance with the License. 095 * You may obtain a copy of the License at 096 * 097 * http://www.apache.org/licenses/LICENSE-2.0 098 * 099 * Unless required by applicable law or agreed to in writing, software 100 * distributed under the License is distributed on an "AS IS" BASIS, 101 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 102 * See the License for the specific language governing permissions and 103 * limitations under the License. 104 * 105 * 106 */ 107 public class Groovy extends Task { 108 /** 109 * files to load 110 */ 111 private Vector filesets = new Vector(); 112 113 /** 114 * input file 115 */ 116 private File srcFile = null; 117 118 /** 119 * input command 120 */ 121 private String command = ""; 122 123 /** 124 * Print results. 125 */ 126 private boolean print = false; 127 128 /** 129 * Results Output file. 130 */ 131 private File output = null; 132 133 /** 134 * Append to an existing file or overwrite it? 135 */ 136 private boolean append = false; 137 138 /** 139 * Used for caching loaders / driver. This is to avoid 140 * getting an OutOfMemoryError when calling this task 141 * multiple times in a row. 142 */ 143 private static Hashtable loaderMap = new Hashtable(3); 144 145 private Path classpath; 146 147 /** 148 * User name. 149 */ 150 private String userId = null; 151 152 /** 153 * Groovy Version needed for this collection of statements. 154 **/ 155 private String version = null; 156 157 /** 158 * Compiler configuration. 159 * 160 * Used to specify the debug output to print stacktraces in case something fails. 161 * TODO: Could probably be reused to specify the encoding of the files to load or other properties. 162 */ 163 private CompilerConfiguration configuration = new CompilerConfiguration(); 164 165 /** 166 * Enable compiler to report stack trace information if a problem occurs 167 * during compilation. 168 * @param stacktrace 169 */ 170 public void setStacktrace(boolean stacktrace) { 171 configuration.setDebug(stacktrace); 172 } 173 174 175 /** 176 * Set the name of the file to be run. 177 * Required unless statements are enclosed in the build file 178 */ 179 public void setSrc(File srcFile) { 180 this.srcFile = srcFile; 181 } 182 183 /** 184 * Set an inline command to execute. 185 * NB: Properties are not expanded in this text. 186 */ 187 public void addText(String txt) { 188 log("addText('"+txt+"')", Project.MSG_VERBOSE); 189 this.command += txt; 190 } 191 192 /** 193 * Adds a set of files (nested fileset attribute). 194 */ 195 public void addFileset(FileSet set) { 196 filesets.addElement(set); 197 } 198 199 /** 200 * Print results from the statements; 201 * optional, default false 202 */ 203 public void setPrint(boolean print) { 204 this.print = print; 205 } 206 207 /** 208 * Set the output file; 209 * optional, defaults to the Ant log. 210 */ 211 public void setOutput(File output) { 212 this.output = output; 213 } 214 215 /** 216 * whether output should be appended to or overwrite 217 * an existing file. Defaults to false. 218 * 219 * @since Ant 1.5 220 */ 221 public void setAppend(boolean append) { 222 this.append = append; 223 } 224 225 226 /** 227 * Sets the classpath for loading. 228 * @param classpath The classpath to set 229 */ 230 public void setClasspath(Path classpath) { 231 this.classpath = classpath; 232 } 233 234 /** 235 * Add a path to the classpath for loading. 236 */ 237 public Path createClasspath() { 238 if (this.classpath == null) { 239 this.classpath = new Path(getProject()); 240 } 241 return this.classpath.createPath(); 242 } 243 244 /** 245 * Set the classpath for loading 246 * using the classpath reference. 247 */ 248 public void setClasspathRef(Reference r) { 249 createClasspath().setRefid(r); 250 } 251 252 /** 253 * Sets the version string, execute task only if 254 * groovy version match; optional. 255 * @param version The version to set 256 */ 257 public void setVersion(String version) { 258 this.version = version; 259 } 260 261 262 protected static Hashtable getLoaderMap() { 263 return loaderMap; 264 } 265 266 267 268 269 /** 270 * Gets the classpath. 271 * @return Returns a Path 272 */ 273 public Path getClasspath() { 274 return classpath; 275 } 276 277 /** 278 * Gets the userId. 279 * @return Returns a String 280 */ 281 public String getUserId() { 282 return userId; 283 } 284 285 /** 286 * Set the user name for the connection; required. 287 * @param userId The userId to set 288 */ 289 public void setUserid(String userId) { 290 this.userId = userId; 291 } 292 293 /** 294 * Gets the version. 295 * @return Returns a String 296 */ 297 public String getVersion() { 298 return version; 299 } 300 301 /** 302 * Load the file and then execute it 303 */ 304 public void execute() throws BuildException { 305 log("execute()", Project.MSG_VERBOSE); 306 307 command = command.trim(); 308 309 try { 310 if (srcFile == null && command.length() == 0 311 && filesets.isEmpty()) { 312 throw new BuildException("Source file does not exist!", getLocation()); 313 } 314 315 if (srcFile != null && !srcFile.exists()) { 316 throw new BuildException("Source file does not exist!", getLocation()); 317 } 318 319 // deal with the filesets 320 for (int i = 0; i < filesets.size(); i++) { 321 FileSet fs = (FileSet) filesets.elementAt(i); 322 DirectoryScanner ds = fs.getDirectoryScanner(getProject()); 323 File srcDir = fs.getDir(getProject()); 324 325 String[] srcFiles = ds.getIncludedFiles(); 326 } 327 328 try { 329 PrintStream out = System.out; 330 try { 331 if (output != null) { 332 log("Opening PrintStream to output file " + output, 333 Project.MSG_VERBOSE); 334 out = new PrintStream( 335 new BufferedOutputStream( 336 new FileOutputStream(output 337 .getAbsolutePath(), 338 append))); 339 } 340 341 // if there are no groovy statements between the enclosing Groovy tags 342 // then read groovy statements in from a text file using the src attribute 343 if (command == null || command.trim().length() == 0) { 344 command = getText(new BufferedReader(new FileReader(srcFile))); 345 } 346 347 348 if (command != null) { 349 execGroovy(command,out); 350 } else { 351 throw new BuildException("Source file does not exist!", getLocation()); 352 } 353 354 } finally { 355 if (out != null && out != System.out) { 356 out.close(); 357 } 358 } 359 } catch (IOException e) { 360 throw new BuildException(e, getLocation()); 361 } 362 363 log("statements executed successfully"); 364 } finally{} 365 } 366 367 368 private static String getText(BufferedReader reader) throws IOException { 369 StringBuffer answer = new StringBuffer(); 370 // reading the content of the file within a char buffer allow to keep the correct line endings 371 char[] charBuffer = new char[4096]; 372 int nbCharRead = 0; 373 while ((nbCharRead = reader.read(charBuffer)) != -1) { 374 // appends buffer 375 answer.append(charBuffer, 0, nbCharRead); 376 } 377 reader.close(); 378 return answer.toString(); 379 } 380 381 382 /** 383 * read in lines and execute them 384 */ 385 protected void runStatements(Reader reader, PrintStream out) 386 throws IOException { 387 log("runStatements()", Project.MSG_VERBOSE); 388 389 StringBuffer txt = new StringBuffer(); 390 String line = ""; 391 392 BufferedReader in = new BufferedReader(reader); 393 394 while ((line = in.readLine()) != null) { 395 line = getProject().replaceProperties(line); 396 397 if (line.indexOf("--") >= 0) { 398 txt.append("\n"); 399 } 400 } 401 // Catch any statements not followed by ; 402 if (!txt.equals("")) { 403 execGroovy(txt.toString(), out); 404 } 405 } 406 407 408 /** 409 * Exec the statement. 410 */ 411 protected void execGroovy(String txt, PrintStream out) { 412 log("execGroovy()", Project.MSG_VERBOSE); 413 414 // Check and ignore empty statements 415 if ("".equals(txt.trim())) { 416 return; 417 } 418 419 log("Groovy: " + txt, Project.MSG_VERBOSE); 420 421 //log(getClasspath().toString(),Project.MSG_VERBOSE); 422 GroovyShell groovy = null; 423 Object mavenPom = null; 424 Project project = getProject(); 425 // treat the case Ant is run through Maven, and 426 if ("org.apache.commons.grant.GrantProject".equals(project.getClass().getName())) { 427 try { 428 Object propsHandler = project.getClass().getMethod("getPropsHandler", new Class[0]).invoke(project, new Object[0]); 429 Field contextField = propsHandler.getClass().getDeclaredField("context"); 430 contextField.setAccessible(true); 431 Object context = contextField.get(propsHandler); 432 mavenPom = InvokerHelper.invokeMethod(context, "getProject", new Object[0]); 433 } 434 catch (Exception e) { 435 throw new BuildException("Impossible to retrieve Maven's Ant project: " + e.getMessage(), getLocation()); 436 } 437 // let ASM lookup "root" classloader 438 Thread.currentThread().setContextClassLoader(GroovyShell.class.getClassLoader()); 439 // load groovy into "root.maven" classloader instead of "root" so that 440 // groovy script can access Maven classes 441 groovy = new GroovyShell(mavenPom.getClass().getClassLoader(), new Binding(), configuration); 442 } else { 443 groovy = new GroovyShell(GroovyShell.class.getClassLoader(), new Binding(), configuration); 444 } 445 try { 446 Script script = groovy.parse(txt); 447 script.setProperty("ant", new AntBuilder(project)); 448 script.setProperty("project", project); 449 script.setProperty("properties", new AntProjectPropertiesDelegate(project)); 450 script.setProperty("target", getOwningTarget()); 451 script.setProperty("task", this); 452 if(mavenPom != null) { 453 script.setProperty("pom", mavenPom); 454 } 455 script.run(); 456 } catch (CompilationFailedException e) { 457 StringWriter writer = new StringWriter(); 458 new ErrorReporter( e, false ).write( new PrintWriter(writer) ); 459 String message = writer.toString(); 460 throw new BuildException("Script Failed: "+ message, getLocation()); 461 } 462 } 463 464 /** 465 * print any results in the statement. 466 */ 467 protected void printResults(PrintStream out) { 468 log("printResults()", Project.MSG_VERBOSE); 469 StringBuffer line = new StringBuffer(); 470 out.println(line); 471 line = new StringBuffer(); 472 out.println(); 473 } 474 }