001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.tools.ant.taskdefs.optional.depend;
019:
020: import java.io.BufferedReader;
021: import java.io.File;
022: import java.io.FileReader;
023: import java.io.FileWriter;
024: import java.io.IOException;
025: import java.io.PrintWriter;
026: import java.net.URL;
027: import java.util.Enumeration;
028: import java.util.Hashtable;
029: import java.util.Vector;
030: import org.apache.tools.ant.AntClassLoader;
031: import org.apache.tools.ant.BuildException;
032: import org.apache.tools.ant.DirectoryScanner;
033: import org.apache.tools.ant.Project;
034: import org.apache.tools.ant.taskdefs.MatchingTask;
035: import org.apache.tools.ant.taskdefs.rmic.DefaultRmicAdapter;
036: import org.apache.tools.ant.taskdefs.rmic.WLRmic;
037: import org.apache.tools.ant.types.Path;
038: import org.apache.tools.ant.types.Reference;
039: import org.apache.tools.ant.util.FileUtils;
040: import org.apache.tools.ant.util.depend.DependencyAnalyzer;
041:
042: /**
043: * Generates a dependency file for a given set of classes.
044: *
045: */
046: public class Depend extends MatchingTask {
047: /**
048: * A class (struct) user to manage information about a class
049: *
050: */
051: private static class ClassFileInfo {
052: /** The file where the class file is stored in the file system */
053: private File absoluteFile;
054:
055: /** The Java class name of this class */
056: private String className;
057:
058: /** The source File containing this class */
059: private File sourceFile;
060:
061: /** if user has been warned about this file not having a source file */
062: private boolean isUserWarned = false;
063: }
064:
065: /** The path where source files exist */
066: private Path srcPath;
067:
068: /** The path where compiled class files exist. */
069: private Path destPath;
070:
071: /** The directory which contains the dependency cache. */
072: private File cache;
073:
074: /** The list of source paths derived from the srcPath field. */
075: private String[] srcPathList;
076:
077: /**
078: * A map which gives for every class a list of the class which it
079: * affects.
080: */
081: private Hashtable affectedClassMap;
082:
083: /** A map which gives information about a class */
084: private Hashtable classFileInfoMap;
085:
086: /**
087: * A map which gives the list of jars and classes from the classpath
088: * that a class depends upon
089: */
090: private Hashtable classpathDependencies;
091:
092: /** The list of classes which are out of date. */
093: private Hashtable outOfDateClasses;
094:
095: /**
096: * indicates that the dependency relationships should be extended beyond
097: * direct dependencies to include all classes. So if A directly affects
098: * B and B directly affects C, then A indirectly affects C.
099: */
100: private boolean closure = false;
101:
102: /**
103: * flag to enable warning if we encounter RMI stubs
104: */
105: private boolean warnOnRmiStubs = true;
106:
107: /**
108: * Flag which controls whether the reversed dependencies should be
109: * dumped to the log
110: */
111: private boolean dump = false;
112:
113: /** The classpath to look for additional dependencies */
114: private Path dependClasspath;
115:
116: /** constants used with the cache file */
117: private static final String CACHE_FILE_NAME = "dependencies.txt";
118: /** String Used to separate classnames in the dependency file */
119: private static final String CLASSNAME_PREPEND = "||:";
120:
121: /**
122: * Set the classpath to be used for this dependency check.
123: *
124: * @param classpath the classpath to be used when checking for
125: * dependencies on elements in the classpath
126: */
127: public void setClasspath(Path classpath) {
128: if (dependClasspath == null) {
129: dependClasspath = classpath;
130: } else {
131: dependClasspath.append(classpath);
132: }
133: }
134:
135: /**
136: * Gets the classpath to be used for this dependency check.
137: *
138: * @return the current dependency classpath
139: */
140: public Path getClasspath() {
141: return dependClasspath;
142: }
143:
144: /**
145: * Adds a classpath to be used for this dependency check.
146: *
147: * @return A path object to be configured by Ant
148: */
149: public Path createClasspath() {
150: if (dependClasspath == null) {
151: dependClasspath = new Path(getProject());
152: }
153: return dependClasspath.createPath();
154: }
155:
156: /**
157: * Adds a reference to a classpath defined elsewhere.
158: *
159: * @param r a reference to a path object to be used as the depend
160: * classpath
161: */
162: public void setClasspathRef(Reference r) {
163: createClasspath().setRefid(r);
164: }
165:
166: /**
167: * Flag to set to true if you want dependency issues with RMI
168: * stubs to appear at warning level.
169: * @param warnOnRmiStubs if true set dependency issues to appear at warning level.
170: * @since Ant1.7
171: */
172: public void setWarnOnRmiStubs(boolean warnOnRmiStubs) {
173: this .warnOnRmiStubs = warnOnRmiStubs;
174: }
175:
176: /**
177: * Read the dependencies from cache file
178: *
179: * @return a collection of class dependencies
180: * @exception IOException if the dependency file cannot be read
181: */
182: private Hashtable readCachedDependencies(File depFile)
183: throws IOException {
184: Hashtable dependencyMap = new Hashtable();
185:
186: BufferedReader in = null;
187: try {
188: in = new BufferedReader(new FileReader(depFile));
189: String line = null;
190: Vector dependencyList = null;
191: String className = null;
192: int prependLength = CLASSNAME_PREPEND.length();
193: while ((line = in.readLine()) != null) {
194: if (line.startsWith(CLASSNAME_PREPEND)) {
195: dependencyList = new Vector();
196: className = line.substring(prependLength);
197: dependencyMap.put(className, dependencyList);
198: } else {
199: dependencyList.addElement(line);
200: }
201: }
202: } finally {
203: if (in != null) {
204: in.close();
205: }
206: }
207:
208: return dependencyMap;
209: }
210:
211: /**
212: * Write the dependencies to cache file
213: *
214: * @param dependencyMap the map of dependencies to be written out.
215: * @exception IOException if the dependency file cannot be written out.
216: */
217: private void writeCachedDependencies(Hashtable dependencyMap)
218: throws IOException {
219: if (cache != null) {
220: PrintWriter pw = null;
221: try {
222: cache.mkdirs();
223: File depFile = new File(cache, CACHE_FILE_NAME);
224:
225: pw = new PrintWriter(new FileWriter(depFile));
226: Enumeration e = dependencyMap.keys();
227: while (e.hasMoreElements()) {
228: String className = (String) e.nextElement();
229:
230: pw.println(CLASSNAME_PREPEND + className);
231:
232: Vector dependencyList = (Vector) dependencyMap
233: .get(className);
234: int size = dependencyList.size();
235: for (int x = 0; x < size; x++) {
236: pw.println(dependencyList.elementAt(x));
237: }
238: }
239: } finally {
240: if (pw != null) {
241: pw.close();
242: }
243: }
244: }
245: }
246:
247: /**
248: * Get the classpath for dependency checking.
249: *
250: * This method removes the dest dirs if it is given from the dependency classpath
251: */
252: private Path getCheckClassPath() {
253: if (dependClasspath == null) {
254: return null;
255: }
256:
257: String[] destPathElements = destPath.list();
258: String[] classpathElements = dependClasspath.list();
259: String checkPath = "";
260: for (int i = 0; i < classpathElements.length; ++i) {
261: String element = classpathElements[i];
262: boolean inDestPath = false;
263: for (int j = 0; j < destPathElements.length && !inDestPath; ++j) {
264: inDestPath = destPathElements[j].equals(element);
265: }
266: if (!inDestPath) {
267: if (checkPath.length() == 0) {
268: checkPath = element;
269: } else {
270: checkPath += ":" + element;
271: }
272: }
273: }
274:
275: if (checkPath.length() == 0) {
276: return null;
277: }
278:
279: return new Path(getProject(), checkPath);
280: }
281:
282: /**
283: * Determine the dependencies between classes. Class dependencies are
284: * determined by examining the class references in a class file to other
285: * classes.
286: *
287: * This method sets up the following fields
288: * <ul>
289: * <li>affectedClassMap - the list of classes each class affects</li>
290: * <li>classFileInfoMap - information about each class</li>
291: * <li>classpathDependencies - the list of jars and classes from the
292: * classpath that each class depends upon.</li>
293: * </ul>
294: *
295: * If required, the dependencies are written to the cache.
296: *
297: * @exception IOException if either the dependencies cache or the class
298: * files cannot be read or written
299: */
300: private void determineDependencies() throws IOException {
301: affectedClassMap = new Hashtable();
302: classFileInfoMap = new Hashtable();
303: boolean cacheDirty = false;
304:
305: Hashtable dependencyMap = new Hashtable();
306: File cacheFile = null;
307: boolean cacheFileExists = true;
308: long cacheLastModified = Long.MAX_VALUE;
309:
310: // read the dependency cache from the disk
311: if (cache != null) {
312: cacheFile = new File(cache, CACHE_FILE_NAME);
313: cacheFileExists = cacheFile.exists();
314: cacheLastModified = cacheFile.lastModified();
315: if (cacheFileExists) {
316: dependencyMap = readCachedDependencies(cacheFile);
317: }
318: }
319: Enumeration classfileEnum = getClassFiles(destPath).elements();
320: while (classfileEnum.hasMoreElements()) {
321: ClassFileInfo info = (ClassFileInfo) classfileEnum
322: .nextElement();
323: log("Adding class info for " + info.className,
324: Project.MSG_DEBUG);
325: classFileInfoMap.put(info.className, info);
326:
327: Vector dependencyList = null;
328:
329: if (cache != null) {
330: // try to read the dependency info from the map if it is
331: // not out of date
332: if (cacheFileExists
333: && cacheLastModified > info.absoluteFile
334: .lastModified()) {
335: // depFile exists and is newer than the class file
336: // need to get dependency list from the map.
337: dependencyList = (Vector) dependencyMap
338: .get(info.className);
339: }
340: }
341:
342: if (dependencyList == null) {
343: // not cached - so need to read directly from the class file
344: DependencyAnalyzer analyzer = new AntAnalyzer();
345: analyzer.addRootClass(info.className);
346: analyzer.addClassPath(destPath);
347: analyzer.setClosure(false);
348: dependencyList = new Vector();
349: Enumeration depEnum = analyzer.getClassDependencies();
350: while (depEnum.hasMoreElements()) {
351: dependencyList.addElement(depEnum.nextElement());
352: }
353: cacheDirty = true;
354: dependencyMap.put(info.className, dependencyList);
355: }
356:
357: // This class depends on each class in the dependency list. For each
358: // one of those, add this class into their affected classes list
359: Enumeration depEnum = dependencyList.elements();
360: while (depEnum.hasMoreElements()) {
361: String dependentClass = (String) depEnum.nextElement();
362:
363: Hashtable affectedClasses = (Hashtable) affectedClassMap
364: .get(dependentClass);
365: if (affectedClasses == null) {
366: affectedClasses = new Hashtable();
367: affectedClassMap.put(dependentClass,
368: affectedClasses);
369: }
370:
371: affectedClasses.put(info.className, info);
372: }
373: }
374:
375: classpathDependencies = null;
376: Path checkPath = getCheckClassPath();
377: if (checkPath != null) {
378: // now determine which jars each class depends upon
379: classpathDependencies = new Hashtable();
380: AntClassLoader loader = getProject().createClassLoader(
381: checkPath);
382:
383: Hashtable classpathFileCache = new Hashtable();
384: Object nullFileMarker = new Object();
385: for (Enumeration e = dependencyMap.keys(); e
386: .hasMoreElements();) {
387: String className = (String) e.nextElement();
388: Vector dependencyList = (Vector) dependencyMap
389: .get(className);
390: Hashtable dependencies = new Hashtable();
391: classpathDependencies.put(className, dependencies);
392: Enumeration e2 = dependencyList.elements();
393: while (e2.hasMoreElements()) {
394: String dependency = (String) e2.nextElement();
395: Object classpathFileObject = classpathFileCache
396: .get(dependency);
397: if (classpathFileObject == null) {
398: classpathFileObject = nullFileMarker;
399:
400: if (!dependency.startsWith("java.")
401: && !dependency.startsWith("javax.")) {
402: URL classURL = loader
403: .getResource(dependency.replace(
404: '.', '/')
405: + ".class");
406: if (classURL != null) {
407: if (classURL.getProtocol()
408: .equals("jar")) {
409: String jarFilePath = classURL
410: .getFile();
411: int classMarker = jarFilePath
412: .indexOf('!');
413: jarFilePath = jarFilePath
414: .substring(0, classMarker);
415: if (jarFilePath.startsWith("file:")) {
416: classpathFileObject = new File(
417: FileUtils
418: .getFileUtils()
419: .fromURI(
420: jarFilePath));
421: } else {
422: throw new IOException(
423: "Bizarre nested path in jar: protocol: "
424: + jarFilePath);
425: }
426: } else if (classURL.getProtocol()
427: .equals("file")) {
428: classpathFileObject = new File(
429: FileUtils
430: .getFileUtils()
431: .fromURI(
432: classURL
433: .toExternalForm()));
434: }
435: log("Class " + className
436: + " depends on "
437: + classpathFileObject
438: + " due to " + dependency,
439: Project.MSG_DEBUG);
440: }
441: }
442: classpathFileCache.put(dependency,
443: classpathFileObject);
444: }
445: if (classpathFileObject != null
446: && classpathFileObject != nullFileMarker) {
447: // we need to add this jar to the list for this class.
448: File jarFile = (File) classpathFileObject;
449: dependencies.put(jarFile, jarFile);
450: }
451: }
452: }
453: }
454:
455: // write the dependency cache to the disk
456: if (cache != null && cacheDirty) {
457: writeCachedDependencies(dependencyMap);
458: }
459: }
460:
461: /**
462: * Delete all the class files which are out of date, by way of their
463: * dependency on a class which is out of date
464: *
465: * @return the number of files deleted.
466: */
467: private int deleteAllAffectedFiles() {
468: int count = 0;
469: for (Enumeration e = outOfDateClasses.elements(); e
470: .hasMoreElements();) {
471: String className = (String) e.nextElement();
472: count += deleteAffectedFiles(className);
473: ClassFileInfo classInfo = (ClassFileInfo) classFileInfoMap
474: .get(className);
475: if (classInfo != null && classInfo.absoluteFile.exists()) {
476: classInfo.absoluteFile.delete();
477: count++;
478: }
479: }
480: return count;
481: }
482:
483: /**
484: * Delete all the class files of classes which depend on the given class
485: *
486: * @param className the name of the class whose dependent classes will be
487: * deleted
488: * @return the number of class files removed
489: */
490: private int deleteAffectedFiles(String className) {
491: int count = 0;
492:
493: Hashtable affectedClasses = (Hashtable) affectedClassMap
494: .get(className);
495: if (affectedClasses == null) {
496: return count;
497: }
498: for (Enumeration e = affectedClasses.keys(); e
499: .hasMoreElements();) {
500: String affectedClass = (String) e.nextElement();
501: ClassFileInfo affectedClassInfo = (ClassFileInfo) affectedClasses
502: .get(affectedClass);
503:
504: if (!affectedClassInfo.absoluteFile.exists()) {
505: continue;
506: }
507:
508: if (affectedClassInfo.sourceFile == null) {
509: warnOutOfDateButNotDeleted(affectedClassInfo,
510: affectedClass, className);
511: continue;
512: }
513:
514: log("Deleting file "
515: + affectedClassInfo.absoluteFile.getPath()
516: + " since " + className + " out of date",
517: Project.MSG_VERBOSE);
518:
519: affectedClassInfo.absoluteFile.delete();
520: count++;
521: if (closure) {
522: count += deleteAffectedFiles(affectedClass);
523: } else {
524: // without closure we may delete an inner class but not the
525: // top level class which would not trigger a recompile.
526:
527: if (affectedClass.indexOf("$") == -1) {
528: continue;
529: }
530: // need to delete the main class
531: String topLevelClassName = affectedClass.substring(0,
532: affectedClass.indexOf("$"));
533: log("Top level class = " + topLevelClassName,
534: Project.MSG_VERBOSE);
535: ClassFileInfo topLevelClassInfo = (ClassFileInfo) classFileInfoMap
536: .get(topLevelClassName);
537: if (topLevelClassInfo != null
538: && topLevelClassInfo.absoluteFile.exists()) {
539: log(
540: "Deleting file "
541: + topLevelClassInfo.absoluteFile
542: .getPath()
543: + " since one of its inner classes was removed",
544: Project.MSG_VERBOSE);
545: topLevelClassInfo.absoluteFile.delete();
546: count++;
547: if (closure) {
548: count += deleteAffectedFiles(topLevelClassName);
549: }
550: }
551: }
552: }
553: return count;
554: }
555:
556: /**
557: * warn when a class is out of date, but not deleted as its source is unknown.
558: * MSG_WARN is the normal level, but we downgrade to MSG_VERBOSE for RMI files
559: * if {@link #warnOnRmiStubs is false}
560: * @param affectedClassInfo info about the affectd class
561: * @param affectedClass the name of the affected .class file
562: * @param className the file that is triggering the out of dateness
563: */
564: private void warnOutOfDateButNotDeleted(
565: ClassFileInfo affectedClassInfo, String affectedClass,
566: String className) {
567: if (affectedClassInfo.isUserWarned) {
568: return;
569: }
570: int level = Project.MSG_WARN;
571: if (!warnOnRmiStubs) {
572: //downgrade warnings on RMI stublike classes, as they are generated
573: //by rmic, so there is no need to tell the user that their source is
574: //missing.
575: if (isRmiStub(affectedClass, className)) {
576: level = Project.MSG_VERBOSE;
577: }
578: }
579: log("The class " + affectedClass + " in file "
580: + affectedClassInfo.absoluteFile.getPath()
581: + " is out of date due to " + className
582: + " but has not been deleted because its source file"
583: + " could not be determined", level);
584: affectedClassInfo.isUserWarned = true;
585: }
586:
587: /**
588: * test for being an RMI stub
589: * @param affectedClass class being tested
590: * @param className possible origin of the RMI stub
591: * @return whether the class affectedClass is a RMI stub
592: */
593: private boolean isRmiStub(String affectedClass, String className) {
594: return isStub(affectedClass, className,
595: DefaultRmicAdapter.RMI_STUB_SUFFIX)
596: || isStub(affectedClass, className,
597: DefaultRmicAdapter.RMI_SKEL_SUFFIX)
598: || isStub(affectedClass, className,
599: WLRmic.RMI_STUB_SUFFIX)
600: || isStub(affectedClass, className,
601: WLRmic.RMI_SKEL_SUFFIX);
602: }
603:
604: private boolean isStub(String affectedClass, String baseClass,
605: String suffix) {
606: return (baseClass + suffix).equals(affectedClass);
607: }
608:
609: /**
610: * Dump the dependency information loaded from the classes to the Ant log
611: */
612: private void dumpDependencies() {
613: log("Reverse Dependency Dump for " + affectedClassMap.size()
614: + " classes:", Project.MSG_DEBUG);
615:
616: Enumeration classEnum = affectedClassMap.keys();
617: while (classEnum.hasMoreElements()) {
618: String className = (String) classEnum.nextElement();
619: log(" Class " + className + " affects:", Project.MSG_DEBUG);
620: Hashtable affectedClasses = (Hashtable) affectedClassMap
621: .get(className);
622: Enumeration affectedClassEnum = affectedClasses.keys();
623: while (affectedClassEnum.hasMoreElements()) {
624: String affectedClass = (String) affectedClassEnum
625: .nextElement();
626: ClassFileInfo info = (ClassFileInfo) affectedClasses
627: .get(affectedClass);
628: log(" " + affectedClass + " in "
629: + info.absoluteFile.getPath(),
630: Project.MSG_DEBUG);
631: }
632: }
633:
634: if (classpathDependencies != null) {
635: log("Classpath file dependencies (Forward):",
636: Project.MSG_DEBUG);
637:
638: Enumeration classpathEnum = classpathDependencies.keys();
639: while (classpathEnum.hasMoreElements()) {
640: String className = (String) classpathEnum.nextElement();
641: log(" Class " + className + " depends on:",
642: Project.MSG_DEBUG);
643: Hashtable dependencies = (Hashtable) classpathDependencies
644: .get(className);
645:
646: Enumeration classpathFileEnum = dependencies.elements();
647: while (classpathFileEnum.hasMoreElements()) {
648: File classpathFile = (File) classpathFileEnum
649: .nextElement();
650: log(" " + classpathFile.getPath(),
651: Project.MSG_DEBUG);
652: }
653: }
654: }
655: }
656:
657: private void determineOutOfDateClasses() {
658: outOfDateClasses = new Hashtable();
659: for (int i = 0; i < srcPathList.length; i++) {
660: File srcDir = getProject().resolveFile(srcPathList[i]);
661: if (srcDir.exists()) {
662: DirectoryScanner ds = this .getDirectoryScanner(srcDir);
663: String[] files = ds.getIncludedFiles();
664: scanDir(srcDir, files);
665: }
666: }
667:
668: // now check classpath file dependencies
669: if (classpathDependencies == null) {
670: return;
671: }
672:
673: Enumeration classpathDepsEnum = classpathDependencies.keys();
674: while (classpathDepsEnum.hasMoreElements()) {
675: String className = (String) classpathDepsEnum.nextElement();
676: if (outOfDateClasses.containsKey(className)) {
677: continue;
678: }
679: ClassFileInfo info = (ClassFileInfo) classFileInfoMap
680: .get(className);
681:
682: // if we have no info about the class - it may have been deleted already and we
683: // are using cached info.
684: if (info != null) {
685: Hashtable dependencies = (Hashtable) classpathDependencies
686: .get(className);
687: for (Enumeration e2 = dependencies.elements(); e2
688: .hasMoreElements();) {
689: File classpathFile = (File) e2.nextElement();
690: if (classpathFile.lastModified() > info.absoluteFile
691: .lastModified()) {
692: log("Class " + className
693: + " is out of date with respect to "
694: + classpathFile, Project.MSG_DEBUG);
695: outOfDateClasses.put(className, className);
696: break;
697: }
698: }
699: }
700: }
701: }
702:
703: /**
704: * Does the work.
705: *
706: * @exception BuildException Thrown in case of an unrecoverable error.
707: */
708: public void execute() throws BuildException {
709: try {
710: long start = System.currentTimeMillis();
711: if (srcPath == null) {
712: throw new BuildException(
713: "srcdir attribute must be set", getLocation());
714: }
715:
716: srcPathList = srcPath.list();
717: if (srcPathList.length == 0) {
718: throw new BuildException(
719: "srcdir attribute must be non-empty",
720: getLocation());
721: }
722:
723: if (destPath == null) {
724: destPath = srcPath;
725: }
726:
727: if (cache != null && cache.exists() && !cache.isDirectory()) {
728: throw new BuildException(
729: "The cache, if specified, must "
730: + "point to a directory");
731: }
732:
733: if (cache != null && !cache.exists()) {
734: cache.mkdirs();
735: }
736:
737: determineDependencies();
738: if (dump) {
739: dumpDependencies();
740: }
741: determineOutOfDateClasses();
742: int count = deleteAllAffectedFiles();
743:
744: long duration = (System.currentTimeMillis() - start) / 1000;
745:
746: final int summaryLogLevel;
747: if (count > 0) {
748: summaryLogLevel = Project.MSG_INFO;
749: } else {
750: summaryLogLevel = Project.MSG_DEBUG;
751: }
752:
753: log("Deleted " + count + " out of date files in "
754: + duration + " seconds", summaryLogLevel);
755: } catch (Exception e) {
756: throw new BuildException(e);
757: }
758: }
759:
760: /**
761: * Scans the directory looking for source files that are newer than
762: * their class files. The results are returned in the class variable
763: * compileList
764: *
765: * @param srcDir the source directory
766: * @param files the names of the files in the source dir which are to be
767: * checked.
768: */
769: protected void scanDir(File srcDir, String[] files) {
770:
771: for (int i = 0; i < files.length; i++) {
772: File srcFile = new File(srcDir, files[i]);
773: if (files[i].endsWith(".java")) {
774: String filePath = srcFile.getPath();
775: String className = filePath.substring(srcDir.getPath()
776: .length() + 1, filePath.length()
777: - ".java".length());
778: className = ClassFileUtils.convertSlashName(className);
779: ClassFileInfo info = (ClassFileInfo) classFileInfoMap
780: .get(className);
781: if (info == null) {
782: // there was no class file. add this class to the list
783: outOfDateClasses.put(className, className);
784: } else {
785: if (srcFile.lastModified() > info.absoluteFile
786: .lastModified()) {
787: outOfDateClasses.put(className, className);
788: }
789: }
790: }
791: }
792: }
793:
794: /**
795: * Get the list of class files we are going to analyse.
796: *
797: * @param classLocations a path structure containing all the directories
798: * where classes can be found.
799: * @return a vector containing the classes to analyse.
800: */
801: private Vector getClassFiles(Path classLocations) {
802: // break the classLocations into its components.
803: String[] classLocationsList = classLocations.list();
804:
805: Vector classFileList = new Vector();
806:
807: for (int i = 0; i < classLocationsList.length; ++i) {
808: File dir = new File(classLocationsList[i]);
809: if (dir.isDirectory()) {
810: addClassFiles(classFileList, dir, dir);
811: }
812: }
813:
814: return classFileList;
815: }
816:
817: /**
818: * Find the source file for a given class
819: *
820: * @param classname the classname in slash format.
821: */
822: private File findSourceFile(String classname) {
823: String sourceFilename = classname + ".java";
824: int innerIndex = classname.indexOf("$");
825: if (innerIndex != -1) {
826: sourceFilename = classname.substring(0, innerIndex)
827: + ".java";
828: }
829:
830: // search the various source path entries
831: for (int i = 0; i < srcPathList.length; ++i) {
832: File sourceFile = new File(srcPathList[i], sourceFilename);
833: if (sourceFile.exists()) {
834: return sourceFile;
835: }
836: }
837: return null;
838: }
839:
840: /**
841: * Add the list of class files from the given directory to the class
842: * file vector, including any subdirectories.
843: *
844: * @param classFileList a list of ClassFileInfo objects for all the
845: * files in the directory tree
846: * @param dir the directory tree to be searched, recursively, for class
847: * files
848: * @param root the root of the source tree. This is used to determine
849: * the absolute class name from the relative position in the
850: * source tree
851: */
852: private void addClassFiles(Vector classFileList, File dir, File root) {
853: String[] filesInDir = dir.list();
854:
855: if (filesInDir == null) {
856: return;
857: }
858: int length = filesInDir.length;
859:
860: int rootLength = root.getPath().length();
861: for (int i = 0; i < length; ++i) {
862: File file = new File(dir, filesInDir[i]);
863: if (file.isDirectory()) {
864: addClassFiles(classFileList, file, root);
865: } else if (file.getName().endsWith(".class")) {
866: ClassFileInfo info = new ClassFileInfo();
867: info.absoluteFile = file;
868: String relativeName = file.getPath().substring(
869: rootLength + 1, file.getPath().length() - 6);
870: info.className = ClassFileUtils
871: .convertSlashName(relativeName);
872: info.sourceFile = findSourceFile(relativeName);
873: classFileList.addElement(info);
874: }
875: }
876: }
877:
878: /**
879: * Set the directories path to find the Java source files.
880: *
881: * @param srcPath the source path
882: */
883: public void setSrcdir(Path srcPath) {
884: this .srcPath = srcPath;
885: }
886:
887: /**
888: * Set the destination directory where the compiled Java files exist.
889: *
890: * @param destPath the destination areas where build files are written
891: */
892: public void setDestDir(Path destPath) {
893: this .destPath = destPath;
894: }
895:
896: /**
897: * Sets the dependency cache file.
898: *
899: * @param cache the dependency cache file
900: */
901: public void setCache(File cache) {
902: this .cache = cache;
903: }
904:
905: /**
906: * If true, transitive dependencies are followed until the
907: * closure of the dependency set if reached.
908: * When not set, the depend task will only follow
909: * direct dependencies between classes.
910: *
911: * @param closure indicate if dependency closure is required.
912: */
913: public void setClosure(boolean closure) {
914: this .closure = closure;
915: }
916:
917: /**
918: * If true, the dependency information will be written
919: * to the debug level log.
920: *
921: * @param dump set to true to dump dependency information to the log
922: */
923: public void setDump(boolean dump) {
924: this.dump = dump;
925: }
926: }
|