I've been tasked with resolving all dependencies to a project. Because it couldn't be simple (it never is, right?), the requirements include a few different dependency management tools. For a first version those are:MavenOSGIBecause those dependencies (and their sources) need to be analyzed for licenses, I have to download the actual files (and preferrably sources) of those items.What that means is that I have to support following functionality:Download jars in the wide interwebs from basically arbitrary urls as detailed in the maven / osgi protocol.Keep track of artifact name, version and the actual URL that we resolved to. In the best case (aka. in the future) the program should already put a preliminary license onto the artifact.Write out the results into a csv and shut down the program.To support this I devised the following main routine, which takes care of coordinating all processes involved in this mess.public class Main {    private static final String MAVEN_SHORT = m;    private static final String MAVEN_LONG = maven;    private static final String OSGI_SHORT = o;    private static final String OSGI_LONG = osgi;    private static final String THREADS_SHORT = t;    private static final String THREADS_LONG = threads;    private static final Executor consumer = Executors.newSingleThreadExecutor();    private static final int DEFAULT_THREAD_COUNT = 8;    private static Executor workers;    private static final PriorityBlockingQueue<ResolutionTask> taskQueue = new PriorityBlockingQueue<>();    public static void main(String[] args) {        CommandLineParser cli = new DefaultParser();        Options opts = new Options();        // TODO Option.Builder?        opts.addOption(Option.builder(THREADS_SHORT).longOpt(THREADS_LONG).hasArg(true).desc(The number of threads to use for resolution).build());        opts.addOption(MAVEN_SHORT, MAVEN_LONG, true, A maven-dependency declaration xml to analyze);        opts.addOption(OSGI_SHORT, OSGI_LONG, true, An osgi-target descriptor xml to analyze);        List<DependencyResult> depResults = new ArrayList<>(args.length / 2);        try {            CommandLine commandLine = cli.parse(opts, args);            int threads = 0;            for (Iterator<Option> options = commandLine.iterator(); options.hasNext(); ) {                Option currentOption = options.next();                switch (currentOption.getOpt()) {                    case THREADS_SHORT:                        threads = Integer.parseInt(currentOption.getValue());                        break;                    case MAVEN_SHORT:                        taskQueue.put(new MavenResolutionTask(taskQueue, currentOption.getValue()));                        break;                    case OSGI_SHORT:                        taskQueue.put(new OsgiResolutionTask(taskQueue, currentOption.getValue()));                        break;                }            }            if (threads == 0) {                threads = DEFAULT_THREAD_COUNT;            }            workers = Executors.newFixedThreadPool(threads, runnable -> {                Thread result = new Thread(runnable, WorkerQueue Thread);                result.setDaemon(true);                return result;            });            ResolutionTask finalizer = new ResultProcessingTask(depResults);            taskQueue.put(finalizer);            consumer.execute(() -> {                List<CompletableFuture<DependencyResult>> currentCompletables = new ArrayList<>();                for (Iterator<ResolutionTask> taskIterator = taskQueue.iterator();                     taskIterator.hasNext(); ) {                    ResolutionTask task = taskIterator.next();                    if (task == finalizer) {                        CompletableFuture<Void> cleanup;                        // TODO is synchronized necessary??                        synchronized (currentCompletables) {                            cleanup = CompletableFuture.allOf(currentCompletables.toArray(new CompletableFuture<?>[0]));                            currentCompletables.clear();                        }                        cleanup.join();                        if (taskQueue.isEmpty()) {                            try {                                finalizer.call();                            } catch (Exception e) {                                e.printStackTrace(System.err);                            }                        } else {                            taskQueue.put(finalizer);                        }                    } else {                        CompletableFuture<DependencyResult> completable = new CompletableFuture<>();                        workers.execute(() -> {                            try {                                DependencyResult res = task.call();                                completable.complete(res);                            } catch (Exception e) {                                completable.completeExceptionally(e);                            }                        });                        // TODO is synchronized necessary??                        synchronized (currentCompletables) {                            currentCompletables.add(completable);                        }                    }                }            });        } catch (ParseException e) {            // Something really went wrong.            e.printStackTrace(System.out);        }    }}For the sake of completeness:MavenResolutionTask, OsgiResolutionTask and ResultProcessingTask all extend the abstract class ResolutionTask, which is defined as follows:public abstract class ResolutionTask implements Callable<DependencyResult>, Comparable<ResolutionTask> {    protected static final int HIGH_PRIORITY = 10;    protected static final int MEDIUM_PRIORITY = 5;    protected static final int LAST_EXECUTE = Integer.MIN_VALUE;    protected final int priority;    protected ResolutionTask(int priority) {        this.priority = priority;    }    @Override    public int compareTo(ResolutionTask resolutionTask) {        return priority - resolutionTask.priority;    }}I'm looking for a review especially on how I solved the synchronization and termination problem. While there is a logical execution graph, I disliked the standard termination detection mechanisms (such as the Dijkstra-Scholten-Algorithm), since the execution graph could possibly be quite large.I'm aware there's some todo comments that need adressing, as well as some major readability issues, but those are not worth tackling if the algorithm doesn't work quite as I intended, nor should I be tackling them when a simpler solution to this exists.
Recur-ratively resolve dependencies and summarize all results
java;algorithm;concurrency