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 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 }