Re: How to set up a fast correct java build?
Well, I just spent a good many hours today whipping this up. I think
it'll work. It's rough though, and it could definitely be cleaned up.
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.TreeScanner;
public class JavaDepends {
private static class ParsedClassFile {
public String sourceFile = null;
public ParsedClassFile(File classfile) throws Exception {
InputStream fin = new FileInputStream(classfile);
try {
DataInputStream in = new DataInputStream(new
BufferedInputStream(fin));
init(in);
} finally {
fin.close();
}
}
private void init(DataInputStream in) throws Exception {
//magic number
byte[] magicNumber = new byte[4];
byte[] expectedMagicNumber = new byte[]
{ (byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)
0xBE };
if (4 != in.read(magicNumber))
throw new Exception("Unexpected end of file");
if ( ! Arrays.equals(magicNumber, expectedMagicNumber))
throw new Exception("Unexpected magic number"
+ " " + magicNumber[0]
+ " " + magicNumber[1]
+ " " + magicNumber[2]
+ " " + magicNumber[3]
);
//minor_version
final short minorVersion = in.readShort();
//major_version
final short majorVersion = in.readShort();
//constant_pool_count
final short constantPoolCount = in.readShort();
//constant_pool
final String cpStringLiterals[] = new String
[constantPoolCount];
final Set<Short> qualifiedClassNameCpIndexes = new
HashSet<Short>();
for (short index = 1; index < constantPoolCount; ++index)
{
final byte tag = in.readByte();
switch (tag)
{
case 1://a literal string
final short byteLen = in.readShort();
final byte[] javaUtf8Str = new byte[byteLen];
in.read(javaUtf8Str);
cpStringLiterals[index] = new String(javaUtf8Str,
"UTF-8");
break;
case 3:
{ final int x = in.readInt();
break;
}
case 4:
{ final float f = in.readFloat();
break;
}
case 5:
{ final long x = in.readLong();
++index;
break;
}
case 6:
{ final double x = in.readDouble();
++index;
break;
}
case 7: //class reference, refers to fully qualified
name
{ final short cpIndex = in.readShort();
qualifiedClassNameCpIndexes.add(cpIndex);
break;
}
case 8: //string object
{ final short cpIndex = in.readShort();
break;
}
case 9: //field
{ final short classCpIndex = in.readShort();
final short nameAndTypeCpIndex = in.readShort
();
break;
}
case 10: //method
{ final short classCpIndex = in.readShort();
final short nameAndTypeCpIndex = in.readShort
();
break;
}
case 11: //interface method
{ final short classCpIndex = in.readShort();
final short nameAndTypeCpIndex = in.readShort
();
break;
}
case 12: //name and type descriptor
{ final short nameCpIndex = in.readShort();
final short typeDescriptorCpIndex =
in.readShort();
break;
}
default: throw new Exception("Unknown tag " + tag + "
at index " + index);
}
}
//access_flags
final short accessFlags = in.readShort();
//this_class
final short thisClass = in.readShort();
//super_class
final short superClass = in.readShort();
//interfaces_count
final short interfacesCount = in.readShort();
//interfaces
final short[] interfaces = new short[interfacesCount];
for (int i=0; i<interfaces.length; ++i)
interfaces[i] = in.readShort();
//fields_count
final short fieldsCount = in.readShort();
//fields
final FieldInfo[] fields = new FieldInfo[fieldsCount];
for (int i=0; i<fields.length; ++i)
fields[i] = readFieldInfo(in);
//methods_count
final short methodsCount = in.readShort();
//methods
final MethodInfo[] methods = new MethodInfo[methodsCount];
for (int i=0; i<methods.length; ++i)
methods[i] = readMethodInfo(in);
//attributes_count
final short attributesCount = in.readShort();
//attributes
final AttributeInfo[] attributes = new AttributeInfo
[attributesCount];
for (int i=0; i<attributes.length; ++i)
attributes[i] = readAttributeInfo(in);
try {
in.readByte();
throw new Exception("Unexpected: not at end of file");
} catch (EOFException e) {
}
// //
for (AttributeInfo attribute : attributes) {
if ("SourceFile".equals(cpStringLiterals
[attribute.attributeNameIndex])) {
if (attribute.attributeLength != 2)
throw new Exception("Malformed class file.
SourceFile should have attribute_length == 2. Current " +
attribute.attributeLength);
short cpIndex = (new DataInputStream(new
ByteArrayInputStream(attribute.info))).readShort();
sourceFile = cpStringLiterals[cpIndex];
break;
}
}
}
private static class FieldInfo {
public short accessFlags;
public short nameIndex;
public short descriptorIndex;
public short attributesCount;
public AttributeInfo[] attributes;
}
private static FieldInfo readFieldInfo(DataInputStream in)
throws Exception {
FieldInfo x = new FieldInfo();
x.accessFlags = in.readShort();
x.nameIndex = in.readShort();
x.descriptorIndex = in.readShort();
x.attributesCount = in.readShort();
x.attributes = new AttributeInfo[x.attributesCount];
for (int i=0; i<x.attributes.length; ++i)
x.attributes[i] = readAttributeInfo(in);
return x;
}
private static class MethodInfo {
public short accessFlags;
public short nameIndex;
public short descriptorIndex;
public short attributesCount;
public AttributeInfo[] attributes;
}
private static MethodInfo readMethodInfo(DataInputStream in)
throws Exception {
MethodInfo x = new MethodInfo();
x.accessFlags = in.readShort();
x.nameIndex = in.readShort();
x.descriptorIndex = in.readShort();
x.attributesCount = in.readShort();
x.attributes = new AttributeInfo[x.attributesCount];
for (int i=0; i<x.attributes.length; ++i)
x.attributes[i] = readAttributeInfo(in);
return x;
}
private static class AttributeInfo {
public short attributeNameIndex;
public int attributeLength;
public byte[] info;
}
private static AttributeInfo readAttributeInfo(DataInputStream
in) throws Exception {
AttributeInfo x = new AttributeInfo();
x.attributeNameIndex = in.readShort();
x.attributeLength = in.readInt();
x.info = new byte[x.attributeLength];
for (int i=0; i<x.info.length; ++i)
x.info[i] = in.readByte();
return x;
}
}
private static class CollectTypesTreeVisitor extends
TreeScanner<Void, Object> {
public String lastImport = null;
public String lastQualifiedClass = null;
public List<String> imports = new ArrayList<String>();
public Set<String> potentialClassIdentifiers = new
TreeSet<String>();
public Void visitIdentifier(IdentifierTree node, Object p) {
if (lastImport != null) {
if (0 != lastImport.length())
lastImport = node.getName() + "." + lastImport;
else
throw new AssertionError();
imports.add(lastImport);
lastImport = null;
} else {
if (null == lastQualifiedClass)
potentialClassIdentifiers.add(node.getName
().toString());
else if (0 == lastQualifiedClass.length())
throw new AssertionError();
else {
potentialClassIdentifiers.add(node.getName
().toString() + "." + lastQualifiedClass);
lastQualifiedClass = null;
}
}
return super.visitIdentifier(node, p);
}
public Void visitImport(ImportTree node, Object p) {
if (null != lastImport)
throw new AssertionError();
lastImport = "";
Void x = super.visitImport(node, p);
if (null != lastImport)
throw new AssertionError();
return x;
}
public Void visitMemberSelect(MemberSelectTree node, Object
p) {
if (null == lastImport) {
if (null == lastQualifiedClass)
lastQualifiedClass = node.getIdentifier().toString
();
else if (0 == lastQualifiedClass.length())
throw new AssertionError();
else
lastQualifiedClass = node.getIdentifier() + "." +
lastQualifiedClass;
}else if (lastImport.equals(""))
lastImport = node.getIdentifier().toString();
else
lastImport = node.getIdentifier() + "." + lastImport;
return super.visitMemberSelect(node, p);
}
}
public static void main(String[] args) throws Exception {
List<String> javaFiles = new ArrayList<String>();
for (String arg : args)
javaFiles.add(arg);
final JavaCompiler compiler =
ToolProvider.getSystemJavaCompiler();
final DiagnosticCollector<JavaFileObject> diagnostics = new
DiagnosticCollector<JavaFileObject>();
final StandardJavaFileManager fileManager =
compiler.getStandardFileManager(diagnostics, null, null);
final Iterable<? extends JavaFileObject> fileObjects =
fileManager.getJavaFileObjectsFromStrings(javaFiles);
final JavaCompiler.CompilationTask task = compiler.getTask
(null, fileManager, diagnostics, null, null, fileObjects);
final JavacTask javacTask = (JavacTask) task;
final Iterable<? extends CompilationUnitTree> ASTs =
javacTask.parse();
final ClassLoader loader = Thread.currentThread
().getContextClassLoader();
for (CompilationUnitTree ast : ASTs) {
CollectTypesTreeVisitor visitor = new
CollectTypesTreeVisitor();
ast.accept(visitor, null);
if (visitor.lastImport != null)
throw new AssertionError();
List<String> imports = new ArrayList<String>();
for (String imp : visitor.imports)
imports.add(imp.replace('.', '/'));
Set<String> resourceFiles = new TreeSet<String>();
ClassIdLoop: for (String id :
visitor.potentialClassIdentifiers) {
while (true) {
//try taking it as is
{
String x = id.replace('.', '/') + ".class";
URL url = loader.getResource(x);
if (null != url) {
resourceFiles.add(url.toString());
continue ClassIdLoop;
}
}
//try adding java.lang.
{
URL url = loader.getResource("java/lang/" + id
+ ".class");
if (null != url) {
resourceFiles.add(url.toString());
continue ClassIdLoop;
}
}
//try imports
for (String imp : imports) {
if ('*' == imp.charAt(imp.length() - 1)) {
String classResourceName = imp.substring
(0, imp.length()-1) + id + ".class";
URL url = loader.getResource
(classResourceName);
if (null != url) {
resourceFiles.add(url.toString());
continue ClassIdLoop;
}
} else {
int index = imp.lastIndexOf('/');
String classNameInImport = imp.substring
(index + 1);
if (classNameInImport.equals(id)) {
//check that it's findable
URL url = loader.getResource(imp +
".class");
if (null != url) {
resourceFiles.add(url.toString());
continue ClassIdLoop;
} else {
System.out.println("Should have
found " + imp + ".class");
}
}
}
}
int index = id.indexOf('.');
if (-1 == index)
break;
id = id.substring(0, index);
}
System.out.println("Unable to find class file for " +
id);
}
System.out.println("-- " + ast.getSourceFile());
for (String resource : resourceFiles) {
if (resource.indexOf("file:/") == 0) {
String file = resource.substring("file:/".length
()).replace("%20", " ");
String sourceFile = (new ParsedClassFile(new File
(file))).sourceFile;
System.out.println("file resource " + file + ",
SourceFile " + sourceFile);
} else if (resource.indexOf("jar:file:/") == 0) {
System.out.println("jar resource " + resource);
} else
throw new Exception("Unknown resource type: " +
resource);
}
}
}
}