1 18 package org.apache.tools.ant.taskdefs.optional.depend; 19 20 import java.io.BufferedReader ; 21 import java.io.File ; 22 import java.io.FileReader ; 23 import java.io.FileWriter ; 24 import java.io.IOException ; 25 import java.io.PrintWriter ; 26 import java.net.URL ; 27 import java.util.Enumeration ; 28 import java.util.Hashtable ; 29 import java.util.Vector ; 30 import org.apache.tools.ant.AntClassLoader; 31 import org.apache.tools.ant.BuildException; 32 import org.apache.tools.ant.DirectoryScanner; 33 import org.apache.tools.ant.Project; 34 import org.apache.tools.ant.taskdefs.MatchingTask; 35 import org.apache.tools.ant.taskdefs.rmic.DefaultRmicAdapter; 36 import org.apache.tools.ant.taskdefs.rmic.WLRmic; 37 import org.apache.tools.ant.types.Path; 38 import org.apache.tools.ant.types.Reference; 39 import org.apache.tools.ant.util.FileUtils; 40 import org.apache.tools.ant.util.depend.DependencyAnalyzer; 41 42 46 public class Depend extends MatchingTask { 47 51 private static class ClassFileInfo { 52 53 private File absoluteFile; 54 55 56 private String className; 57 58 59 private File sourceFile; 60 61 62 private boolean isUserWarned = false; 63 } 64 65 66 private Path srcPath; 67 68 69 private Path destPath; 70 71 72 private File cache; 73 74 75 private String [] srcPathList; 76 77 81 private Hashtable affectedClassMap; 82 83 84 private Hashtable classFileInfoMap; 85 86 90 private Hashtable classpathDependencies; 91 92 93 private Hashtable outOfDateClasses; 94 95 100 private boolean closure = false; 101 102 105 private boolean warnOnRmiStubs = true; 106 107 111 private boolean dump = false; 112 113 114 private Path dependClasspath; 115 116 117 private static final String CACHE_FILE_NAME = "dependencies.txt"; 118 119 private static final String CLASSNAME_PREPEND = "||:"; 120 121 127 public void setClasspath(Path classpath) { 128 if (dependClasspath == null) { 129 dependClasspath = classpath; 130 } else { 131 dependClasspath.append(classpath); 132 } 133 } 134 135 140 public Path getClasspath() { 141 return dependClasspath; 142 } 143 144 149 public Path createClasspath() { 150 if (dependClasspath == null) { 151 dependClasspath = new Path(getProject()); 152 } 153 return dependClasspath.createPath(); 154 } 155 156 162 public void setClasspathRef(Reference r) { 163 createClasspath().setRefid(r); 164 } 165 166 172 public void setWarnOnRmiStubs(boolean warnOnRmiStubs) { 173 this.warnOnRmiStubs = warnOnRmiStubs; 174 } 175 176 182 private Hashtable readCachedDependencies(File depFile) throws IOException { 183 Hashtable dependencyMap = new Hashtable (); 184 185 BufferedReader in = null; 186 try { 187 in = new BufferedReader (new FileReader (depFile)); 188 String line = null; 189 Vector dependencyList = null; 190 String className = null; 191 int prependLength = CLASSNAME_PREPEND.length(); 192 while ((line = in.readLine()) != null) { 193 if (line.startsWith(CLASSNAME_PREPEND)) { 194 dependencyList = new Vector (); 195 className = line.substring(prependLength); 196 dependencyMap.put(className, dependencyList); 197 } else { 198 dependencyList.addElement(line); 199 } 200 } 201 } finally { 202 if (in != null) { 203 in.close(); 204 } 205 } 206 207 return dependencyMap; 208 } 209 210 216 private void writeCachedDependencies(Hashtable dependencyMap) 217 throws IOException { 218 if (cache != null) { 219 PrintWriter pw = null; 220 try { 221 cache.mkdirs(); 222 File depFile = new File (cache, CACHE_FILE_NAME); 223 224 pw = new PrintWriter (new FileWriter (depFile)); 225 Enumeration e = dependencyMap.keys(); 226 while (e.hasMoreElements()) { 227 String className = (String ) e.nextElement(); 228 229 pw.println(CLASSNAME_PREPEND + className); 230 231 Vector dependencyList 232 = (Vector ) dependencyMap.get(className); 233 int size = dependencyList.size(); 234 for (int x = 0; x < size; x++) { 235 pw.println(dependencyList.elementAt(x)); 236 } 237 } 238 } finally { 239 if (pw != null) { 240 pw.close(); 241 } 242 } 243 } 244 } 245 246 251 private Path getCheckClassPath() { 252 if (dependClasspath == null) { 253 return null; 254 } 255 256 String [] destPathElements = destPath.list(); 257 String [] classpathElements = dependClasspath.list(); 258 String checkPath = ""; 259 for (int i = 0; i < classpathElements.length; ++i) { 260 String element = classpathElements[i]; 261 boolean inDestPath = false; 262 for (int j = 0; j < destPathElements.length && !inDestPath; ++j) { 263 inDestPath = destPathElements[j].equals(element); 264 } 265 if (!inDestPath) { 266 if (checkPath.length() == 0) { 267 checkPath = element; 268 } else { 269 checkPath += ":" + element; 270 } 271 } 272 } 273 274 if (checkPath.length() == 0) { 275 return null; 276 } 277 278 return new Path(getProject(), checkPath); 279 } 280 281 299 private void determineDependencies() throws IOException { 300 affectedClassMap = new Hashtable (); 301 classFileInfoMap = new Hashtable (); 302 boolean cacheDirty = false; 303 304 Hashtable dependencyMap = new Hashtable (); 305 File cacheFile = null; 306 boolean cacheFileExists = true; 307 long cacheLastModified = Long.MAX_VALUE; 308 309 if (cache != null) { 311 cacheFile = new File (cache, CACHE_FILE_NAME); 312 cacheFileExists = cacheFile.exists(); 313 cacheLastModified = cacheFile.lastModified(); 314 if (cacheFileExists) { 315 dependencyMap = readCachedDependencies(cacheFile); 316 } 317 } 318 Enumeration classfileEnum = getClassFiles(destPath).elements(); 319 while (classfileEnum.hasMoreElements()) { 320 ClassFileInfo info = (ClassFileInfo) classfileEnum.nextElement(); 321 log("Adding class info for " + info.className, Project.MSG_DEBUG); 322 classFileInfoMap.put(info.className, info); 323 324 Vector dependencyList = null; 325 326 if (cache != null) { 327 if (cacheFileExists 330 && cacheLastModified > info.absoluteFile.lastModified()) { 331 dependencyList = (Vector ) dependencyMap.get(info.className); 334 } 335 } 336 337 if (dependencyList == null) { 338 DependencyAnalyzer analyzer = new AntAnalyzer(); 340 analyzer.addRootClass(info.className); 341 analyzer.addClassPath(destPath); 342 analyzer.setClosure(false); 343 dependencyList = new Vector (); 344 Enumeration depEnum = analyzer.getClassDependencies(); 345 while (depEnum.hasMoreElements()) { 346 dependencyList.addElement(depEnum.nextElement()); 347 } 348 cacheDirty = true; 349 dependencyMap.put(info.className, dependencyList); 350 } 351 352 Enumeration depEnum = dependencyList.elements(); 355 while (depEnum.hasMoreElements()) { 356 String dependentClass = (String ) depEnum.nextElement(); 357 358 Hashtable affectedClasses 359 = (Hashtable ) affectedClassMap.get(dependentClass); 360 if (affectedClasses == null) { 361 affectedClasses = new Hashtable (); 362 affectedClassMap.put(dependentClass, affectedClasses); 363 } 364 365 affectedClasses.put(info.className, info); 366 } 367 } 368 369 classpathDependencies = null; 370 Path checkPath = getCheckClassPath(); 371 if (checkPath != null) { 372 classpathDependencies = new Hashtable (); 374 AntClassLoader loader = getProject().createClassLoader(checkPath); 375 376 Hashtable classpathFileCache = new Hashtable (); 377 Object nullFileMarker = new Object (); 378 for (Enumeration e = dependencyMap.keys(); e.hasMoreElements();) { 379 String className = (String ) e.nextElement(); 380 Vector dependencyList = (Vector ) dependencyMap.get(className); 381 Hashtable dependencies = new Hashtable (); 382 classpathDependencies.put(className, dependencies); 383 Enumeration e2 = dependencyList.elements(); 384 while (e2.hasMoreElements()) { 385 String dependency = (String ) e2.nextElement(); 386 Object classpathFileObject 387 = classpathFileCache.get(dependency); 388 if (classpathFileObject == null) { 389 classpathFileObject = nullFileMarker; 390 391 if (!dependency.startsWith("java.") 392 && !dependency.startsWith("javax.")) { 393 URL classURL 394 = loader.getResource(dependency.replace('.', '/') + ".class"); 395 if (classURL != null) { 396 if (classURL.getProtocol().equals("jar")) { 397 String jarFilePath = classURL.getFile(); 398 int classMarker = jarFilePath.indexOf('!'); 399 jarFilePath = jarFilePath.substring(0, classMarker); 400 if (jarFilePath.startsWith("file:")) { 401 classpathFileObject = new File ( 402 FileUtils.getFileUtils().fromURI(jarFilePath)); 403 } else { 404 throw new IOException ( 405 "Bizarre nested path in jar: protocol: " 406 + jarFilePath); 407 } 408 } else if (classURL.getProtocol().equals("file")) { 409 classpathFileObject = new File ( 410 FileUtils.getFileUtils() 411 .fromURI(classURL.toExternalForm())); 412 } 413 log("Class " + className 414 + " depends on " + classpathFileObject 415 + " due to " + dependency, Project.MSG_DEBUG); 416 } 417 } 418 classpathFileCache.put(dependency, classpathFileObject); 419 } 420 if (classpathFileObject != null && classpathFileObject != nullFileMarker) { 421 File jarFile = (File ) classpathFileObject; 423 dependencies.put(jarFile, jarFile); 424 } 425 } 426 } 427 } 428 429 if (cache != null && cacheDirty) { 431 writeCachedDependencies(dependencyMap); 432 } 433 } 434 435 441 private int deleteAllAffectedFiles() { 442 int count = 0; 443 for (Enumeration e = outOfDateClasses.elements(); e.hasMoreElements();) { 444 String className = (String ) e.nextElement(); 445 count += deleteAffectedFiles(className); 446 ClassFileInfo classInfo 447 = (ClassFileInfo) classFileInfoMap.get(className); 448 if (classInfo != null && classInfo.absoluteFile.exists()) { 449 classInfo.absoluteFile.delete(); 450 count++; 451 } 452 } 453 return count; 454 } 455 456 463 private int deleteAffectedFiles(String className) { 464 int count = 0; 465 466 Hashtable affectedClasses = (Hashtable ) affectedClassMap.get(className); 467 if (affectedClasses == null) { 468 return count; 469 } 470 for (Enumeration e = affectedClasses.keys(); e.hasMoreElements();) { 471 String affectedClass = (String ) e.nextElement(); 472 ClassFileInfo affectedClassInfo 473 = (ClassFileInfo) affectedClasses.get(affectedClass); 474 475 if (!affectedClassInfo.absoluteFile.exists()) { 476 continue; 477 } 478 479 if (affectedClassInfo.sourceFile == null) { 480 warnOutOfDateButNotDeleted(affectedClassInfo, affectedClass, className); 481 continue; 482 } 483 484 log("Deleting file " + affectedClassInfo.absoluteFile.getPath() 485 + " since " + className + " out of date", Project.MSG_VERBOSE); 486 487 affectedClassInfo.absoluteFile.delete(); 488 count++; 489 if (closure) { 490 count += deleteAffectedFiles(affectedClass); 491 } else { 492 495 if (affectedClass.indexOf("$") == -1) { 496 continue; 497 } 498 String topLevelClassName 500 = affectedClass.substring(0, affectedClass.indexOf("$")); 501 log("Top level class = " + topLevelClassName, 502 Project.MSG_VERBOSE); 503 ClassFileInfo topLevelClassInfo 504 = (ClassFileInfo) classFileInfoMap.get(topLevelClassName); 505 if (topLevelClassInfo != null 506 && topLevelClassInfo.absoluteFile.exists()) { 507 log("Deleting file " 508 + topLevelClassInfo.absoluteFile.getPath() 509 + " since one of its inner classes was removed", 510 Project.MSG_VERBOSE); 511 topLevelClassInfo.absoluteFile.delete(); 512 count++; 513 if (closure) { 514 count += deleteAffectedFiles(topLevelClassName); 515 } 516 } 517 } 518 } 519 return count; 520 } 521 522 530 private void warnOutOfDateButNotDeleted( 531 ClassFileInfo affectedClassInfo, String affectedClass, 532 String className) { 533 if (affectedClassInfo.isUserWarned) { 534 return; 535 } 536 int level = Project.MSG_WARN; 537 if (!warnOnRmiStubs) { 538 if (isRmiStub(affectedClass, className)) { 542 level = Project.MSG_VERBOSE; 543 } 544 } 545 log("The class " + affectedClass + " in file " 546 + affectedClassInfo.absoluteFile.getPath() 547 + " is out of date due to " + className 548 + " but has not been deleted because its source file" 549 + " could not be determined", level); 550 affectedClassInfo.isUserWarned = true; 551 } 552 553 559 private boolean isRmiStub(String affectedClass, String className) { 560 return isStub(affectedClass, className, DefaultRmicAdapter.RMI_STUB_SUFFIX) 561 || isStub(affectedClass, className, DefaultRmicAdapter.RMI_SKEL_SUFFIX) 562 || isStub(affectedClass, className, WLRmic.RMI_STUB_SUFFIX) 563 || isStub(affectedClass, className, WLRmic.RMI_SKEL_SUFFIX); 564 } 565 566 private boolean isStub(String affectedClass, String baseClass, String suffix) { 567 return (baseClass + suffix).equals(affectedClass); 568 } 569 570 573 private void dumpDependencies() { 574 log("Reverse Dependency Dump for " + affectedClassMap.size() 575 + " classes:", Project.MSG_DEBUG); 576 577 Enumeration classEnum = affectedClassMap.keys(); 578 while (classEnum.hasMoreElements()) { 579 String className = (String ) classEnum.nextElement(); 580 log(" Class " + className + " affects:", Project.MSG_DEBUG); 581 Hashtable affectedClasses 582 = (Hashtable ) affectedClassMap.get(className); 583 Enumeration affectedClassEnum = affectedClasses.keys(); 584 while (affectedClassEnum.hasMoreElements()) { 585 String affectedClass = (String ) affectedClassEnum.nextElement(); 586 ClassFileInfo info 587 = (ClassFileInfo) affectedClasses.get(affectedClass); 588 log(" " + affectedClass + " in " 589 + info.absoluteFile.getPath(), Project.MSG_DEBUG); 590 } 591 } 592 593 if (classpathDependencies != null) { 594 log("Classpath file dependencies (Forward):", Project.MSG_DEBUG); 595 596 Enumeration classpathEnum = classpathDependencies.keys(); 597 while (classpathEnum.hasMoreElements()) { 598 String className = (String ) classpathEnum.nextElement(); 599 log(" Class " + className + " depends on:", Project.MSG_DEBUG); 600 Hashtable dependencies 601 = (Hashtable ) classpathDependencies.get(className); 602 603 Enumeration classpathFileEnum = dependencies.elements(); 604 while (classpathFileEnum.hasMoreElements()) { 605 File classpathFile = (File ) classpathFileEnum.nextElement(); 606 log(" " + classpathFile.getPath(), Project.MSG_DEBUG); 607 } 608 } 609 } 610 } 611 612 private void determineOutOfDateClasses() { 613 outOfDateClasses = new Hashtable (); 614 for (int i = 0; i < srcPathList.length; i++) { 615 File srcDir = getProject().resolveFile(srcPathList[i]); 616 if (srcDir.exists()) { 617 DirectoryScanner ds = this.getDirectoryScanner(srcDir); 618 String [] files = ds.getIncludedFiles(); 619 scanDir(srcDir, files); 620 } 621 } 622 623 if (classpathDependencies == null) { 625 return; 626 } 627 628 Enumeration classpathDepsEnum = classpathDependencies.keys(); 629 while (classpathDepsEnum.hasMoreElements()) { 630 String className = (String ) classpathDepsEnum.nextElement(); 631 if (outOfDateClasses.containsKey(className)) { 632 continue; 633 } 634 ClassFileInfo info 635 = (ClassFileInfo) classFileInfoMap.get(className); 636 637 if (info != null) { 640 Hashtable dependencies 641 = (Hashtable ) classpathDependencies.get(className); 642 for (Enumeration e2 = dependencies.elements(); e2.hasMoreElements();) { 643 File classpathFile = (File ) e2.nextElement(); 644 if (classpathFile.lastModified() 645 > info.absoluteFile.lastModified()) { 646 log("Class " + className 647 + " is out of date with respect to " 648 + classpathFile, Project.MSG_DEBUG); 649 outOfDateClasses.put(className, className); 650 break; 651 } 652 } 653 } 654 } 655 } 656 657 662 public void execute() throws BuildException { 663 try { 664 long start = System.currentTimeMillis(); 665 if (srcPath == null) { 666 throw new BuildException("srcdir attribute must be set", 667 getLocation()); 668 } 669 670 srcPathList = srcPath.list(); 671 if (srcPathList.length == 0) { 672 throw new BuildException("srcdir attribute must be non-empty", 673 getLocation()); 674 } 675 676 if (destPath == null) { 677 destPath = srcPath; 678 } 679 680 if (cache != null && cache.exists() && !cache.isDirectory()) { 681 throw new BuildException("The cache, if specified, must " 682 + "point to a directory"); 683 } 684 685 if (cache != null && !cache.exists()) { 686 cache.mkdirs(); 687 } 688 689 determineDependencies(); 690 if (dump) { 691 dumpDependencies(); 692 } 693 determineOutOfDateClasses(); 694 int count = deleteAllAffectedFiles(); 695 696 long duration = (System.currentTimeMillis() - start) / 1000; 697 698 final int summaryLogLevel; 699 if (count > 0) { 700 summaryLogLevel = Project.MSG_INFO; 701 } else { 702 summaryLogLevel = Project.MSG_DEBUG; 703 } 704 705 log("Deleted " + count + " out of date files in " 706 + duration + " seconds", summaryLogLevel); 707 } catch (Exception e) { 708 throw new BuildException(e); 709 } 710 } 711 712 721 protected void scanDir(File srcDir, String [] files) { 722 723 for (int i = 0; i < files.length; i++) { 724 File srcFile = new File (srcDir, files[i]); 725 if (files[i].endsWith(".java")) { 726 String filePath = srcFile.getPath(); 727 String className 728 = filePath.substring(srcDir.getPath().length() + 1, 729 filePath.length() - ".java".length()); 730 className = ClassFileUtils.convertSlashName(className); 731 ClassFileInfo info 732 = (ClassFileInfo) classFileInfoMap.get(className); 733 if (info == null) { 734 outOfDateClasses.put(className, className); 736 } else { 737 if (srcFile.lastModified() 738 > info.absoluteFile.lastModified()) { 739 outOfDateClasses.put(className, className); 740 } 741 } 742 } 743 } 744 } 745 746 747 754 private Vector getClassFiles(Path classLocations) { 755 String [] classLocationsList = classLocations.list(); 757 758 Vector classFileList = new Vector (); 759 760 for (int i = 0; i < classLocationsList.length; ++i) { 761 File dir = new File (classLocationsList[i]); 762 if (dir.isDirectory()) { 763 addClassFiles(classFileList, dir, dir); 764 } 765 } 766 767 return classFileList; 768 } 769 770 775 private File findSourceFile(String classname) { 776 String sourceFilename = classname + ".java"; 777 int innerIndex = classname.indexOf("$"); 778 if (innerIndex != -1) { 779 sourceFilename = classname.substring(0, innerIndex) + ".java"; 780 } 781 782 for (int i = 0; i < srcPathList.length; ++i) { 784 File sourceFile = new File (srcPathList[i], sourceFilename); 785 if (sourceFile.exists()) { 786 return sourceFile; 787 } 788 } 789 return null; 790 } 791 792 804 private void addClassFiles(Vector classFileList, File dir, File root) { 805 String [] filesInDir = dir.list(); 806 807 if (filesInDir == null) { 808 return; 809 } 810 int length = filesInDir.length; 811 812 int rootLength = root.getPath().length(); 813 for (int i = 0; i < length; ++i) { 814 File file = new File (dir, filesInDir[i]); 815 if (file.isDirectory()) { 816 addClassFiles(classFileList, file, root); 817 } else if (file.getName().endsWith(".class")) { 818 ClassFileInfo info = new ClassFileInfo(); 819 info.absoluteFile = file; 820 String relativeName = file.getPath().substring(rootLength + 1, 821 file.getPath().length() - 6); 822 info.className 823 = ClassFileUtils.convertSlashName(relativeName); 824 info.sourceFile = findSourceFile(relativeName); 825 classFileList.addElement(info); 826 } 827 } 828 } 829 830 831 836 public void setSrcdir(Path srcPath) { 837 this.srcPath = srcPath; 838 } 839 840 845 public void setDestDir(Path destPath) { 846 this.destPath = destPath; 847 } 848 849 854 public void setCache(File cache) { 855 this.cache = cache; 856 } 857 858 866 public void setClosure(boolean closure) { 867 this.closure = closure; 868 } 869 870 876 public void setDump(boolean dump) { 877 this.dump = dump; 878 } 879 } 880 881 | Popular Tags |