001    /**
002     * Copyright (c) 2009 Timothy Cleaver. All rights reserved.
003     *
004     * Redistribution and use in source and binary forms, with or without
005     * modification, are permitted provided that the following conditions
006     * are met:
007     * 1. Redistributions of source code must retain the above copyright
008     *    notice, this list of conditions and the following disclaimer.
009     * 2. Redistributions in binary form must reproduce the above copyright
010     *    notice, this list of conditions and the following disclaimer in the
011     *    documentation and/or other materials provided with the distribution.
012     * 3. Neither the name of the copyright holders nor the names of its
013     *    contributors may be used to endorse or promote products derived from
014     *    this software without specific prior written permission.
015     *
016     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
017     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
018     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
019     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
020     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
021     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
022     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
023     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
024     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
025     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
026     * THE POSSIBILITY OF SUCH DAMAGE.
027     */
028    package com.thoughtworks.paranamer.ant;
029    
030    import java.util.Arrays;
031    import java.util.Vector;
032    import java.util.Collection;
033    
034    import org.apache.tools.ant.BuildException;
035    import org.apache.tools.ant.Task;
036    import org.apache.tools.ant.DirectoryScanner;
037    
038    import org.apache.tools.ant.types.FileSet;
039    
040    import com.thoughtworks.qdox.JavaDocBuilder;
041    
042    import com.thoughtworks.paranamer.generator.QdoxParanamerGenerator;
043    
044    import java.io.File;
045    import java.io.IOException;
046    
047    /**
048     * Ant Task to process parameter names. This ant task facilitates the
049     * specification of the java source and class files to process as attributes,
050     * nested file sets or a combination of both. The attributes applicable to
051     * this ant task include a source directory, class directory, include pattern
052     * and exclude pattern. i.e.:
053     * <paranamer srcdir="src" classdir="classes" includes="*.java" excludes="excluded.java"/>
054     * Each of these attributes contains a default:
055     *  - srcdir: the base directory of the project
056     *  - classdir: srcdir
057     *  - includes: "**//*.java" (all java files below the current directory)
058     *              this is to be consistent with the default fileset includes.
059     *  - excludes: "" (none are excluded)
060     * Thus,
061     * <paranamer srcdir="." classdir="." includes="*.java"/>
062     * <paranamer srcdir="." includes="*.java"/>
063     * <paranamer srcdir="."/>
064     * <paranamer/>
065     * are equivalent.
066     *
067     * Files can be included in nested file sets via:
068     * <paranamer>
069     *  <fileset dir="." includes="*.java"/>
070     * </paranamer>
071     * In this case, for each file set the defaults are:
072     *  - srcdir: dir
073     *  - classdir: srcdir
074     *  - includes: the default for ant standard file sets
075     *  - excludes: the default for ant standard file sets
076     * No additional fileset generated from the attribute defaults will be
077     * constructed when embedded filesets are used. i.e.
078     * <paranamer>
079     *  <fileset dir="directory" includes="*.java"/>
080     * </paranamer>
081     * will not apply paranamer to the java files in ".".
082     *
083     * When mixing file sets and attributes, the classdir attribute is treated
084     * differently. The classdir attribute will be applied to any files included
085     * via srcdir or an embedded file set element. For example:
086     * <paranamer classdir="classes"/>
087     *  <fileset dir="." includes="*.java"/>
088     *  <fileset dir="directory" includes="*.java"/>
089     * </paranamer>
090     * will require the classes to modify for the first fileset to reside in
091     * the classes directory and those of the second fileset to reside in
092     * classes/directory. Note that the default srcdir is not applied when embedded
093     * filesets are used instead.
094     *
095     * @author Timothy Cleaver
096     */
097    public class ParanamerTask extends Task {
098        /**
099         * The directory that contains the java source from which to extract the
100         * parameter names. By default this is the current directory.
101         */
102        private String srcdir = null;
103    
104        /**
105         * Boolean that is true when the srcdir was set as an attribute of the
106         * target, and false otherwise.
107         */
108        private boolean srcdirSet = false;
109    
110        /**
111         * The directory that contains the class files to modify. By default
112         * this is the current directory.
113         */
114        private String classdir = srcdir;
115    
116        /**
117         * Boolean that is true when the classdir was set as an attribute of the
118         * target, and false otherwise.
119         */
120        private boolean classdirSet = false;
121    
122        /**
123         * The pattern used to include java files to be processed within the
124         * specified source directory. By default this is the set of java files
125         * in the current directory and all its sub-directories. This is to
126         * match the default matching semantics of the fileset target.
127         */
128        private String includes = "**/*.java";
129    
130        /**
131         * Boolean that is true when the includes was set as an attribute of the
132         * target, and false otherwise.
133         */
134        private boolean includesSet = false;
135    
136        /**
137         * The pattern used to exclude java files from processing. By default this
138         * is empty.
139         */
140        private String excludes = "";
141    
142        /**
143         * Boolean that is true when the excludes  was set as an attribute of the
144         * target, and false otherwise.
145         */
146        private boolean excludesSet = false;
147    
148        /**
149         * The collection of nested file sets containing the files to be processed.
150         */
151        private Collection<FileSet> filesets = new Vector<FileSet>();
152    
153        /**
154         * Execute the task.
155         */
156        public void execute() throws BuildException {
157            if (srcdir == null) { srcdir = getProject().getBaseDir().getPath(); }
158            if (filesets.isEmpty() || srcdirSet || includesSet || excludesSet) {
159                FileSet set = new FileSet();
160                set.setDir(getProject().resolveFile(srcdir));
161                // ensure whitespace is ignored from around the components of the includes
162                for (Object o : Arrays.asList(includes.split(","))) {
163                    set.appendIncludes(new String[]{((String) o).trim()});
164                }
165                // ensure whitespace is ignored from around the components of the excludes
166                for (Object o : Arrays.asList(excludes.split(","))) {
167                    set.appendExcludes(new String[]{((String) o).trim()});
168                }
169                filesets.add(set);
170            }
171            for (Object fileset : filesets) {
172                FileSet fs = (FileSet) fileset;
173                DirectoryScanner ds = fs.getDirectoryScanner(getProject());
174                String[] includedFiles = ds.getIncludedFiles();
175                log("Generating parameter names for"
176                        + includedFiles.length
177                        + " files in "
178                        + ds.getBasedir());
179                for (Object o : Arrays.asList(includedFiles)) {
180                    String file = (String) o;
181                    JavaDocBuilder builder = new JavaDocBuilder();
182                    try {
183                        builder.addSource(new File(ds.getBasedir(), file));
184                        // if the classdir is set then we source the classes
185                        // relative to classdir. otherwise we source the classes
186                        // relative to the base directory of the file set.
187                        if (classdirSet) {
188                            makeQdoxParanamerGenerator()
189                                    .processClasses(builder.getClasses(),
190                                            classdir);
191                        } else {
192                            makeQdoxParanamerGenerator()
193                                    .processClasses(builder.getClasses(),
194                                            ds.getBasedir().getPath());
195                        }
196                    } catch (final IOException exception) {
197                        throw new BuildException("Error processing: "
198                                + file
199                                + ". "
200                                + exception.getMessage());
201                    }
202                }
203            }
204        }
205    
206        /**
207         * Called automatically by ant when an embedded fileset element is present.
208         *
209         * @param fileset
210         *  the fileset specification
211         */
212        public void addFileset(final FileSet fileset) {
213            this.filesets.add(fileset);
214        }
215    
216        /**
217         * Called automatically by ant when the srcdir attribute is present.
218         *
219         * @param srcdir
220         *  the content of the srcdir attribute.
221         */
222        public void setSrcdir(final String srcdir) {
223            this.srcdir = srcdir;
224            this.srcdirSet = true;
225        }
226    
227        /**
228         * Called automatically by ant when the includes attribute is present.
229         *
230         * @param includes
231         *  the content of the includes attribute.
232         */
233        public void setIncludes(final String includes) {
234            this.includes = includes;
235            this.includesSet = true;
236        }
237    
238        /**
239         * Called automatically by ant when the excludes attribute is present.
240         *
241         * @param excludes
242         *  the content of the excludes attribute.
243         */
244        public void setExcludes(final String excludes) {
245            this.excludes = excludes;
246            this.excludesSet = true;
247        }
248    
249        /**
250         * Called automatically by ant when the classdir attribute is present.
251         *
252         * @param classdir
253         *  the content of the classdir attribute.
254         */
255        public void setClassdir(final String classdir) {
256            this.classdir = classdir;
257            this.classdirSet = true;
258        }
259    
260        /**
261         * Provide this as a method so that it can be overridden and custom
262         * QdoxParanamerGenerators can be returned in place of the default.
263         * This is used for testing purposes.
264         *
265         * @return
266         *  the qdox paranamer generated instance to use to generate the
267         *  paranamer data.
268         */
269        protected QdoxParanamerGenerator makeQdoxParanamerGenerator() {
270            return new QdoxParanamerGenerator();
271        }
272    }