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    }