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 }