package plume;
import java.io.*;
import java.util.jar.*;
import java.util.*;
/**
* Given a list of .class files, print the class file version and also the
* JDK/JRE version required to run it.
* A .jar file can also be supplied, in which case each .class file within
* it is procesed.
* Example use: <pre> java ClassFileVersion MyClass.class</pre>
* Supplying the "-min JDKVER" argument suppresses output except for .class
* files that require at least that JDK version. For instance, to list all
* the .class/.jar files that require JDK 6 or later, in this or any
* subdirectory, run
* <pre> find . \( -name '*.class' -o -name '*.jar' \) -print | xargs java ClassFileVersion -min 6</pre>
**/
public class ClassFileVersion {
/** Only report versions that are at least this large. **/
static double minversion = 0;
public static void main(String[] args) throws Exception {
if (args.length == 0) {
System.out.println("Supplied no arguments.");
System.out.println("Usage: java ClassFileVersion [-min JDKVER] <.class or .jar files>");
System.exit(1);
}
if ((args.length >= 2)
&& (args[0].equals("-min"))) {
minversion = Double.parseDouble(args[1]);
if (minversion == 1.6)
minversion = 6;
else if (minversion == 1.7)
minversion = 7;
String[] newargs = new String[args.length - 2];
System.arraycopy(args, 2, newargs, 0, args.length - 2);
args = newargs;
}
// System.out.println("newargs: " + java.util.Arrays.toString(args));
for (String filename : args) {
if (! new File(filename).exists()) {
System.out.println(filename + " does not exist!");
continue;
}
if (filename.endsWith(".class")) {
processClassFile(filename, new FileInputStream(filename));
} else if (filename.endsWith(".jar")) {
JarFile jarFile = new JarFile(filename);
for (Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements();) {
JarEntry entry = e.nextElement();
String entryName = entry.getName();
// Should really process recursively included jar files...
if (entryName.endsWith(".class")) {
InputStream is = jarFile.getInputStream(entry);
processClassFile(filename + ":" + entryName, is);
}
}
} else {
System.out.println(filename + " is neither a .class nor a .jar file");
}
}
}
public static void processClassFile(String filename, InputStream is) {
double[] versions = versionNumbers(is);
if (versions == null) {
System.out.println(filename + " is not a .class file (or IOException)");
} else {
double major = versions[0];
double minor = versions[1];
double jdkVersion = versions[2];
if (jdkVersion >= minversion) {
System.out.println(filename + " class file version is "
+ (int)major + "." + (int)minor + ", requires JDK "
+ ((jdkVersion == (int)jdkVersion)
? Integer.toString((int)jdkVersion) : Double.toString(jdkVersion))
+ " or later");
}
}
}
/** Returns null if there is an error or the input isn't a class file. */
public static double /*@Nullable*/ [] versionNumbers(InputStream is) {
try {
DataInputStream dis = new DataInputStream(is);
int magic = dis.readInt();
if (magic != 0xcafebabe) {
return null;
}
double minor = dis.readShort();
double major = dis.readShort();
double jdkVersion;
if (major < 48) {
jdkVersion = 1.3; // really 1.3.1
} else if (major == 48) {
jdkVersion = 1.4; // really 1.4.2
} else if (major == 49) {
jdkVersion = 1.5;
} else if (major == 50) {
jdkVersion = 6;
} else {
jdkVersion = 7;
}
return new double[] { major, minor, jdkVersion };
} catch (IOException e) {
return null;
}
}
}