changeset 1036:361b7725dfce

Tool to generate a list of public/exported API Reviewed-by: neugens, vanaltj Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-March/006095.html
author Omair Majid <omajid@redhat.com>
date Tue, 19 Mar 2013 17:54:56 -0400
parents e7725b744e57
children 136bdf5e6223
files distribution/tools/ListExportedClasses.java
diffstat 1 files changed, 192 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/tools/ListExportedClasses.java	Tue Mar 19 17:54:56 2013 -0400
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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.
+ *
+ * Thermostat 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
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+public class ListExportedClasses {
+
+    public static void main(String[] args) {
+        if (args.length == 0) {
+            System.out.println("Usage:");
+            System.out.println("\tjava ListExportedClass path/to/jar1 [path/to/jar2 [path/to/jar3 [...]]]");
+            System.out.println();
+            System.out.println("Prints a list of OSGi-exported packages and public classes in those packages");
+        }
+
+        List<String> jarNames = Arrays.asList(args);
+        for (String jarName : jarNames) {
+            System.out.println(jarName);
+            try {
+                JarFile jarFile = new JarFile(jarName);
+                for (String exportedPackage : findOSGiExportedPackages(jarFile)) {
+                    System.out.println("\t" + exportedPackage);
+                    for (String exportedClass : findOSGiExportedClassesInPackage(jarFile, exportedPackage)) {
+                        System.out.println("\t\t" + exportedClass);
+                    }
+                }
+            } catch (IOException ioe) {
+                System.err.println("error reading " + jarName);
+            }
+        }
+    }
+
+    private static List<String> findOSGiExportedClassesInPackage(JarFile jarFile, String packageName) throws IOException {
+        List<String> exportedClasses = new ArrayList<>();
+
+        List<String> allClasses = findClassNames(jarFile);
+
+        for (String className : allClasses) {
+            if (!packageName.equals(getPackageName(className))) {
+                continue;
+            }
+
+            if (!isClassPublic(jarFile, className)) {
+                continue;
+            }
+
+            exportedClasses.add(className);
+        }
+        Collections.sort(exportedClasses);
+        return exportedClasses;
+    }
+
+    private static List<String> findClassNames(JarFile file) {
+        List<String> result = new ArrayList<>();
+        Enumeration<JarEntry> entries = file.entries();
+        while (entries.hasMoreElements()) {
+            String entryName = entries.nextElement().getName();
+            if (!entryName.endsWith(".class")) {
+                continue;
+            }
+
+            String className = entryName.substring(0, entryName.length() - ".class".length());
+            className = className.replace('/', '.');
+            result.add(className);
+        }
+        return result;
+    }
+
+    private static boolean isClassPublic(JarFile jarFile, String className) throws IOException {
+        /*
+         * we can not use reflection without loading the class itself (and its
+         * dependencies). so lets just parse the class file itself.
+         */
+
+        ZipEntry entry = jarFile.getEntry(className.replace('.', '/').concat(".class"));
+        InputStream classInputStream = jarFile.getInputStream(entry);
+        DataInputStream in = new DataInputStream(classInputStream);
+
+        int magic = in.readInt();
+        if (!(magic == 0xCAFEBABE)) {
+            throw new InternalError("Tried parsing a maformed class file");
+        }
+        /* int minorVersion = */in.readUnsignedShort();
+        /* int majorVersion = */in.readUnsignedShort();
+        int constPoolCount = in.readUnsignedShort();
+
+        for (int i = 1; i < constPoolCount; i++) {
+            int tag = in.readUnsignedByte();
+            switch (tag) {
+            case 7:
+            case 8:
+                in.readUnsignedShort();
+                break;
+            case 3:
+            case 4:
+            case 9:
+            case 10:
+            case 11:
+            case 12:
+                in.readInt();
+                break;
+            case 5:
+            case 6:
+                in.readLong();
+                i++; // these take up two entries in the pool
+                break;
+            case 1:
+                int length = in.readUnsignedShort();
+                for (int j = 0; j < length; j++) {
+                    in.readUnsignedByte();
+                }
+                break;
+            }
+        }
+
+        int accessFlags = in.readUnsignedShort();
+
+        return (accessFlags & 0x0001) == 0x0001;
+    }
+
+    private static String getPackageName(String fullyQualifiedClassName) {
+        int lastDot = fullyQualifiedClassName.lastIndexOf('.');
+        if (lastDot == -1) {
+            return "";
+        }
+        return fullyQualifiedClassName.substring(0, lastDot);
+    }
+
+    private static List<String> findOSGiExportedPackages(JarFile jarFile) throws IOException {
+        Manifest manifest = jarFile.getManifest();
+        String exportedPackagesString = manifest.getMainAttributes().getValue("Export-Package");
+        if (exportedPackagesString == null) {
+            return new ArrayList<>();
+        }
+        List<String> exportedPackages = Arrays.asList(exportedPackagesString.split(","));
+        for (int i = 0; i < exportedPackages.size(); i++) {
+            String exportedPackage = exportedPackages.get(i);
+            // exports can have a ";uses.." part after the package name. remove it.
+            int indexOfUses = exportedPackage.indexOf(";");
+            if (indexOfUses != -1) {
+                exportedPackage = exportedPackage.substring(0, indexOfUses);
+            }
+            exportedPackages.set(i, exportedPackage);
+        }
+        Collections.sort(exportedPackages);
+        return exportedPackages;
+    }
+}