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}