docs: @Override
      public DecompilationResult decompileArchiveImpl(final Path archive, Path outputDir, Filter<ZipEntry> filter, final DecompilationListener listener)
                  throws DecompilationException
      {
          Checks.checkFileToBeRead(archive.toFile(), "Archive to decompile");
          Checks.checkDirectoryToBeFilled(outputDir.toFile(), "Output directory");
  
          log.info("Decompiling archive '" + archive.toAbsolutePath() + "' to '" + outputDir.toAbsolutePath() + "'");
  
          JarFile jar = loadJar(archive.toFile());
          try
          {
              final AtomicInteger jarEntryCount = new AtomicInteger(0);
              Enumeration<JarEntry> countEnum = jar.entries();
              while (countEnum.hasMoreElements())
              {
                  countEnum.nextElement();
                  jarEntryCount.incrementAndGet();
              }
  
              // MetadataSystem, TypeLoader's
              final DecompilerSettings settings = getDefaultSettings(outputDir.toFile());
              settings.setTypeLoader(new CompositeTypeLoader(new WindupJarTypeLoader(jar), settings.getTypeLoader()));
  
              final DecompilationResult res = new DecompilationResult();
  
              final AtomicInteger current = new AtomicInteger(0);
              final Enumeration<JarEntry> entries = jar.entries();
              Collection<Callable<File>> tasks = new ArrayList<>();
  
              final Queue<WindupMetadataSystem> metadataSystemCache = new LinkedList<>();
              refreshMetadataCache(metadataSystemCache, settings);
  
              while (entries.hasMoreElements())
              {
                  final JarEntry entry = entries.nextElement();
  
                  final String name = entry.getName();
  
                  if (!name.endsWith(".class"))
                  {
                      jarEntryCount.decrementAndGet();
                      continue;
                  }
  
                  if (filter != null)
                  {
                      Filter.Result filterRes = filter.decide(entry);
  
                      if (filterRes == Filter.Result.REJECT)
                      {
                          jarEntryCount.decrementAndGet();
                          continue;
                      }
                      if (filterRes == Filter.Result.STOP)
                      {
                          break;
                      }
                  }
  
                  final String typeName = StringUtils.removeEnd(name, ".class");
  
                  // TODO - This approach is a hack, but it should work around the Procyon decompiler hangs for now
                  Callable<File> callable = new Callable<File>()
                  {
                      @Override
                      public File call() throws Exception
                      {
                          WindupMetadataSystem metadataSystem = null;
                          try
                          {
                              synchronized (metadataSystemCache)
                              {
                                  if (current.incrementAndGet() % 50 == 0)
                                  {
                                      log.info("Decompiling " + current + " / " + jarEntryCount);
                                      refreshMetadataCache(metadataSystemCache, settings);
                                  }
                                  metadataSystem = metadataSystemCache.remove();
                              }
  
                              ExecutionStatistics.get().begin("ProcyonDecompiler.decompileIndividualItem");
                              final DecompileExecutor t = new DecompileExecutor(settings, metadataSystem, typeName);
                              // TODO - This approach is a hack, but it should work around the Procyon decompiler hangs
                              // for now
                              t.start();
                              t.join(60000L); // timeout if it is taking too long
                              if (!t.success)
                              {
                                  if (t.e == null)
                                  {
                                      t.cancelDecompilation();
                                      throw new RuntimeException("Failed to decompile file within 60 seconds... attempting abort", t.e);
                                  }
                                  else
                                  {
                                      throw new RuntimeException(t.e);
                                  }
                              }
  
                              File outputFile = t.outputFile;
                              if (outputFile != null)
                              {
                                  listener.fileDecompiled(Collections.singletonList(name), outputFile.getAbsolutePath());
                                  res.addDecompiled(Collections.singletonList(name), outputFile.getAbsolutePath());
                              }
                              return outputFile;
                          }
                          catch (WindupStopException ex)
                          {
                              String msg = "Detected a request to stop during decompilation of " + archive.toString() + "!" + name + ":\n    "
                                      + ex.getMessage();
                              log.log(Level.WARNING, msg + System.lineSeparator()+"     (Rethrowing)");
                              throw new WindupStopException(msg, ex);
                          }
                          catch (Throwable th)
                          {
                              String msg = "Error during decompilation of " + archive.toString() + "!" + name + ":\n    "
                                          + th.getMessage();
                              DecompilationFailure ex = new DecompilationFailure(msg, Collections.singletonList(name), th);
                              log.log(Level.SEVERE, msg, ex);
                              res.addFailure(ex);
                          }
                          finally
                          {
                              if (metadataSystem != null)
                              {
                                  synchronized (metadataSystemCache)
                                  {
                                      metadataSystemCache.add(metadataSystem);
                                  }
                              }
                              ExecutionStatistics.get().end("ProcyonDecompiler.decompileIndividualItem");
                          }
                          return null;
                      }
                  };
                  tasks.add(callable);
              }
              try
              {
                  // Wait until all finish.
                  getExecutorService().invokeAll(tasks);
              }
              catch (InterruptedException e)
              {
                  throw new IllegalStateException("Decompilation was interrupted.");
              }
              finally
              {
                  listener.decompilationProcessComplete();
              }
              return res;
          }
          finally
          {
              try
              {
                  jar.close();
              }
              catch (IOException e)
              {
                  log.warning("Failed to close jar file: " + jar.getName());
              }
          }
      }