package org.rhq.test; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** * This will walk a set of jar files and directories and print out * packages that are found in more than one jar file. * * @author John Mazzitelli */ public class DuplicatePackagesDetector { static Visitor visitor; static boolean verbose = Boolean.getBoolean("rhq.verbose"); private static void debug(String msg) { if (verbose) { System.out.println(msg); } } /** * Each argument is the name of a .jar file or a directory that will * be walked looking for jar files. */ public static void main(String[] args) { visitor = new Visitor(); if (args.length == 0) { // no arguments given, try to use the user's local maven repo of all the RHQ libraries String homeEnv = System.getenv("HOME"); if (homeEnv != null && homeEnv.length() > 0) { String s = File.separator; args = new String[] { homeEnv + s + ".m2" + s + "repository" + s + "org" + s + "rhq" }; } } for (String arg : args) { File file = new File(arg); debug("Processing [" + file.getAbsolutePath() + "]..."); if (file.exists()) { if (file.isDirectory()) { processDirectory(file); } else { processFile(file); } } else { System.err.println("File does not exist: " + arg); } } // print out what we've found System.out.println("======================================================================"); Map<String, Set<String>> map = visitor.getMap(); // key=dir (pkg) name; value=list of jars where dir is for (Entry<String, Set<String>> entry : map.entrySet()) { String packageName = entry.getKey(); Set<String> jarFiles = entry.getValue(); if (jarFiles.size() > 1 || verbose) { System.out.println(packageName); for (String jarFile : jarFiles) { System.out.println("\t" + jarFile); } } } } public static void processDirectory(File dir) { File[] dirEntries = dir.listFiles(); if (dirEntries != null) { for (File dirEntry : dirEntries) { if (dirEntry.isDirectory()) { processDirectory(dirEntry); } else { processFile(dirEntry); } } } } public static void processFile(File file) { // if its not a .jar file, ignore it if (!file.getName().endsWith(".jar")) { debug("Not a jar file, skipping [" + file.getAbsolutePath() + "]"); return; } try { walkZipFile(file, visitor); } catch (Exception e) { System.err.println("Cannot process jar file [" + file + "]:" + e.toString()); } } /** * Walks the entries of a zip file, allowing a listener to "visit" each node and perform tasks on * the zip entry. * * @param zipFile the zip file to walk * @param visitor the object that will be notified for each entry in the zip file * * @throws Exception if any errors occur during the reading or visiting */ public static void walkZipFile(File zipFile, Visitor visitor) throws Exception { FileInputStream fis = new FileInputStream(zipFile); try { InputStream zipContent = new BufferedInputStream(fis); try { ZipInputStream zis = new ZipInputStream(zipContent); ZipEntry e; while ((e = zis.getNextEntry()) != null) { boolean keepGoing = visitor .visit(e, zis, (verbose) ? zipFile.getAbsolutePath() : zipFile.getName()); if (!keepGoing) { break; // visitor told us to stop } } } finally { zipContent.close(); } } finally { fis.close(); } } public static class Visitor { // keyed on directory name - the value is all the jar files that had that directory in it private Map<String, Set<String>> map = new HashMap<String, Set<String>>(); public Map<String, Set<String>> getMap() { return map; } /** * Visits a specific zip file entry. Implementations can read the entry content from the given stream but * must <b>not</b> close the stream - the caller of this method will handle the lifecycle of the stream. * * @param entry the entry being visited * @param stream the stream containing the zip content * @param zipFilePath the actual zip file that is being walked * @return the visitor should return <code>true</code> if everything is OK and processing of the zip content * should continue; returning <code>false</code> will tell the walker to abort further traversing * of the zip content. * @throws Exception if the visitation failed for some reason - this will abort further walking of the zip content */ public boolean visit(ZipEntry entry, ZipInputStream stream, String zipFilePath) throws Exception { if (!entry.isDirectory()) { String[] dirAndName = splitPathName(entry.getName()); if (dirAndName != null) { String dirName = dirAndName[0]; Set<String> zipFilePaths = map.get(dirName); if (zipFilePaths == null) { zipFilePaths = new HashSet<String>(); map.put(dirName, zipFilePaths); } zipFilePaths.add(zipFilePath); } } return true; } /** * Splits the path from the filename and returns as a 2-dimension array. * If the full name does NOT end with .class, <code>null</code> is returned. * Thus, this only processes Java classes. * @param fullname the name to split * @return first element is the path, second element is the filename */ private String[] splitPathName(String fullname) { if (fullname.endsWith(".class")) { int lastSlash = fullname.lastIndexOf('/'); if (lastSlash >= 0) { return new String[] { fullname.substring(0, lastSlash), fullname.substring(lastSlash + 1) }; } else { return new String[] { "", fullname }; } } else { return null; } } } }