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 }