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 }