package com.creative.studio.component.dependency; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLClassLoader; import java.security.DigestInputStream; import java.security.MessageDigest; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.regex.Pattern; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; /** * <p> * 1. Support directory scan,including classpath * <p> * 2. Support component scan,including jar,war,ear and sar * <p> * 3. Support conflicting classes scan,conflicting means the same fully-qualified * class name, but not the same digest or incompatible class(details see <a * href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html">jls</a> * and <a href= * "http://www.oracle.com/technetwork/java/javase/compatibility-137541.html" * >class compatibility</a>) * <p> * 4. Check who using this conflicting classes * * @author <a href="mailto:[email protected]">Von Gosling</a> */ public class DependencyMediator { /** * Whether to check <code>.jar</code> inside files */ private static boolean checkJars = true; /** * Whether to check class compatible */ private static boolean checkCompatible = true; public static final String CLASS_SUFFIX = ".class"; public static final Pattern JAR_FILE_PATTERN = Pattern.compile("^.+\\.(jar|JAR)$"); /** * Recursively finds class files and process * * @param file Directory full of class files or jar files (in which case all * of them are processed recursively), or a class file (in which * case that single class is processed), or a jar file (in which * case all the classes in this jar file are processed.) */ public static void process(File file) throws IOException { List<File> files = new ArrayList<File>(); if (file.isDirectory()) { files = processDirectory(file); for (File f : files) { doProcess(f); } } else { doProcess(file); } if (checkCompatible) { //processCompatible(file, jarFile, classMap); } } private static String getFileExtension(String fullName) { String fileName = new File(fullName).getName(); int dotIndex = fileName.lastIndexOf('.'); return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1); } protected static void doProcess(File file) throws IOException { String fileFormat = getFileExtension(file.getName()); ComponentFormat compFormat = ComponentFormat.fromString(fileFormat); if (null == compFormat) { System.err.printf("Not support file format [%s] now !", file.getName()); System.exit(-1); } switch (compFormat) { case WAR: case EAR: case SAR: case ZIP: case GZIP: case JAR: processJarFile(file, checkJars); break; case CLASS: processClassFile(file); break; default: break; } } protected static List<File> processDirectory(File dir) throws IOException { List<File> totalFiles = new ArrayList<File>(); listFiles(dir, totalFiles); //Ensure that outer classes are visited before inner classes Collections.sort(totalFiles, new Comparator<File>() { public int compare(File file1, File file2) { String n1 = file1.getName(); String n2 = file2.getName(); int diff = n1.length() - n2.length(); return diff != 0 ? diff : n1.compareTo(n2); } }); return totalFiles; } protected static void listFiles(File dir, List<File> totalFiles) { //Performance problems: using Files.newDirectoryStream File[] files = dir.listFiles(); if (files != null) { for (File f : files) { if (f.isDirectory()) { listFiles(f, totalFiles); } else { if (JAR_FILE_PATTERN.matcher(f.getName()).matches() || f.getName().endsWith(CLASS_SUFFIX)) { totalFiles.add(f); } } } } } /** * Nothing to do about the Class-Path property in MANIFEST.MF file now * * @param file * @param checkJars * @throws IOException */ public static void processJarFile(File file, boolean checkJars) throws IOException { JarFile jarFile = null; try { jarFile = new JarFile(file); if (checkJars) { Enumeration<JarEntry> jarEntries = jarFile.entries(); while (jarEntries.hasMoreElements()) { JarEntry jarEntry = jarEntries.nextElement(); if (!jarEntry.getName().endsWith(".class")) { continue; } //Check whether the same class String keyName = jarEntry.getName() .substring(0, jarEntry.getName().length() - 6).replace("/", "."); ComponentEntry cEntry = new ComponentEntry(); cEntry.setPathName(jarFile.getName() + ":" + jarEntry.getName()); cEntry.setJarName(jarFile.getName()); cEntry.setName(keyName); cEntry.setEntry(jarEntry); cEntry.setDigest(getDigest(jarFile.getInputStream(jarEntry))); ComponentContainer.put(keyName, cEntry); } } else { //Handle MANIFEST String name = jarFile.getName().substring(jarFile.getName().lastIndexOf("/") + 1); Attributes attr = jarFile.getManifest().getMainAttributes(); String buildJdk = attr.getValue("Build-Jdk"); String builtBy = attr.getValue("Built-By"); String keyName = name; if (!buildJdk.isEmpty()) { keyName = keyName + ":" + buildJdk; } if (!builtBy.isEmpty()) { keyName = keyName + ":" + builtBy; } ComponentEntry cEntry = new ComponentEntry(); cEntry.setName(name); cEntry.setPathName(jarFile.getName()); cEntry.setDigest(getDigest(new FileInputStream(new File(jarFile.getName())))); ComponentContainer.put(keyName, cEntry); } } catch (Throwable e) { e.printStackTrace(); } finally { if (null != jarFile) { jarFile.close(); } } } public static void output(HashMap<String, TreeSet<ComponentEntry>> classMap) { System.out.println("Output component reactor info......"); int count = 0; for (Entry<String, TreeSet<ComponentEntry>> entry : classMap.entrySet()) { if (entry.getValue().size() > 1) { count++; System.out.printf("Conflicting component [%s] was founded in the path : \n", entry.getKey()); for (ComponentEntry jar : entry.getValue()) { System.out.printf(" \t%s\n", jar.getPathName()); } } } if (count == 0) { System.out.println("Congratulations,no conflicting component exist!"); } } // private static void processCompatible(File file, JarFile jarFile, // HashMap<String, TreeSet<ComponentEntry>> classMap) // throws IOException { // Iterator<Entry<String, TreeSet<ComponentEntry>>> iter = classMap.entrySet().iterator(); // while (iter.hasNext()) { // Entry<String, TreeSet<ComponentEntry>> jarEntryInfoEntry = iter.next(); // Set<ComponentEntry> jarEntryInfos = jarEntryInfoEntry.getValue(); // Iterator<ComponentEntry> jarEntryInfoIter = jarEntryInfos.iterator(); // while (jarEntryInfoIter.hasNext()) { // JarEntry jarEntry = jarEntryInfoIter.next().getEntry(); // InputStream is = jarFile.getInputStream(jarEntry); // loadByteCode(file.getPath() + ":" + jarEntry.getName(), is); // } // } // } private static void loadByteCode(final String fileName, final InputStream is) throws IOException { { try { FileInputStream fis = new FileInputStream(new File(fileName)); final byte[] dd = getDigest(fis); ClassReader cr = new ClassReader(is); cr.accept(new ClassVisitor(Opcodes.ASM5) { public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { //Check whether the same class String keyName = name.replace('/', '.'); ComponentEntry cEntry = new ComponentEntry(); cEntry.setPathName(fileName); cEntry.setName(keyName); cEntry.setDigest(dd); ComponentContainer.put(keyName, cEntry); } }, 0); } catch (ArrayIndexOutOfBoundsException e) { // MANIMALSNIFFER-9 it is a pity that ASM does not throw a nicer error on encountering a malformed class file. IOException ioException = new IOException("Bad class file " + fileName); ioException.initCause(e); throw ioException; } } } private static byte[] getDigest(InputStream is) { DigestInputStream dis = null; try { MessageDigest md = MessageDigest.getInstance("MD5"); dis = new DigestInputStream(is, md); byte[] bytes = new byte[1024]; int numBytes = -1; while ((numBytes = is.read(bytes)) != -1) { md.update(bytes, 0, numBytes); } return md.digest(); } catch (Throwable e) { e.printStackTrace(); } finally { if (dis != null) { try { dis.close(); } catch (IOException e) { e.printStackTrace(); } } } return null; } protected static void processClassFile(File file) throws IOException { InputStream in = new FileInputStream(file); try { loadByteCode(file.getPath(), in); } finally { in.close(); } } private static Collection<URLClassLoader> getClassLoaders(ClassLoader baseClassLoader) { Collection<URLClassLoader> loaders = new ArrayList<URLClassLoader>(); ClassLoader loader = baseClassLoader; while (loader != null) { //Ignore if ("sun.misc.Launcher$ExtClassLoader".equals(loader.getClass().getName())) { break; } if (loader instanceof URLClassLoader) { loaders.add((URLClassLoader) loader); } loader = loader.getParent(); } return loaders; } public static void scanClassPath() { Set<URLClassLoader> loaders = new LinkedHashSet<URLClassLoader>(); loaders.addAll(getClassLoaders(Thread.currentThread().getContextClassLoader())); loaders.addAll(getClassLoaders(DependencyMediator.class.getClassLoader())); for (URLClassLoader cl : loaders) { for (URL url : cl.getURLs()) { String file = url.getFile(); File dir = new File(file); try { process(dir); } catch (IOException e1) { e1.printStackTrace(); } } } } public static void main(String args[]) { File dir = null; boolean scanClasspath = SystemPropertyUtils.getBoolean("scanClasspath", false); if (args.length == 0) { if (scanClasspath) { scanClassPath(); } } else { dir = new File(args[0]); try { process(dir); } catch (IOException e) { e.printStackTrace(); } } output(ComponentContainer.compMaps); } }