package checkers.util; import com.sun.jna.*; import java.io.File; import java.io.BufferedInputStream; import java.io.FileOutputStream; import java.io.BufferedOutputStream; import java.util.zip.*; import java.util.Enumeration; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.Charset; import java.lang.management.RuntimeMXBean; import java.lang.management.ManagementFactory; import java.util.Arrays; import java.util.List; import java.util.ArrayList; /** * The main class for the Checkers when using the binary distribution, that * delegates the compilation invocation to javac.<p> * * The class has two responsibilities: * <ul> * <li>it adds the annotated JDK to the bootclasspath * <li>if invoked when the compiler classes is in the bootclasspath (e.g. with * Apple JVM), it restarts the JVM and prepends the JSR 308 compiler to * bootclasspath. * </ul> */ public class CheckerMain { private static final String VERSION = "1"; public static void main(String[] args) throws Exception { if (isUsingJSR308Compiler()) { String[] newArgs = new String[args.length + 1]; newArgs[0] = "-Xbootclasspath/p:" + jdkJar(); System.arraycopy(args, 0, newArgs, 1, args.length); com.sun.tools.javac.Main.main(newArgs); } else { System.out.println("Manipulating bootclasspath"); List<String> cmdArgs = newCommandArgs(args); execute(cmdArgs); } } /** * The new command to restart java, with compiler jar prepended to the * bootclasspath */ static List<String> newCommandArgs(String[] currArgs) { List<String> args = new ArrayList<String>(currArgs.length + 5); args.add("java"); // Java's Arguments RuntimeMXBean mxBean = ManagementFactory.getRuntimeMXBean(); args.addAll(mxBean.getInputArguments()); String jarPath = findPathJar(CheckerMain.class); args.add("-Xbootclasspath/p:" + jarPath); args.add("-jar"); args.add(jarPath); args.add("-Xbootclasspath/p:" + jdkJar()); args.addAll(Arrays.asList(currArgs)); return args; } private static File tempJDKPath() { String userSupplied = System.getProperty("jsr308.jdk"); if (userSupplied != null) return new File(userSupplied); String tmpFolder = System.getProperty("java.io.tmpdir"); File jdkFile = new File(tmpFolder, "jdk-" + VERSION + ".jar"); return jdkFile; } /** returns the path to annotated JDK */ private static String jdkJar() { // case 1: running from binary String thisJar = findPathJar(CheckerMain.class); File potential = new File(new File(thisJar).getParentFile(), "jdk.jar"); if (potential.exists()) { //System.out.println("from adjacent jdk.jar"); return potential.getPath(); } // case 2: there was a temporary copy File jdkFile = tempJDKPath(); //System.out.println(jdkFile); if (jdkFile.exists()) { //System.out.println("From temporary"); return jdkFile.getPath(); } // case 3: extract zipped jdk.jar try { extractFile(thisJar, "jdk.jar", jdkFile); } catch (Exception e) { throw new RuntimeException(e); } if (jdkFile.exists()) { //System.out.println("Extracted jar"); return jdkFile.getPath(); } throw new AssertionError("Couldn't find annotated JDK"); } private static void extractFile(String jar, String fileName, File output) throws Exception { int BUFFER = 2048; File jarFile = new File(jar); ZipFile zip = new ZipFile(jarFile); ZipEntry entry = zip.getEntry(fileName); assert !entry.isDirectory(); BufferedInputStream is = new BufferedInputStream(zip.getInputStream(entry)); int currentByte; // establish buffer for writing file byte data[] = new byte[BUFFER]; // write the current file to disk FileOutputStream fos = new FileOutputStream(output); BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER); // read and write until last byte is encountered while ((currentByte = is.read(data, 0, BUFFER)) != -1) { dest.write(data, 0, currentByte); } dest.flush(); dest.close(); is.close(); } /** * Find the jar file containing the annotated JDK (i.e. jar containing * this file */ public static String findPathJar(Class<?> context) throws IllegalStateException { if (context == null) context = CheckerMain.class; String rawName = context.getName(); String classFileName; /* rawName is something like package.name.ContainingClass$ClassName. We need to turn this into ContainingClass$ClassName.class. */ { int idx = rawName.lastIndexOf('.'); classFileName = (idx == -1 ? rawName : rawName.substring(idx+1)) + ".class"; } String uri = context.getResource(classFileName).toString(); if (uri.startsWith("file:")) throw new IllegalStateException("This class has been loaded from a directory and not from a jar file."); if (!uri.startsWith("jar:file:")) { int idx = uri.indexOf(':'); String protocol = idx == -1 ? "(unknown)" : uri.substring(0, idx); throw new IllegalStateException("This class has been loaded remotely via the " + protocol + " protocol. Only loading from a jar on the local file system is supported."); } int idx = uri.indexOf('!'); //As far as I know, the if statement below can't ever trigger, so it's more of a sanity check thing. if (idx == -1) throw new IllegalStateException("You appear to have loaded this class from a local jar file, but I can't make sense of the URL!"); try { String fileName = URLDecoder.decode(uri.substring("jar:file:".length(), idx), Charset.defaultCharset().name()); return new File(fileName).getAbsolutePath(); } catch (UnsupportedEncodingException e) { throw new InternalError("default charset doesn't exist. Your VM is borked."); } } /** Returns true if the JSR308 classes are being used */ static boolean isUsingJSR308Compiler() { try { Class<?> clazz = com.sun.source.tree.MethodTree.class; clazz.getMethod("getReceiverAnnotations"); return true; } catch (Throwable e) { // Error either due to MethodTree not loadable, or that the JSR308 // specific getReceiverAnnotations() method isn't present return false; } } /** * Helper class to invoke the libc system() native call * * Using the system() native call, rather than Runtime.exec(), to handle * IO "redirection" **/ public interface CLibrary extends Library { CLibrary INSTANCE = (CLibrary)Native.loadLibrary("c", CLibrary.class); int system(String command); } /** * Helper method to do the proper escaping of arguments to pass to * system() */ static String constructCommand(Iterable<String> args) { StringBuilder sb = new StringBuilder(); for (String arg: args) { sb.append('"'); sb.append(arg.replace("\"", "\\\"")); sb.append("\" "); } return sb.toString(); } /** Execute the cmmands, with IO redirection */ static void execute(Iterable<String> cmdArray) { String command = constructCommand(cmdArray); CLibrary.INSTANCE.system(command); } }