@@ -1,7 +1,7 @@
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000 The Apache Software Foundation. All rights
* Copyright (c) 2000 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -9,7 +9,7 @@
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
@@ -17,15 +17,15 @@
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Ant", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
@@ -54,30 +54,29 @@
package org.apache.tools.ant.taskdefs.optional.depend;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;
import org.apache.tools.ant.taskdefs.MatchingTask;
import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;
import java.io.File;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.PrintWriter;
import java.io.FileWriter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;
/**
* Generate a dependency file for a given set of classes
* Generate a dependency file for a given set of classes
*
* @author Conor MacNeill
*/
@@ -88,25 +87,25 @@ public class Depend extends MatchingTask {
private static class ClassFileInfo {
/** The file where the class file is stored in the file system */
public File absoluteFile;
/** The location of the file relative to its base directory - the root
of the package namespace */
of the package namespace */
public String relativeName;
/** The Java class name of this class */
public String className;
}
/**
* The path where source files exist
*/
*/
private Path srcPath;
/**
* The path where compiled class files exist.
*/
private Path destPath;
/**
* The directory which contains the dependency cache.
*/
@@ -121,10 +120,10 @@ public class Depend extends MatchingTask {
* A map which gives information about a class
*/
private Hashtable classFileInfoMap;
/**
* A map which gives the list of jars and classes from the classpath that
* a class depends upon
* a class depends upon
*/
private Hashtable classpathDependencies;
@@ -132,14 +131,14 @@ public class Depend extends MatchingTask {
* The list of classes which are out of date.
*/
private Hashtable outOfDateClasses;
/**
* indicates that the dependency relationships should be extended
* beyond direct dependencies to include all classes. So if A directly
* affects B abd B directly affects C, then A indirectly affects C.
*/
private boolean closure = false;
/**
* Flag which controls whether the reversed dependencies should be dumped
* to the log
@@ -187,13 +186,13 @@ public class Depend extends MatchingTask {
public void setClasspathRef(Reference r) {
createClasspath().setRefid(r);
}
/**
* Read the dependencies from cache file
*/
private Hashtable readCachedDependencies() throws IOException{
private Hashtable readCachedDependencies() throws IOException {
Hashtable dependencyMap = new Hashtable();
if (cache != null) {
File depFile = new File(cache, CACHE_FILE_NAME);
BufferedReader in = null;
@@ -209,48 +208,45 @@ public class Depend extends MatchingTask {
dependencyList = new Vector();
className = line.substring(prependLength);
dependencyMap.put(className, dependencyList);
}
else {
} else {
dependencyList.addElement(line);
}
}
}
finally {
if (in != null) {
} finally {
if (in != null) {
in.close();
}
}
}
}
return dependencyMap;
}
/**
* Write the dependencies to cache file
*/
private void writeCachedDependencies(Hashtable dependencyMap) throws IOException{
private void writeCachedDependencies(Hashtable dependencyMap) throws IOException {
if (cache != null) {
PrintWriter pw = null;
try {
cache.mkdirs();
cache.mkdirs();
File depFile = new File(cache, CACHE_FILE_NAME);
pw = new PrintWriter(new FileWriter(depFile));
for (Enumeration deps = dependencyMap.keys(); deps.hasMoreElements();) {
String className = (String)deps.nextElement();
String className = (String) deps.nextElement();
pw.println(CLASSNAME_PREPEND + className);
Vector dependencyList = (Vector)dependencyMap.get(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) {
} finally {
if (pw != null) {
pw.close();
}
}
@@ -259,21 +255,21 @@ public class Depend extends MatchingTask {
/**
* Determine the dependencies between classes.
* Determine the dependencies between classes.
*
* Class dependencies are determined by examining the class references in a class file
* to other classes
* to other classes
*/
private void determineDependencies() throws IOException {
affectedClassMap = new Hashtable();
classFileInfoMap = new Hashtable();
boolean cacheDirty = false;
Hashtable dependencyMap = new Hashtable();
File depCacheFile = null;
boolean depCacheFileExists = true;
long depCacheFileLastModified = Long.MAX_VALUE;
// read the dependency cache from the disk
if (cache != null) {
dependencyMap = readCachedDependencies();
@@ -281,22 +277,22 @@ public class Depend extends MatchingTask {
depCacheFileExists = depCacheFile.exists();
depCacheFileLastModified = depCacheFile.lastModified();
}
for (Enumeration e = getClassFiles(destPath).elements(); e.hasMoreElements(); ) {
ClassFileInfo info = (ClassFileInfo)e.nextElement();
for (Enumeration e = getClassFiles(destPath).elements(); e.hasMoreElements();) {
ClassFileInfo info = (ClassFileInfo) e.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 (depCacheFileExists && depCacheFileLastModified > 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);
dependencyList = (Vector) dependencyMap.get(info.className);
}
}
if (dependencyList == null) {
// not cached - so need to read directly from the class file
FileInputStream inFileStream = null;
@@ -304,55 +300,54 @@ public class Depend extends MatchingTask {
inFileStream = new FileInputStream(info.absoluteFile);
ClassFile classFile = new ClassFile();
classFile.read(inFileStream);
dependencyList = classFile.getClassRefs();
if (dependencyList != null) {
cacheDirty = true;
dependencyMap.put(info.className, dependencyList);
}
}
finally {
} finally {
if (inFileStream != null) {
inFileStream.close();
}
}
}
// This class depends on each class in the dependency list. For each
// one of those, add this class into their affected classes list
for (Enumeration depEnum = dependencyList.elements(); depEnum.hasMoreElements(); ) {
String dependentClass = (String)depEnum.nextElement();
Hashtable affectedClasses = (Hashtable)affectedClassMap.get(dependentClass);
// one of those, add this class into their affected classes list
for (Enumeration depEnum = dependencyList.elements(); 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;
if (dependClasspath != null) {
// now determine which jars each class depends upon
classpathDependencies = new Hashtable();
AntClassLoader loader = new AntClassLoader(getProject(), dependClasspath);
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);
String className = (String) e.nextElement();
Vector dependencyList = (Vector) dependencyMap.get(className);
Hashtable dependencies = new Hashtable();
classpathDependencies.put(className, dependencies);
for (Enumeration e2 = dependencyList.elements(); e2.hasMoreElements();) {
String dependency =(String)e2.nextElement();
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) {
@@ -363,78 +358,76 @@ public class Depend extends MatchingTask {
jarFilePath = jarFilePath.substring(5, classMarker);
}
classpathFileObject = new File(jarFilePath);
}
else if (classURL.getProtocol().equals("file")) {
} else if (classURL.getProtocol().equals("file")) {
String classFilePath = classURL.getFile();
classpathFileObject = new File(classFilePath);
}
log("Class " + className +
" depends on " + classpathFileObject +
" due to " + dependency, Project.MSG_DEBUG);
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;
File jarFile = (File) classpathFileObject;
dependencies.put(jarFile, jarFile);
}
}
}
}
// write the dependency cache to the disk
if (cache != null && cacheDirty) {
writeCachedDependencies(dependencyMap);
}
}
private int deleteAllAffectedFiles() {
int count = 0;
for (Enumeration e = outOfDateClasses.elements(); e.hasMoreElements();) {
String className = (String)e.nextElement();
String className = (String) e.nextElement();
count += deleteAffectedFiles(className);
ClassFileInfo classInfo = (ClassFileInfo)classFileInfoMap.get(className);
ClassFileInfo classInfo = (ClassFileInfo) classFileInfoMap.get(className);
if (classInfo != null && classInfo.absoluteFile.exists()) {
classInfo.absoluteFile.delete();
count++;
}
}
return count;
return count;
}
private int deleteAffectedFiles(String className) {
int count = 0;
Hashtable affectedClasses = (Hashtable)affectedClassMap.get(className);
Hashtable affectedClasses = (Hashtable) affectedClassMap.get(className);
if (affectedClasses != null) {
for (Enumeration e = affectedClasses.keys(); e.hasMoreElements(); ) {
String affectedClassName = (String)e.nextElement();
ClassFileInfo affectedClassInfo = (ClassFileInfo)affectedClasses.get(affectedClassName);
for (Enumeration e = affectedClasses.keys(); e.hasMoreElements();) {
String affectedClassName = (String) e.nextElement();
ClassFileInfo affectedClassInfo = (ClassFileInfo) affectedClasses.get(affectedClassName);
if (affectedClassInfo.absoluteFile.exists()) {
log("Deleting file " + affectedClassInfo.absoluteFile.getPath() + " since " +
className + " out of date", Project.MSG_VERBOSE);
log("Deleting file " + affectedClassInfo.absoluteFile.getPath() + " since " +
className + " out of date", Project.MSG_VERBOSE);
affectedClassInfo.absoluteFile.delete();
count++;
if (closure) {
count += deleteAffectedFiles(affectedClassName);
}
else {
} else {
// without closure we may delete an inner class but not the
// top level class which would not trigger a recompile.
if (affectedClassName.indexOf("$") != -1) {
// need to delete the main class
String topLevelClassName
= affectedClassName.substring(0, affectedClassName.indexOf("$"));
String topLevelClassName
= affectedClassName.substring(0, affectedClassName.indexOf("$"));
log("Top level class = " + topLevelClassName, Project.MSG_VERBOSE);
ClassFileInfo topLevelClassInfo
= (ClassFileInfo)classFileInfoMap.get(topLevelClassName);
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.exists()) {
log("Deleting file " + topLevelClassInfo.absoluteFile.getPath() + " since " +
"one of its inner classes was removed", Project.MSG_VERBOSE);
topLevelClassInfo.absoluteFile.delete();
count++;
if (closure) {
@@ -457,59 +450,59 @@ public class Depend extends MatchingTask {
public void execute() throws BuildException {
try {
long start = System.currentTimeMillis();
String [] srcPathList = srcPath.list();
String[] srcPathList = srcPath.list();
if (srcPathList.length == 0) {
throw new BuildException("srcdir attribute must be set!", location);
}
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) {
log("Reverse Dependency Dump for " + affectedClassMap.size() +
" classes:", Project.MSG_DEBUG);
for (Enumeration e = affectedClassMap.keys(); e.hasMoreElements(); ) {
String className = (String)e.nextElement();
if (dump) {
log("Reverse Dependency Dump for " + affectedClassMap.size() +
" classes:", Project.MSG_DEBUG);
for (Enumeration e = affectedClassMap.keys(); e.hasMoreElements();) {
String className = (String) e.nextElement();
log(" Class " + className + " affects:", Project.MSG_DEBUG);
Hashtable affectedClasses = (Hashtable)affectedClassMap.get(className);
for (Enumeration e2 = affectedClasses.keys(); e2.hasMoreElements(); ) {
String affectedClass = (String)e2.nextElement();
ClassFileInfo info = (ClassFileInfo)affectedClasses.get(affectedClass);
Hashtable affectedClasses = (Hashtable) affectedClassMap.get(className);
for (Enumeration e2 = affectedClasses.keys(); e2.hasMoreElements();) {
String affectedClass = (String) e2.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);
for (Enumeration e = classpathDependencies.keys(); e.hasMoreElements();) {
String className = (String)e.nextElement();
for (Enumeration e = classpathDependencies.keys(); e.hasMoreElements();) {
String className = (String) e.nextElement();
log(" Class " + className + " depends on:", Project.MSG_DEBUG);
Hashtable dependencies = (Hashtable)classpathDependencies.get(className);
Hashtable dependencies = (Hashtable) classpathDependencies.get(className);
for (Enumeration e2 = dependencies.elements(); e2.hasMoreElements();) {
File classpathFile = (File)e2.nextElement();
File classpathFile = (File) e2.nextElement();
log(" " + classpathFile.getPath(), Project.MSG_DEBUG);
}
}
}
}
// we now need to scan for out of date files. When we have the list
// we go through and delete all class files which are affected by these files.
outOfDateClasses = new Hashtable();
for (int i=0; i < srcPathList.length; i++) {
File srcDir = (File)project.resolveFile(srcPathList[i]);
for (int i = 0; i < srcPathList.length; i++) {
File srcDir = (File) project.resolveFile(srcPathList[i]);
if (srcDir.exists()) {
DirectoryScanner ds = this.getDirectoryScanner(srcDir);
String[] files = ds.getIncludedFiles();
@@ -519,20 +512,20 @@ public class Depend extends MatchingTask {
// now check classpath file dependencies
if (classpathDependencies != null) {
for (Enumeration e = classpathDependencies.keys(); e.hasMoreElements();) {
String className = (String)e.nextElement();
for (Enumeration e = classpathDependencies.keys(); e.hasMoreElements();) {
String className = (String) e.nextElement();
if (!outOfDateClasses.containsKey(className)) {
ClassFileInfo info = (ClassFileInfo)classFileInfoMap.get(className);
// if we have no info about the class - it may have been deleted already and we
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);
Hashtable dependencies = (Hashtable) classpathDependencies.get(className);
for (Enumeration e2 = dependencies.elements(); e2.hasMoreElements();) {
File classpathFile = (File)e2.nextElement();
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);
log("Class " + className +
" is out of date with respect to " + classpathFile, Project.MSG_DEBUG);
outOfDateClasses.put(className, className);
break;
}
@@ -541,11 +534,11 @@ public class Depend extends MatchingTask {
}
}
}
// we now have a complete list of classes which are out of date
// We scan through the affected classes, deleting any affected classes.
int count = deleteAllAffectedFiles();
long duration = (System.currentTimeMillis() - start) / 1000;
log("Deleted " + count + " out of date files in " + duration + " seconds");
} catch (Exception e) {
@@ -566,14 +559,13 @@ public class Depend extends MatchingTask {
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);
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 {
} else {
if (srcFile.lastModified() > info.absoluteFile.lastModified()) {
outOfDateClasses.put(className, className);
}
@@ -583,7 +575,7 @@ public class Depend extends MatchingTask {
}
/**
/**
* Get the list of class files we are going to analyse.
*
* @param classLocations a path structure containing all the directories
@@ -591,23 +583,23 @@ public class Depend extends MatchingTask {
* @return a vector containing the classes to analyse.
*/
private Vector getClassFiles(Path classLocations) {
// break the classLocations into its components.
String[] classLocationsList = classLocations.list();
// 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;
}
/**
* Add the list of class files from the given directory to the
/**
* Add the list of class files from the given directory to the
* class file vector, including any subdirectories.
*
* @param classLocations a path structure containing all the directories
@@ -616,7 +608,7 @@ public class Depend extends MatchingTask {
*/
private void addClassFiles(Vector classFileList, File dir, File root) {
String[] filesInDir = dir.list();
if (filesInDir != null) {
int length = filesInDir.length;
@@ -624,20 +616,19 @@ public class Depend extends MatchingTask {
File file = new File(dir, filesInDir[i]);
if (file.isDirectory()) {
addClassFiles(classFileList, file, root);
}
else if (file.getName().endsWith(".class")) {
} else if (file.getName().endsWith(".class")) {
ClassFileInfo info = new ClassFileInfo();
info.absoluteFile = file;
info.relativeName = file.getPath().substring(root.getPath().length() + 1,
file.getPath().length() - 6);
info.className = ClassFileUtils.convertSlashName(info.relativeName);
file.getPath().length() - 6);
info.className = ClassFileUtils.convertSlashName(info.relativeName);
classFileList.addElement(info);
}
}
}
}
}
}
/**
* Set the source dirs to find the source Java files.
*/
@@ -651,11 +642,11 @@ public class Depend extends MatchingTask {
public void setDestDir(Path destPath) {
this.destPath = destPath;
}
public void setCache(File cache) {
this.cache = cache;
}
public void setClosure(boolean closure) {
this.closure = closure;
}