001    package net.sourceforge.retroweaver.ant;
002    
003    import java.io.File;
004    import java.util.ArrayList;
005    import java.util.HashMap;
006    import java.util.List;
007    import java.util.Map;
008    
009    import net.sourceforge.retroweaver.RefVerifier;
010    import net.sourceforge.retroweaver.RetroWeaver;
011    import net.sourceforge.retroweaver.event.VerifierListener;
012    import net.sourceforge.retroweaver.event.WeaveListener;
013    import net.sourceforge.retroweaver.translator.NameSpace;
014    
015    import org.apache.tools.ant.BuildException;
016    import org.apache.tools.ant.DirectoryScanner;
017    import org.apache.tools.ant.ExitStatusException;
018    import org.apache.tools.ant.Project;
019    import org.apache.tools.ant.Task;
020    import org.apache.tools.ant.types.DirSet;
021    import org.apache.tools.ant.types.FileSet;
022    import org.apache.tools.ant.types.Path;
023    import org.apache.tools.ant.types.Reference;
024    import org.objectweb.asm.commons.EmptyVisitor;
025    
026    /**
027     * An Ant task for running RetroWeaver on a set of class files.
028     */
029    public class RetroWeaverTask extends Task {
030    
031            ////////////////////////////////////////////////////////////////////////////////
032            //      Constants and variables.
033    
034            /**
035             * The destination directory for processd classes, or <code>null</code> for in place
036             * processing.
037             */
038            private File itsDestDir;
039    
040            /**
041             * Indicates if an error should cause the script to fail. Default to <code>true</code>.
042             */
043            private boolean itsFailOnError = true;
044    
045            /**
046             * The set of files to be weaved.
047             */
048            private final List<FileSet> itsFileSets = new ArrayList<FileSet>();
049    
050            private final List<DirSet> itsDirSets = new ArrayList<DirSet>();
051    
052            private String inputJar;
053    
054            private String outputJar;
055    
056            /**
057             * Indicates if classes should only be processed if their current version differ from the target version. Initially <code>true</code>.
058             */
059            private boolean itsLazy = true;
060    
061            /**
062             * Indicates whether the generic signatures should be stripped. Default to <code>false</code>.
063             */
064            private boolean stripSignatures;
065    
066            /**
067             * Indicates whether the custom retroweaver attributes should be stripped. Default to <code>false</code>.
068             */
069            private boolean stripAttributes;
070    
071            /**
072             * Indicates if each processed class should be logged. Initially set to <code>false</code>.
073             */
074            private boolean itsVerbose = false;
075    
076            /**
077             * The classpath to use to verify the weaved result
078             */
079            private Path verifyClasspath;
080    
081            private boolean verify = true;
082    
083            /**
084             * The class file version number.
085             */
086            private int itsVersion = 48;
087    
088            /**
089             * The class file version number.
090             */
091            private static final Map<String, Integer> itsVersionMap = new HashMap<String, Integer>();
092    
093            /**
094             * Initialize the version map.
095             */
096            static {
097                    itsVersionMap.put("1.2", 46);
098                    itsVersionMap.put("1.3", 47);
099                    itsVersionMap.put("1.4", 48);
100                    itsVersionMap.put("1.5", 49);
101            }
102    
103            ////////////////////////////////////////////////////////////////////////////////
104            //      Property accessors and mutators.
105    
106            /**
107             * Set the destination directory for processed classes. Unless specified the classes
108             * are processed in place.
109             * @param pDir The destination directory. 
110             */
111            public void setDestDir(File pDir) {
112                    if (!pDir.isDirectory()) {
113                            throw new BuildException(
114                                            "The destination directory doesn't exist: " + pDir,
115                                            getLocation());
116                    }
117    
118                    itsDestDir = pDir;
119            }
120    
121            /**
122             * Specify if an error should cause the script to fail. Default to <code>true</code>.
123             *
124             * @param pFailOnError <code>true</code> to fail, <code>false</code> to keep going.
125             */
126            public void setFailOnError(boolean pFailOnError) {
127                    itsFailOnError = pFailOnError;
128            }
129    
130            /**
131             * Add a set of files to be weaved.
132             * @param pSet The fileset.
133             */
134            public void addFileSet(FileSet pFileSet) {
135                    itsFileSets.add(pFileSet);
136            }
137    
138            public void addDirSet(DirSet pFileSet) {
139                    itsDirSets.add(pFileSet);
140            }
141    
142            /**
143             * Specify if classes should only be processed if their current version differ from the target version. Initially <code>true</code>.
144             * @param pLazy <code>true</code> for lazy processing.
145             */
146            public void setLazy(boolean pLazy) {
147                    itsLazy = pLazy;
148            }
149    
150            /**
151             * Set the source directory containing classes to process. This is a shortcut to
152             * using an embedded fileset with the specified base directory and which includes
153             * all class files.
154             * @param pDir The directory. 
155             */
156            public void setSrcDir(File pDir) {
157                    FileSet fileSet = new FileSet();
158                    fileSet.setDir(pDir);
159                    fileSet.setIncludes("**/*.class");
160    
161                    addFileSet(fileSet);
162            }
163    
164            /**
165             * Specify if each processed class should be logged. Initially set to <code>false</code>.
166             * @param pVerbose <code>true</code> for verbose processing.
167             */
168            public void setVerbose(boolean pVerbose) {
169                    itsVerbose = pVerbose;
170            }
171    
172            /**
173             * Set the target class file version. Initially set to "1.4".
174             * @param target The JDK target version, e&nbsp;g "1.3". 
175             */
176            public void setTarget(String target) {
177                    Integer v = itsVersionMap.get(target);
178                    if (v == null) {
179                            throw new BuildException("Unknown target: " + target, getLocation());
180                    }
181                    itsVersion = v;
182            }
183    
184            /**
185             * Set the classpath to be used for verification.
186             * Retroweaver will report any references to fields/methods/classes which don't appear
187             * on refClassPath.
188             * @param classpath an Ant Path object containing the compilation classpath.
189             */
190            public void setClasspath(Path classpath) {
191                    if (verifyClasspath == null) {
192                            verifyClasspath = classpath;
193                    } else {
194                            verifyClasspath.append(classpath);
195                    }
196            }
197    
198            /**
199             * Gets the classpath to be used for verification.
200             * @return the class path
201             public Path getClasspath() {
202             return verifyClasspath;
203             }
204    
205             /**
206             * Adds a path to the classpath.
207             * @return a class path to be configured
208             */
209            public Path createClasspath() {
210                    if (verifyClasspath == null) {
211                            verifyClasspath = new Path(getProject());
212                    }
213                    return verifyClasspath.createPath();
214            }
215    
216            /**
217             * Adds a reference to a classpath defined elsewhere.
218             * @param r a reference to a classpath
219             */
220            public void setClasspathRef(Reference r) {
221                    createClasspath().setRefid(r);
222            }
223    
224            /**
225             * Turn off verification if desired
226             * @return is verification enabled?
227             */
228            public void setVerify(boolean newVerify) {
229                    verify = newVerify;
230            }
231    
232            /**
233             * Turn off verification if desired
234             * @return is verification enabled?
235             */
236            public boolean doVerify() {
237                    return verify;
238            }
239    
240            /** NameSpace in translator package is immutable. Temporary values are
241             * stored using this Namespace class.
242             */
243            public static final class Namespace {
244                    private String from;
245                    private String to;
246                    public String getFrom() { return from; }
247                    public void setFrom(String from) { this.from = from; }
248                    public String getTo() { return to; }
249                    public void setTo(String to) { this.to = to; }          
250            }
251    
252            private List<Namespace> namespaces = new ArrayList<Namespace>();
253    
254            public Namespace createNameSpace() {
255                    Namespace n = new Namespace();
256                    namespaces.add(n);
257                    return n;
258            }
259    
260            ////////////////////////////////////////////////////////////////////////////////
261            //      Operations.
262    
263            /**
264             * Run the RetroWeaver task.
265             * @throws BuildException If a build exception occurs.
266             */
267            public void execute() throws BuildException {
268    
269                    for (DirSet set : itsDirSets) {
270                            File baseDir = set.getDir(getProject());
271                            DirectoryScanner scanner = set.getDirectoryScanner(getProject());
272    
273                            // create a non recursive file set for each included directory
274                            for (String fileName : scanner.getIncludedDirectories()) {
275                                    FileSet fileSet = new FileSet();
276                                    fileSet.setDir(new File(baseDir, fileName));
277                                    fileSet.setIncludes("*.class");
278                                    addFileSet(fileSet);
279                            }
280                    }
281    
282                    //      Check arguments.
283    
284                    boolean hasFileSet = !itsFileSets.isEmpty() || !itsDirSets.isEmpty();
285    
286                    if (inputJar != null) {
287                            if (outputJar == null) {
288                                    throw new BuildException("'outputjar' must be set.");
289                            }
290                            if (hasFileSet) {
291                                    throw new BuildException(
292                                                    "'inputjar' is incompatible with filesets and dirsets");
293                            }
294                    } else if (!hasFileSet) {
295                            throw new BuildException(
296                                            "Either attribute 'srcdir' or 'inputjar' must be used or atleast one fileset or dirset must be embedded.",
297                                            getLocation());
298                    }
299                    //      Create and configure the weaver.
300    
301                    RetroWeaver weaver = new RetroWeaver(itsVersion);
302                    weaver.setLazy(itsLazy);
303                    weaver.setStripSignatures(stripSignatures);
304                    weaver.setStripAttributes(stripAttributes);
305                    
306                    // Name space conversion
307                    List<NameSpace> l = new ArrayList<NameSpace>();
308                    for(Namespace n: namespaces) {
309                            l.add(new NameSpace(n.getFrom(), n.getTo()));
310                    }
311                    weaver.addNameSpaces(l);
312    
313                    //      Set up a listener.
314                    weaver.setListener(new WeaveListener() {
315                            public void weavingStarted(String msg) {
316                                    getProject().log(RetroWeaverTask.this, msg, Project.MSG_INFO);
317                            }
318    
319                            public void weavingCompleted(String msg) {
320                                    getProject().log(RetroWeaverTask.this, msg, Project.MSG_INFO);
321                            }
322    
323                            public void weavingError(String msg) {
324                                    getProject().log(RetroWeaverTask.this, msg, Project.MSG_ERR);
325                                    throw new ExitStatusException("weaving error", 1);
326                            }
327    
328                            public void weavingPath(String pPath) {
329                                    if (itsVerbose) {
330                                            getProject().log(RetroWeaverTask.this, "Weaving " + pPath,
331                                                            Project.MSG_INFO);
332                                    }
333                            }
334                    });
335    
336                    if (verifyClasspath != null && doVerify()) {
337    
338                            List<String> refPath = new ArrayList<String>();
339    
340                            for (String pathItem : verifyClasspath.list()) {
341                                    refPath.add(pathItem);
342                            }
343                            if (itsDestDir != null) {
344                                    refPath.add(itsDestDir.getPath());
345                            }
346    
347                            RefVerifier rv = new RefVerifier(itsVersion, new EmptyVisitor(), refPath, new VerifierListener() {
348                                    public void verifyPathStarted(String msg) {
349                                            getProject().log(RetroWeaverTask.this, msg,
350                                                            Project.MSG_INFO);
351                                    }
352    
353                                    public void verifyClassStarted(String msg) {
354                                            if (itsVerbose) {
355                                                    getProject().log(RetroWeaverTask.this, msg, Project.MSG_INFO);
356                                            }
357                                    }
358    
359                                    public void acceptWarning(String msg) {
360                                            getProject().log(RetroWeaverTask.this, msg, Project.MSG_WARN);
361                                    }
362    
363                                    public void displaySummary(int warningCount) {
364                                            String msg = "Verification complete, " + warningCount
365                                                            + " warning(s).";
366                                            getProject().log(RetroWeaverTask.this, msg,
367                                                            Project.MSG_WARN);
368    
369                                            if (itsFailOnError) {
370                                                    throw new ExitStatusException(Integer
371                                                                    .toString(warningCount)
372                                                                    + " warning(s)", 1);
373                                            }
374                                    }
375                            });
376                            weaver.setVerifier(rv);
377                    }
378    
379                    try {
380                            if (inputJar != null) {
381                                    weaver.weaveJarFile(inputJar, outputJar);
382                            } else {
383                                    //      Weave the files in the filesets.
384    
385                                    //      Process each fileset.
386                                    String[][] fileSets = new String[itsFileSets.size()][];
387                                    File[] baseDirs = new File[itsFileSets.size()];
388                                    int i = 0;
389                                    for (FileSet fileSet : itsFileSets) {
390                                            //      Create a directory scanner for the fileset.
391                                            File baseDir = fileSet.getDir(getProject());
392                                            DirectoryScanner scanner = fileSet
393                                                            .getDirectoryScanner(getProject());
394                                            fileSets[i] = scanner.getIncludedFiles();
395                                            baseDirs[i++] = baseDir;
396                                    }
397    
398                                    weaver.weave(baseDirs, fileSets, itsDestDir);
399                            }
400                    } catch (BuildException ex) {
401                            throw ex;
402                    } catch (Exception ex) {
403                            // unexpected exception
404                            throw new BuildException(ex, getLocation());
405                    }
406            }
407    
408            /**
409             * @return Returns the inputJar.
410             */
411            public String getInputJar() {
412                    return inputJar;
413            }
414    
415            /**
416             * @param inputJar The inputJar to set.
417             */
418            public void setInputJar(String inputJar) {
419                    this.inputJar = inputJar;
420            }
421    
422            /**
423             * @return Returns the outputJar.
424             */
425            public String getOutputJar() {
426                    return outputJar;
427            }
428    
429            /**
430             * @param outputJar The outputJar to set.
431             */
432            public void setOutputJar(String outputJar) {
433                    this.outputJar = outputJar;
434            }
435    
436            /**
437             * @param stripSignatures The stripSignatures to set.
438             */
439            public void setStripSignatures(boolean stripSignatures) {
440                    this.stripSignatures = stripSignatures;
441            }
442    
443            /**
444             * @param stripAttributes The stripAttributes to set
445             */
446            public void setStripAttributes(boolean stripAttributes) {
447                    this.stripAttributes = stripAttributes;
448            }
449    }