package jetbrains.mps.deepcompare; import com.google.common.collect.Sets; import java.io.*; import java.util.*; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; /** * evgeny, 4/24/12 */ public class FolderCompare { private CompareStatus status; public FolderCompare(CompareStatus status) { this.status = status; } private ZipFile openZip(File file, String path) { try { return new ZipFile(file); } catch (ZipException e) { status.report(path, "cannot open zip: " + e.getMessage()); } catch (IOException e) { status.report(path, "cannot open zip: " + e.getMessage()); } return null; } private Map<String, ZipEntry> loadEntries(ZipFile file) { Map<String, ZipEntry> result = new HashMap<String, ZipEntry>(); Enumeration<? extends ZipEntry> entries = file.entries(); while (entries.hasMoreElements()) { ZipEntry zipEntry = entries.nextElement(); result.put(zipEntry.getName(), zipEntry); } return result; } private void compareZip(ZipFile expected, ZipFile actual, String path) { Map<String, ZipEntry> expEntries = loadEntries(expected); Map<String, ZipEntry> actEntries = loadEntries(actual); List<String> common = new ArrayList<String>(Sets.intersection(expEntries.keySet(), actEntries.keySet())); Collections.sort(common); for (String entry : common) { ZipEntry exp = expEntries.get(entry); ZipEntry act = actEntries.get(entry); if (exp.isDirectory() && !act.isDirectory()) { status.report(path(path, "!" + entry), "file was found instead of directory"); } else if (!exp.isDirectory() && act.isDirectory()) { status.report(path(path, "!" + entry), "directory was found instead of file"); } else if (!exp.isDirectory() && !act.isDirectory()) { compareZipEntries(exp, expected, act, actual, path(path, "!" + entry)); } else if (exp.isDirectory() && act.isDirectory()) { // skip, ok } else { status.report(path, "internal error"); } } expEntries.keySet().removeAll(common); actEntries.keySet().removeAll(common); List<String> strings = new ArrayList<String>(expEntries.keySet()); Collections.sort(strings); ZipFoldersReporter reporter = new ZipFoldersReporter(actual); for (String s : strings) { boolean isDir = expEntries.get(s).isDirectory(); if (isDir) continue; String entryName = reporter.report(s); if (entryName != null) absentFile(path(path, "!" + entryName), false); } reporter = new ZipFoldersReporter(expected); strings = new ArrayList<String>(actEntries.keySet()); Collections.sort(strings); for (String s : strings) { boolean isDir = actEntries.get(s).isDirectory(); if (isDir) continue; String entryName = reporter.report(s); if (entryName != null) createdFile(path(path, "!" + entryName), false); } } private boolean isIgnoredFile(String fileName) { return fileName.endsWith(".class") || fileName.equalsIgnoreCase("MANIFEST.MF"); } private boolean isTextFile(String fileName) { String extension = fileName.contains(".") ? fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase() : ""; return extension.equals("xml") || extension.equals("msd") || extension.equals("mpl") || extension.equals("mps") || extension.equals("number") || extension.equals("java") || extension.equals("bat") || extension.equals("sh") || fileName.equals("trace.info") || extension.equals("history") || extension.equals("properties") || extension.equals("dtd") || extension.equals("txt") || extension.equals("html") || fileName.equals("dependencies") || fileName.equals("generated") || fileName.endsWith(".java.template") || fileName.endsWith(".java.ft") || extension.equals("xsd") || fileName.endsWith(".html.template") || fileName.endsWith(".xml.template") || extension.equals("flex") || fileName.equals("license") || fileName.equals("notice") || extension.equals("devkit") || extension.equals("metadata"); } private void compareZipEntries(ZipEntry expected, ZipFile ezip, ZipEntry actual, ZipFile azip, String path) { String fileName = toShortName(expected.getName()).toLowerCase(); if (fileName.endsWith(".jar") || fileName.endsWith(".zip")) { status.stream("archive_in_archive").println(path); return; } if (isTextFile(fileName)) { reportCompare(read(expected, ezip), read(actual, azip), path); } else if (!isIgnoredFile(fileName)) { if (!equal(open(expected, ezip), open(actual, azip))) { status.stream("binary_diff", path).println(path); } } } private void compareFiles(File expected, File actual, String path) { String fileName = expected.getName().toLowerCase(); if (fileName.endsWith(".jar") || fileName.endsWith(".zip")) { if (equal(open(expected), open(actual))) { return; } ZipFile ezip = openZip(expected, path); ZipFile azip = openZip(actual, path); if (ezip == null || azip == null) { return; } compareZip(ezip, azip, path); return; } if (isTextFile(fileName)) { reportCompare(read(expected), read(actual), path); } else if (!isIgnoredFile(fileName)) { if (!equal(open(expected), open(actual))) { status.stream("binary_diff", path).println(path); } } } private void compareItems(File expected, File actual, String path) { if (expected.isDirectory() && actual.isFile()) { status.report(path, "file was found instead of directory"); } else if (expected.isFile() && actual.isDirectory()) { status.report(path, "directory was found instead of file"); } else if (expected.isFile() && actual.isFile()) { compareFiles(expected, actual, path); } else if (expected.isDirectory() && actual.isDirectory()) { compare(expected, actual, path); } else { status.report(path, "internal error"); } } private void reportCompare(String expected, String actual, String path) { if (expected.equals(actual)) return; if (path.toLowerCase().endsWith("/module.xml")) { Pattern pattern = Pattern.compile("<dependencies>.*</dependencies>", Pattern.DOTALL); expected = pattern.matcher(expected).replaceFirst("<dependencies/>"); actual = pattern.matcher(actual).replaceFirst("<dependencies/>"); if (expected.equals(actual)) return; } status.reportDiff(path, expected, actual); } public void compare(File expected, File actual, String path) { Set<String> expList = new HashSet(Arrays.asList(expected.list())); Set<String> actList = new HashSet(Arrays.asList(actual.list())); List<String> intersection = new ArrayList<String>(Sets.intersection(expList, actList)); Collections.sort(intersection); for (String s : intersection) { compareItems(new File(expected, s), new File(actual, s), path(path, s)); } expList.removeAll(intersection); actList.removeAll(intersection); ArrayList<String> strings = new ArrayList<String>(expList); Collections.sort(strings); for (String s : strings) { absentFile(path(path, s), new File(expected, s).isDirectory()); } strings = new ArrayList<String>(actList); Collections.sort(strings); for (String s : strings) { createdFile(path(path, s), new File(actual, s).isDirectory()); } } private void createdFile(String path, boolean isDirectory) { if (!isDirectory && path.toLowerCase().endsWith("trace.info")) { status.stream("created_trace.info", path).println(path); } else if (isDirectory) { status.stream("created_folders", path).println(path); } else { status.stream("created_files", path).println(path); } } private void absentFile(String path, boolean isDirectory) { if (path.toLowerCase().endsWith("trace.info")) { status.stream("absent_trace.info", path).println(path); } else if (isDirectory) { status.stream("absent_folders", path).println(path); } else { status.stream("absent_files", path).println(path); } } private static String path(String prefix, String filename) { return prefix.isEmpty() ? filename : prefix + "/" + filename; } public static String read(File file) { try { return read(new FileReader(file)); } catch (IOException e) { System.out.println("cannot read file " + file.getAbsolutePath()); return ""; } } private boolean equal(InputStream s1, InputStream s2) { try { if (s1 == null || s2 == null) { return true; } byte[] b1 = new byte[4096]; byte[] b2 = new byte[4096]; int c1; int c2; while (true) { c1 = s1.read(b1); c2 = s2.read(b2); if (c1 != c2) return false; if (c1 == -1) break; for (int i = 0; i < c1; i++) { if (b1[i] != b2[i]) return false; } } } catch (IOException e) { System.err.println("cannot read: " + e.getMessage()); } finally { try { if (s1 != null) s1.close(); if (s2 != null) s2.close(); } catch (IOException ex) { } } return true; } private InputStream open(File file) { try { return new FileInputStream(file); } catch (IOException e) { System.out.println("cannot read file " + file.getName()); return null; } } public static InputStream open(ZipEntry entry, ZipFile file) { try { return file.getInputStream(entry); } catch (IOException e) { System.out.println("cannot read file " + file.getName() + "!" + entry.getName()); return null; } } public static String read(ZipEntry entry, ZipFile file) { try { return read(new InputStreamReader(file.getInputStream(entry))); } catch (IOException e) { System.out.println("cannot read file " + file.getName() + "!" + entry.getName()); return ""; } } public static String read(Reader reader) throws IOException { BufferedReader r = null; try { r = new BufferedReader(reader); StringBuilder result = new StringBuilder(); String line; while ((line = r.readLine()) != null) { result.append(line).append("\n"); } return result.toString(); } catch (IOException e) { throw e; } finally { try { if (r != null) { r.close(); } } catch (IOException e) { } } } private static String toShortName(String fileName) { int f = fileName.lastIndexOf('/'); if (f >= 0) { return fileName.substring(f + 1); } return fileName; } }