|
Ant example source code file (Depend.java)
The Depend.java source code/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.ant.taskdefs.optional.depend; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; 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.taskdefs.rmic.DefaultRmicAdapter; import org.apache.tools.ant.taskdefs.rmic.WLRmic; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.Reference; import org.apache.tools.ant.util.FileUtils; import org.apache.tools.ant.util.depend.DependencyAnalyzer; /** * Generates a dependency file for a given set of classes. * */ public class Depend extends MatchingTask { /** * A class (struct) user to manage information about a class * */ private static class ClassFileInfo { /** The file where the class file is stored in the file system */ private File absoluteFile; /** The Java class name of this class */ private String className; /** The source File containing this class */ private File sourceFile; /** if user has been warned about this file not having a source file */ private boolean isUserWarned = false; } /** 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. */ private File cache; /** The list of source paths derived from the srcPath field. */ private String[] srcPathList; /** * A map which gives for every class a list of the class which it * affects. */ private Hashtable affectedClassMap; /** 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 */ private Hashtable classpathDependencies; /** 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 and B directly affects C, then A indirectly affects C. */ private boolean closure = false; /** * flag to enable warning if we encounter RMI stubs */ private boolean warnOnRmiStubs = true; /** * Flag which controls whether the reversed dependencies should be * dumped to the log */ private boolean dump = false; /** The classpath to look for additional dependencies */ private Path dependClasspath; /** constants used with the cache file */ private static final String CACHE_FILE_NAME = "dependencies.txt"; /** String Used to separate classnames in the dependency file */ private static final String CLASSNAME_PREPEND = "||:"; /** * Set the classpath to be used for this dependency check. * * @param classpath the classpath to be used when checking for * dependencies on elements in the classpath */ public void setClasspath(Path classpath) { if (dependClasspath == null) { dependClasspath = classpath; } else { dependClasspath.append(classpath); } } /** * Gets the classpath to be used for this dependency check. * * @return the current dependency classpath */ public Path getClasspath() { return dependClasspath; } /** * Adds a classpath to be used for this dependency check. * * @return A path object to be configured by Ant */ public Path createClasspath() { if (dependClasspath == null) { dependClasspath = new Path(getProject()); } return dependClasspath.createPath(); } /** * Adds a reference to a classpath defined elsewhere. * * @param r a reference to a path object to be used as the depend * classpath */ public void setClasspathRef(Reference r) { createClasspath().setRefid(r); } /** * Flag to set to true if you want dependency issues with RMI * stubs to appear at warning level. * @param warnOnRmiStubs if true set dependency issues to appear at warning level. * @since Ant1.7 */ public void setWarnOnRmiStubs(boolean warnOnRmiStubs) { this.warnOnRmiStubs = warnOnRmiStubs; } /** * Read the dependencies from cache file * * @return a collection of class dependencies * @exception IOException if the dependency file cannot be read */ private Hashtable readCachedDependencies(File depFile) throws IOException { 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; } /** * Write the dependencies to cache file * * @param dependencyMap the map of dependencies to be written out. * @exception IOException if the dependency file cannot be written out. */ private void writeCachedDependencies(Hashtable dependencyMap) throws IOException { 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(); } } } } /** * Get the classpath for dependency checking. * * This method removes the dest dirs if it is given from the dependency classpath */ private Path getCheckClassPath() { 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); } /** * 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 * <ul> * <li>affectedClassMap - the list of classes each class affects * <li>classFileInfoMap - information about each class * <li>classpathDependencies - the list of jars and classes from the * classpath that each class depends upon.</li> * </ul> * * If required, the dependencies are written to the cache. * * @exception IOException if either the dependencies cache or the class * files cannot be read or written */ private void determineDependencies() throws IOException { 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); } } /** * Delete all the class files which are out of date, by way of their * dependency on a class which is out of date * * @return the number of files deleted. */ private int deleteAllAffectedFiles() { 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; } /** * Delete all the class files of classes which depend on the given class * * @param className the name of the class whose dependent classes will be * deleted * @return the number of class files removed */ private int deleteAffectedFiles(String className) { 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; } /** * 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} * @param affectedClassInfo info about the affectd class * @param affectedClass the name of the affected .class file * @param className the file that is triggering the out of dateness */ private void warnOutOfDateButNotDeleted( ClassFileInfo affectedClassInfo, String affectedClass, String className) { 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; } /** * test for being an RMI stub * @param affectedClass class being tested * @param className possible origin of the RMI stub * @return whether the class affectedClass is a RMI stub */ private boolean isRmiStub(String affectedClass, String className) { 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(String affectedClass, String baseClass, String suffix) { return (baseClass + suffix).equals(affectedClass); } /** * Dump the dependency information loaded from the classes to the Ant log */ private void dumpDependencies() { 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); } } } } 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; } } } } } /** * Does the work. * * @exception BuildException Thrown in case of an unrecoverable error. */ public void execute() throws BuildException { 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); } } /** * Scans the directory looking for source files that are newer than * their class files. The results are returned in the class variable * compileList * * @param srcDir the source directory * @param files the names of the files in the source dir which are to be * checked. */ protected void scanDir(File srcDir, String[] files) { 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); } } } } } /** * Get the list of class files we are going to analyse. * * @param classLocations a path structure containing all the directories * where classes can be found. * @return a vector containing the classes to analyse. */ private Vector getClassFiles(Path classLocations) { // 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; } /** * Find the source file for a given class * * @param classname the classname in slash format. */ private File findSourceFile(String classname) { 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; } /** * Add the list of class files from the given directory to the class * file vector, including any subdirectories. * * @param classFileList a list of ClassFileInfo objects for all the * files in the directory tree * @param dir the directory tree to be searched, recursively, for class * files * @param root the root of the source tree. This is used to determine * the absolute class name from the relative position in the * source tree */ private void addClassFiles(Vector classFileList, File dir, File root) { 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); } } } /** * Set the directories path to find the Java source files. * * @param srcPath the source path */ public void setSrcdir(Path srcPath) { this.srcPath = srcPath; } /** * Set the destination directory where the compiled Java files exist. * * @param destPath the destination areas where build files are written */ public void setDestDir(Path destPath) { this.destPath = destPath; } /** * Sets the dependency cache file. * * @param cache the dependency cache file */ public void setCache(File cache) { this.cache = cache; } /** * 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. * * @param closure indicate if dependency closure is required. */ public void setClosure(boolean closure) { this.closure = closure; } /** * If true, the dependency information will be written * to the debug level log. * * @param dump set to true to dump dependency information to the log */ public void setDump(boolean dump) { this.dump = dump; } } Other Ant examples (source code examples)Here is a short list of links related to this Ant Depend.java source code file: |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.