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    }