/* * This file is part of the OpenJML project. * Author: David R. Cok */ package org.jmlspecs.openjml; import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.eclipse.core.runtime.Platform; import org.jmlspecs.annotation.Nullable; import org.jmlspecs.openjml.ext.Elemtype; import org.jmlspecs.openjml.ext.Erasure; import org.osgi.framework.Bundle; import com.sun.tools.javac.parser.ExpressionExtension; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; /** This class manages extensions. It finds them at application startup, and * creates instances of individual extension classes for compilation contexts. * This class is a tool in the compiler tool chain, though without a * corresponding OpenJDK tool to mimic. This class is not expected to be * derived. * */ public class Extensions { protected static final Context.Key<Extensions> extensionsKey = new Context.Key<Extensions>(); /** The compilation context, set when the class is instantiated */ protected /*@ non_null */ Context context; //@ public constraint context == \old(context); protected static Utils utils; /** A constructor for the class; this class should not be * instantiated directly by users - use instance instead to get a * singleton instance (for each compilation context). */ protected Extensions(Context context) { context.put(extensionsKey, this); this.context = context; } /** A factory that returns a singleton instance of the class for the * given compilation context. */ static public Extensions instance(Context context) { Extensions instance = context.get(extensionsKey); if (instance == null) instance = new Extensions(context); return instance; } /** Returns a (derived instance of) ExpressionExtension if there is one associated * with the given token. Returns null if there is no * extension for the given token. * @param pos the position of the token, for error reporting * @param token the extension token * @param complain if true and failed to create an Extension instance, an error is issued * @return an instance of a ExpressionExtension object, or null if unrecoverable error */ public @Nullable ExpressionExtension find(int pos, JmlTokenKind token, boolean complain) { ExpressionExtension e = extensionInstances.get(token); if (e == null) { Class<? extends ExpressionExtension> c = extensionClasses.get(token); if (c == null) { if (complain) Log.instance(context).error(pos,"jml.failure.to.create.ExpressionExtension",token.internedName()); return null; } try { Constructor<? extends ExpressionExtension> constructor = c.getDeclaredConstructor(Context.class); ExpressionExtension instance = constructor.newInstance(context); extensionInstances.put(token,instance); e = instance; } catch (Exception ee) { if (complain) Log.instance(context).error(pos,"jml.failure.to.create.ExpressionExtension",token.internedName()); return null; } } return e; } /** The list of classes that add extensions to the Parser */ static Class<?>[] extensions = { Elemtype.class, Erasure.class }; /** A map from token type to the extension class that implements the token */ static protected Map<JmlTokenKind,Class<? extends ExpressionExtension>> extensionClasses = new HashMap<JmlTokenKind,Class<? extends ExpressionExtension>>(); protected Map<JmlTokenKind,ExpressionExtension> extensionInstances = new HashMap<JmlTokenKind,ExpressionExtension>(); // This static block runs through all the extension classes and adds // appropriate information to the HashMap above, so extensions can be // looked up at runtime. public static void register(Context context) { Package p = Package.getPackage("org.jmlspecs.openjml.ext"); try { registerPackage(context,p); } catch (java.io.IOException e) { throw new RuntimeException(e); } if (JmlOption.isOption(context, JmlOption.STRICT)) return; String exts = JmlOption.value(context, JmlOption.EXTENSIONS); if (exts == null) return; for (String extname : exts.split(",")) { try { Class<?> cl = Class.forName(extname); if (!registerClass(cl)) { Log.instance(context).error("jml.extension.failed", extname, "Improperly formed extension"); } continue; } catch (ClassNotFoundException e) { // OK - go on to see if it is a package } try { p = Package.getPackage(extname); registerPackage(context,p); } catch (Exception e) { Log.instance(context).error("jml.extension.failed", extname, p == null ? "No such package found" : e.toString()); } } } public static void registerPackage(Context context, Package p) throws java.io.IOException { java.util.List<Class<?>> extensions; extensions = findClasses(context,p); for (Class<?> cc: extensions) { registerClass(cc); } } public static boolean registerClass(Class<?> cc) { if (!ExpressionExtension.class.isAssignableFrom(cc)) return false; @SuppressWarnings("unchecked") Class<? extends ExpressionExtension> c = (Class<? extends ExpressionExtension>)cc; JmlTokenKind[] tokens; try { Method m = c.getMethod("tokens"); tokens = (JmlTokenKind[])m.invoke(null); for (JmlTokenKind t: tokens) { extensionClasses.put(t, c); } } catch (Exception e) { return false; } return true; } // This method finds all the classes in a given package that are OpenJML // extensions. // 1) In the development environment, the first method of finding elements // of a class works, but that does not work in an Eclipse plug-in. // 2) In the plug-in, the Bundle approach works. Note though that the Extensions // class is not a part of the OpenJMLUI plug-in, thus we need to reference // the plug-in ID as a literal; this approach won't work and may fail // catastrophically when used outside of Eclipse. public static java.util.List<Class<?>> findClasses(Context context, Package p) throws java.io.IOException { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); assert classLoader != null; String packageName = p.getName(); String path = packageName.replace('.', '/'); ArrayList<String> foundClassNames = new ArrayList<String>(); // This approach works in the development environment Enumeration<URL> resources = classLoader.getResources(path); while (resources.hasMoreElements()) { URL resource = resources.nextElement(); JarFile jar = null; try { String n = resource.toString().replace('\\', '/'); if (n.startsWith("jar:file:")) { int k = n.indexOf("!"); if (k < 0) continue; jar = new JarFile(n.substring(10,k)); Enumeration<JarEntry> entries = jar.entries(); // Really would like to iterator over the directory named // by 'path', instead of over every entry in the jar file while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); String name = entry.getName(); if (name.startsWith(path)) { name = name.substring(path.length()+1); k = name.indexOf('.'); if (k < 0) continue; name = name.substring(0,k); //System.out.println("FOUND1 " + name); foundClassNames.add(name); } } } else { File dir = new File(resource.getFile()); File[] files = dir.listFiles(); if (files == null) continue; for (File f: files) { if (f.isDirectory()) continue; String name = f.getName(); int k = name.indexOf('.'); if (k < 0) continue; name = name.substring(0,k); //System.out.println("FOUND2 " + name); foundClassNames.add(name); } } } catch (Exception e) { // Just continue - this method does not work } finally { if (jar != null) jar.close(); } } bl: { // This approach works when running the plug-in in development mode try { Bundle b = Platform.getBundle("org.jmlspecs.OpenJMLUI"); if (b == null) break bl; Enumeration<String> paths = b.getEntryPaths("/bin/" + path); while (paths.hasMoreElements()) { String pn = paths.nextElement(); int k = pn.lastIndexOf('/'); if (k > 0) pn = pn.substring(k+1); k = pn.lastIndexOf('.'); if (k > 0) pn = pn.substring(0,k); //System.out.println("FOUND3 " + name); foundClassNames.add(pn); } } catch (Throwable e) { // This will happen if we are not in a plug-in } } if (foundClassNames.isEmpty()) { // Last resort //Log.instance(context).warning("jml.internal.notsobad","Last resort loading of extensions"); String[] cn = { "Elemtype", "Erasure", "PureModifier", "ReachableStatement", "Arithmetic", "Key" }; foundClassNames.addAll(Arrays.asList(cn)); } ArrayList<Class<?>> classes = new ArrayList<Class<?>>(); for (String name: foundClassNames) { String fullname = packageName + "." + name; try { Class<?> c = Class.forName(fullname); if (Modifier.isAbstract(c.getModifiers())) continue; Method m = c.getMethod("register",Context.class); m.invoke(null,context); // Purposely fails if there is no static register method classes.add(c); //if (Utils.instance(context).jmlverbose >= Utils.JMLDEBUG) Log.instance(context).noticeWriter.println("Registered extension " + fullname); } catch (Exception e) { // Just skip if there is any exception, such as a // Class or Method not found. //if (Utils.instance(context).jmlverbose >= Utils.JMLDEBUG) Log.instance(context).noticeWriter.println("Failed to register " + fullname); continue; } } return classes; } }