package com.macmillan.nmeyers; import java.io.*; import com.sun.java.util.collections.*; import java.util.*; /* * JMakeDepend11: Generates Makefile dependencies by examining classfiles. * * Usage: JMakeDepend11 [-noinner] [-sourcepath ] \ * [] * If no files specified, a list is read from stdin. * * Author: Nathan Meyers, nmeyers@javalinux.net * $Id: JMakeDepend11.java,v 1.9 1999/11/11 20:42:47 nathanm Exp $ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * with this program. If not, the license is available from the * GNU project, at http://www.gnu.org. */ class JMakeDepend11 { static class Dependencies { String clsfile; HashSet srcfile = new HashSet(); String clsname; HashSet providers = new HashSet(); HashSet visited = new HashSet(); Dependencies(String c, String s, String o) { clsfile = c; srcfile.add(s); clsname = o; providers.add(o); visited.add(o); } boolean add(String s) { boolean result = providers.add(s); if (result && visited.size() > 1) { visited.clear(); visited.add(clsname); } return result; } boolean add(Collection s) { boolean result = providers.addAll(s); if (result && visited.size() > 1) { visited.clear(); visited.add(clsname); } return result; } public void parseAndAddClasses(String s) { int idx = 0; while ((idx = s.indexOf('L', idx)) != -1) { int idx2 = s.indexOf(';', idx); if (idx2 == -1) break; add(s.substring(idx + 1, idx2)); idx = idx2; } } public int hashCode() { return clsname.hashCode(); } } static int convb(byte[] b) { int result = 0; for (int i = 0; i < b.length; i++) { int b2 = b[i]; if (b2 < 0) b2 += 0x100; result = (result << 8) + b2; } return result; } private static String addsep(String s) { return s + (s.endsWith(File.separator) ? "" : File.separator); } private static String ddollar(String s) { String result = ""; int idx1 = 0, idx2; while ((idx2 = s.indexOf('$', idx1)) != -1) { result += s.substring(idx1, idx2) + "$$"; idx1 = idx2 + 1; } result += s.substring(idx1); return result; } public static void processFile( String filename, HashMap dependencies, HashMap > sourcemap) { // Should be a .java or a .class file. Is it source? if (!filename.endsWith(".class")) { // Appears to be source. Add it to the source map. int idx = filename.lastIndexOf(java.io.File.separatorChar); String key = ((idx == -1) ? filename : filename.substring(idx + 1)); LinkedList targets = sourcemap.get(key); if (targets == null) targets = new LinkedList(); targets.add(filename); sourcemap.put(key, targets); return; } // Read classfile ClassFileContents cf = null; FileInputStream is; try { is = new FileInputStream(filename); cf = new ClassFileContents(is); } catch (FileNotFoundException e) { System.err.println(filename + ": " + e); return; } catch (IOException e) { System.err.println(filename + ": " + e); return; } // Note the name of the class file String clsfile = filename; // Compute the name of the class String clsname = ((ClassFileContents.CONSTANT_Utf8_info) cf.constant_pool[ ((ClassFileContents.CONSTANT_Class_info) cf.constant_pool[cf.this_class]).name_index]). getString(); // Compute the name of the source String srcname = null; for (int j = 0; j < cf.attributes_count; j++) { String attrname = ((ClassFileContents.CONSTANT_Utf8_info) cf.constant_pool[ cf.attributes[j].attribute_name_index]). getString(); if (attrname.equals("SourceFile") && cf.attributes[j].attribute_length == 2) { int sourceNameIndex = convb(cf.attributes[j].info); srcname = ((ClassFileContents.CONSTANT_Utf8_info) cf.constant_pool[sourceNameIndex]).getString(); break; } } if (srcname == null) { System.err.println(clsfile + ": No source name found"); return; } // We can add a dependency record Dependencies depend = new Dependencies(clsfile, srcname, clsname); if (dependencies.containsKey(clsname)) { System.err.println(clsfile + ": Class " + clsname + " already loaded"); } dependencies.put(clsname, depend); // Now find all classes we depend on... // ...the superclass... String superclass = ((ClassFileContents.CONSTANT_Utf8_info) cf.constant_pool[ ((ClassFileContents.CONSTANT_Class_info) cf.constant_pool[cf.super_class]).name_index]). getString(); depend.add(superclass); // ...the interfaces... for (int j = 0; j < cf.interfaces_count; j++) { String interfaceName = ((ClassFileContents. CONSTANT_Utf8_info)cf.constant_pool[ ((ClassFileContents.CONSTANT_Class_info) cf.constant_pool[cf.interfaces[j]]).name_index]). getString(); depend.add(interfaceName); } // ...the fields... for (int j = 0; j < cf.fields_count; j++) { String fieldDescriptor = ((ClassFileContents. CONSTANT_Utf8_info)cf.constant_pool[ cf.fields[j].descriptor_index]). getString(); depend.parseAndAddClasses(fieldDescriptor); } // ...and the methods for (int j = 0; j < cf.methods_count; j++) { String methodDescriptor = ((ClassFileContents. CONSTANT_Utf8_info)cf.constant_pool[ cf.methods[j].descriptor_index]). getString(); depend.parseAndAddClasses(methodDescriptor); } // Finally, since the previous steps has missed all of // the locals, step through the constant pool and // log all of the classes found there for (int j = 0; j < cf.constant_pool_count; j++) { if (cf.constant_pool[j] != null && cf.constant_pool[j].getClass().equals( ClassFileContents.CONSTANT_Class_info.class)) { String className = ((ClassFileContents. CONSTANT_Utf8_info)cf.constant_pool[ ((ClassFileContents.CONSTANT_Class_info) cf.constant_pool[j]).name_index]). getString(); depend.add(className); } } } private static void usage() { System.err.println("Usage: JMakeDepend11 [-noinner] " + "[-sourcepath ] " + "[]"); System.err.println("\nFile names read from stdin if no " + "files specified in the command."); System.exit(1); } public static void main(String[] argv) { HashMap dependencies = new HashMap(); HashMap > sourcemap = new HashMap >(); boolean hideInnerClasses = false; LinkedList sourcepath = null; int firstarg = 0; while (firstarg < argv.length && argv[firstarg].startsWith("-")) { if (argv[firstarg].equals("-noinner")) hideInnerClasses = true; else if (argv[firstarg].equals("-sourcepath")) { if (++firstarg >= argv.length) usage(); sourcepath = new LinkedList(); String path = argv[firstarg]; int idx; while ((idx = path.indexOf(File.pathSeparatorChar)) != -1) { sourcepath.add(addsep(path.substring(0, idx))); path = path.substring(idx + 1); } sourcepath.add(addsep(path)); } else usage(); firstarg++; } // Process cmdline args if any for (int i = firstarg; i < argv.length; i++) processFile(argv[i], dependencies, sourcemap); // If none, accept file names from stdin if (firstarg == argv.length) { BufferedReader reader = new BufferedReader( new InputStreamReader(System.in)); String line; try { int ch = 0; char chr = 0; while (ch != -1) { StringBuffer filename = new StringBuffer(); while ((ch = reader.read()) != -1 && Character.isWhitespace((char)ch)) ; while (ch != -1 && !Character.isWhitespace(chr = (char)ch)) { filename.append(chr); ch = reader.read(); } if (filename.length() > 0) { processFile(filename.toString(), dependencies, sourcemap); } } } catch (IOException e) { System.err.println(e); } } // // Try to validate the source file names read from the classfiles. // If we can't find them, or map to a real file through the // sourcemap, then complain. // // A sourcefile associated with a class can be located in one of // three ways: // // - In the current directory (iff sourcepath not specified) // - In the source path, under a hierarchy reflecting the package // name (iff sourcepath is specified) // - From a list of source names included in the input // // The second and third solutions offer some potential ambiguity // - there are cases in which multiple possible sources might // be identified for a particular classfile (the third case is // particularly vulnerable). In that case, JMakeDepend will // complain and will consider the class to be dependent on // *multiple* sources. This is a conservative but safe solution. // HashMap > srcToClass = new HashMap >(); for (Iterator i = dependencies.values().iterator(); i.hasNext();) { // For each dependency in the collection... Dependencies dep = i.next(); HashSet sources = dep.srcfile; // At present there is exactly one source recorded... // does it point to a file we can read? String source = sources.iterator().next(); // Use sourcepath to determine how to check for source if (sourcepath == null) { // This test covers the presence of the source in // the current directory. It also covers the Generic // Java compiler, which records a relative pathname // for the source in the classfile. if ((new File(source)).canRead()) continue; } else { // This test covers specification of a source path Iterator iter = sourcepath.iterator(); while (iter.hasNext()) { int idx = dep.clsname.lastIndexOf(File.separatorChar); String relpath = (idx == -1) ? "" : dep.clsname.substring(0, idx); File file = new File(iter.next() + relpath, source); String newsource = file.getPath(); if (file.canRead()) sources.add(newsource); // Record a source->class mapping for later checks LinkedList deplist = srcToClass.get(newsource); if (deplist == null) deplist = new LinkedList(); deplist.add(dep); srcToClass.put(newsource, deplist); } } // If we're here with just one entry in the sources array, // a source has not yet been found if (sources.size() < 2) { // Do we have any name mappings LinkedList newnames = sourcemap.get(source); if (newnames == null) System.err.println("Warning: No valid source file" + " found for class " + dep.clsname + ", using " + source); else { // If all is well, we'll find exactly one good // name mapping. Iterator iter = newnames.iterator(); while (iter.hasNext()) { String newsource = iter.next(); if ((new File(newsource)).canRead()) { // Add to the source list for this class sources.add(newsource); // Record this src->class dependency LinkedList deplist = srcToClass.get(newsource); if (deplist == null) deplist = new LinkedList(); deplist.add(dep); srcToClass.put(newsource, deplist); } } } } // How did we do? if (sources.size() == 1) { // Bad news. Didn't find a good mapping System.err.println("Warning: No valid source file" + " found for class " + dep.clsname + ", using " + source); } else { // Found at least one good mapping. Remove // the old name. sources.remove(source); if (sources.size() > 1) { // Bad news: found too many good mappings. // Multiple sources with the same name. // And since we're sharing source dependencies, // we must also share class dependencies. System.err.print("Warning: Multiple" + " possible source files found for" + " class " + dep.clsname + ":"); Iterator iter = sources.iterator(); while (iter.hasNext()) { String newsource = iter.next(); // Complain System.err.print(" " + newsource); // Swap dependencies LinkedList deplist = srcToClass.get(newsource); Iterator dep1 = deplist.iterator(); while (dep1.hasNext()) { Dependencies d1 = dep1.next(); Iterator dep2 = deplist.iterator(); while (dep2.hasNext()) { Dependencies d2 = dep2.next(); d2.add(d1.providers); } } } System.err.println(""); } } } // Done with this hashmap srcToClass = null; // Now for the O(N^2) part: identify higher-order dependencies. for (boolean done = false; !done;) { done = true; // Step through our current dependency records for (Iterator i = dependencies.values().iterator(); i.hasNext();) { Dependencies dep = i.next(); // Step through each class in this record { // Make a copy of the classes in the record to avoid // fast-fail on the iterator HashSet temp = new HashSet(dep.providers); for (Iterator j = temp.iterator(); j.hasNext();) { String key = j.next(); if (!dep.visited.add(key)) continue; // Is there a dependency record for this object? Dependencies dep2 = dependencies.get(key); if (dep2 == null) continue; // Yes... transfer its dependencies to dep's // record if (dep.add(dep2.providers)) done = false; } } } } // If we're hiding inner classes, merge their dependencies into // outer-class dependency list if (hideInnerClasses) { HashMap > outerClasses = new HashMap >(); // Step through our objects, compiling a list of outer classes for (Iterator i = dependencies.values().iterator(); i.hasNext();) { // Next entry Dependencies dep = i.next(); // Is it an inner class? If not, add to our outerClasses map // keyed by source file name if (dep.clsname.indexOf("$") == -1) { Iterator s = dep.srcfile.iterator(); while (s.hasNext()) { String srcname = s.next(); LinkedList deps = outerClasses.get(srcname); if (deps == null) deps = new LinkedList(); deps.add(dep); outerClasses.put(srcname, deps); } } } // Step through a copy of our set of objects (to avoid // iterator fast-fail) and weed out inner classes. ArrayList list = new ArrayList(dependencies.values()); for (Iterator i = list.iterator(); i.hasNext();) { // Next entry Dependencies dep = i.next(); // Is it an inner class? if (dep.clsname.indexOf("$") != -1) { // Yes. Look for the enclosing class based on the // source name. Iterator s = dep.srcfile.iterator(); while (s.hasNext()) { LinkedList outer = outerClasses.get(s.next()); if (outer != null) { // Found it! Transfer the dependencies // (this step is probably redundant) Iterator outerIter = outer.iterator(); while (outerIter.hasNext()) outerIter.next().add(dep.providers); // And remove ourself from the master map dependencies.remove(dep.clsname); } } } } } // Print out dependencies HashSet allSources = new HashSet(); HashSet allTargets = new HashSet(); for (Iterator i = dependencies.values().iterator(); i.hasNext();) { Dependencies dep = i.next(); allSources.addAll(dep.srcfile); allTargets.add(dep.clsfile); // Print out the source dependency System.out.print(ddollar(dep.clsfile) + ":"); Iterator iter = dep.srcfile.iterator(); while (iter.hasNext()) System.out.print(" " + ddollar(iter.next())); // Collect the secondary sourcefile dependencies HashSet otherSources = new HashSet(); for (Iterator j = dep.providers.iterator(); j.hasNext();) { String key = j.next(); if (key.equals(dep.clsname)) continue; Dependencies dep2 = dependencies.get(key); if (dep2 == null || dep.srcfile.equals(dep2.srcfile)) continue; otherSources.addAll(dep2.srcfile); } // Print them out for (Iterator iterator = otherSources.iterator(); iterator.hasNext();) System.out.print(" " + ddollar(iterator.next())); // Print the command System.out.println("\n\techo $< $? >>.rawtargets\n"); } System.out.print("JSOURCES ="); for (Iterator i = allSources.iterator(); i.hasNext();) System.out.print(" " + ddollar(i.next())); System.out.print("\n\nJOBJECTS ="); for (Iterator i = allTargets.iterator(); i.hasNext();) System.out.print(" " + ddollar(i.next())); System.out.println("\n\n.rawtargets:\t$(JOBJECTS)"); System.out.println("\n.targets:"); System.out.println("\trm -f .rawtargets"); System.out.println("\t$(MAKE) .rawtargets"); System.out.println("\t[ -f .rawtargets ] && tr -s ' ' '\\012' " + "<.rawtargets | sort -u > .targets || true"); System.out.println("\trm -f .rawtargets"); System.exit(0); } }