    001package com.hammurapi.common;
    002
    003import java.io.File;
    004import java.io.FileInputStream;
    005import java.net.MalformedURLException;
    006import java.net.URL;
    007import java.util.ArrayList;
    008import java.util.List;
    009import java.util.Map;
    010import java.util.jar.Attributes;
    011import java.util.jar.Manifest;
    012import java.util.logging.Logger;
    013import java.util.zip.ZipEntry;
    014import java.util.zip.ZipInputStream;
    015
    016import org.apache.bcel.classfile.Constant;
    017import org.apache.bcel.classfile.ConstantClass;
    018import org.apache.bcel.classfile.ConstantPool;
    019import org.apache.bcel.classfile.JavaClass;
    020import org.apache.bcel.util.ClassLoaderRepository;
    021
    022/**
    023 * Scans class dependencies and returns a list of classpath entries URL's which given class depends on.
    024 * This class requires BCEL 5.2 in the classpath.
    025 * @author Pavel Vlasov
    026 *
    027 */
    028public class DependencyScanner {
    029
    030        private static final String CLASS_EXTENSION = ".class";
    031
    032        private static final Logger logger = Logger.getLogger(DependencyScanner.class.getName());
    033
    034        private static Map<ClassLoader, ClassLoaderRepository> repoCache = new java.util.HashMap<ClassLoader, ClassLoaderRepository>();
    035
    036        private static URL classContainer(Class<?> clazz) {
    037                String className = clazz.getName();
    038                int lastDotIdx = className.lastIndexOf('.');
    039                String shortName = lastDotIdx==-1 ? clazz.getName() : className.substring(lastDotIdx+1);
    040                URL classResource = clazz.getResource(shortName+CLASS_EXTENSION);
    041                if (classResource!=null) {
    042                        if ("jar".equalsIgnoreCase(classResource.getProtocol())) {
    043                                String path = classResource.getPath();
    044                                int exlaIdx = path.indexOf('!');
    045                                if (exlaIdx!=-1) {
    046                                        path = path.substring(0, exlaIdx);
    047                                        try {
    048                                                return new URL(path);
    049                                        } catch (MalformedURLException e) {
    050                                                logger.warning(e.toString());
    051                                        }
    052                                }
    053                        } else {
    054                                String postfix = clazz.getName().replace('.', '/')+CLASS_EXTENSION;
    055                                String cref = classResource.toExternalForm();
    056                                if (cref.endsWith(postfix)) {
    057                                        try {
    058                                                return new URL(cref.substring(0, cref.length()-postfix.length()-1));
    059                                        } catch (MalformedURLException e) {
    060                                                logger.warning(e.toString());
    061                                        }
    062                                }
    063                        }
    064                }
    065                return null;
    066        }
    067
    068        public static List<URL> scan(Class<?> clazz) {
    069                List<URL> ret = new ArrayList<URL>();
    070                if (clazz.getClassLoader()!=null) {
    071                        URL cc = classContainer(clazz);
    072                        if (cc!=null) {
    073                                ret.add(cc);
    074                                URL rtJar = classContainer(Object.class);
    075                                String rtJarPath = rtJar.getPath();
    076                                int idx = rtJarPath.lastIndexOf('/');
    077                                scanEntry(ret, clazz.getClassLoader(), 0, idx == -1 ? rtJarPath : rtJarPath.substring(0, idx));
    078                        }
    079                }
    080                return ret;
    081        }
    082
    083        private static void scanEntry(List<URL> collector, ClassLoader classLoader, int idx, String jreLibDir) {
    084                URL toScan = collector.get(idx);
    085                String toScanPath = toScan.getPath();
    086                int slashIdx = toScanPath.lastIndexOf('/');
    087                if (slashIdx!=-1 && toScanPath.substring(0, slashIdx).equals(jreLibDir)) {
    088                        scanEntry(collector, classLoader, idx+1, jreLibDir);
    089                        return;
    090                }
    091
    092                ClassLoaderRepository repo = repoCache.get(classLoader);
    093                if (repo == null) {
    094                        repo = new ClassLoaderRepository(classLoader);
    095                        repoCache.put(classLoader, repo);
    096                }
    097
    098                try {
    099                        if ("file".equalsIgnoreCase(toScan.getProtocol()) && new File(toScan.toURI()).isDirectory()) {
    100                                for (File child: new File(toScan.toURI()).listFiles()) {
    101                                        scanFile(collector, child, null, repo, classLoader, jreLibDir);
    102                                }
    103                        } else {
    104                                ZipInputStream zis = new ZipInputStream(toScan.openStream());
    105                                ZipEntry ze;
    106                                while ((ze = zis.getNextEntry())!=null) {
    107                                        if ("META-INF/manifest.mf".equalsIgnoreCase(ze.getName())) {
    108                                                Manifest manifest = new Manifest(zis);
    109                                                Attributes mattrs = manifest.getMainAttributes();
    110                                                String classPath = mattrs.getValue("Class-Path");
    111                                                if (classPath!=null) {
    112                                                        for (String cpe: classPath.split(" ")) {
    113                                                                try {
    114                                                                        URL reference = new URL(toScan, cpe);
    115                                                                        if (!collector.contains(reference)) {
    116                                                                                collector.add(reference);
    117                                                                        }
    118                                                                } catch (MalformedURLException mee) {
    119                                                                        logger.warning(mee.toString());
    120                                                                }
    121                                                        }
    122                                                }
    123                                        } else if (ze.getName().endsWith(CLASS_EXTENSION)) {
    124                                                JavaClass jc = repo.loadClass(ze.getName().substring(0, ze.getName().length() - CLASS_EXTENSION.length()).replace('/', '.'));
    125                                                ConstantPool constantPool = jc.getConstantPool();
    126                                                for (Constant constant: constantPool.getConstantPool()) {
    127                                                        if (constant instanceof ConstantClass) {
    128                                                                ConstantClass cc = (ConstantClass) constant;
    129                                                                String cName = String.valueOf(cc.getConstantValue(constantPool)).replace('/', '.');
    130                                                                if (cName.startsWith("[")) {
    131                                                                        continue;
    132                                                                }
    133                                                                URL container = classContainer(classLoader.loadClass(cName));
    134                                                                if (!collector.contains(container)) {
    135                                                                        String containerPath = container.getPath();
    136                                                                        int sIdx = containerPath.lastIndexOf('/');
    137                                                                        if (sIdx!=-1 && containerPath.substring(0, sIdx).equals(jreLibDir)) {
    138                                                                                continue;
    139                                                                        }
    140                                                                        collector.add(container);
    141                                                                }
    142                                                        }
    143                                                }
    144
    145                                        }
    146                                }
    147                        }
    148                } catch (Exception e) {
    149                        logger.warning(e.toString());
    150                }
    151
    152        }
    153
    154        private static void scanFile(List<URL> collector, File file, String path, ClassLoaderRepository repo, ClassLoader classLoader, String jreLibDir) throws Exception {
    155                String fullName = path==null ? file.getName() : path+"/"+file.getName();
    156                if (file.isDirectory()) {
    157                        for (File child: file.listFiles()) {
    158                                scanFile(collector, child, fullName, repo, classLoader, jreLibDir);
    159                        }
    160                } else if ("META-INF/manifest.mf".equalsIgnoreCase(fullName)) {
    161                        Manifest manifest = new Manifest(new FileInputStream(file));
    162                        Attributes mattrs = manifest.getMainAttributes();
    163                        String classPath = mattrs.getValue("Class-Path");
    164                        if (classPath!=null) {
    165                                for (String cpe: classPath.split(" ")) {
    166                                        try {
    167                                                URL reference = new URL(file.toURI().toURL(), cpe);
    168                                                if (!collector.contains(reference)) {
    169                                                        collector.add(reference);
    170                                                }
    171                                        } catch (MalformedURLException mee) {
    172                                                logger.warning(mee.toString());
    173                                        }
    174                                }
    175                        }
    176                } else if (file.getName().endsWith(CLASS_EXTENSION)) {
    177                        JavaClass jc = repo.loadClass(fullName.substring(0, fullName.length() - CLASS_EXTENSION.length()).replace('/', '.'));
    178                        ConstantPool constantPool = jc.getConstantPool();
    179                        for (Constant constant: constantPool.getConstantPool()) {
    180                                if (constant instanceof ConstantClass) {
    181                                        ConstantClass cc = (ConstantClass) constant;
    182                                        String cName = String.valueOf(cc.getConstantValue(constantPool)).replace('/', '.');
    183                                        if (cName.startsWith("[")) {
    184                                                continue;
    185                                        }
    186                                        URL container = classContainer(classLoader.loadClass(cName));
    187                                        if (!collector.contains(container)) {
    188                                                String containerPath = container.getPath();
    189                                                int sIdx = containerPath.lastIndexOf('/');
    190                                                if (sIdx!=-1 && containerPath.substring(0, sIdx).equals(jreLibDir)) {
    191                                                        continue;
    192                                                }
    193                                                collector.add(container);
    194                                        }
    195                                }
    196                        }
    197
    198                }
    199        }
    200
    201        public static void main(String[] args) throws Exception {
    202
    203                List<URL> classPath = scan(DependencyScanner.class);
    204                for (URL entry: classPath) {
    205                        System.out.println(entry);
    206                }
    207
    208        }
    209
    210}