package com.macmillan.nmeyers; import java.io.*; import java.util.*; /* * JMakeDepend: Generates Makefile dependencies by examining classfiles. * * Usage: JMakeDepend [] * If no classfiles specified, a list is read from stdin. * * Author: Nathan Meyers, nmeyers@javalinux.net * $Id: JMakeDepend.java,v 1.8 1999/11/08 03:40:42 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 JMakeDepend { static class Dependencies { String clsfile; String srcfile; String clsname; HashSet providers; HashSet visited; Dependencies(String c, String s, String o) { clsfile = c; srcfile = s; clsname = o; providers = new HashSet(); visited = new HashSet(); 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 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 processClassFile( String filename, HashMap dependencies) { // 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 srcfile = 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); srcfile = ((ClassFileContents.CONSTANT_Utf8_info) cf.constant_pool[sourceNameIndex]).getString(); break; } } if (srcfile == null) { System.err.println(clsfile + ": No source name found"); return; } // We can add a dependency record Dependencies depend = new Dependencies(clsfile, srcfile, 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: JMakeDepend [-noinner] " + "[]"); System.err.println("\nClass file names read from stdin if no " + " specified"); System.exit(1); } public static void main(String[] argv) { HashMap dependencies = new HashMap(); boolean hideInnerClasses = false; int firstarg = 0; while (firstarg < argv.length && argv[firstarg].startsWith("-")) { if (argv[firstarg].equals("-noinner")) hideInnerClasses = true; else usage(); firstarg++; } // Process cmdline args if any for (int i = firstarg; i < argv.length; i++) processClassFile(argv[i], dependencies); // If none, accept file names from stdin, one per line if (firstarg == argv.length) { BufferedReader reader = new BufferedReader( new InputStreamReader(System.in)); String line; try { while ((line = reader.readLine()) != null) processClassFile(line, dependencies); } catch (IOException e) { System.err.println(e); } } // 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) outerClasses.put(dep.srcfile, dep); } // 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. Dependencies outer = outerClasses.get(dep.srcfile); if (outer != null) { // Found it! Transfer the dependencies // (this step is probably redundant) outer.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.add(dep.srcfile); allTargets.add(dep.clsfile); // Print out the source dependency System.out.print(ddollar(dep.clsfile) + ": " + ddollar(dep.srcfile)); // 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.add(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); } }