package project.generator; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Arrays; import java.util.Comparator; import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collector; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import jdk.internal.org.objectweb.asm.ClassReader; import jdk.internal.org.objectweb.asm.Opcodes; import project.Plugin; import utils.lists.ArrayList; import utils.lists.Files; import utils.lists.HashMap; import utils.lists.HashMap.Entry; import utils.lists.HashSet; import utils.lists.List; import utils.lists.Map; import utils.lists.Pair; import utils.lists.Paths; import utils.lists.ReadOnlyList; import utils.lists.Set; import utils.streams2.Collectors; import utils.streams2.IOStream; import utils.streams2.Stream; import utils.streams2.Streams; public class RegenerateProjectsMain { private static final Path PLUGINS_FOLDER = Paths.get(System.getProperty("user.home", ""), "evening"); private static final Set<Path> CLASS_FILE_INDICATORS = Set.of( Paths.get("target"), Paths.get("bin"), Paths.get("classes")); private static HashMap<Pair<String, String>, Integer> cachedDistances = new HashMap<>(); public static void main(String[] args) throws IOException { Path root = Paths.get("").toAbsolutePath().getParent().getParent(); ArrayList<Plugin> plugins = readModelFiles(); ArrayList<String> pluginNames = plugins.map(Plugin::name).sort(); Map<String, List<Path>> sourceFolders = findSourceFolders(root, pluginNames); printRoots(sourceFolders); generateDotProjects(root, plugins, sourceFolders); generateDotClasspaths(root, plugins, sourceFolders); } private static void generateDotClasspaths( Path root, ReadOnlyList<Plugin> plugins, Map<String, List<Path>> sourceFolders) throws IOException { Map<String, List<String>> depending = combineDependencies(plugins); Map<String, List<String>> exported = reexportedDependencies(plugins); for(Plugin plugin : plugins) { printManifestInfo(PLUGINS_FOLDER, depending, plugin); } Map<String, List<String>> dependents = invert(depending).remove("system.bundle"); Map<String, List<String>> depended = invert(dependents); printDependencies(depended, "->"); printDependencies(dependents, "<-"); generateClasspaths(root, depended, exported, plugins, sourceFolders); } private static ArrayList<Plugin> readModelFiles() throws IOException { ArrayList<Plugin> plugins = readPluginContents(PLUGINS_FOLDER); for(Plugin plugin : plugins) { Stream<Pair<Path, byte[]>> stream = plugin.contents.entrySet().map(Entry::toPair).stream(); IOStream<Pair<Path, byte[]>> expanded = stream.toIO().flatMap(RegenerateProjectsMain::deepExpand); Path bundle = Paths.get(plugin.id + plugin.shape); HashMap<Path, byte[]> hashMap = expanded.toMap(p -> bundle.resolve(p.lhs), Pair::rhs); plugin.contents.clear().putAll(hashMap); } return plugins; } private static IOStream<Pair<Path, byte[]>> deepExpand(Pair<Path, byte[]> content) throws IOException { String name = content.lhs.getFileName().toString(); IOStream<Pair<Path, byte[]>> ioStream = Stream.of(content).toIO(); if(name.endsWith(".jar")) { IOStream<Pair<Path, byte[]>> io = readZip(content.rhs).entrySet().stream().toIO(); IOStream<Pair<Path, byte[]>> expanded = io.flatMap(RegenerateProjectsMain::deepExpand); IOStream<Pair<Path, byte[]>> ioStream2 = expanded.map(p -> p.keepingRhs(content.lhs.resolve(p.lhs))); return ioStream.concat(ioStream2); } return ioStream; } private static void generateClasspaths( Path root, Map<String, List<String>> depending, Map<String, List<String>> exported, ReadOnlyList<Plugin> plugins, Map<String, List<Path>> sourceFolders) throws IOException { HashSet<Path> projectFolders = HashSet.of(); Path projects = root.resolve("projects"); for(Plugin plugin : plugins) { String pluginName = plugin.id; String projectName = projectName(sourceFolders, pluginName); if(projectName == null) { System.out.println("--- Missing library --- " + pluginName); continue; } Path projectFolder = projects.resolve(projectName); Files.createDirectories(projectFolder); projectFolders.add(projectFolder); List<Path> sources = sourceFolders.get(pluginName); List<Path> libs = sources.replaceAll(p -> root.resolve(p)); List<String> required = depending.getOrDefault(pluginName, List.of()); Set<String> exports = exported.get(pluginName).toSet(); writeDotClasspath(projectFolder, pluginName, libs, sourceFolders, required, exports, plugin); } printUnusedProjects(projectFolders, projects); printDependencyLoops(projects); } private static void generateDotProjects(Path root, ArrayList<Plugin> plugins, Map<String, List<Path>> sourceFolders) throws IOException { HashSet<Path> projectFolders = HashSet.of(); Path projects = root.resolve("projects"); for(int i = 0, n = plugins.size(); i < n; i++) { Plugin plugin = plugins.get(i); String pluginName = plugin.id; String projectName = projectName(sourceFolders, pluginName); if(projectName == null) { continue; } Path projectFolder = projects.resolve(projectName); Files.createDirectories(projectFolder); projectFolders.add(projectFolder); List<Path> sources = sourceFolders.get(pluginName); writeDotProject(projectFolder, projectName, sources); HashMap<String, String> manifest = findRealManifest(root, projectFolder, sources, plugin); plugins.set(i, plugin.manifest(manifest)); } } private static HashMap<String, String> findRealManifest( Path root, Path projectFolder, List<Path> sources, Plugin plugin) throws IOException { Path manifestPath = projectFolder.resolve("res/META-INF/MANIFEST.MF"); if(Files.isRegularFile(manifestPath)) { return Plugin.parseManifest(Files.readAllBytes(manifestPath)); } for(Path source : sources) { manifestPath = root.resolve(source).resolve("META-INF/MANIFEST.MF"); if(Files.isRegularFile(manifestPath)) { return Plugin.parseManifest(Files.readAllBytes(manifestPath)); } } System.out.println("Real manifest not found for plugin: " + plugin.id); return plugin.manifest; } private static void printDependencyLoops(Path projects) throws IOException { Map<String, List<String>> deps; deps = Files.list(projects).map(p -> p.resolve(".classpath")).filter(Files::isRegularFile).map( RegenerateProjectsMain::readRequiredProjects).toMap(Pair::lhs, Pair::rhs).toMap(); HashSet<Pair<String, String>> checked = HashSet.of(); List<List<String>> chains = deps.keySet().stream().map(List::of).toList().toList(); while(chains.notEmpty()) { ArrayList<List<String>> newChains = new ArrayList<>(chains.size() * 10); for(List<String> chain : chains) { if(chain.size() > 1) { String start = chain.get(0); String end = chain.get(-1); if(start.equals(end)) { System.out.println("Chain " + chain.size() + ":\t" + chain); } else { Pair<String, String> pair = Pair.of(start, end); if(checked.contains(pair)) { continue; } checked.add(pair); } } List<String> dep = deps.getOrDefault(chain.get(-1), List.of()); List<List<String>> newChain = dep.map(s -> chain.add(s)).toList(); newChains.addAll(newChain); } chains = newChains.toList(); } } private static Pair<String, List<String>> readRequiredProjects(Path classpath) throws IOException { ArrayList<String> list = Files.readAllLines(classpath); list.replaceAll(String::trim); list.filter(s -> s.contains("kind=\"src\" path=\"/IDE") && s.endsWith("\"/>")); list.replaceAll(s -> s.substring(s.lastIndexOf("kind=\"src\" path=\"/IDE") + 18, s.length() - 3)); String project = classpath.getParent().getFileName().toString(); return Pair.of(project, list.toList()); } private static void printUnusedProjects(HashSet<Path> projectFolders, Path projects) throws IOException { HashSet<String> set = Files.list(projects).toSet().removeAll(projectFolders).map(p -> p.getFileName().toString()); set.removeIf(p -> p.startsWith("IDE-compile-") || p.startsWith("IDE-all-") || p.equals("IDE")); ArrayList<String> list = set.toArrayList().sort(); if(list.notEmpty()) { System.out.println("\nUnused folders inside projects folder:"); for(String path : list) { System.out.println(path); } } } private static void writeDotClasspath( Path project, String pluginName, List<Path> libs, Map<String, List<Path>> sourceFolders, List<String> depending, Set<String> exported, Plugin plugin) throws IOException { List<Path> pluginFiles = sourceFilesForPlugin(plugin).toList(); List<Path> locallyAvailableFiles = listLocallyAvailableFiles(project); List<Path> availableFiles = deepListAvailableFiles(locallyAvailableFiles, libs, pluginFiles, pluginName, project); Map<Pair<Path, Path>, ArrayList<Path>> srcMappings = mapAvailableToFiles(availableFiles, pluginFiles).toMap(); ArrayList<String> classpath = ArrayList.of("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<classpath>"); List<Pair<Path, Path>> srcs = srcMappings.keySet().toList().sort( Comparator.<Pair<Path, Path>, Path> comparing(Pair::lhs).thenComparing(Pair::rhs)); ArrayList<String> outputs = new ArrayList<>(); for(int i = 0, n = srcs.size(); i < n; i++) { Pair<Path, Path> mapping = srcs.get(i); ArrayList<Path> files = srcMappings.get(mapping); classpath.add(files.stream().map(p -> p.toString().replace('\\', '/')).collect( Collectors.joining("\n\t ", "\t<!-- ", " -->"))); boolean commented = mapping.lhs.toString().contains("x-miss-x"); String quoteBeg = commented ? "<!-- " : ""; String quoteEnd = commented ? " -->" : ""; String outputPath = combinableUnixPath(mapping.rhs); String outputPathParent = combinableUnixPath(mapping.rhs.getParent()); if(commented == false && outputs.contains(outputPathParent) == false) { outputs.add(outputPathParent); } int sequenceNum = commented ? 0 : outputs.indexOf(outputPathParent) + 1; String output = "bin/" + sequenceNum + "/plugins" + outputPath; String path = toUnixPath(mapping.lhs); String including = commented ? "" : collectInclusions(availableFiles, files, mapping.lhs); String excluding = commented ? "" : collectExclusions(srcs, mapping.lhs, locallyAvailableFiles, libs); String newItem = String.format( " %s<classpathentry%s%s kind=\"src\" output=\"%s\" path=\"%s\"/>%s", quoteBeg, excluding, including, output, path, quoteEnd); classpath.add(newItem); } classpath.add(" <classpathentry kind=\"con\" path=\"org.eclipse.jdt.launching.JRE_CONTAINER\"/>"); List<String> listReexportedProjects = listReexportedProjects(pluginName, exported); for(String name : listRequiredProjects(pluginName, depending)) { String projectName = customizeChangeProjectName(name); String path = projectPath(sourceFolders, projectName); if(path != null && !listReexportedProjects.contains(name)) { classpath.add(" <classpathentry kind=\"src\" path=\"" + path + "\"/>"); } } for(String name : listReexportedProjects) { String projectName = customizeChangeProjectName(name); String path = projectPath(sourceFolders, projectName); if(path != null) { classpath.add(" <classpathentry exported=\"true\" kind=\"src\" path=\"" + path + "\"/>"); } } for(String path : customizeAddRequiredClasspathLibs(pluginName)) { classpath.add(" <classpathentry kind=\"lib\" path=\"" + path + "\"/>"); } for(String path : customizeAddReexportedLibsClasspath(pluginName)) { classpath.add(" <classpathentry exported=\"true\" kind=\"lib\" path=\"" + path + "\"/>"); } classpath.add(" <classpathentry kind=\"output\" path=\"classes\"/>"); classpath.add("</classpath>"); classpath.add(""); String dotClasspath = classpath.stream().collect(Collectors.joining("\n")); String fileName = ".classpath"; writeFile(project, dotClasspath, fileName); cleanOldOutputs(project, outputs); } private static List<String> listReexportedProjects(String pluginName, Set<String> exported) { List<String> projects = customizeAddReexportedProjects(pluginName, exported.toList()); return projects.sort(); } private static List<String> listRequiredProjects(String pluginName, List<String> depending) { List<String> list = customizeAddRequiredProjects(pluginName, depending); List<String> projects = customizeRemoveRequiredProjects(pluginName, list); return projects.sort(); } private static Map<String, List<String>> reexportedDependencies(ReadOnlyList<Plugin> plugins) { return gatherNamesWith(plugins, "Require-Bundle", "visibility:=reexport").toMap(); } private static String projectPath(Map<String, List<Path>> sourceFolders, String plugin) { if(plugin.startsWith("lib")) { return plugin; } String projectName = projectName(sourceFolders, plugin); if(projectName == null) { return null; } return "/" + projectName; } private static String projectName(Map<String, List<Path>> sourceFolders, String plugin) { if(plugin.startsWith("IDE-compile")) { return plugin; } List<Path> list = sourceFolders.get(plugin); if(list == null || list.isEmpty()) { return null; } Path source = list.get(0); String name = source.getName(1).toString(); return name.startsWith("eclipse.") ? "IDE-" + name + "-" + plugin : "IDE-other-" + plugin; } private static void cleanOldOutputs(Path projectFolder, ArrayList<String> outputs) throws IOException { Path binPath = projectFolder.resolve("bin"); if(Files.isDirectory(binPath) == false) { return; } HashSet<Path> all = Files.walk(binPath).map(projectFolder::relativize).toSet(); for(int i = 0, n = outputs.size(); i < n; i++) { Path other = Paths.get("bin", String.valueOf(i + 1), "plugins" + outputs.get(i)); all.removeIf(p -> p.startsWith(other)); for(int j = 1, m = other.getNameCount(); j < m; j++) { all.remove(other.subpath(0, j)); } } if(all.isEmpty()) { return; } ArrayList<Path> list = all.toArrayList().sort(Comparator.comparingInt(Path::getNameCount).reversed()); for(Path path : list) { Path path2 = projectFolder.resolve(path); if(path2.normalize().startsWith(projectFolder) == false) { throw new IllegalStateException("Invalid bin " + path2); } Files.delete(path2); } } private static String customizeChangeProjectName(String name) { switch(name) { case "org.eclipse.ecf.discovery": return "IDE-compile-eclipse.ecf"; default: return name; } } private static List<String> customizeAddRequiredClasspathLibs(String plugin) { switch(plugin) { case "org.eclipse.jem.util": return List.of("org.eclipse.jem.util/org.eclipse.perfmsr.core.stub/perfmsr.jar"); default: return List.of(); } } private static List<String> customizeAddReexportedLibsClasspath(String plugin) { switch(plugin) { case "net.jeeeyul.eclipse.themes": return List.of( "lib/org.eclipse.xtend.lib.macro_2.7.1.v201409090713.jar", "lib/com.google.guava_15.0.0.v201403281430.jar", "lib/org.eclipse.xtend.lib_2.7.1.v201409090713.jar", "lib/org.eclipse.xtext.xbase.lib_2.7.1.v201409090713.jar"); case "org.eclipse.m2e.maven.indexer": return List.of( "lib/indexer-artifact-3.1.0.jar", "lib/indexer-core-3.1.0.jar", "lib/lucene-core-2.4.1.jar", "lib/lucene-highlighter-2.4.1.jar"); default: return List.of(); } } private static List<String> customizeRemoveRequiredProjects(String plugin, List<String> required) { switch(plugin) { case "org.apache.batik.util": return required.remove("org.apache.batik.util.gui"); case "org.eclipse.core.runtime": return required.remove("org.eclipse.core.runtime.compatibility.auth"); case "org.eclipse.egit.doc": return required.remove("org.eclipse.mylyn.wikitext.mediawiki.core").remove( "org.eclipse.mylyn.wikitext.core").remove("org.eclipse.mylyn.wikitext.core.ant"); case "org.eclipse.swt.win32.win32.x86_64": return required.remove("org.eclipse.swt"); case "org.eclipse.team.cvs.ssh2": return required.remove("org.eclipse.team.cvs.ssh"); default: return required; } } private static List<String> customizeAddReexportedProjects(String plugin, List<String> exported) { switch(plugin) { case "org.eclipse.swt": return exported.add("org.eclipse.swt.win32.win32.x86_64"); case "org.eclipse.jdt.junit": return exported.add("org.eclipse.debug.ui"); case "org.eclipse.mylyn.tasks.core": case "org.eclipse.mylyn.tasks.ui": return exported.add("org.eclipse.jdt.annotation"); default: return exported; } } private static List<String> customizeAddRequiredProjects(String plugin, List<String> required) { switch(plugin) { case "ch.qos.logback.classic": case "ch.qos.logback.core": return required.addAll("IDE-compile-logback", "IDE-compile-various"); case "com.google.gerrit.common": return required.addAll("com.google.guava", "IDE-compile-gwt"); case "com.google.gerrit.prettify": return required.addAll("com.google.gerrit.reviewdb", "IDE-compile-gwt"); case "com.google.gerrit.reviewdb": return required.addAll("com.google.guava"); case "com.google.guava": return required.addAll("IDE-compile-jsr305"); case "com.google.gwt.servlet": return required.addAll( "com.google.gson", "com.google.guava", "com.google.protobuf", "org.w3c.css.sac", "org.eclipse.jdt.core", "org.junit", "IDE-compile-gwt", "IDE-compile-jsr305"); case "com.google.gwtjsonrpc": return required.addAll("org.apache.commons.codec", "IDE-compile-gwt"); case "com.google.gwtorm": return required.addAll("com.google.guava", "com.google.protobuf", "org.antlr.runtime"); case "it.unibz.instasearch": return required.addAll("IDE-compile-lucene-2.9.4"); case "net.jeeeyul.eclipse.themes.ui": return required.addAll("org.eclipse.ui.workbench.texteditor"); case "org.apache.ant": return required.addAll("IDE-compile-various", "IDE-compile-netrexx", "org.junit"); case "org.apache.batik.util.gui": return required.add("org.apache.batik.util"); case "org.apache.commons.compress": return required.add("IDE-compile-org.tukaani.xz"); case "org.apache.commons.logging": return required.addAll("javax.servlet", "IDE-compile-various"); case "org.apache.felix.gogo.command": return required.add("IDE-compile-apache.felix"); case "org.apache.httpcomponents.httpclient": return required.add("IDE-compile-various.caches"); case "org.apache.lucene.core": return required.add("com.ibm.icu"); case "org.apache.lucene": case "org.apache.lucene.highlighter": case "org.apache.lucene.memory": case "org.apache.lucene.misc": case "org.apache.lucene.queries": case "org.apache.lucene.snowball": case "org.apache.lucene.spellchecker": return required.add("IDE-compile-lucene-2.9.4"); case "org.apache.xmlrpc": return required.add("javax.servlet"); case "org.eclipse.ant.core": return required.add("org.apache.ant"); case "org.eclipse.core.resources": return required.add("org.apache.ant"); case "org.eclipse.core.runtime.compatibility.registry": return required.addAll("org.eclipse.core.runtime", "org.eclipse.osgi"); case "org.eclipse.e4.ui.model.workbench": return required.add("org.eclipse.emf.common"); case "org.eclipse.e4.ui.workbench": return required.addAll("org.eclipse.emf.ecore", "org.eclipse.emf.common"); case "org.eclipse.e4.ui.workbench.addons.swt": return required.addAll("org.eclipse.emf.ecore", "org.eclipse.emf.common"); case "org.eclipse.e4.ui.workbench.renderers.swt": return required.add("org.eclipse.emf.common"); case "org.eclipse.e4.ui.workbench.renderers.swt.cocoa": return required.addAll( "javax.inject", "org.eclipse.core.commands", "org.eclipse.e4.core.commands", "org.eclipse.e4.core.contexts", "org.eclipse.e4.core.di", "org.eclipse.e4.core.services", "org.eclipse.e4.ui.bindings", "org.eclipse.e4.ui.model.workbench", "org.eclipse.e4.ui.services", "org.eclipse.e4.ui.workbench", "org.eclipse.equinox.common", "org.eclipse.osgi", "org.eclipse.osgi.services", "org.eclipse.swt.cocoa.macosx.x86_64"); case "org.eclipse.e4.ui.workbench.swt": return required.addAll("org.eclipse.emf.ecore", "org.eclipse.emf.common"); case "org.eclipse.ecf.provider.filetransfer.httpclient4.ssl": return required.addAll("org.eclipse.ecf.filetransfer", "org.eclipse.osgi"); case "org.eclipse.ecf.provider.filetransfer.ssl": return required.add("org.eclipse.osgi"); case "org.eclipse.egit.ui": return required.add("org.eclipse.ui.views"); case "org.eclipse.emf.ecore.change": case "org.eclipse.emf.ecore.edit": case "org.eclipse.emf.ecore.xmi": return required.add("org.eclipse.emf.common"); case "org.eclipse.equinox.http.servlet": return required.add("IDE-compile-org.osgi.annotation.versioning"); case "org.eclipse.equinox.p2.director.app": return required.add("org.apache.ant"); case "org.eclipse.equinox.p2.garbagecollector": return required.add("org.eclipse.equinox.common"); case "org.eclipse.equinox.p2.jarprocessor": return required.add("org.apache.ant"); case "org.eclipse.equinox.p2.metadata.repository": return required.add("org.apache.ant"); case "org.eclipse.equinox.p2.publisher.eclipse": return required.add("org.apache.ant"); case "org.eclipse.equinox.p2.repository.tools": return required.addAll("org.apache.ant", "org.eclipse.equinox.p2.jarprocessor"); case "org.eclipse.equinox.security.macosx": case "org.eclipse.equinox.security.win32.x86_64": return required.add("org.eclipse.osgi"); case "org.eclipse.help.base": return required.add("org.apache.ant"); case "org.eclipse.jem.util": return required.add("org.eclipse.emf.common"); case "org.eclipse.jetty.continuation": return required.add("IDE-compile-org.mortbay.jetty.util"); case "org.eclipse.jetty.security": return required.add("org.eclipse.jetty.io"); case "org.eclipse.jetty.server": case "org.eclipse.jetty.servlet": return required.add("IDE-compile-org.eclipse.jetty.jmx"); case "org.eclipse.jdt.core": return required.add("org.apache.ant"); case "org.eclipse.m2e.core": case "org.eclipse.m2e.core.ui": case "org.eclipse.m2e.editor.xml": case "org.eclipse.m2e.jdt": case "org.eclipse.m2e.jdt.ui": case "org.eclipse.m2e.launching": case "org.eclipse.m2e.profiles.core": case "org.eclipse.m2e.scm": return required.addAll("IDE-compile-apache.maven"); case "org.eclipse.m2e.logback.appender": return required.addAll("ch.qos.logback.core", "org.eclipse.ui.workbench"); case "org.eclipse.m2e.discovery": return required.addAll("IDE-compile-apache.maven", "org.eclipse.e4.ui.workbench"); case "org.eclipse.m2e.model.edit": case "org.eclipse.m2e.refactoring": return required.addAll("IDE-compile-apache.maven", "org.eclipse.emf.common"); case "org.eclipse.m2e.editor": return required.addAll("IDE-compile-apache.maven", "org.eclipse.ui.workbench.texteditor"); case "org.eclipse.m2e.maven.indexer": return required.addAll("javax.inject", "org.slf4j.api", "IDE-compile-apache.maven"); case "org.eclipse.m2e.workspace.cli": return required.addAll("javax.inject", "IDE-compile-apache.maven"); case "org.eclipse.mylyn.gerrit.core": return required.addAll("org.apache.commons.httpclient", "IDE-compile-gwt"); case "org.eclipse.mylyn.reviews.ui": return required.add("org.eclipse.emf.common"); case "org.eclipse.mylyn.tasks.core": case "org.eclipse.mylyn.tasks.ui": return required.addAll("IDE-compile-apache.maven"); case "org.eclipse.mylyn.wikitext.mediawiki.core": return required.addAll("org.apache.ant"); case "org.eclipse.pde.api.tools": return required.add("org.apache.ant"); case "org.eclipse.pde.api.tools.ui": return required.add("org.eclipse.ui.views"); case "org.eclipse.pde.build": return required.add("org.apache.ant"); case "org.eclipse.pde.core": return required.add("org.apache.ant"); case "org.eclipse.osgi": case "org.eclipse.osgi.services": return required.add("IDE-compile-org.osgi.annotation.versioning"); case "org.eclipse.pde.ds.ui": return required.addAll("org.eclipse.ui.workbench.texteditor", "org.eclipse.ui.views"); case "org.eclipse.pde.ua.ui": return required.add("org.eclipse.ui.views"); // case "org.eclipse.swt": // return required.add("org.eclipse.swt.win32.win32.x86_64"); case "org.eclipse.team.ui": return required.add("org.eclipse.ui.workbench.texteditor"); case "org.eclipse.ui.win32": return required.addAll( "com.ibm.icu", "org.eclipse.core.resources", "org.eclipse.equinox.common", "org.eclipse.equinox.registry", "org.eclipse.jface", "org.eclipse.swt", "org.eclipse.ui.workbench"); case "org.eclipse.ui.cocoa": return required.addAll("org.eclipse.swt.cocoa.macosx.x86_64"); case "org.eclipse.wst.common.emf": case "org.eclipse.wst.common.emfworkbench.integration": case "org.eclipse.wst.xml.core": case "org.eclipse.wst.xsd.core": return required.addAll("org.eclipse.emf.common", "org.eclipse.emf.ecore"); case "org.eclipse.xsd": return required.addAll("org.eclipse.emf.common"); case "org.apache.jasper.glassfish": return required.addAll( "org.apache.ant", "org.eclipse.jdt.core", "org.eclipse.equinox.common", "org.eclipse.core.contenttype", "org.eclipse.core.filesystem", "org.eclipse.core.jobs", "org.eclipse.core.resources", "org.eclipse.core.runtime", "org.eclipse.text"); case "org.slf4j.api": return required.add("IDE-compile-org.slf4j"); case "org.springsource.ide.eclipse.commons.quicksearch": return required.add("org.eclipse.ui.workbench.texteditor"); case "com.jcraft.jsch": return required.add("IDE-compile-com.jcraft.jzlib"); default: return required; } } private static String collectInclusions(List<Path> files, ArrayList<Path> usedFiles, Path src) { int count = src.getNameCount(); if(count == 1 && (src.startsWith("src") || src.startsWith("res"))) { return ""; } List<Path> allFiles = files.filter(p -> p.startsWith(src) && p.getNameCount() > count).map(src::relativize); Set<String> all = gatherInclusionsFromFileList(allFiles); Set<String> used = gatherInclusionsFromFileList(usedFiles.toList()); if(all.removeAll(used).isEmpty() || used.isEmpty()) { return ""; } ArrayList<String> included = used.toArrayList().replaceAll(s -> s.replace('#', '?')).sort(); replaceInclusion(included, "org.apache.batik.", "Version"); replaceInclusion(included, "com.google.gwt.dev.util.*", "Name", "StringKey"); replaceInclusion( included, "com.google.gwt.core.ext.*", "LinkerContext", "TreeLogger", "UnableToCompleteException"); replaceInclusion( included, "com.google.gwt.core.ext.linker.*", "AbstractLinker", "Artifact", "ArtifactSet", "ConfigurationProperty", "CompilationResult", "EmittedArtifact", "LinkerOrder", "PropertyProviderGenerator", "SelectionProperty", "Shardable", "SoftPermutation", "SymbolData", "SyntheticArtifact"); replaceInclusion(included, "com.google.gwt.core.linker.*", "SymbolMapsLinker"); replaceInclusion(included, "com.google.gwt.user.rebind.*", "SourceWriter", "StringSourceWriter"); replaceInclusion(included, "com.google.gwt.i18n.rebind.keygen.*", "KeyGenerator"); replaceInclusion(included, "com.google.gerrit.extensions.api.projects.*", "ProjectState"); replaceInclusion(included, "com.google.gerrit.extensions.common.*", "InheritableBoolean", "SubmitType"); return " including=\"" + String.join("|", included) + "\""; } private static void replaceInclusion(ArrayList<String> included, String inclusion, String... newInclusions) { inclusion = inclusion.replace('.', '/'); if(included.contains(inclusion)) { included.remove(inclusion); if(inclusion.endsWith("*")) { inclusion = inclusion.substring(0, inclusion.length() - 1); } for(String newInclusion : newInclusions) { included.add(inclusion + newInclusion + ".java"); } } } private static Set<String> gatherInclusionsFromFileList(List<Path> coveredFiles) { HashSet<String> included = coveredFiles.filter(p -> p.getNameCount() == 1).map(p -> p.getFileName().toString()).toHashSet(); included.addAll(coveredFiles.filter(p -> p.getNameCount() > 1).map( p -> toUnixPath(p.subpath(0, p.getNameCount() - 1)) + "/*").toSet()); return included.toSet(); } private static String collectExclusions( List<Pair<Path, Path>> srcMappings, Path src, List<Path> locallyAvailableFiles, List<Path> libs) throws IOException { locallyAvailableFiles = locallyAvailableFiles.replaceAll(RegenerateProjectsMain::flattenOne); libs = libs.filter(p -> p.endsWith(src.getName(0))); Path lib = libs.size() == 1 ? libs.get(0) : null; if(libs.isEmpty() && src.startsWith("src") == false && src.startsWith("res") == false) { throw new IllegalStateException("libs became empty"); } else if(libs.notEmpty() && lib == null) { throw new IllegalStateException("libs contains " + libs); } List<Path> srcs = srcMappings.map(Pair::lhs); int srcNameCount = src.getNameCount(); List<Path> list = srcs.removeIf(p -> p.getNameCount() <= srcNameCount); List<Path> filter = list.filter(p -> p.startsWith(src)); List<String> excluded = filter.map(p -> toUnixPath(p.subpath(srcNameCount, p.getNameCount())) + "/"); excluded = customizeAddClasspathEntryExclusions(src, excluded).removeIf(s -> s.contains("x-miss-x")); ArrayList<String> excluded2 = new ArrayList<>(); if(lib != null) { for(Path local : locallyAvailableFiles) { if(Files.isRegularFile(lib.getParent().resolve(src).resolve(local))) { excluded2.add(toUnixPath(local)); continue; } } List<String> commonExclusions = excluded.filter(s -> testExclusion(src, lib, excluded2, s)); if(commonExclusions.notEmpty()) { System.out.println("Unnecessary exclusion " + src + ": " + commonExclusions); } } Stream<String> stream = excluded.stream(); List<String> overlappingExclusions = excluded2.toList().filter(s -> stream.anyMatch(s2 -> s.startsWith(s2))); if(overlappingExclusions.notEmpty()) { System.out.println("Overlapping exclusion " + src + ": " + overlappingExclusions); } excluded = excluded.addAll(excluded2); if(excluded.isEmpty()) { return ""; } return " excluding=\"" + String.join("|", excluded) + "\""; } private static boolean testExclusion(Path src, Path lib, ArrayList<String> excluded2, String exclusion) throws IOException { if(excluded2.contains(exclusion)) { return true; } if(exclusion.contains("*")) { if(exclusion.equals(".*")) { Path path = lib.getParent().resolve(src); return Files.list(path).noneMatch( p -> Files.isRegularFile(p) && p.getFileName().toString().startsWith(".")); } else if(exclusion.equals(".*/")) { Path path = lib.getParent().resolve(src); return Files.list(path).noneMatch( p -> Files.isDirectory(p) && p.getFileName().toString().startsWith(".")); } return false; } Path path = lib.getParent().resolve(src).resolve(exclusion); return exclusion.endsWith("/") ? Files.isDirectory(path) == false : Files.isRegularFile(path) == false; } private static Path flattenOne(Path p) { return p.subpath(1, p.getNameCount()); } private static List<String> customizeAddClasspathEntryExclusions(Path src, List<String> excluded) { String unixPath = toUnixPath(src); switch(unixPath) { case "lucene-2.9.4/src/test": return excluded.add("org/apache/lucene/util/*Test*.java"); case "apache.commons-lang/src/main/java": return excluded.add("org/apache/commons/lang/enum/"); case "antlr.antlr3/runtime/Java/src/main/java": return excluded.addAll("org/antlr/runtime/tree/DOTTreeGenerator.java"); case "apache.maven.indexer/indexer-core/src/main/java": return excluded.addAll("org/apache/maven/index/DefaultIndexerEngine.java"); case "com.google.gerrit.common": case "com.google.gerrit.prettify": case "com.google.gerrit.reviewdb": case "com.google.gwt.servlet": case "com.google.gwtjsonrpc": case "com.google.gwtorm": case "org.apache.commons.httpclient": case "org.eclipse.m2e.lifecyclemapping.defaults": case "org.nodeclipse.pluginslist.core": case "org.slf4j.api": return excluded; case "org.eclipse.m2e.core.ui": case "org.eclipse.m2e.discovery": case "org.eclipse.m2e.editor": case "org.eclipse.m2e.editor.xml": case "org.eclipse.m2e.jdt": case "org.eclipse.m2e.jdt.ui": case "org.eclipse.m2e.launching": case "org.eclipse.m2e.logback.appender": case "org.eclipse.m2e.logback.configuration": case "org.eclipse.m2e.model.edit": case "org.eclipse.m2e.profiles.core": case "org.eclipse.m2e.profiles.ui": case "org.eclipse.m2e.refactoring": case "org.eclipse.m2e.scm": return excluded.addAll(".*/"); case "pm.eclipse.editbox": return excluded.addAll(".*"); case "com.ibm.icu": case "com.jcraft.jsch": case "com.sun.el": case "javax.annotation": case "javax.el": case "javax.servlet": case "javax.servlet.jsp": case "org.hamcrest.core": return excluded.addAll("META-INF/ECLIPSE_.RSA", "META-INF/ECLIPSE_.SF", "META-INF/eclipse.inf"); case "javax.inject": case "org.w3c.css.sac": case "org.w3c.dom.events": case "org.w3c.dom.smil": case "org.w3c.dom.svg": return excluded.addAll("META-INF/ECLIPSEF.RSA", "META-INF/ECLIPSEF.SF", "META-INF/eclipse.inf"); case "external/src": return excluded.addAll("org/apache/xmlcommons/"); case "org.apache.jasper.glassfish": return excluded.addAll( "META-INF/ECLIPSE_.RSA", "META-INF/ECLIPSE_.SF", "META-INF/eclipse.inf", "org/apache/jasper/runtime/PerThreadTagHandlerPool.java"); case "org.eclipse.ant.ui": return excluded.addAll(".*", ".*/", "Ant Editor Content Assist Dev/"); case "org.eclipse.core.net": return excluded.addAll(".*", ".*/", "fragments/"); case "org.eclipse.jdt.core": return excluded.addAll(".*", ".*/", "scripts/"); case "org.eclipse.jdt.ui": return excluded.addAll(".*", ".*/", "jar in jar loader/"); case "jetty-http/src/test/java": return excluded.addAll("org/eclipse/jetty/http/*Test.java"); case "org.eclipse.swt.win32.win32.x86_64": case "org.eclipse.swt.cocoa.macosx.x86_64": case "org.eclipse.swt.gtk.linux.x86_64": return excluded.addAll(".*"); case "org.eclipse.equinox.launcher.cocoa.macosx.x86_64": case "org.eclipse.equinox.launcher.win32.win32.x86_64": return excluded.addAll(".*"); case "org.eclipse.pde.ui.templates": return excluded.addAll( ".*", ".*/", "templates_3.0/", "templates_3.1/", "templates_3.3/", "templates_3.5/"); case "org.sat4j.core": case "org.sat4j.pb": return excluded.addAll(".*", ".*/", "src/test/"); case "src": case "res": return excluded; default: return unixPath.contains("/") ? excluded : excluded.addAll(".*", ".*/"); } } private static String combinableUnixPath(Path path) { if(path == null) { return ""; } String relative = toUnixPath(path); if(relative.length() > 0) { return "/" + relative; } return relative; } private static HashMap<Pair<Path, Path>, ArrayList<Path>> mapAvailableToFiles( List<Path> available, List<Path> target) { HashMap<Path, ArrayList<Path>> map = available.stream().toMap(Path::getFileName); HashMap<Pair<Path, Path>, ArrayList<Path>> multiMap = new HashMap<>(); Path previousTargetFile = null; for(Path targetFile : target) { if(previousTargetFile == null || targetFile.startsWith(previousTargetFile) == false) { Pair<Pair<Path, Path>, Path> splitLongest = splitLongest(targetFile, map.get(targetFile.getFileName())); if(splitLongest != null) { Pair<Path, Path> key = splitLongest.lhs; Path value = splitLongest.rhs; boolean found = key.lhs.endsWith("x-miss-x") == false; if(found) { previousTargetFile = targetFile; } if(found || value.getFileName().toString().endsWith(".jar") == false) { multiMap.computeIfAbsent(key, p -> ArrayList.of()); multiMap.get(key).add(value); } } } } return multiMap; } private static Pair<Pair<Path, Path>, Path> splitLongest(Path target, ArrayList<Path> available) { switch(target.getFileName().toString()) { case "ECLIPSE_.RSA": case "ECLIPSE_.SF": case "VMWARE.RSA": case "VMWARE.SF": case "ECLIPSEF.RSA": case "ECLIPSEF.SF": case "JEEEYUL_.DSA": case "JEEEYUL_.SF": case "eclipse.inf": case ".api_description": return null; } if(available != null) { Comparator<Path> comparator = Comparator.comparing(p -> p.toString().contains("x-miss-x") ? Integer.MAX_VALUE : p.getNameCount()); for(int i = 1, n = target.getNameCount(); i < n; i++) { Path subpath = target.subpath(i, n); Stream<Path> stream = available.stream().filter(p -> p.endsWith(subpath) && p.getNameCount() > subpath.getNameCount()).sorted( comparator); ArrayList<Path> sorted = stream.toList(); if(sorted.notEmpty()) { Path found = sorted.get(0); int endIndex = found.getNameCount() - subpath.getNameCount(); if(endIndex == 0) { throw new IllegalStateException("Ambiguous match from " + target + " and " + available); } Path src = found.subpath(0, endIndex); Path dst = target.subpath(0, i); return new Pair<>(new Pair<>(src, dst), subpath); } } } return null; } private static List<Path> listLocallyAvailableFiles(Path root) throws IOException { ArrayList<Path> list = listSourceCode(root.resolve("src")); list.addAll(listSourceCode(root.resolve("res"))); list.removeIf(p -> p.getNameCount() == 1); return list.toList(); } private static List<Path> deepListAvailableFiles( List<Path> localFiles, List<Path> libs, List<Path> pluginFiles, String plugin, Path project) throws IOException { ArrayList<Path> list = localFiles.toArrayList(); if(Files.isRegularFile(project.resolve(".project.override"))) { list.addAll(Files.readAllLines(project.resolve(".project.override")).map(Paths::get)); } for(Path lib : libs) { ArrayList<Path> listSourceCode = listSourceCode(lib); listSourceCode.removeIf(p -> p.getNameCount() > 2 && CLASS_FILE_INDICATORS.contains(p.getName(1))); list.addAll(customizeAvailableFilesList(listSourceCode, plugin)); } List<Path> missed = pluginFiles.map(p -> missedFileCatcher(p)); list.addAll(missed); return list.toList(); } private static ArrayList<Path> listSourceCode(Path src) throws IOException { if(Files.isDirectory(src) == false) { return new ArrayList<>(); } Path srcParent = src.getParent(); return Files.walk(src).filter(Files::isRegularFile).map(RegenerateProjectsMain::canonicalizeFileNameToMatch).map( srcParent::relativize).toList(); } private static Path missedFileCatcher(Path p) { int len = p.getNameCount(); int off = Math.max(1, len - 3); Path subpath1 = p.subpath(0, off); Path subpath2 = p.subpath(off, len); return subpath1.resolve("x-miss-x").resolve(subpath2); } private static ArrayList<Path> sourceFilesForPlugin(Plugin plugin) { HashMap<Path, byte[]> pluginContent = plugin.contents; Stream<Path> stream = pluginContent.keySet().stream().sorted(); ArrayList<Path> list = stream.filter(p -> removesInnerClasses(p, pluginContent.get(p))).map( RegenerateProjectsMain::canonicalizeFileNameToMatch).toList(); return customizeModelFilesList(plugin.id, list); } private static ArrayList<Path> customizeModelFilesList(String pluginName, ArrayList<Path> list) { switch(pluginName) { case "com.google.gerrit.prettify": return customizeModelFilesListAdd( list, pluginName, ".jar/", "com/google/gwtexpui/safehtml/client/SafeHtml.java"); case "com.google.guava": return customizeModelFilesListRemove( customizeModelFilesListAdd( list, pluginName, ".jar/", "com/google/thirdparty/publicsuffix/PublicSuffixPatterns.java"), pluginName, ".jar/", "com/google/common/util/concurrent/ForwardingService.java", "com/google/common/hash/HashCodes.java"); case "com.google.gwtjsonrpc": return customizeModelFilesListAdd( list, pluginName, ".jar/", "com/google/gwt/json/client/JSONValue.java"); case "com.sun.el": return customizeModelFilesListAdd( customizeModelFilesListRemove( list.replaceAll(p -> RegenerateProjectsMain.convertComSunToOrgApache(p)), pluginName, ".jar/", "org/apache/el/parser/AstMethodArguments.java"), pluginName, ".jar/", "org/apache/el/stream/Optional.java"); case "javaewah": return customizeModelFilesListAdd( customizeModelFilesListRemoveIfStartsWith( list, pluginName, ".jar/", "com/googlecode/javaewah/benchmark"), pluginName, ".jar/", "com/googlecode/javaewah/datastructure/PriorityQ.java", "com/googlecode/javaewah/symmetric/RunningBitmapMerge.java", "com/googlecode/javaewah32/symmetric/RunningBitmapMerge32.java"); case "javax.el": return customizeModelFilesListRemove(list, pluginName, ".jar/", "javax/el/PrivateMessages.properties"); case "javax.servlet": return customizeModelFilesListAdd( customizeModelFilesListRemove( list, pluginName, ".jar/", "javax/servlet/annotation/package.html", "javax/servlet/descriptor/package.html"), pluginName, ".jar/", "javax/servlet/resources/web-jsptaglibrary_2_0.xsd"); case "javax.servlet.jsp": return customizeModelFilesListRemove( customizeModelFilesListAdd(list, pluginName, ".jar/", "javax/servlet/jsp/resources/jspxml.xsd"), pluginName, ".jar/", "javax/servlet/jsp/resources/jspxml_2_0.dtd", "javax/servlet/jsp/resources/jspxml_2_0.xsd", "javax/servlet/jsp/resources/jsp_2_0.xsd", "javax/servlet/jsp/resources/jsp_2_1.xsd", "javax/servlet/jsp/resources/jsp_2_2.xsd", "javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd", "javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd", "javax/servlet/jsp/resources/web-jsptaglibrary_2_0.xsd", "javax/servlet/jsp/resources/web-jsptaglibrary_2_1.xsd"); case "org.apache.batik.css": return customizeModelFilesListAdd( list, pluginName, ".jar/", "org/apache/batik/css/engine/value/svg12/AbstractCIEColor.java"); case "org.apache.commons.codec": return customizeModelFilesListAdd( list, pluginName, ".jar/", "org/apache/commons/codec/language/dmrules.txt"); case "org.apache.commons.compress": return customizeModelFilesListAdd( list, pluginName, ".jar/", "org/apache/commons/compress/PasswordRequiredException.java", "org/apache/commons/compress/parallel/FileBasedScatterGatherBackingStore.java", "org/apache/commons/compress/compressors/lzw/LZWInputStream.java", "org/apache/commons/compress/compressors/deflate/DeflateCompressorInputStream.java", "org/apache/commons/compress/compressors/snappy/FramedSnappyCompressorInputStream.java", "org/apache/commons/compress/compressors/z/ZCompressorInputStream.java"); case "org.apache.httpcomponents.httpclient": return customizeModelFilesListAdd( list, pluginName, ".jar/", "org/apache/http/osgi/impl/OSGiClientBuilderFactory.java", "org/apache/http/osgi/services/HttpClientBuilderFactory.java"); case "org.apache.jasper.glassfish": return customizeModelFilesListAdd( customizeModelFilesListRemove( list, pluginName, ".jar/", "org/eclipse/jdt/internal/compiler/flow/NullInfoRegistry.java"), pluginName, ".jar/", "org/apache/jasper/util/SystemLogHandler.java"); case "org.apache.xerces": return customizeModelFilesListRemove( list, pluginName, ".jar/", "org/apache/xerces/impl/xs/util/NSItemListImpl.java"); case "org.eclipse.egit.ui": return customizeModelFilesListRemove(list, pluginName, ".jar/", "icons/ovr/symlink_ovr.gif"); case "org.eclipse.equinox.http.servlet": return customizeModelFilesListAdd( list, pluginName, ".jar/", "org/eclipse/equinox/http/servlet/internal/context/DefaultServletContextHelper.java", "org/eclipse/equinox/http/servlet/internal/customizer/FilterTrackerCustomizer.java"); case "org.eclipse.help.webapp": return customizeModelFilesListRemoveIfFilenameEndsWith(list, "_jsp.java"); case "org.eclipse.jdt.doc.isv": return customizeModelFilesListRemoveIfStartsWith(list, pluginName, ".jar/", "index", "reference"); case "org.eclipse.jdt.doc.user": return customizeModelFilesListRemoveIfStartsWith(list, pluginName, ".jar/", "index"); case "org.eclipse.jgit": return customizeModelFilesListAdd( list, pluginName, ".jar/", "org/eclipse/jgit/attributes/AttributesNode.java"); case "org.eclipse.m2e.editor.xml": return customizeModelFilesListAdd( list, pluginName, ".jar/", "org/eclipse/m2e/editor/xml/internal/mojo/MojoParameter.java"); case "org.eclipse.m2e.maven.indexer": return customizeModelFilesListRemoveIfStartsWith(list, pluginName, "/", "org"); case "org.eclipse.pde.ui.templates": return customizeModelFilesListRemoveIfName1StartsWith(list, "templates_"); case "org.eclipse.pde.doc.user": return customizeModelFilesListRemoveIfStartsWith( list, pluginName, ".jar/", "index", "reference", "whatsNew"); case "org.eclipse.platform.doc.isv": return customizeModelFilesListRemoveIfStartsWith( list, pluginName, ".jar/", "index", "reference", "samples"); case "org.eclipse.platform.doc.user": return customizeModelFilesListRemoveIfStartsWith(list, pluginName, ".jar/", "index", "whatsNew"); case "org.eclipse.swt.win32.win32.x86_64": return customizeModelFilesListAdd( list, pluginName, ".jar/", "org/eclipse/swt/browser/Mozilla.java", "org/eclipse/swt/browser/WebKit.java", "org/eclipse/swt/browser/Website.java", "org/eclipse/swt/browser/MozillaDelegate.java"); case "org.kohsuke.args4j": return customizeModelFilesListRemove( list, pluginName, ".jar/", "org/kohsuke/args4j/MapSetter.java", "org/kohsuke/args4j/Messages_de_DE.properties", "org/kohsuke/args4j/Messages_ru_RU.properties"); case "org.sat4j.core": return customizeModelFilesListRemove( list, pluginName, ".jar/", "org/sat4j/minisat/core/Constr.java", "org/sat4j/minisat/core/Propagatable.java", "org/sat4j/MoreThanSAT.java"); case "org.sat4j.pb": return customizeModelFilesListAdd( customizeModelFilesListRemove( list, pluginName, ".jar/", "org/sat4j/pb/PseudoBitsAdderDecorator.java"), pluginName, ".jar/", "org/sat4j/pb/multiobjective/IMultiObjOptimizationProblem.java"); case "org.springsource.ide.eclipse.commons.livexp": return customizeModelFilesListAdd( list, pluginName, ".jar/", "org/springsource/ide/eclipse/commons/livexp/util/Parser.java"); case "pm.eclipse.editbox": return customizeModelFilesListAdd( customizeModelFilesListRemove(list, pluginName, ".jar/", "icons/editbox.gif", "Java_PaleBlue.eb"), pluginName, ".jar/", "icons/editbox.png"); default: return list; } } private static ArrayList<Path> customizeModelFilesListRemoveIfFilenameEndsWith(ArrayList<Path> list, String suffix) { int size = list.size(); ArrayList<Path> list2 = list.removeIf(p -> p.getFileName().toString().endsWith(suffix)); if(size == (size = list2.size())) { System.out.println("Unnecessary removeIfFilenameEndsWith " + suffix); } return list2; } private static ArrayList<Path> customizeModelFilesListRemoveIfName1StartsWith(ArrayList<Path> list, String prefix) { int size = list.size(); ArrayList<Path> list2 = list.removeIf(p -> p.getName(1).toString().startsWith(prefix)); if(size == (size = list2.size())) { System.out.println("Unnecessary removeIfName1StartsWith " + prefix); } return list2; } private static ArrayList<Path> customizeModelFilesListRemoveIfStartsWith( ArrayList<Path> list, String pluginName, String root, String... others) { int size = list.size(); for(String other : others) { String other2 = pluginName + root + other; list = list.removeIf(p -> p.startsWith(other2)); if(size == (size = list.size())) { System.out.println("Unnecessary removeIfStartsWith " + other2); } } return list; } private static ArrayList<Path> customizeModelFilesListAdd( ArrayList<Path> list, String pluginName, String root, String... paths) { for(String path : paths) { Path path2 = Paths.get(pluginName + root + path); if(list.contains(path2)) { System.out.println("Unnecessary add " + path2); } list = list.add(path2); } return list; } private static ArrayList<Path> customizeModelFilesListRemove( ArrayList<Path> list, String pluginName, String root, String... paths) { for(String path : paths) { Path path2 = Paths.get(pluginName + root + path); if(list.contains(path2) == false) { System.out.println("Unnecessary remove " + path2); } list = list.remove(path2); } return list; } static Path convertComSunToOrgApache(Path p) { return Paths.get(toUnixPath(p).replace("com/sun/", "org/apache/")); } private static ArrayList<Path> customizeAvailableFilesList(ArrayList<Path> list, String plugin) { switch(plugin) { case "com.ibm.icu": return list.filter(p -> p.startsWith("com.ibm.icu/com")); case "org.apache.batik.css": return list.remove(Paths.get("apache.batik/sources/org/apache/batik/css/engine/value/svg12/AbstractCIEColor.java")); case "org.apache.batik.util.gui": return list.remove(Paths.get("apache.batik/sources/org/apache/batik/util/gui/xmleditor/XMLScanner.java")); case "org.eclipse.ecf.identity": return list.remove(Paths.get("org.eclipse.ecf.identity/src/org/eclipse/ecf/core/identity/URIID.java")); case "org.eclipse.equinox.launcher.carbon.macosx": case "org.eclipse.equinox.launcher.cocoa.macosx": case "org.eclipse.equinox.launcher.cocoa.macosx.x86_64": case "org.eclipse.equinox.launcher.gtk.aix.ppc": case "org.eclipse.equinox.launcher.gtk.aix.ppc64": case "org.eclipse.equinox.launcher.gtk.hpux.ia64": case "org.eclipse.equinox.launcher.gtk.hpux.ia64_32": case "org.eclipse.equinox.launcher.gtk.linux.ppc": case "org.eclipse.equinox.launcher.gtk.linux.ppc64": case "org.eclipse.equinox.launcher.gtk.linux.s390": case "org.eclipse.equinox.launcher.gtk.linux.s390x": case "org.eclipse.equinox.launcher.gtk.linux.x86": case "org.eclipse.equinox.launcher.gtk.linux.x86_64": case "org.eclipse.equinox.launcher.gtk.solaris.sparc": case "org.eclipse.equinox.launcher.gtk.solaris.x86": case "org.eclipse.equinox.launcher.motif.aix.ppc": case "org.eclipse.equinox.launcher.motif.hpux.ia64_32": case "org.eclipse.equinox.launcher.motif.hpux.PA_RISC": case "org.eclipse.equinox.launcher.motif.linux.x86": case "org.eclipse.equinox.launcher.motif.solaris.sparc": case "org.eclipse.equinox.launcher.win32.win32.ia64": case "org.eclipse.equinox.launcher.win32.win32.x86": case "org.eclipse.equinox.launcher.win32.win32.x86_64": case "org.eclipse.equinox.launcher.wpf.win32.x86": return list.filter(p -> pathContains(p, plugin)); case "org.eclipse.jgit": return list.remove(Paths.get("org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java")); case "org.eclipse.swt.win32.win32.x86_64": return list.removeIf(RegenerateProjectsMain::notForWindows); case "org.eclipse.swt.cocoa.macosx.x86_64": return list.removeIf(RegenerateProjectsMain::notForMacOSX); case "org.eclipse.swt.gtk.linux.x86_64": return list.removeIf(RegenerateProjectsMain::notForLinux); case "org.junit": return list.filter(p -> p.startsWith("org.junit/junitsrc")); case "com.jcraft.jsch": case "com.sun.el": case "javax.annotation": case "javax.el": case "javax.inject": case "javax.servlet": case "javax.servlet.jsp": case "org.apache.jasper.glassfish": case "org.hamcrest.core": case "org.w3c.css.sac": case "org.w3c.dom.events": case "org.w3c.dom.smil": case "org.w3c.dom.svg": return list.removeIf(RegenerateProjectsMain::orbitSourceIdentifier); default: return list; } } private static boolean orbitSourceIdentifier(Path p) { if(p.getNameCount() > 1) { switch(p.getName(1).toString()) { case "about_files": case "META-INF": case "about.html": case "plugin.properties": return true; } } return false; } private static boolean pathContains(Path path, String part) { for(Path p : path) { if(p.startsWith(part)) { return true; } } return false; } private static boolean notForWindows(Path p) { for(Path path : p) { switch(path.toString()) { case "cairo": case "carbon": case "cocoa": case "common_j2me": case "emulated": case "gtk": case "motif": case "photon": case "wpf": return true; } } return false; } private static boolean notForMacOSX(Path p) { for(Path path : p) { switch(path.toString()) { case "cairo": case "carbon": case "common_j2me": case "gtk": case "motif": case "photon": case "win32": case "wpf": return true; } } return false; } private static boolean notForLinux(Path p) { boolean afterEmulated = false; for(Path path : p) { switch(path.toString()) { case "carbon": case "cocoa": case "common_j2me": case "motif": case "photon": case "win32": case "wpf": return true; case "emulated": afterEmulated = true; break; case "org": case "bidi": case "coolbar": case "taskbar": if(afterEmulated) { return path.toString().equals("org"); } default: afterEmulated = false; } } return false; } private static Path canonicalizeFileNameToMatch(Path p) { String file = p.getFileName().toString(); if(file.endsWith(".class")) { return p.resolveSibling(file.substring(0, file.length() - 6) + ".java"); } else if(file.endsWith(".dll") || file.endsWith(".so") || file.endsWith(".jnilib")) { return p.resolveSibling(file.replaceAll("[0-9]", "#")); } return p; } private static boolean removesInnerClasses(Path p, byte[] bytes) { String file = p.getFileName().toString(); if((file.endsWith(".class") || file.endsWith(".java")) && file.contains("$")) { return false; } if(file.endsWith(".class")) { try { return (new ClassReader(bytes).getAccess() & Opcodes.ACC_PUBLIC) != 0; } catch(Exception e) { System.out.println("Invalid class " + file + ": " + e); return false; } } return true; } private static void writeDotProject(Path projectFolder, String projectName, List<Path> libs) throws IOException { if(Files.isRegularFile(projectFolder.resolve(".project.override"))) { return; } ArrayList<String> lines = new ArrayList<>(); lines.add("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); lines.add("<projectDescription>"); lines.add(" <name>" + projectName + "</name>"); lines.add(" <comment></comment>"); lines.add(" <projects>"); lines.add(" </projects>"); lines.add(" <buildSpec>"); lines.add(" <buildCommand>"); lines.add(" <name>org.eclipse.jdt.core.javabuilder</name>"); lines.add(" <arguments>"); lines.add(" </arguments>"); lines.add(" </buildCommand>"); lines.add(" </buildSpec>"); lines.add(" <natures>"); lines.add(" <nature>org.eclipse.jdt.core.javanature</nature>"); lines.add(" </natures>"); lines.add(" <linkedResources>"); for(Path lib : libs) { lines.add(" <link>"); lines.add(" <name>" + lib.getFileName() + "</name>"); lines.add(" <type>2</type>"); lines.add(" <locationURI>PARENT-2-PROJECT_LOC/" + toUnixPath(lib) + "</locationURI>"); lines.add(" </link>"); } lines.add(" </linkedResources>"); lines.add("</projectDescription>"); lines.add(""); String dotProject = String.join("\n", lines); writeFile(projectFolder, dotProject, ".project"); } private static void writeFile(Path projectFolder, String fileData, String fileName) throws IOException { byte[] bytes = fileData.getBytes(StandardCharsets.UTF_8); Path file = projectFolder.resolve(fileName); if(Files.notExists(file) || Arrays.equals(bytes, Files.readAllBytes(file)) == false) { Files.createDirectories(file.getParent()); Files.write(file, bytes); } } private static String toUnixPath(Path path) { return String.join("/", Stream.from(path).map(Path::toString).iterable()); } private static Map<String, List<Path>> findSourceFolders(Path root, ArrayList<String> plugins) throws IOException { Path libraries = root.resolve("libraries"); ArrayList<Path> unused = Files.walk(libraries, 5).filter(Files::isDirectory).toList(); List<Path> folders = unused.toList(); HashSet<Path> usedLibraries = HashSet.of(); HashSet<Path> unusedLibraries = folders.stream().map(libraries::relativize).filter(p -> p.getNameCount() > 1).map(p -> p.subpath(0, 2)).toSet(); filterCompileDependencies(unusedLibraries, root); HashMap<String, List<Path>> map = HashMap.of(); for(String plugin : plugins) { List<Path> libraryFolders = libraryFolders(root, folders, plugin); map.put(plugin, libraryFolders); for(Path libraryFolder : libraryFolders) { if(libraryFolder.getNameCount() > 2) { Path prefix = libraryFolder.subpath(1, 3); unusedLibraries.remove(prefix); usedLibraries.add(prefix.getName(0)); } else if(libraryFolder.getNameCount() > 1) { Path prefix = libraryFolder.getName(1); unusedLibraries.removeIf(p -> p.startsWith(prefix)); usedLibraries.add(prefix); } } } if(unused.notEmpty()) { System.out.println("\nUnused libraries paths:"); for(Path path : unusedLibraries.toArrayList().sort()) { if(path.endsWith(".git")) { continue; } if(usedLibraries.contains(path.getName(0)) && path.startsWith("orbit-sources") == false && path.startsWith("retired-apache") == false) { System.out.println("(partial) " + path); } else { System.out.println("(fully) " + path); } } System.out.println(); } return map.toMap(); } private static void filterCompileDependencies(HashSet<Path> unusedLibraries, Path root) throws IOException { Path projects = root.resolve("projects"); ArrayList<Path> list = Files.list(projects).filter(p -> p.endsWith("IDE") || p.getFileName().toString().contains("-compile-")).toList(); Pattern pattern = Pattern.compile("<locationURI>PARENT-2-PROJECT_LOC/libraries/(.*)</locationURI>"); for(Path project : list) { Path dotProject = project.resolve(".project"); if(Files.isRegularFile(dotProject)) { ArrayList<String> l = Files.readAllLines(dotProject); Stream<Path> s = l.stream().map(pattern::matcher).filter(Matcher::find).map(m -> Paths.get(m.group(1))); ArrayList<Path> links = s.toList(); for(Path link : links) { unusedLibraries.removeIf(p -> p.startsWith(link)); } } } } private static void printRoots(Map<String, List<Path>> pluginRoots) { for(String name : sortedKeys(pluginRoots)) { List<Path> list = pluginRoots.get(name); System.out.printf("%-60s : %s%n", name, list); } } private static List<Path> libraryFolders(Path root, List<Path> folders, String pluginName) { if(pluginName.endsWith("-2") || pluginName.endsWith("-3")) { pluginName = pluginName.substring(0, pluginName.length() - 2); } switch(pluginName) { case "org.eclipse.equinox.launcher.carbon.macosx": case "org.eclipse.equinox.launcher.cocoa.macosx": case "org.eclipse.equinox.launcher.cocoa.macosx.x86_64": case "org.eclipse.equinox.launcher.gtk.aix.ppc": case "org.eclipse.equinox.launcher.gtk.aix.ppc64": case "org.eclipse.equinox.launcher.gtk.hpux.ia64": case "org.eclipse.equinox.launcher.gtk.hpux.ia64_32": case "org.eclipse.equinox.launcher.gtk.linux.ppc": case "org.eclipse.equinox.launcher.gtk.linux.ppc64": case "org.eclipse.equinox.launcher.gtk.linux.s390": case "org.eclipse.equinox.launcher.gtk.linux.s390x": case "org.eclipse.equinox.launcher.gtk.linux.x86": case "org.eclipse.equinox.launcher.gtk.linux.x86_64": case "org.eclipse.equinox.launcher.gtk.solaris.sparc": case "org.eclipse.equinox.launcher.gtk.solaris.x86": case "org.eclipse.equinox.launcher.motif.aix.ppc": case "org.eclipse.equinox.launcher.motif.hpux.ia64_32": case "org.eclipse.equinox.launcher.motif.hpux.PA_RISC": case "org.eclipse.equinox.launcher.motif.linux.x86": case "org.eclipse.equinox.launcher.motif.solaris.sparc": case "org.eclipse.equinox.launcher.win32.win32.ia64": case "org.eclipse.equinox.launcher.win32.win32.x86": case "org.eclipse.equinox.launcher.win32.win32.x86_64": case "org.eclipse.equinox.launcher.wpf.win32.x86": return List.of( Paths.get("libraries/eclipse.rt.equinox.binaries"), Paths.get("libraries/eclipse.rt.equinox.framework/bundles/" + pluginName)); } Path pluginPath = customizePluginNameToLibrariesPathMapping(pluginName); Path pluginPathGuess = pluginNameToLibrariesPathLevenshtein(pluginName, folders); if(pluginPath == null) { pluginPath = pluginPathGuess; } else if(pluginPathGuess.equals(pluginPath)) { System.out.println("Unnecessary librariesPath " + pluginName + " \t\t--- " + pluginPath); } List<Path> libraries = sourceInLibraries(root, folders, pluginPath); return customizeAddLibrariesPaths(pluginName, libraries); } private static List<Path> customizeAddLibrariesPaths(String pluginName, List<Path> libraries) { switch(pluginName) { case "com.google.gerrit.prettify": return libraries.add(Paths.get("libraries/google.gerrit/gerrit-gwtexpui")); case "com.google.gerrit.reviewdb": return libraries.add(Paths.get("libraries/google.gerrit/gerrit-extension-api")); case "net.jeeeyul.eclipse.themes": return libraries.add(Paths.get("libraries/jeeeyul.eclipse-themes/net.jeeeyul.eclipse.themes/lib")); case "org.apache.jasper.glassfish": return libraries.add(Paths.get("libraries/eclipse.jdt.core")); case "org.eclipse.m2e.maven.indexer": return libraries.addAll( Paths.get("libraries/apache.maven.indexer"), Paths.get("libraries/apache.lucene-solr/lucene")); case "org.eclipse.swt.win32.win32.x86_64": case "org.eclipse.swt.cocoa.macosx.x86_64": case "org.eclipse.swt.gtk.linux.x86_64": return libraries.add(Paths.get("libraries/eclipse.platform.swt/bundles/org.eclipse.swt")); case "org.apache.felix.gogo.command": return libraries.add(Paths.get("libraries/eclipse.rt.equinox.framework/bundles/org.eclipse.osgi.services")); default: return libraries; } } private static Path customizePluginNameToLibrariesPathMapping(String pluginName) { switch(pluginName) { case "com.sun.el": case "javax.annotation": case "javax.el": case "javax.servlet": case "javax.servlet.jsp": return Paths.get("apache.tomcat"); case "org.antlr.runtime": return Paths.get("antlr.antlr3"); case "org.apache.batik.util.gui": return Paths.get("apache.batik"); case "org.apache.ws.commons.util": return Paths.get("ws-commons-util-1.0.1"); case "org.apache.felix.gogo.command": case "org.apache.felix.gogo.runtime": case "org.apache.felix.gogo.shell": return Paths.get("gogo", pluginName.replace("org.apache.felix.gogo.", "")); case "org.apache.httpcomponents.httpclient": case "org.apache.httpcomponents.httpcore": return Paths.get(pluginName.replace("org.apache.httpcomponents.", "apache.")); case "org.apache.lucene.analysis": case "org.apache.lucene.core": return Paths.get("lucene"); case "org.apache.lucene": case "org.apache.lucene.highlighter": case "org.apache.lucene.memory": case "org.apache.lucene.misc": case "org.apache.lucene.queries": case "org.apache.lucene.snowball": case "org.apache.lucene.spellchecker": return Paths.get("lucene-2.9.4"); case "org.apache.xmlrpc": return Paths.get("apache-xmlrpc-3.1.3"); case "org.apache.xml.resolver": return Paths.get("apache.xerces.xml-commons"); case "org.apache.xml.serializer": return Paths.get("apache.xalan-j"); case "javax.xml": return Paths.get("apache.xerces.xml-commons/java/external"); case "org.eclipse.jetty.continuation": case "org.eclipse.jetty.http": case "org.eclipse.jetty.io": case "org.eclipse.jetty.security": case "org.eclipse.jetty.server": case "org.eclipse.jetty.servlet": case "org.eclipse.jetty.util": return Paths.get(pluginName.replace("org.eclipse.jetty.", "jetty-")); case "org.objectweb.asm": case "org.objectweb.asm.tree": return Paths.get("ow2.asm/asm"); default: return null; } } private static Path pluginNameToLibrariesPathLevenshtein(String pluginName, List<Path> folders) { Comparator<Path> c = Comparator.comparingInt(p -> distanceLevenshtein(p.getFileName().toString(), pluginName)); Optional<Path> min = folders.stream().min(c); return min.get().getFileName(); } private static int distanceLevenshtein(String s1, String s2) { if(Objects.equals(s1, s2)) { return 0; } if(s1.compareTo(s2) > 0) { String tmp = s2; s2 = s1; s1 = tmp; } Pair<String, String> key = Pair.of(s1, s2); Integer result = cachedDistances.get(key); if(result != null) { return result; } int[][] distance1 = new int[s1.length() + 1][s2.length() + 1]; for(int i = 0; i <= s1.length(); i++) { distance1[i][0] = i; } for(int j = 0; j <= s2.length(); j++) { distance1[0][j] = j; } for(int i = 1; i <= s1.length(); i++) { for(int j = 1; j <= s2.length(); j++) { int d1 = distance1[i - 1][j] + 1; int d2 = distance1[i][j - 1] + 1; int d3 = distance1[i - 1][j - 1] + (s1.charAt(i - 1) == s2.charAt(j - 1) ? 0 : 1); distance1[i][j] = Math.min(Math.min(d1, d2), d3); } } result = distance1[s1.length()][s2.length()]; cachedDistances.put(key, result); return result; } private static List<Path> sourceInLibraries(Path root, List<Path> folders, Path pluginName) { Stream<Path> stream = folders.stream().filter(path -> path.endsWith(pluginName)); Stream<Path> sorted = stream.sorted(Comparator.comparing(path -> path.getNameCount())); return sorted.limit(1).toList().map(path -> root.relativize(path)).toList(); } private static Map<String, List<String>> combineDependencies(ReadOnlyList<Plugin> plugins) { HashMap<String, List<String>> depending = gatherNames(plugins, "Require-Bundle"); addFromFragmentHosts(plugins, depending); HashMap<String, List<String>> importers = gatherNames(plugins, "Import-Package"); importers.put( "org.apache.httpcomponents.httpclient", importers.get("org.apache.httpcomponents.httpclient").addAll("org.osgi.framework", "org.osgi.service.cm")); HashMap<String, List<String>> exported = gatherNames(plugins, "Export-Package"); exported.keySet().remove("org.apache.felix.gogo.command"); HashMap<String, List<String>> exporters = invert(exported); for(String bundle : importers.keySet().toArrayList().sort()) { HashSet<String> bundleDependencies = new HashSet<>(); if(depending.containsKey(bundle)) { bundleDependencies.addAll(depending.get(bundle)); } for(String module : importers.get(bundle)) { List<String> moduleExporters = exporters.computeIfAbsent(module, k -> List.of()).get(module); bundleDependencies.addAll(moduleExporters); } bundleDependencies.remove(bundle); depending.put(bundle, bundleDependencies.toArrayList().sort().toList()); } return depending.toMap(); } private static void addFromFragmentHosts(ReadOnlyList<Plugin> plugins, HashMap<String, List<String>> depending) { HashMap<String, List<String>> hosts = gatherNames(plugins, "Fragment-Host"); for(Pair<String, List<String>> pair : hosts.entrySet()) { depending.compute(pair.lhs, (k, v) -> v == null ? pair.rhs : v.addAll(pair.rhs)); } } private static void printDependencies(Map<String, List<String>> deps, String arrow) { for(String name : sortedKeys(deps)) { List<String> list = deps.get(name); System.out.printf("%-60s %s %s%n", name, arrow, list); } } private static <T extends Comparable<T>> ArrayList<T> sortedKeys(Map<T, ?> deps) { return deps.keySet().stream().sorted().toList(); } private static HashMap<String, List<String>> gatherNames(ReadOnlyList<Plugin> plugins, String key) { return gatherNamesWith(plugins, key, ""); } private static HashMap<String, List<String>> gatherNamesWith(ReadOnlyList<Plugin> plugins, String key, String tag) { return plugins.stream().toMultiMap(p -> p.id, p -> nameList(key, tag, p), m -> m); } private static List<String> nameList(String key, String tag, ArrayList<Plugin> p) { Plugin plugin = p.sort().get(-1); String required = plugin.manifest.get(key); if(required == null) { return List.of(); } String[] split = required.replaceAll("\"\\[?([^\",]+),?[^\"]*\"", "$1").split(","); return Stream.of(split).filter(s -> s.contains(tag)).map(RegenerateProjectsMain::toBundleName).toList().toList(); } private static <A, B> HashMap<B, List<A>> invert(HashMap<A, List<B>> dag) { Collector<A, ArrayList<A>, ArrayList<A>> toArrayList = Collectors.toList(); Collector<A, ArrayList<A>, List<A>> toList = Collectors.collectingAndThen(toArrayList, ArrayList::toList); Collector<Pair<A, B>, ?, List<A>> mapping = Collectors.mapping(Pair<A, B>::lhs, toList); Collector<Pair<A, B>, ?, HashMap<B, List<A>>> groupingBy = Collectors.groupingBy(Pair<A, B>::rhs, mapping); return graphToEdges(dag).collect(groupingBy); } private static <A, B> Map<B, List<A>> invert(Map<A, List<B>> dag) { Collector<A, ArrayList<A>, ArrayList<A>> toArrayList = Collectors.toList(); Collector<A, ArrayList<A>, List<A>> toList = Collectors.collectingAndThen(toArrayList, ArrayList::toList); Collector<Pair<A, B>, ?, List<A>> mapping = Collectors.mapping(Pair<A, B>::lhs, toList); Collector<Pair<A, B>, ?, HashMap<B, List<A>>> groupingBy = Collectors.groupingBy(Pair<A, B>::rhs, mapping); return graphToEdges(dag).collect(groupingBy).toMap(); } private static <A, B> Stream<Pair<A, B>> graphToEdges(Map<A, List<B>> dag) { Stream<Pair<A, List<B>>> stream = dag.entrySet().stream(); return stream.flatMap(p -> p.rhs.stream().map(p::keepingLhs)); } private static <A, B> Stream<Pair<A, B>> graphToEdges(HashMap<A, List<B>> dag) { Stream<Entry<A, List<B>>> stream = dag.entrySet().stream(); return stream.flatMap(p -> p.rhs.stream().map(p::keepingLhs)); } private static ArrayList<Plugin> readPluginContents(Path pluginsFolder) throws IOException { Path binaryInclusions = Paths.get("").toAbsolutePath().resolveSibling("IDE-all-extras/extras/plugins"); HashSet<Path> skipped = HashSet.of(); if(Files.isDirectory(binaryInclusions)) { ArrayList<Path> list = Files.list(binaryInclusions).map(p -> pluginsFolder.resolve(binaryInclusions.relativize(p))).toList(); skipped.addAll(list); } IOStream<Path> stream = Files.walk(pluginsFolder, 3).filter(p -> p.endsWith("plugins") && Files.isDirectory(p)).flatMap(Files::list); IOStream<Path> stream2 = stream.filter(path -> filterExtraPluginsEntries(path, skipped)); IOStream<Pair<Path, Map<Path, byte[]>>> stream3 = stream2.map(p -> readContents(p)); IOStream<Plugin> stream4 = stream3.map(p -> new Plugin(p.lhs, p.rhs.toHashMap())); HashMap<String, Plugin> map = stream4.toMultiMap(Plugin::name, l -> combinePluginContents(l), m -> m); return map.values().toArrayList().sort(); } private static Plugin combinePluginContents(ArrayList<Plugin> plugins) throws IOException { Plugin plugin2 = plugins.sort().get(-1); if(plugin2.name().contains("sdk")) { System.out.println("BREAK"); } Path root = plugin2.root; HashMap<Path, byte[]> map = HashMap.of(); for(Plugin plugin : plugins) { map.putAll(plugin.contents); } return new Plugin(root, map); } static void duplicatePlugin(HashMap<String, Plugin> map, String name, String suffix) { if(map.containsKey(name)) { String newName = name + suffix; map.put(newName, map.get(name).name(newName)); } } private static boolean filterExtraPluginsEntries(Path path, HashSet<Path> skipped) { return !skipped.contains(path) && !path.toString().contains(".source_"); } private static void printManifestInfo(Path pluginsFolder, Map<String, List<String>> depending, Plugin plugin) { Path relativePath = pluginsFolder.relativize(plugin.root); List<String> dependencies = depending.get(plugin.id); System.out.printf("%80s: %60s -> %s%n", relativePath, plugin.id, dependencies); } private static String toBundleName(String symbolicName) { return symbolicName.split(";")[0].trim(); } private static Pair<Path, Map<Path, byte[]>> readContents(Path fileOrFolder) throws IOException { boolean isFolder = Files.isDirectory(fileOrFolder); Map<Path, byte[]> contents = isFolder ? readFolder(fileOrFolder) : readFile(fileOrFolder); return new Pair<>(fileOrFolder, contents); } private static Map<Path, byte[]> readFolder(Path folder) throws IOException { IOStream<Path> stream = Files.walk(folder).filter(path -> !Files.isDirectory(path)); return stream.toMap(folder::relativize, Files::readAllBytes).toMap(); } private static Map<Path, byte[]> readFile(Path file) throws IOException { if(!file.getFileName().toString().toLowerCase(Locale.ENGLISH).endsWith(".jar")) { return Map.of(); } byte[] readAllBytes = Files.readAllBytes(file); return readZip(readAllBytes); } private static Map<Path, byte[]> readZip(byte[] bytes) throws IOException { HashMap<Path, byte[]> contents = new HashMap<>(); try(ZipInputStream zip = new ZipInputStream(new ByteArrayInputStream(bytes));) { ZipEntry entry; while((entry = zip.getNextEntry()) != null) { if(!entry.isDirectory()) { Path path = Paths.get(entry.getName()); bytes = Streams.readAllBytes(zip); contents.put(path, bytes); } } } return contents.toMap(); } }