Clover coverage report - Ant Coverage
Coverage timestamp: Tue Apr 8 2003 20:43:55 EST
file stats: LOC: 846   Methods: 22
NCLOC: 504   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
Depend.java 60.1% 72.2% 90.9% 69%
 1   
 /*
 2   
  * The Apache Software License, Version 1.1
 3   
  *
 4   
  * Copyright (c) 2000-2003 The Apache Software Foundation.  All rights
 5   
  * reserved.
 6   
  *
 7   
  * Redistribution and use in source and binary forms, with or without
 8   
  * modification, are permitted provided that the following conditions
 9   
  * are met:
 10   
  *
 11   
  * 1. Redistributions of source code must retain the above copyright
 12   
  *    notice, this list of conditions and the following disclaimer.
 13   
  *
 14   
  * 2. Redistributions in binary form must reproduce the above copyright
 15   
  *    notice, this list of conditions and the following disclaimer in
 16   
  *    the documentation and/or other materials provided with the
 17   
  *    distribution.
 18   
  *
 19   
  * 3. The end-user documentation included with the redistribution, if
 20   
  *    any, must include the following acknowlegement:
 21   
  *       "This product includes software developed by the
 22   
  *        Apache Software Foundation (http://www.apache.org/)."
 23   
  *    Alternately, this acknowlegement may appear in the software itself,
 24   
  *    if and wherever such third-party acknowlegements normally appear.
 25   
  *
 26   
  * 4. The names "Ant" and "Apache Software
 27   
  *    Foundation" must not be used to endorse or promote products derived
 28   
  *    from this software without prior written permission. For written
 29   
  *    permission, please contact apache@apache.org.
 30   
  *
 31   
  * 5. Products derived from this software may not be called "Apache"
 32   
  *    nor may "Apache" appear in their names without prior written
 33   
  *    permission of the Apache Group.
 34   
  *
 35   
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 36   
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 37   
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 38   
  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 39   
  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 40   
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 41   
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 42   
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 43   
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 44   
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 45   
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 46   
  * SUCH DAMAGE.
 47   
  * ====================================================================
 48   
  *
 49   
  * This software consists of voluntary contributions made by many
 50   
  * individuals on behalf of the Apache Software Foundation.  For more
 51   
  * information on the Apache Software Foundation, please see
 52   
  * <http://www.apache.org/>.
 53   
  */
 54   
 package org.apache.tools.ant.taskdefs.optional.depend;
 55   
 
 56   
 import java.io.BufferedReader;
 57   
 import java.io.File;
 58   
 import java.io.FileReader;
 59   
 import java.io.FileWriter;
 60   
 import java.io.IOException;
 61   
 import java.io.PrintWriter;
 62   
 import java.net.URL;
 63   
 import java.util.Enumeration;
 64   
 import java.util.Hashtable;
 65   
 import java.util.Vector;
 66   
 import org.apache.tools.ant.AntClassLoader;
 67   
 import org.apache.tools.ant.BuildException;
 68   
 import org.apache.tools.ant.DirectoryScanner;
 69   
 import org.apache.tools.ant.Project;
 70   
 import org.apache.tools.ant.taskdefs.MatchingTask;
 71   
 import org.apache.tools.ant.types.Path;
 72   
 import org.apache.tools.ant.types.Reference;
 73   
 import org.apache.tools.ant.util.depend.DependencyAnalyzer;
 74   
 
 75   
 /**
 76   
  * Generates a dependency file for a given set of classes.
 77   
  *
 78   
  * @author Conor MacNeill
 79   
  */
 80   
 public class Depend extends MatchingTask {
 81   
     /**
 82   
      * A class (struct) user to manage information about a class
 83   
      *
 84   
      * @author Conor MacNeill
 85   
      */
 86   
     private static class ClassFileInfo {
 87   
         /** The file where the class file is stored in the file system */
 88   
         private File absoluteFile;
 89   
 
 90   
         /** The Java class name of this class */
 91   
         private String className;
 92   
 
 93   
         /** The source File containing this class */
 94   
         private File sourceFile;
 95   
 
 96   
         /** if user has been warned about this file not having a source file */
 97   
         private boolean isUserWarned = false;
 98   
     }
 99   
 
 100   
     /** The path where source files exist */
 101   
     private Path srcPath;
 102   
 
 103   
     /** The path where compiled class files exist. */
 104   
     private Path destPath;
 105   
 
 106   
     /** The directory which contains the dependency cache. */
 107   
     private File cache;
 108   
 
 109   
     /** The list of source paths derived from the srcPath field. */
 110   
     private String[] srcPathList;
 111   
 
 112   
     /**
 113   
      * A map which gives for every class a list of the class which it
 114   
      * affects.
 115   
      */
 116   
     private Hashtable affectedClassMap;
 117   
 
 118   
     /** A map which gives information about a class */
 119   
     private Hashtable classFileInfoMap;
 120   
 
 121   
     /**
 122   
      * A map which gives the list of jars and classes from the classpath
 123   
      * that a class depends upon
 124   
      */
 125   
     private Hashtable classpathDependencies;
 126   
 
 127   
     /** The list of classes which are out of date. */
 128   
     private Hashtable outOfDateClasses;
 129   
 
 130   
     /**
 131   
      * indicates that the dependency relationships should be extended beyond
 132   
      * direct dependencies to include all classes. So if A directly affects
 133   
      * B abd B directly affects C, then A indirectly affects C.
 134   
      */
 135   
     private boolean closure = false;
 136   
 
 137   
     /**
 138   
      * Flag which controls whether the reversed dependencies should be
 139   
      * dumped to the log
 140   
      */
 141   
     private boolean dump = false;
 142   
 
 143   
     /** The classpath to look for additional dependencies */
 144   
     private Path dependClasspath;
 145   
 
 146   
     /** constants used with the cache file */
 147   
     private static final String CACHE_FILE_NAME = "dependencies.txt";
 148   
     /** String Used to separate classnames in the dependency file */
 149   
     private static final String CLASSNAME_PREPEND = "||:";
 150   
 
 151   
     /**
 152   
      * Set the classpath to be used for this dependency check.
 153   
      *
 154   
      * @param classpath the classpath to be used when checking for
 155   
      *      dependencies on elements in the classpath
 156   
      */
 157  0
     public void setClasspath(Path classpath) {
 158  0
         if (dependClasspath == null) {
 159  0
             dependClasspath = classpath;
 160   
         } else {
 161  0
             dependClasspath.append(classpath);
 162   
         }
 163   
     }
 164   
 
 165   
     /**
 166   
      * Gets the classpath to be used for this dependency check.
 167   
      *
 168   
      * @return the current dependency classpath
 169   
      */
 170  0
     public Path getClasspath() {
 171  0
         return dependClasspath;
 172   
     }
 173   
 
 174   
     /**
 175   
      * Adds a classpath to be used for this dependency check.
 176   
      *
 177   
      * @return A path object to be configured by Ant
 178   
      */
 179  1
     public Path createClasspath() {
 180  1
         if (dependClasspath == null) {
 181  1
             dependClasspath = new Path(getProject());
 182   
         }
 183  1
         return dependClasspath.createPath();
 184   
     }
 185   
 
 186   
     /**
 187   
      * Adds a reference to a classpath defined elsewhere.
 188   
      *
 189   
      * @param r a reference to a path object to be used as the depend
 190   
      *      classpath
 191   
      */
 192  1
     public void setClasspathRef(Reference r) {
 193  1
         createClasspath().setRefid(r);
 194   
     }
 195   
 
 196   
     /**
 197   
      * Read the dependencies from cache file
 198   
      *
 199   
      * @return a collection of class dependencies
 200   
      * @exception IOException if the dependnecy file cannot be read
 201   
      */
 202  1
     private Hashtable readCachedDependencies(File depFile) throws IOException {
 203  1
         Hashtable dependencyMap = new Hashtable();
 204   
 
 205  1
         BufferedReader in = null;
 206  1
         try {
 207  1
             in = new BufferedReader(new FileReader(depFile));
 208  1
             String line = null;
 209  1
             Vector dependencyList = null;
 210  1
             String className = null;
 211  1
             int prependLength = CLASSNAME_PREPEND.length();
 212  ?
             while ((line = in.readLine()) != null) {
 213  21
                 if (line.startsWith(CLASSNAME_PREPEND)) {
 214  5
                     dependencyList = new Vector();
 215  5
                     className = line.substring(prependLength);
 216  5
                     dependencyMap.put(className, dependencyList);
 217   
                 } else {
 218  16
                     dependencyList.addElement(line);
 219   
                 }
 220   
             }
 221   
         } finally {
 222  1
             if (in != null) {
 223  1
                 in.close();
 224   
             }
 225   
         }
 226   
 
 227  1
         return dependencyMap;
 228   
     }
 229   
 
 230   
     /**
 231   
      * Write the dependencies to cache file
 232   
      *
 233   
      * @param dependencyMap the map of dependencies to be written out.
 234   
      * @exception IOException if the dependency file cannot be written out.
 235   
      */
 236  1
     private void writeCachedDependencies(Hashtable dependencyMap)
 237   
          throws IOException {
 238  1
         if (cache != null) {
 239  1
             PrintWriter pw = null;
 240  1
             try {
 241  1
                 cache.mkdirs();
 242  1
                 File depFile = new File(cache, CACHE_FILE_NAME);
 243   
 
 244  1
                 pw = new PrintWriter(new FileWriter(depFile));
 245  1
                 Enumeration e = dependencyMap.keys();
 246  1
                 while (e.hasMoreElements()) {
 247  5
                     String className = (String) e.nextElement();
 248   
 
 249  5
                     pw.println(CLASSNAME_PREPEND + className);
 250   
 
 251  5
                     Vector dependencyList
 252   
                          = (Vector) dependencyMap.get(className);
 253  5
                     int size = dependencyList.size();
 254  5
                     for (int x = 0; x < size; x++) {
 255  16
                         pw.println(dependencyList.elementAt(x));
 256   
                     }
 257   
                 }
 258   
             } finally {
 259  1
                 if (pw != null) {
 260  1
                     pw.close();
 261   
                 }
 262   
             }
 263   
         }
 264   
     }
 265   
 
 266   
     /**
 267   
      * Get the classpath for dependency checking.
 268   
      *
 269   
      * This method removes the dest dirs if it is given from the dependency classpath
 270   
      */
 271  8
     private Path getCheckClassPath() {
 272  8
         if (dependClasspath == null) {
 273  7
             return null;
 274   
         }
 275   
 
 276  1
         String[] destPathElements = destPath.list();
 277  1
         String[] classpathElements = dependClasspath.list();
 278  1
         String checkPath = "";
 279  1
         for (int i = 0; i < classpathElements.length; ++i) {
 280  1
             String element = classpathElements[i];
 281  1
             boolean inDestPath = false;
 282  1
             for (int j = 0; j < destPathElements.length && !inDestPath; ++j) {
 283  1
                 inDestPath = destPathElements[j].equals(element);
 284   
             }
 285  1
             if (!inDestPath) {
 286  0
                 if (checkPath.length() == 0) {
 287  0
                     checkPath = element;
 288   
                 } else {
 289  0
                     checkPath += ":" + element;
 290   
                 }
 291   
             }
 292   
         }
 293   
 
 294  1
         if (checkPath.length() == 0) {
 295  1
             return null;
 296   
         }
 297   
 
 298  0
         return new Path(getProject(), checkPath);
 299   
     }
 300   
 
 301   
     /**
 302   
      * Determine the dependencies between classes. Class dependencies are
 303   
      * determined by examining the class references in a class file to other
 304   
      * classes.
 305   
      *
 306   
      * This method sets up the following fields
 307   
      * <ul>
 308   
      *   <li>affectedClassMap - the list of classes each class affects</li>
 309   
      *   <li>classFileInfoMap - information about each class</li>
 310   
      *   <li>classpathDependencies - the list of jars and classes from the
 311   
      *                             classpath that each class depends upon.</li>
 312   
      * </ul>
 313   
      *
 314   
      * If required, the dependencies are written to the cache.
 315   
      *
 316   
      * @exception IOException if either the dependencies cache or the class
 317   
      *      files cannot be read or written
 318   
      */
 319  8
     private void determineDependencies() throws IOException {
 320  8
         affectedClassMap = new Hashtable();
 321  8
         classFileInfoMap = new Hashtable();
 322  8
         boolean cacheDirty = false;
 323   
 
 324  8
         Hashtable dependencyMap = new Hashtable();
 325  8
         File cacheFile = null;
 326  8
         boolean cacheFileExists = true;
 327  8
         long cacheLastModified = Long.MAX_VALUE;
 328   
 
 329   
         // read the dependency cache from the disk
 330  8
         if (cache != null) {
 331  2
             cacheFile = new File(cache, CACHE_FILE_NAME);
 332  2
             cacheFileExists = cacheFile.exists();
 333  2
             cacheLastModified = cacheFile.lastModified();
 334  2
             if (cacheFileExists) {
 335  1
                 dependencyMap = readCachedDependencies(cacheFile);
 336   
             }
 337   
         }
 338  8
         Enumeration classfileEnum = getClassFiles(destPath).elements();
 339  8
         while (classfileEnum.hasMoreElements()) {
 340  32
             ClassFileInfo info = (ClassFileInfo) classfileEnum.nextElement();
 341  32
             log("Adding class info for " + info.className, Project.MSG_DEBUG);
 342  32
             classFileInfoMap.put(info.className, info);
 343   
 
 344  32
             Vector dependencyList = null;
 345   
 
 346  32
             if (cache != null) {
 347   
                 // try to read the dependency info from the map if it is
 348   
                 // not out of date
 349  10
                 if (cacheFileExists
 350   
                     && cacheLastModified > info.absoluteFile.lastModified()) {
 351   
                     // depFile exists and is newer than the class file
 352   
                     // need to get dependency list from the map.
 353  5
                     dependencyList = (Vector) dependencyMap.get(info.className);
 354   
                 }
 355   
             }
 356   
 
 357  32
             if (dependencyList == null) {
 358   
                 // not cached - so need to read directly from the class file
 359  27
                 DependencyAnalyzer analyzer = new AntAnalyzer();
 360  27
                 analyzer.addRootClass(info.className);
 361  27
                 analyzer.addClassPath(destPath);
 362  27
                 analyzer.setClosure(false);
 363  27
                 dependencyList = new Vector();
 364  27
                 Enumeration depEnum = analyzer.getClassDependencies();
 365  27
                 while (depEnum.hasMoreElements()) {
 366  82
                     dependencyList.addElement(depEnum.nextElement());
 367   
                 }
 368  27
                 if (dependencyList != null) {
 369  27
                     cacheDirty = true;
 370  27
                     dependencyMap.put(info.className, dependencyList);
 371   
                 }
 372   
             }
 373   
 
 374   
             // This class depends on each class in the dependency list. For each
 375   
             // one of those, add this class into their affected classes list
 376  32
             Enumeration depEnum = dependencyList.elements();
 377  32
             while (depEnum.hasMoreElements()) {
 378  98
                 String dependentClass = (String) depEnum.nextElement();
 379   
 
 380  98
                 Hashtable affectedClasses
 381   
                     = (Hashtable) affectedClassMap.get(dependentClass);
 382  98
                 if (affectedClasses == null) {
 383  60
                     affectedClasses = new Hashtable();
 384  60
                     affectedClassMap.put(dependentClass, affectedClasses);
 385   
                 }
 386   
 
 387  98
                 affectedClasses.put(info.className, info);
 388   
             }
 389   
         }
 390   
 
 391  8
         classpathDependencies = null;
 392  8
         Path checkPath = getCheckClassPath();
 393  8
         if (checkPath != null) {
 394   
             // now determine which jars each class depends upon
 395  0
             classpathDependencies = new Hashtable();
 396  0
             AntClassLoader loader = getProject().createClassLoader(checkPath);
 397   
 
 398  0
             Hashtable classpathFileCache = new Hashtable();
 399  0
             Object nullFileMarker = new Object();
 400  0
             for (Enumeration e = dependencyMap.keys(); e.hasMoreElements();) {
 401  0
                 String className = (String) e.nextElement();
 402  0
                 Vector dependencyList = (Vector) dependencyMap.get(className);
 403  0
                 Hashtable dependencies = new Hashtable();
 404  0
                 classpathDependencies.put(className, dependencies);
 405  0
                 Enumeration e2 = dependencyList.elements();
 406  0
                 while (e2.hasMoreElements()) {
 407  0
                     String dependency = (String) e2.nextElement();
 408  0
                     Object classpathFileObject
 409   
                         = classpathFileCache.get(dependency);
 410  0
                     if (classpathFileObject == null) {
 411  0
                         classpathFileObject = nullFileMarker;
 412   
 
 413  0
                         if (!dependency.startsWith("java.")
 414   
                             && !dependency.startsWith("javax.")) {
 415  0
                             URL classURL = loader.getResource(dependency.replace('.', '/') + ".class");
 416  0
                             if (classURL != null) {
 417  0
                                 if (classURL.getProtocol().equals("jar")) {
 418  0
                                     String jarFilePath = classURL.getFile();
 419  0
                                     if (jarFilePath.startsWith("file:")) {
 420  0
                                         int classMarker = jarFilePath.indexOf('!');
 421  0
                                         jarFilePath = jarFilePath.substring(5, classMarker);
 422   
                                     }
 423  0
                                     classpathFileObject = new File(jarFilePath);
 424  0
                                 } else if (classURL.getProtocol().equals("file")) {
 425  0
                                     String classFilePath = classURL.getFile();
 426  0
                                     classpathFileObject = new File(classFilePath);
 427   
                                 }
 428  0
                                 log("Class " + className +
 429   
                                     " depends on " + classpathFileObject +
 430   
                                     " due to " + dependency, Project.MSG_DEBUG);
 431   
                             }
 432   
                         }
 433  0
                         classpathFileCache.put(dependency, classpathFileObject);
 434   
                     }
 435  0
                     if (classpathFileObject != null && classpathFileObject != nullFileMarker) {
 436   
                         // we need to add this jar to the list for this class.
 437  0
                         File jarFile = (File) classpathFileObject;
 438  0
                         dependencies.put(jarFile, jarFile);
 439   
                     }
 440   
                 }
 441   
             }
 442   
         }
 443   
 
 444   
         // write the dependency cache to the disk
 445  8
         if (cache != null && cacheDirty) {
 446  1
             writeCachedDependencies(dependencyMap);
 447   
         }
 448   
     }
 449   
 
 450   
     /**
 451   
      * Delete all the class files which are out of date, by way of their
 452   
      * dependency on a class which is out of date
 453   
      *
 454   
      * @return the number of files deleted.
 455   
      */
 456  8
     private int deleteAllAffectedFiles() {
 457  8
         int count = 0;
 458  8
         for (Enumeration e = outOfDateClasses.elements(); e.hasMoreElements();) {
 459  5
             String className = (String) e.nextElement();
 460  5
             count += deleteAffectedFiles(className);
 461  5
             ClassFileInfo classInfo
 462   
                 = (ClassFileInfo) classFileInfoMap.get(className);
 463  5
             if (classInfo != null && classInfo.absoluteFile.exists()) {
 464  0
                 classInfo.absoluteFile.delete();
 465  0
                 count++;
 466   
             }
 467   
         }
 468  8
         return count;
 469   
     }
 470   
 
 471   
     /**
 472   
      * Delete all the class files of classes which depend on the given class
 473   
      *
 474   
      * @param className the name of the class whose dependent classes willbe
 475   
      *      deleted
 476   
      * @return the number of class files removed
 477   
      */
 478  16
     private int deleteAffectedFiles(String className) {
 479  16
         int count = 0;
 480   
 
 481  16
         Hashtable affectedClasses = (Hashtable) affectedClassMap.get(className);
 482  16
         if (affectedClasses == null) {
 483  0
             return count;
 484   
         }
 485  16
         for (Enumeration e = affectedClasses.keys(); e.hasMoreElements();) {
 486  33
             String affectedClass = (String) e.nextElement();
 487  33
             ClassFileInfo affectedClassInfo
 488   
                 = (ClassFileInfo) affectedClasses.get(affectedClass);
 489   
 
 490  33
             if (!affectedClassInfo.absoluteFile.exists()) {
 491  18
                 continue;
 492   
             }
 493   
 
 494  15
             if (affectedClassInfo.sourceFile == null) {
 495  2
                 if (!affectedClassInfo.isUserWarned) {
 496  1
                     log("The class " + affectedClass + " in file "
 497   
                         + affectedClassInfo.absoluteFile.getPath()
 498   
                         + " is out of date due to " + className
 499   
                         + " but has not been deleted because its source file"
 500   
                         + " could not be determined", Project.MSG_WARN);
 501  1
                     affectedClassInfo.isUserWarned = true;
 502   
                 }
 503  2
                 continue;
 504   
             }
 505   
 
 506  13
             log("Deleting file " + affectedClassInfo.absoluteFile.getPath()
 507   
                 + " since " + className + " out of date", Project.MSG_VERBOSE);
 508   
 
 509  13
             affectedClassInfo.absoluteFile.delete();
 510  13
             count++;
 511  13
             if (closure) {
 512  11
                 count += deleteAffectedFiles(affectedClass);
 513   
             } else {
 514   
                 // without closure we may delete an inner class but not the
 515   
                 // top level class which would not trigger a recompile.
 516   
 
 517  2
                 if (affectedClass.indexOf("$") == -1) {
 518  2
                     continue;
 519   
                 }
 520   
                 // need to delete the main class
 521  0
                 String topLevelClassName
 522   
                      = affectedClass.substring(0, affectedClass.indexOf("$"));
 523  0
                 log("Top level class = " + topLevelClassName,
 524   
                     Project.MSG_VERBOSE);
 525  0
                 ClassFileInfo topLevelClassInfo
 526   
                      = (ClassFileInfo) classFileInfoMap.get(topLevelClassName);
 527  0
                 if (topLevelClassInfo != null &&
 528   
                     topLevelClassInfo.absoluteFile.exists()) {
 529  0
                     log("Deleting file "
 530   
                         + topLevelClassInfo.absoluteFile.getPath()
 531   
                         + " since one of its inner classes was removed",
 532   
                         Project.MSG_VERBOSE);
 533  0
                     topLevelClassInfo.absoluteFile.delete();
 534  0
                     count++;
 535  0
                     if (closure) {
 536  0
                         count += deleteAffectedFiles(topLevelClassName);
 537   
                     }
 538   
                 }
 539   
             }
 540   
         }
 541  16
         return count;
 542   
     }
 543   
 
 544   
     /**
 545   
      * Dump the dependency information loaded from the classes to the Ant log
 546   
      */
 547  1
     private void dumpDependencies() {
 548  1
         log("Reverse Dependency Dump for " + affectedClassMap.size() +
 549   
             " classes:", Project.MSG_DEBUG);
 550   
 
 551  1
         Enumeration classEnum = affectedClassMap.keys();
 552  1
         while (classEnum.hasMoreElements()) {
 553  3
             String className = (String) classEnum.nextElement();
 554  3
             log(" Class " + className + " affects:", Project.MSG_DEBUG);
 555  3
             Hashtable affectedClasses
 556   
                 = (Hashtable) affectedClassMap.get(className);
 557  3
             Enumeration affectedClassEnum = affectedClasses.keys();
 558  3
             while (affectedClassEnum.hasMoreElements()) {
 559  6
                 String affectedClass = (String) affectedClassEnum.nextElement();
 560  6
                 ClassFileInfo info
 561   
                     = (ClassFileInfo) affectedClasses.get(affectedClass);
 562  6
                 log("    " + affectedClass + " in "
 563   
                     + info.absoluteFile.getPath(), Project.MSG_DEBUG);
 564   
             }
 565   
         }
 566   
 
 567  1
         if (classpathDependencies != null) {
 568  0
             log("Classpath file dependencies (Forward):", Project.MSG_DEBUG);
 569   
 
 570  0
             Enumeration classpathEnum = classpathDependencies.keys();
 571  0
             while (classpathEnum.hasMoreElements()) {
 572  0
                 String className = (String) classpathEnum.nextElement();
 573  0
                 log(" Class " + className + " depends on:", Project.MSG_DEBUG);
 574  0
                 Hashtable dependencies
 575   
                     = (Hashtable) classpathDependencies.get(className);
 576   
 
 577  0
                 Enumeration classpathFileEnum = dependencies.elements();
 578  0
                 while (classpathFileEnum.hasMoreElements()) {
 579  0
                     File classpathFile = (File) classpathFileEnum.nextElement();
 580  0
                     log("    " + classpathFile.getPath(), Project.MSG_DEBUG);
 581   
                 }
 582   
             }
 583   
         }
 584   
     }
 585   
 
 586  8
     private void determineOutOfDateClasses() {
 587  8
         outOfDateClasses = new Hashtable();
 588  8
         for (int i = 0; i < srcPathList.length; i++) {
 589  8
             File srcDir = (File) getProject().resolveFile(srcPathList[i]);
 590  8
             if (srcDir.exists()) {
 591  8
                 DirectoryScanner ds = this.getDirectoryScanner(srcDir);
 592  8
                 String[] files = ds.getIncludedFiles();
 593  8
                 scanDir(srcDir, files);
 594   
             }
 595   
         }
 596   
 
 597   
         // now check classpath file dependencies
 598  8
         if (classpathDependencies == null) {
 599  8
             return;
 600   
         }
 601   
 
 602  0
         Enumeration classpathDepsEnum = classpathDependencies.keys();
 603  0
         while (classpathDepsEnum.hasMoreElements()) {
 604  0
             String className = (String) classpathDepsEnum.nextElement();
 605  0
             if (outOfDateClasses.containsKey(className)) {
 606  0
                 continue;
 607   
             }
 608  0
             ClassFileInfo info
 609   
                 = (ClassFileInfo) classFileInfoMap.get(className);
 610   
 
 611   
             // if we have no info about the class - it may have been deleted already and we
 612   
             // are using cached info.
 613  0
             if (info != null) {
 614  0
                 Hashtable dependencies
 615   
                     = (Hashtable) classpathDependencies.get(className);
 616  0
                 for (Enumeration e2 = dependencies.elements(); e2.hasMoreElements();) {
 617  0
                     File classpathFile = (File) e2.nextElement();
 618  0
                     if (classpathFile.lastModified()
 619   
                         > info.absoluteFile.lastModified()) {
 620  0
                         log("Class " + className +
 621   
                             " is out of date with respect to " + classpathFile, Project.MSG_DEBUG);
 622  0
                         outOfDateClasses.put(className, className);
 623  0
                         break;
 624   
                     }
 625   
                 }
 626   
             }
 627   
         }
 628   
     }
 629   
 
 630   
     /**
 631   
      * Does the work.
 632   
      *
 633   
      * @exception BuildException Thrown in case of an unrecoverable error.
 634   
      */
 635  10
     public void execute() throws BuildException {
 636  10
         try {
 637  10
             long start = System.currentTimeMillis();
 638  10
             if (srcPath == null) {
 639  1
                 throw new BuildException("srcdir attribute must be set",
 640   
                                          getLocation());
 641   
             }
 642   
 
 643  9
             srcPathList = srcPath.list();
 644  9
             if (srcPathList.length == 0) {
 645  1
                 throw new BuildException("srcdir attribute must be non-empty",
 646   
                                          getLocation());
 647   
             }
 648   
 
 649  8
             if (destPath == null) {
 650  0
                 destPath = srcPath;
 651   
             }
 652   
 
 653  8
             if (cache != null && cache.exists() && !cache.isDirectory()) {
 654  0
                 throw new BuildException("The cache, if specified, must "
 655   
                     + "point to a directory");
 656   
             }
 657   
 
 658  8
             if (cache != null && !cache.exists()) {
 659  0
                 cache.mkdirs();
 660   
             }
 661   
 
 662  8
             determineDependencies();
 663  8
             if (dump) {
 664  1
                 dumpDependencies();
 665   
             }
 666  8
             determineOutOfDateClasses();
 667  8
             int count = deleteAllAffectedFiles();
 668   
 
 669  8
             long duration = (System.currentTimeMillis() - start) / 1000;
 670  8
             log("Deleted " + count + " out of date files in "
 671   
                 + duration + " seconds");
 672   
         } catch (Exception e) {
 673  2
             throw new BuildException(e);
 674   
         }
 675   
     }
 676   
 
 677   
     /**
 678   
      * Scans the directory looking for source files that are newer than
 679   
      * their class files. The results are returned in the class variable
 680   
      * compileList
 681   
      *
 682   
      * @param srcDir the source directory
 683   
      * @param files the names of the files in the source dir which are to be
 684   
      *      checked.
 685   
      */
 686  8
     protected void scanDir(File srcDir, String files[]) {
 687   
 
 688  8
         for (int i = 0; i < files.length; i++) {
 689  29
             File srcFile = new File(srcDir, files[i]);
 690  29
             if (files[i].endsWith(".java")) {
 691  27
                 String filePath = srcFile.getPath();
 692  27
                 String className
 693   
                     = filePath.substring(srcDir.getPath().length() + 1,
 694   
                         filePath.length() - ".java".length());
 695  27
                 className = ClassFileUtils.convertSlashName(className);
 696  27
                 ClassFileInfo info
 697   
                     = (ClassFileInfo) classFileInfoMap.get(className);
 698  27
                 if (info == null) {
 699   
                     // there was no class file. add this class to the list
 700  0
                     outOfDateClasses.put(className, className);
 701   
                 } else {
 702  27
                     if (srcFile.lastModified()
 703   
                         > info.absoluteFile.lastModified()) {
 704  5
                         outOfDateClasses.put(className, className);
 705   
                     }
 706   
                 }
 707   
             }
 708   
         }
 709   
     }
 710   
 
 711   
 
 712   
     /**
 713   
      * Get the list of class files we are going to analyse.
 714   
      *
 715   
      * @param classLocations a path structure containing all the directories
 716   
      *      where classes can be found.
 717   
      * @return a vector containing the classes to analyse.
 718   
      */
 719  8
     private Vector getClassFiles(Path classLocations) {
 720   
         // break the classLocations into its components.
 721  8
         String[] classLocationsList = classLocations.list();
 722   
 
 723  8
         Vector classFileList = new Vector();
 724   
 
 725  8
         for (int i = 0; i < classLocationsList.length; ++i) {
 726  8
             File dir = new File(classLocationsList[i]);
 727  8
             if (dir.isDirectory()) {
 728  8
                 addClassFiles(classFileList, dir, dir);
 729   
             }
 730   
         }
 731   
 
 732  8
         return classFileList;
 733   
     }
 734   
 
 735   
     /**
 736   
      * Find the source file for a given class
 737   
      *
 738   
      * @param classname the classname in slash format.
 739   
      */
 740  32
     private File findSourceFile(String classname) {
 741  32
         String sourceFilename = classname + ".java";
 742  32
         int innerIndex = classname.indexOf("$");
 743  32
         if (innerIndex != -1) {
 744  4
             sourceFilename = classname.substring(0, innerIndex) + ".java";
 745   
         }
 746   
 
 747   
         // search the various source path entries
 748  32
         for (int i = 0; i < srcPathList.length; ++i) {
 749  32
             File sourceFile = new File(srcPathList[i], sourceFilename);
 750  32
             if (sourceFile.exists()) {
 751  31
                 return sourceFile;
 752   
             }
 753   
         }
 754  1
         return null;
 755   
     }
 756   
 
 757   
     /**
 758   
      * Add the list of class files from the given directory to the class
 759   
      * file vector, including any subdirectories.
 760   
      *
 761   
      * @param classFileList a list of ClassFileInfo objects for all the
 762   
      *      files in the diretcort tree
 763   
      * @param dir tyhe directory tree to be searched, recursivley, for class
 764   
      *      files
 765   
      * @param root the root of the source tree. This is used to determine
 766   
      *      the absoluate class name from the relative position in the
 767   
      *      source tree
 768   
      */
 769  9
     private void addClassFiles(Vector classFileList, File dir, File root) {
 770  9
         String[] filesInDir = dir.list();
 771   
 
 772  9
         if (filesInDir == null) {
 773  0
             return;
 774   
         }
 775  9
         int length = filesInDir.length;
 776   
 
 777  9
         int rootLength = root.getPath().length();
 778  9
         for (int i = 0; i < length; ++i) {
 779  33
             File file = new File(dir, filesInDir[i]);
 780  33
             if (file.isDirectory()) {
 781  1
                 addClassFiles(classFileList, file, root);
 782  32
             } else if (file.getName().endsWith(".class")) {
 783  32
                 ClassFileInfo info = new ClassFileInfo();
 784  32
                 info.absoluteFile = file;
 785  32
                 String relativeName = file.getPath().substring(rootLength + 1,
 786   
                     file.getPath().length() - 6);
 787  32
                 info.className
 788   
                     = ClassFileUtils.convertSlashName(relativeName);
 789  32
                 info.sourceFile = findSourceFile(relativeName);
 790  32
                 classFileList.addElement(info);
 791   
             }
 792   
         }
 793   
     }
 794   
 
 795   
 
 796   
     /**
 797   
      * Set the directories path to find the Java source files.
 798   
      *
 799   
      * @param srcPath the source path
 800   
      */
 801  9
     public void setSrcdir(Path srcPath) {
 802  9
         this.srcPath = srcPath;
 803   
     }
 804   
 
 805   
     /**
 806   
      * Set the destination directory where the compiled Java files exist.
 807   
      *
 808   
      * @param destPath the destination areas where build files are written
 809   
      */
 810  10
     public void setDestDir(Path destPath) {
 811  10
         this.destPath = destPath;
 812   
     }
 813   
 
 814   
     /**
 815   
      * Sets the dependency cache file.
 816   
      *
 817   
      * @param cache the dependency cache file
 818   
      */
 819  2
     public void setCache(File cache) {
 820  2
         this.cache = cache;
 821   
     }
 822   
 
 823   
     /**
 824   
      * If true, transitive dependencies are followed until the
 825   
      * closure of the dependency set if reached.
 826   
      * When not set, the depend task will only follow
 827   
      * direct dependencies between classes.
 828   
      *
 829   
      * @param closure indicate if dependency closure is required.
 830   
      */
 831  9
     public void setClosure(boolean closure) {
 832  9
         this.closure = closure;
 833   
     }
 834   
 
 835   
     /**
 836   
      * If true, the dependency information will be written
 837   
      * to the debug level log.
 838   
      *
 839   
      * @param dump set to true to dump dependency information to the log
 840   
      */
 841  1
     public void setDump(boolean dump) {
 842  1
         this.dump = dump;
 843   
     }
 844   
 }
 845   
 
 846