View Javadoc

1   /*
2    $Id: Groovy.java 4077 2006-09-26 19:51:42Z glaforge $
3   
4    Copyright 2005 (C) Jeremy Rayner. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11      statements and notices.  Redistributions must also contain a
12      copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15      above copyright notice, this list of conditions and the
16      following disclaimer in the documentation and/or other
17      materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20      products derived from this Software without prior written
21      permission of The Codehaus.  For written permission,
22      please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25      nor may "groovy" appear in their names without prior written
26      permission of The Codehaus. "groovy" is a registered
27      trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30      http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
44  
45   */
46  
47  package org.codehaus.groovy.ant;
48  
49  import groovy.lang.Binding;
50  import groovy.lang.GroovyClassLoader;
51  import groovy.lang.GroovyShell;
52  import groovy.lang.Script;
53  import groovy.util.AntBuilder;
54  
55  import java.io.BufferedOutputStream;
56  import java.io.BufferedReader;
57  import java.io.File;
58  import java.io.FileOutputStream;
59  import java.io.FileReader;
60  import java.io.IOException;
61  import java.io.PrintStream;
62  import java.io.PrintWriter;
63  import java.io.Reader;
64  import java.io.StringWriter;
65  import java.lang.reflect.Field;
66  import java.util.Vector;
67  
68  import org.apache.tools.ant.BuildException;
69  import org.apache.tools.ant.DirectoryScanner;
70  import org.apache.tools.ant.Project;
71  import org.apache.tools.ant.Task;
72  import org.apache.tools.ant.types.FileSet;
73  import org.apache.tools.ant.types.Path;
74  import org.apache.tools.ant.types.Reference;
75  import org.codehaus.groovy.control.CompilationFailedException;
76  import org.codehaus.groovy.control.CompilerConfiguration;
77  import org.codehaus.groovy.runtime.InvokerHelper;
78  import org.codehaus.groovy.tools.ErrorReporter;
79  
80  /***
81   * Executes a series of Groovy statements.
82   *
83   * <p>Statements can
84   * either be read in from a text file using the <i>src</i> attribute or from
85   * between the enclosing groovy tags.</p>
86   */
87  public class Groovy extends Task {
88      /***
89       * files to load
90       */
91      private Vector filesets = new Vector();
92  
93      /***
94       * input file
95       */
96      private File srcFile = null;
97  
98      /***
99       * 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 }