Fields Summary |
---|
private org.apache.tools.ant.types.Path | srcPathThe path where source files exist |
private org.apache.tools.ant.types.Path | destPathThe path where compiled class files exist. |
private File | cacheThe directory which contains the dependency cache. |
private String[] | srcPathListThe list of source paths derived from the srcPath field. |
private Hashtable | affectedClassMapA map which gives for every class a list of the class which it
affects. |
private Hashtable | classFileInfoMapA map which gives information about a class |
private Hashtable | classpathDependenciesA map which gives the list of jars and classes from the classpath
that a class depends upon |
private Hashtable | outOfDateClassesThe list of classes which are out of date. |
private boolean | closureindicates that the dependency relationships should be extended beyond
direct dependencies to include all classes. So if A directly affects
B and B directly affects C, then A indirectly affects C. |
private boolean | warnOnRmiStubsflag to enable warning if we encounter RMI stubs |
private boolean | dumpFlag which controls whether the reversed dependencies should be
dumped to the log |
private org.apache.tools.ant.types.Path | dependClasspathThe classpath to look for additional dependencies |
private static final String | CACHE_FILE_NAMEconstants used with the cache file |
private static final String | CLASSNAME_PREPENDString Used to separate classnames in the dependency file |
Methods Summary |
---|
private void | addClassFiles(java.util.Vector classFileList, java.io.File dir, java.io.File root)Add the list of class files from the given directory to the class
file vector, including any subdirectories.
String[] filesInDir = dir.list();
if (filesInDir == null) {
return;
}
int length = filesInDir.length;
int rootLength = root.getPath().length();
for (int i = 0; i < length; ++i) {
File file = new File(dir, filesInDir[i]);
if (file.isDirectory()) {
addClassFiles(classFileList, file, root);
} else if (file.getName().endsWith(".class")) {
ClassFileInfo info = new ClassFileInfo();
info.absoluteFile = file;
String relativeName = file.getPath().substring(rootLength + 1,
file.getPath().length() - 6);
info.className
= ClassFileUtils.convertSlashName(relativeName);
info.sourceFile = findSourceFile(relativeName);
classFileList.addElement(info);
}
}
|
public org.apache.tools.ant.types.Path | createClasspath()Adds a classpath to be used for this dependency check.
if (dependClasspath == null) {
dependClasspath = new Path(getProject());
}
return dependClasspath.createPath();
|
private int | deleteAffectedFiles(java.lang.String className)Delete all the class files of classes which depend on the given class
int count = 0;
Hashtable affectedClasses = (Hashtable) affectedClassMap.get(className);
if (affectedClasses == null) {
return count;
}
for (Enumeration e = affectedClasses.keys(); e.hasMoreElements();) {
String affectedClass = (String) e.nextElement();
ClassFileInfo affectedClassInfo
= (ClassFileInfo) affectedClasses.get(affectedClass);
if (!affectedClassInfo.absoluteFile.exists()) {
continue;
}
if (affectedClassInfo.sourceFile == null) {
warnOutOfDateButNotDeleted(affectedClassInfo, affectedClass, className);
continue;
}
log("Deleting file " + affectedClassInfo.absoluteFile.getPath()
+ " since " + className + " out of date", Project.MSG_VERBOSE);
affectedClassInfo.absoluteFile.delete();
count++;
if (closure) {
count += deleteAffectedFiles(affectedClass);
} else {
// without closure we may delete an inner class but not the
// top level class which would not trigger a recompile.
if (affectedClass.indexOf("$") == -1) {
continue;
}
// need to delete the main class
String topLevelClassName
= affectedClass.substring(0, affectedClass.indexOf("$"));
log("Top level class = " + topLevelClassName,
Project.MSG_VERBOSE);
ClassFileInfo topLevelClassInfo
= (ClassFileInfo) classFileInfoMap.get(topLevelClassName);
if (topLevelClassInfo != null
&& topLevelClassInfo.absoluteFile.exists()) {
log("Deleting file "
+ topLevelClassInfo.absoluteFile.getPath()
+ " since one of its inner classes was removed",
Project.MSG_VERBOSE);
topLevelClassInfo.absoluteFile.delete();
count++;
if (closure) {
count += deleteAffectedFiles(topLevelClassName);
}
}
}
}
return count;
|
private int | deleteAllAffectedFiles()Delete all the class files which are out of date, by way of their
dependency on a class which is out of date
int count = 0;
for (Enumeration e = outOfDateClasses.elements(); e.hasMoreElements();) {
String className = (String) e.nextElement();
count += deleteAffectedFiles(className);
ClassFileInfo classInfo
= (ClassFileInfo) classFileInfoMap.get(className);
if (classInfo != null && classInfo.absoluteFile.exists()) {
classInfo.absoluteFile.delete();
count++;
}
}
return count;
|
private void | determineDependencies()Determine the dependencies between classes. Class dependencies are
determined by examining the class references in a class file to other
classes.
This method sets up the following fields
- affectedClassMap - the list of classes each class affects
- classFileInfoMap - information about each class
- classpathDependencies - the list of jars and classes from the
classpath that each class depends upon.
If required, the dependencies are written to the cache.
affectedClassMap = new Hashtable();
classFileInfoMap = new Hashtable();
boolean cacheDirty = false;
Hashtable dependencyMap = new Hashtable();
File cacheFile = null;
boolean cacheFileExists = true;
long cacheLastModified = Long.MAX_VALUE;
// read the dependency cache from the disk
if (cache != null) {
cacheFile = new File(cache, CACHE_FILE_NAME);
cacheFileExists = cacheFile.exists();
cacheLastModified = cacheFile.lastModified();
if (cacheFileExists) {
dependencyMap = readCachedDependencies(cacheFile);
}
}
Enumeration classfileEnum = getClassFiles(destPath).elements();
while (classfileEnum.hasMoreElements()) {
ClassFileInfo info = (ClassFileInfo) classfileEnum.nextElement();
log("Adding class info for " + info.className, Project.MSG_DEBUG);
classFileInfoMap.put(info.className, info);
Vector dependencyList = null;
if (cache != null) {
// try to read the dependency info from the map if it is
// not out of date
if (cacheFileExists
&& cacheLastModified > info.absoluteFile.lastModified()) {
// depFile exists and is newer than the class file
// need to get dependency list from the map.
dependencyList = (Vector) dependencyMap.get(info.className);
}
}
if (dependencyList == null) {
// not cached - so need to read directly from the class file
DependencyAnalyzer analyzer = new AntAnalyzer();
analyzer.addRootClass(info.className);
analyzer.addClassPath(destPath);
analyzer.setClosure(false);
dependencyList = new Vector();
Enumeration depEnum = analyzer.getClassDependencies();
while (depEnum.hasMoreElements()) {
dependencyList.addElement(depEnum.nextElement());
}
cacheDirty = true;
dependencyMap.put(info.className, dependencyList);
}
// This class depends on each class in the dependency list. For each
// one of those, add this class into their affected classes list
Enumeration depEnum = dependencyList.elements();
while (depEnum.hasMoreElements()) {
String dependentClass = (String) depEnum.nextElement();
Hashtable affectedClasses
= (Hashtable) affectedClassMap.get(dependentClass);
if (affectedClasses == null) {
affectedClasses = new Hashtable();
affectedClassMap.put(dependentClass, affectedClasses);
}
affectedClasses.put(info.className, info);
}
}
classpathDependencies = null;
Path checkPath = getCheckClassPath();
if (checkPath != null) {
// now determine which jars each class depends upon
classpathDependencies = new Hashtable();
AntClassLoader loader = getProject().createClassLoader(checkPath);
Hashtable classpathFileCache = new Hashtable();
Object nullFileMarker = new Object();
for (Enumeration e = dependencyMap.keys(); e.hasMoreElements();) {
String className = (String) e.nextElement();
Vector dependencyList = (Vector) dependencyMap.get(className);
Hashtable dependencies = new Hashtable();
classpathDependencies.put(className, dependencies);
Enumeration e2 = dependencyList.elements();
while (e2.hasMoreElements()) {
String dependency = (String) e2.nextElement();
Object classpathFileObject
= classpathFileCache.get(dependency);
if (classpathFileObject == null) {
classpathFileObject = nullFileMarker;
if (!dependency.startsWith("java.")
&& !dependency.startsWith("javax.")) {
URL classURL
= loader.getResource(dependency.replace('.", '/") + ".class");
if (classURL != null) {
if (classURL.getProtocol().equals("jar")) {
String jarFilePath = classURL.getFile();
int classMarker = jarFilePath.indexOf('!");
jarFilePath = jarFilePath.substring(0, classMarker);
if (jarFilePath.startsWith("file:")) {
classpathFileObject = new File(
FileUtils.getFileUtils().fromURI(jarFilePath));
} else {
throw new IOException(
"Bizarre nested path in jar: protocol: "
+ jarFilePath);
}
} else if (classURL.getProtocol().equals("file")) {
classpathFileObject = new File(
FileUtils.getFileUtils()
.fromURI(classURL.toExternalForm()));
}
log("Class " + className
+ " depends on " + classpathFileObject
+ " due to " + dependency, Project.MSG_DEBUG);
}
}
classpathFileCache.put(dependency, classpathFileObject);
}
if (classpathFileObject != null && classpathFileObject != nullFileMarker) {
// we need to add this jar to the list for this class.
File jarFile = (File) classpathFileObject;
dependencies.put(jarFile, jarFile);
}
}
}
}
// write the dependency cache to the disk
if (cache != null && cacheDirty) {
writeCachedDependencies(dependencyMap);
}
|
private void | determineOutOfDateClasses()
outOfDateClasses = new Hashtable();
for (int i = 0; i < srcPathList.length; i++) {
File srcDir = getProject().resolveFile(srcPathList[i]);
if (srcDir.exists()) {
DirectoryScanner ds = this.getDirectoryScanner(srcDir);
String[] files = ds.getIncludedFiles();
scanDir(srcDir, files);
}
}
// now check classpath file dependencies
if (classpathDependencies == null) {
return;
}
Enumeration classpathDepsEnum = classpathDependencies.keys();
while (classpathDepsEnum.hasMoreElements()) {
String className = (String) classpathDepsEnum.nextElement();
if (outOfDateClasses.containsKey(className)) {
continue;
}
ClassFileInfo info
= (ClassFileInfo) classFileInfoMap.get(className);
// if we have no info about the class - it may have been deleted already and we
// are using cached info.
if (info != null) {
Hashtable dependencies
= (Hashtable) classpathDependencies.get(className);
for (Enumeration e2 = dependencies.elements(); e2.hasMoreElements();) {
File classpathFile = (File) e2.nextElement();
if (classpathFile.lastModified()
> info.absoluteFile.lastModified()) {
log("Class " + className
+ " is out of date with respect to "
+ classpathFile, Project.MSG_DEBUG);
outOfDateClasses.put(className, className);
break;
}
}
}
}
|
private void | dumpDependencies()Dump the dependency information loaded from the classes to the Ant log
log("Reverse Dependency Dump for " + affectedClassMap.size()
+ " classes:", Project.MSG_DEBUG);
Enumeration classEnum = affectedClassMap.keys();
while (classEnum.hasMoreElements()) {
String className = (String) classEnum.nextElement();
log(" Class " + className + " affects:", Project.MSG_DEBUG);
Hashtable affectedClasses
= (Hashtable) affectedClassMap.get(className);
Enumeration affectedClassEnum = affectedClasses.keys();
while (affectedClassEnum.hasMoreElements()) {
String affectedClass = (String) affectedClassEnum.nextElement();
ClassFileInfo info
= (ClassFileInfo) affectedClasses.get(affectedClass);
log(" " + affectedClass + " in "
+ info.absoluteFile.getPath(), Project.MSG_DEBUG);
}
}
if (classpathDependencies != null) {
log("Classpath file dependencies (Forward):", Project.MSG_DEBUG);
Enumeration classpathEnum = classpathDependencies.keys();
while (classpathEnum.hasMoreElements()) {
String className = (String) classpathEnum.nextElement();
log(" Class " + className + " depends on:", Project.MSG_DEBUG);
Hashtable dependencies
= (Hashtable) classpathDependencies.get(className);
Enumeration classpathFileEnum = dependencies.elements();
while (classpathFileEnum.hasMoreElements()) {
File classpathFile = (File) classpathFileEnum.nextElement();
log(" " + classpathFile.getPath(), Project.MSG_DEBUG);
}
}
}
|
public void | execute()Does the work.
try {
long start = System.currentTimeMillis();
if (srcPath == null) {
throw new BuildException("srcdir attribute must be set",
getLocation());
}
srcPathList = srcPath.list();
if (srcPathList.length == 0) {
throw new BuildException("srcdir attribute must be non-empty",
getLocation());
}
if (destPath == null) {
destPath = srcPath;
}
if (cache != null && cache.exists() && !cache.isDirectory()) {
throw new BuildException("The cache, if specified, must "
+ "point to a directory");
}
if (cache != null && !cache.exists()) {
cache.mkdirs();
}
determineDependencies();
if (dump) {
dumpDependencies();
}
determineOutOfDateClasses();
int count = deleteAllAffectedFiles();
long duration = (System.currentTimeMillis() - start) / 1000;
final int summaryLogLevel;
if (count > 0) {
summaryLogLevel = Project.MSG_INFO;
} else {
summaryLogLevel = Project.MSG_DEBUG;
}
log("Deleted " + count + " out of date files in "
+ duration + " seconds", summaryLogLevel);
} catch (Exception e) {
throw new BuildException(e);
}
|
private java.io.File | findSourceFile(java.lang.String classname)Find the source file for a given class
String sourceFilename = classname + ".java";
int innerIndex = classname.indexOf("$");
if (innerIndex != -1) {
sourceFilename = classname.substring(0, innerIndex) + ".java";
}
// search the various source path entries
for (int i = 0; i < srcPathList.length; ++i) {
File sourceFile = new File(srcPathList[i], sourceFilename);
if (sourceFile.exists()) {
return sourceFile;
}
}
return null;
|
private org.apache.tools.ant.types.Path | getCheckClassPath()Get the classpath for dependency checking.
This method removes the dest dirs if it is given from the dependency classpath
if (dependClasspath == null) {
return null;
}
String[] destPathElements = destPath.list();
String[] classpathElements = dependClasspath.list();
String checkPath = "";
for (int i = 0; i < classpathElements.length; ++i) {
String element = classpathElements[i];
boolean inDestPath = false;
for (int j = 0; j < destPathElements.length && !inDestPath; ++j) {
inDestPath = destPathElements[j].equals(element);
}
if (!inDestPath) {
if (checkPath.length() == 0) {
checkPath = element;
} else {
checkPath += ":" + element;
}
}
}
if (checkPath.length() == 0) {
return null;
}
return new Path(getProject(), checkPath);
|
private java.util.Vector | getClassFiles(org.apache.tools.ant.types.Path classLocations)Get the list of class files we are going to analyse.
// break the classLocations into its components.
String[] classLocationsList = classLocations.list();
Vector classFileList = new Vector();
for (int i = 0; i < classLocationsList.length; ++i) {
File dir = new File(classLocationsList[i]);
if (dir.isDirectory()) {
addClassFiles(classFileList, dir, dir);
}
}
return classFileList;
|
public org.apache.tools.ant.types.Path | getClasspath()Gets the classpath to be used for this dependency check.
return dependClasspath;
|
private boolean | isRmiStub(java.lang.String affectedClass, java.lang.String className)test for being an RMI stub
return isStub(affectedClass, className, DefaultRmicAdapter.RMI_STUB_SUFFIX)
|| isStub(affectedClass, className, DefaultRmicAdapter.RMI_SKEL_SUFFIX)
|| isStub(affectedClass, className, WLRmic.RMI_STUB_SUFFIX)
|| isStub(affectedClass, className, WLRmic.RMI_SKEL_SUFFIX);
|
private boolean | isStub(java.lang.String affectedClass, java.lang.String baseClass, java.lang.String suffix)
return (baseClass + suffix).equals(affectedClass);
|
private java.util.Hashtable | readCachedDependencies(java.io.File depFile)Read the dependencies from cache file
Hashtable dependencyMap = new Hashtable();
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(depFile));
String line = null;
Vector dependencyList = null;
String className = null;
int prependLength = CLASSNAME_PREPEND.length();
while ((line = in.readLine()) != null) {
if (line.startsWith(CLASSNAME_PREPEND)) {
dependencyList = new Vector();
className = line.substring(prependLength);
dependencyMap.put(className, dependencyList);
} else {
dependencyList.addElement(line);
}
}
} finally {
if (in != null) {
in.close();
}
}
return dependencyMap;
|
protected void | scanDir(java.io.File srcDir, java.lang.String[] files)Scans the directory looking for source files that are newer than
their class files. The results are returned in the class variable
compileList
for (int i = 0; i < files.length; i++) {
File srcFile = new File(srcDir, files[i]);
if (files[i].endsWith(".java")) {
String filePath = srcFile.getPath();
String className
= filePath.substring(srcDir.getPath().length() + 1,
filePath.length() - ".java".length());
className = ClassFileUtils.convertSlashName(className);
ClassFileInfo info
= (ClassFileInfo) classFileInfoMap.get(className);
if (info == null) {
// there was no class file. add this class to the list
outOfDateClasses.put(className, className);
} else {
if (srcFile.lastModified()
> info.absoluteFile.lastModified()) {
outOfDateClasses.put(className, className);
}
}
}
}
|
public void | setCache(java.io.File cache)Sets the dependency cache file.
this.cache = cache;
|
public void | setClasspath(org.apache.tools.ant.types.Path classpath)Set the classpath to be used for this dependency check.
if (dependClasspath == null) {
dependClasspath = classpath;
} else {
dependClasspath.append(classpath);
}
|
public void | setClasspathRef(org.apache.tools.ant.types.Reference r)Adds a reference to a classpath defined elsewhere.
createClasspath().setRefid(r);
|
public void | setClosure(boolean closure)If true, transitive dependencies are followed until the
closure of the dependency set if reached.
When not set, the depend task will only follow
direct dependencies between classes.
this.closure = closure;
|
public void | setDestDir(org.apache.tools.ant.types.Path destPath)Set the destination directory where the compiled Java files exist.
this.destPath = destPath;
|
public void | setDump(boolean dump)If true, the dependency information will be written
to the debug level log.
this.dump = dump;
|
public void | setSrcdir(org.apache.tools.ant.types.Path srcPath)Set the directories path to find the Java source files.
this.srcPath = srcPath;
|
public void | setWarnOnRmiStubs(boolean warnOnRmiStubs)Flag to set to true if you want dependency issues with RMI
stubs to appear at warning level.
this.warnOnRmiStubs = warnOnRmiStubs;
|
private void | warnOutOfDateButNotDeleted(org.apache.tools.ant.taskdefs.optional.depend.Depend$ClassFileInfo affectedClassInfo, java.lang.String affectedClass, java.lang.String className)warn when a class is out of date, but not deleted as its source is unknown.
MSG_WARN is the normal level, but we downgrade to MSG_VERBOSE for RMI files
if {@link #warnOnRmiStubs is false}
if (affectedClassInfo.isUserWarned) {
return;
}
int level = Project.MSG_WARN;
if (!warnOnRmiStubs) {
//downgrade warnings on RMI stublike classes, as they are generated
//by rmic, so there is no need to tell the user that their source is
//missing.
if (isRmiStub(affectedClass, className)) {
level = Project.MSG_VERBOSE;
}
}
log("The class " + affectedClass + " in file "
+ affectedClassInfo.absoluteFile.getPath()
+ " is out of date due to " + className
+ " but has not been deleted because its source file"
+ " could not be determined", level);
affectedClassInfo.isUserWarned = true;
|
private void | writeCachedDependencies(java.util.Hashtable dependencyMap)Write the dependencies to cache file
if (cache != null) {
PrintWriter pw = null;
try {
cache.mkdirs();
File depFile = new File(cache, CACHE_FILE_NAME);
pw = new PrintWriter(new FileWriter(depFile));
Enumeration e = dependencyMap.keys();
while (e.hasMoreElements()) {
String className = (String) e.nextElement();
pw.println(CLASSNAME_PREPEND + className);
Vector dependencyList
= (Vector) dependencyMap.get(className);
int size = dependencyList.size();
for (int x = 0; x < size; x++) {
pw.println(dependencyList.elementAt(x));
}
}
} finally {
if (pw != null) {
pw.close();
}
}
}
|