package project.generator;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.time.Instant;
import java.util.Arrays;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import project.combiner.MainBuildIDE;
import utils.lists.ArrayList;
import utils.lists.Files;
import utils.lists.HashMap;
import utils.lists.Map;
import utils.lists.Pair;
import utils.lists.Paths;
import utils.lists.Set;
import utils.streams2.Stream;
import utils.streams2.Streams;
class ComparisonSummaryGenerator {
private static final Path USER_HOME_FOLDER = Paths.get(System.getProperty("user.home", ""));
public static void main(String[] args) throws IOException {
Path version1 = MainBuildIDE.determineUnusedTarget();
Path version2 = chooseNewer(USER_HOME_FOLDER, "eclipse", "evening", 1);
Map<Path, Pair<byte[], Instant>> current =
readContents("--- Summary report of different files between two versions, by type of file", version1);
Map<Path, Pair<byte[], Instant>> official =
readContents("--- (from " + version1 + " and " + version2 + ")", version2);
int diffs;
diffs = printReport("Short Report (ignoring all but major version differences)", official, current, 1);
if(diffs > 0) return;
diffs = printReport("Full Report (ignoring only snapshot version differences)", official, current, 3);
if(diffs > 0) return;
printFullReport("Exhaustive Report (not ignoring any differences)", official, current);
}
private static Path chooseNewer(Path folder, String path1, String path2, int version) throws IOException {
Path version1 = folder.resolve(path1);
Path version2 = folder.resolve(path2);
boolean found1 = Files.isDirectory(version1);
boolean found2 = Files.isDirectory(version2);
if(found1 && found2) {
Instant modified1 = Files.getLastModifiedTime(version1).toInstant();
Instant modified2 = Files.getLastModifiedTime(version2).toInstant();
return modified1.isAfter(modified2) ? version1 : version2;
} else if(found1) {
return version1;
} else if(found2) {
return version2;
} else {
System.out.printf(
"Version %d for comparison doesn't exist.%nLooked in: %s%nAnd in: %s%n",
version,
version1,
version2);
System.exit(-1);
throw new AssertionError();
}
}
private static int printFullReport(
String info,
Map<Path, Pair<byte[], Instant>> official,
Map<Path, Pair<byte[], Instant>> current) {
int diffs = 0;
System.out.println();
int i = 0;
int n = 9;
info(info, i++, n);
Map<Path, Pair<byte[], Instant>> officialContents = official;
info(info, i++, n);
Map<Path, Pair<byte[], Instant>> currentContents = current;
info(info, i++, n);
Set<Pair<String, Path>> officialFiles = groupedByFileExt(officialContents);
info(info, i++, n);
Set<Pair<String, Path>> currentFiles = groupedByFileExt(currentContents);
info(info, i++, n);
HashMap<String, ArrayList<Path>> missing = missingFiles(officialFiles, currentFiles);
info(info, i++, n);
HashMap<String, ArrayList<Path>> extras = missingFiles(currentFiles, officialFiles);
info(info, i++, n);
HashMap<String, ArrayList<Path>> different =
differentFiles(officialContents, currentContents, officialFiles, currentFiles);
info(info, i++, n);
ArrayList<String> exts =
missing.keySet().toHashSet().addAll(extras.keySet()).addAll(different.keySet()).toArrayList().sort();
info(info, i++, n);
for(String ext : exts) {
System.out.printf("File type '%s':%n", ext);
diffs += report(missing, ext, "\tMissing files:\t-\t-\t-\t-\t-\t-\t-\t-\t-");
diffs += report(extras, ext, "\tExtra files:\t+\t\t+\t\t+\t\t+\t\t+");
diffs += report(different, ext, "\tDifferent files:");
}
return diffs;
}
private static HashMap<String, ArrayList<Path>> differentFiles(
Map<Path, Pair<byte[], Instant>> officialContents,
Map<Path, Pair<byte[], Instant>> currentContents,
Set<Pair<String, Path>> officialFiles,
Set<Pair<String, Path>> currentFiles) {
Set<Pair<String, Path>> shared = officialFiles.retainAll(currentFiles);
Stream<Pair<String, Path>> stream = shared.stream().filter(p -> differ(officialContents, currentContents, p));
HashMap<String, ArrayList<Path>> different = stream.toMultiMap(Pair::lhs, Pair::rhs);
return different;
}
private static boolean differ(
Map<Path, Pair<byte[], Instant>> officialContents,
Map<Path, Pair<byte[], Instant>> currentContents,
Pair<String, Path> p) {
return Arrays.equals(officialContents.get(p.rhs).lhs, currentContents.get(p.rhs).lhs) == false;
}
private static int printReport(
String info,
Map<Path, Pair<byte[], Instant>> official,
Map<Path, Pair<byte[], Instant>> current,
int versionIgnoreIndex) throws IOException {
int diffs = 0;
System.out.println();
int i = 0;
int n = 11;
info(info, i++, n);
Map<Path, Pair<byte[], Instant>> officialContents = adjustVersions(official, versionIgnoreIndex);
info(info, i++, n);
Map<Path, Pair<byte[], Instant>> currentContents = adjustVersions(current, versionIgnoreIndex);
info(info, i++, n);
officialContents =
officialContents.entrySet().removeIf(e -> e.lhs.toString().contains(".source_")).stream().toMap(
Pair::lhs,
Pair::rhs).toMap();
info(info, i++, n);
officialContents =
officialContents.entrySet().removeIf(e -> e.lhs.startsWith("features")).stream().toMap(Pair::lhs, Pair::rhs).toMap();
info(info, i++, n);
currentContents =
currentContents.entrySet().removeIf(e -> e.lhs.startsWith("features")).stream().toMap(Pair::lhs, Pair::rhs).toMap();
info(info, i++, n);
Set<Pair<String, Path>> officialFiles = groupedByFileExt(officialContents);
info(info, i++, n);
Set<Pair<String, Path>> currentFiles = groupedByFileExt(currentContents);
info(info, i++, n);
HashMap<String, ArrayList<Path>> missing = missingFiles(officialFiles, currentFiles);
info(info, i++, n);
HashMap<String, ArrayList<Path>> extras = missingFiles(currentFiles, officialFiles);
info(info, i++, n);
ArrayList<String> exts = missing.keySet().toHashSet().addAll(extras.keySet()).toArrayList().sort();
info(info, i++, n);
for(String ext : exts) {
System.out.printf("File type '%s':%n", ext);
diffs += report(missing, ext, "\tMissing files:\t-\t-\t-\t-\t-\t-\t-\t-\t-");
diffs += report(extras, ext, "\tExtra files:\t+\t\t+\t\t+\t\t+\t\t+");
}
return diffs;
}
private static int report(HashMap<String, ArrayList<Path>> group, String ext, String info) {
int diffs = 0;
if(group.containsKey(ext)) {
System.out.println(info);
diffs += reportFiles(group.get(ext).sort());
}
return diffs;
}
private static int reportFiles(ArrayList<Path> files) {
int diffs = files.size();
ArrayList<Path> list = files;
for(int i = 0, n = Math.min(list.size(), 10); i < n; i++) {
System.out.printf("\t\t%s%n", list.get(i));
}
if(list.size() > 10) {
System.out.printf("\t\t(and %d more..)%n", list.size() - 10);
}
return diffs;
}
private static HashMap<String, ArrayList<Path>> missingFiles(
Set<Pair<String, Path>> officialFiles,
Set<Pair<String, Path>> currentFiles) {
HashMap<String, ArrayList<Path>> missing =
officialFiles.removeAll(currentFiles).stream().toMultiMap(Pair::lhs, Pair::rhs);
return missing;
}
private static Set<Pair<String, Path>> groupedByFileExt(Map<Path, Pair<byte[], Instant>> currentContents) {
Stream<Pair<Path, Pair<byte[], Instant>>> stream = currentContents.stream();
Stream<Pair<String, Path>> map = stream.map(e -> Pair.of(fileExt(e), e.lhs));
return map.toSet().toSet();
}
private static String fileExt(Pair<Path, Pair<byte[], Instant>> e) {
String name = e.lhs.getFileName().toString();
return name.substring(name.lastIndexOf('.') + 1);
}
private static Map<Path, Pair<byte[], Instant>> readContents(String info, Path folder) throws IOException {
HashMap<Path, Pair<byte[], Instant>> target = new HashMap<>();
ArrayList<Path> list = Files.walk(folder).toList();
for(int i = 0, n = list.size(); i < n; i++) {
info(info, i, n);
Path entry = list.get(i);
if(Files.isRegularFile(entry)) {
byte[] bytes = Files.readAllBytes(entry);
Instant instant = Files.getLastModifiedTime(entry).toInstant();
Path path = folder.relativize(entry);
readFile(target, bytes, path, instant);
}
}
return target.toMap();
}
private static Map<Path, Pair<byte[], Instant>> adjustVersions(
Map<Path, Pair<byte[], Instant>> target,
int versionIgnoreIndex) throws IOException {
ArrayList<Pair<Path, Pair<byte[], Instant>>> list =
target.stream().filter(e -> e.lhs.endsWith(JarFile.MANIFEST_NAME)).toList();
HashMap<Path, Path> adjustments = new HashMap<>();
for(Pair<Path, Pair<byte[], Instant>> entry : list) {
if(entry.lhs.getNameCount() <= 2) {
continue;
}
Path path = entry.lhs.subpath(0, entry.lhs.getNameCount() - 2);
String name = path.getFileName().toString();
Attributes attributes = parseManifest(entry.rhs.lhs);
String bundleID =
generateBundleIDFromManifest(name.endsWith(".jar") ? ".jar" : "", attributes, versionIgnoreIndex);
if(bundleID != null) {
Path changed = path.getParent().resolve(bundleID);
adjustments.put(path, changed);
}
}
Map<Path, Path> adjusted = adjustments.toMap();
return target.entrySet().replaceAll(e -> adjust(adjusted, e)).stream().toMap(Pair::lhs, Pair::rhs).toMap();
}
private static <V> Pair<Path, V> adjust(Map<Path, Path> adjustments, Pair<Path, V> e) {
for(int i = e.lhs.getNameCount() - 1; i > 0; i--) {
Path original = e.lhs.subpath(0, i);
Path target = adjustments.get(original);
if(target != null) {
Path adjustedPath = target.resolve(original.relativize(e.lhs));
e = e.keepingRhs(adjustedPath);
}
}
return e;
}
private static String generateBundleIDFromManifest(String suffix, Attributes attributes, int versionIgnoreIndex) {
String version = attributes.getValue("Bundle-Version");
if(version == null) {
return null;
}
String[] split = version.split("\\.");
for(int i = versionIgnoreIndex, n = split.length; i < n; i++) {
split[i] = "";
}
version = String.join(" ", split).trim().replace(' ', '.');
String symbolicName = attributes.getValue("Bundle-SymbolicName");
if(symbolicName == null) {
return null;
}
String name = symbolicName.split(";")[0].trim();
return name + "_" + version + suffix;
}
private static Attributes parseManifest(byte[] buf) throws IOException {
return new Manifest(new ByteArrayInputStream(buf)).getMainAttributes();
}
private static void
readFile(HashMap<Path, Pair<byte[], Instant>> target, byte[] bytes, Path path, Instant modified)
throws IOException {
String name = path.getFileName().toString();
if(name.endsWith(".jar") || name.endsWith(".zip")) {
readZip(bytes, target, path);
return;
}
if(name.endsWith(".gz")) {
path = path.resolve(name.substring(0, name.length() - 3));
bytes = Streams.readFully(new GZIPInputStream(new ByteArrayInputStream(bytes)));
}
Pair<byte[], Instant> value = new Pair<>(bytes, modified);
target.put(path, value);
}
private static void readZip(byte[] bytes, HashMap<Path, Pair<byte[], Instant>> target, Path root)
throws IOException {
try(ZipInputStream zip = new ZipInputStream(new ByteArrayInputStream(bytes));) {
ZipEntry entry;
while((entry = zip.getNextEntry()) != null) {
if(entry.isDirectory()) {
continue;
}
String name = entry.getName();
if(name.contains("..")) {
continue;
}
bytes = Streams.readAllBytes(zip);
Path path = root.resolve(name);
Instant instant = entry.getLastModifiedTime().toInstant();
readFile(target, bytes, path, instant);
}
}
}
private static void info(String info, int i, int n) {
int beginIndex = info.length() * i / n;
int endIndex = info.length() * ++i / n;
if(beginIndex != endIndex) {
System.out.print(info.substring(beginIndex, endIndex));
}
if(i == n) {
System.out.println();
}
}
}