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: * Set the name of the file to be run. The folder of the file is automatically added to the classpath.
134: * Required unless statements are enclosed in the build file
135: */
136: public void setSrc(final File srcFile) {
137: this .srcFile = srcFile;
138: }
139:
140: /**
141: * Set an inline command to execute.
142: * NB: Properties are not expanded in this text.
143: */
144: public void addText(String txt) {
145: log("addText('" + txt + "')", Project.MSG_VERBOSE);
146: this .command += txt;
147: }
148:
149: /**
150: * Adds a set of files (nested fileset attribute).
151: */
152: public void addFileset(FileSet set) {
153: filesets.addElement(set);
154: }
155:
156: /**
157: * Set the output file;
158: * optional, defaults to the Ant log.
159: */
160: public void setOutput(File output) {
161: this .output = output;
162: }
163:
164: /**
165: * whether output should be appended to or overwrite
166: * an existing file. Defaults to false.
167: *
168: * @since Ant 1.5
169: */
170: public void setAppend(boolean append) {
171: this .append = append;
172: }
173:
174: /**
175: * Sets the classpath for loading.
176: * @param classpath The classpath to set
177: */
178: public void setClasspath(final Path classpath) {
179: this .classpath = classpath;
180: }
181:
182: /**
183: * Returns a new path element that can be configured.
184: * Gets called for instance by Ant when it encounters a nested <classpath> element.
185: */
186: public Path createClasspath() {
187: if (this .classpath == null) {
188: this .classpath = new Path(getProject());
189: }
190: return this .classpath.createPath();
191: }
192:
193: /**
194: * Set the classpath for loading
195: * using the classpath reference.
196: */
197: public void setClasspathRef(final Reference r) {
198: createClasspath().setRefid(r);
199: }
200:
201: /**
202: * Gets the classpath.
203: * @return Returns a Path
204: */
205: public Path getClasspath() {
206: return classpath;
207: }
208:
209: /**
210: * Load the file and then execute it
211: */
212: public void execute() throws BuildException {
213: log("execute()", Project.MSG_VERBOSE);
214:
215: command = command.trim();
216:
217: if (srcFile == null && command.length() == 0
218: && filesets.isEmpty()) {
219: throw new BuildException("Source file does not exist!",
220: getLocation());
221: }
222:
223: if (srcFile != null && !srcFile.exists()) {
224: throw new BuildException("Source file does not exist!",
225: 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(new BufferedOutputStream(
244: new FileOutputStream(output
245: .getAbsolutePath(), append)));
246: }
247:
248: // if there are no groovy statements between the enclosing Groovy tags
249: // then read groovy statements in from a text file using the src attribute
250: if (command == null || command.trim().length() == 0) {
251: createClasspath()
252: .add(
253: new Path(getProject(), srcFile
254: .getParentFile()
255: .getCanonicalPath()));
256: command = getText(new BufferedReader(
257: new FileReader(srcFile)));
258: }
259:
260: if (command != null) {
261: execGroovy(command, out);
262: } else {
263: throw new BuildException(
264: "Source file does not exist!",
265: getLocation());
266: }
267:
268: } finally {
269: if (out != null && out != System.out) {
270: out.close();
271: }
272: }
273: } catch (IOException e) {
274: throw new BuildException(e, getLocation());
275: }
276:
277: log("statements executed successfully", Project.MSG_VERBOSE);
278: }
279:
280: private static String getText(BufferedReader reader)
281: throws IOException {
282: StringBuffer answer = new StringBuffer();
283: // reading the content of the file within a char buffer allow to keep the correct line endings
284: char[] charBuffer = new char[4096];
285: int nbCharRead = 0;
286: while ((nbCharRead = reader.read(charBuffer)) != -1) {
287: // appends buffer
288: answer.append(charBuffer, 0, nbCharRead);
289: }
290: reader.close();
291: return answer.toString();
292: }
293:
294: /**
295: * read in lines and execute them
296: */
297: protected void runStatements(Reader reader, PrintStream out)
298: throws IOException {
299: log("runStatements()", Project.MSG_VERBOSE);
300:
301: StringBuffer txt = new StringBuffer();
302: String line = "";
303:
304: BufferedReader in = new BufferedReader(reader);
305:
306: while ((line = in.readLine()) != null) {
307: line = getProject().replaceProperties(line);
308:
309: if (line.indexOf("--") >= 0) {
310: txt.append("\n");
311: }
312: }
313: // Catch any statements not followed by ;
314: if (!txt.equals("")) {
315: execGroovy(txt.toString(), out);
316: }
317: }
318:
319: /**
320: * Exec the statement.
321: */
322: protected void execGroovy(final String txt, final PrintStream out) {
323: log("execGroovy()", Project.MSG_VERBOSE);
324:
325: // Check and ignore empty statements
326: if ("".equals(txt.trim())) {
327: return;
328: }
329:
330: log("Groovy: " + txt, Project.MSG_VERBOSE);
331:
332: //log(getClasspath().toString(),Project.MSG_VERBOSE);
333: Object mavenPom = null;
334: final Project project = getProject();
335: final ClassLoader baseClassLoader;
336: // treat the case Ant is run through Maven, and
337: if ("org.apache.commons.grant.GrantProject".equals(project
338: .getClass().getName())) {
339: try {
340: final Object propsHandler = project.getClass()
341: .getMethod("getPropsHandler", new Class[0])
342: .invoke(project, new Object[0]);
343: final Field contextField = propsHandler.getClass()
344: .getDeclaredField("context");
345: contextField.setAccessible(true);
346: final Object context = contextField.get(propsHandler);
347: mavenPom = InvokerHelper.invokeMethod(context,
348: "getProject", new Object[0]);
349: } catch (Exception e) {
350: throw new BuildException(
351: "Impossible to retrieve Maven's Ant project: "
352: + e.getMessage(), getLocation());
353: }
354: // let ASM lookup "root" classloader
355: Thread.currentThread().setContextClassLoader(
356: GroovyShell.class.getClassLoader());
357: // load groovy into "root.maven" classloader instead of "root" so that
358: // groovy script can access Maven classes
359: baseClassLoader = mavenPom.getClass().getClassLoader();
360: } else {
361: baseClassLoader = GroovyShell.class.getClassLoader();
362: }
363:
364: final GroovyClassLoader classLoader = new GroovyClassLoader(
365: baseClassLoader);
366: addClassPathes(classLoader);
367:
368: final GroovyShell groovy = new GroovyShell(classLoader,
369: new Binding(), configuration);
370: try {
371: final Script script = groovy.parse(txt);
372: script.setProperty("ant", new AntBuilder(project,
373: getOwningTarget()));
374: script.setProperty("project", project);
375: script.setProperty("properties",
376: new AntProjectPropertiesDelegate(project));
377: script.setProperty("target", getOwningTarget());
378: script.setProperty("task", this );
379: if (mavenPom != null) {
380: script.setProperty("pom", mavenPom);
381: }
382: script.run();
383: } catch (CompilationFailedException e) {
384: StringWriter writer = new StringWriter();
385: new ErrorReporter(e, false).write(new PrintWriter(writer));
386: String message = writer.toString();
387: throw new BuildException("Script Failed: " + message,
388: getLocation());
389: }
390: }
391:
392: /**
393: * Adds the class pathes (if any)
394: * @param classLoader the classloader to configure
395: */
396: protected void addClassPathes(final GroovyClassLoader classLoader) {
397: if (classpath != null) {
398: for (int i = 0; i < classpath.list().length; i++) {
399: classLoader.addClasspath(classpath.list()[i]);
400: }
401: }
402: }
403:
404: /**
405: * print any results in the statement.
406: */
407: protected void printResults(PrintStream out) {
408: log("printResults()", Project.MSG_VERBOSE);
409: StringBuffer line = new StringBuffer();
410: out.println(line);
411: line = new StringBuffer();
412: out.println();
413: }
414: }
|