gt:             try (InputStream in = resources.nextElement().openStream()) {
context: /*
   * To change this template, choose Tools | Templates
   * and open the template in the editor.
   */
  package org.pepsoft.util.plugins;
  
  import org.jetbrains.annotations.NotNull;
  import org.pepsoft.util.StreamUtils;
  import org.pepsoft.util.Version;
  import org.pepsoft.util.mdc.MDCCapturingRuntimeException;
  
  import java.io.*;
  import java.net.HttpURLConnection;
  import java.net.URL;
  import java.net.URLClassLoader;
  import java.nio.file.Files;
  import java.security.PublicKey;
  import java.security.cert.Certificate;
  import java.util.*;
  import java.util.jar.JarEntry;
  import java.util.jar.JarFile;
  import java.util.zip.ZipEntry;
  
  import static java.net.HttpURLConnection.HTTP_OK;
  import static java.nio.charset.StandardCharsets.UTF_8;
  import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
  import static java.util.Collections.unmodifiableList;
  import static org.pepsoft.util.FileUtils.stripDirectory;
  import static org.pepsoft.util.FileUtils.stripExtension;
  import static org.pepsoft.util.ObjectMapperHolder.OBJECT_MAPPER;
  
  /**
   * A general purpose plugin manager.
   * 
   * @author pepijn
   */
  public final class PluginManager {
      private PluginManager() {
          // Prevent instantiation
      }
  
      /**
       * Load plugin jars from a directory, which are signed with a particular
       * private key. Do <em>not</em> check for updates.
       *
       * <p>This method should be invoked only once. Any discovered and properly
       * signed plugin jars will be available to be returned by later invocations
       * of the {@link #findPlugins(Class, String, ClassLoader)} method.
       *
       * @param pluginDir The directory from which to load the plugins.
       * @param publicKey The public key corresponding to the private key with
       *                  which the plugins must have been signed.
       * @param descriptorPath The resource path of the file containing the plugin
       *                       descriptor.
       */
      public static void loadPlugins(File pluginDir, PublicKey publicKey, String descriptorPath) {
          loadPlugins(pluginDir, publicKey, descriptorPath, null);
      }
  
      /**
       * Load plugin jars from a directory, which are signed with a particular
       * private key. Optionally check for updates and replace versions which have
       * updates with their newer versions.
       *
       * <p>This method should be invoked only once. Any discovered and properly
       * signed plugin jars will be available to be returned by later invocations
       * of the {@link #findPlugins(Class, String, ClassLoader)} method.
       *
       * @param pluginDir The directory from which to load the plugins.
       * @param publicKey The public key corresponding to the private key with
       *                  which the plugins must have been signed.
       * @param descriptorPath The resource path of the file containing the plugin
       *                       descriptor.
       * @param hostVersion The version of the host, for the update checking
       *                    process, or {@code null} if no update check should be
       *                    performed.
       */
      public static void loadPlugins(File pluginDir, PublicKey publicKey, String descriptorPath, Version hostVersion) {
          if (logger.isDebugEnabled()) {
              logger.debug("Loading plugins");
          }
          File[] pluginFiles = pluginDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".jar"));
          if (pluginFiles != null) {
              for (File pluginFile: pluginFiles) {
                  try {
                      JarFile jarFile = new JarFile(pluginFile);
                      if (! isSigned(jarFile, publicKey)) {
                          String message = stripDirectory(jarFile.getName()) + " is not official or has been tampered with";
                          errors.add(message);
                          logger.error(message + "; not loading it");
                          continue;
                      }
                      if (hostVersion != null) {
                          checkForUpdates(jarFile, publicKey, descriptorPath, hostVersion);
                      }
                      ClassLoader pluginClassLoader = new URLClassLoader(new URL[] {pluginFile.toURI().toURL()});
                      jarClassLoaders.put(jarFile, pluginClassLoader);
                  } catch (IOException e) {
                      errors.add(pluginFile.getName() + " could not be loaded due to an I/O error");
                      logger.error("{} while loading plugin from file {} (message: {}); not loading it", e.getClass().getSimpleName(), pluginFile.getName(), e.getMessage(), e);
                  }
              }
          }
      }
  
      /**
       * Obtain a list of instances of all plugins available through a particular
       * classloader, or from plugin jars discovered by a previous invocation of
       * {@link #loadPlugins(File, PublicKey, String)}, which implement a
       * particular type.
       *
       * @param type The type of plugin to return.
       * @param descriptorPath The resource path of the file containing the plugin
       *                       descriptor.
       * @param classLoader The classloader from which to discover plugins.
       * @param <T> The type of plugin to return.
       * @return A list of newly instantiated plugin objects of the specified
       * type available from the specified classloader and/or any earlier
       * discovered plugin jars.
       */
      public static <T> List<T> findPlugins(Class<T> type, String descriptorPath, ClassLoader classLoader) {
          try {
              List<T> plugins = new ArrayList<>();
              findPlugins(type, descriptorPath, classLoader, plugins);
              for (JarFile pluginJar: jarClassLoaders.keySet()) {
                  try {
                      findPlugins(type, descriptorPath, pluginJar, plugins);
                  } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | NoClassDefFoundError e) {
                      errors.add(stripDirectory(pluginJar.getName()) + " could not be loaded; perhaps it is not compatible with this version of WorldPainter");
                      logger.error("{} while instantiating plugin {} (message: {}); skipping plugin", e.getClass().getSimpleName(), pluginJar.getName(), e.getMessage(), e);
                  }
              }
              return plugins;
          } catch (IOException e) {
              throw new MDCCapturingRuntimeException("I/O error while loading plugins", e);
          }
      }
  
      /**
       * Get a classloader which gives access to the classes of all the plugins.
       * 
       * @return A classloader which gives access to the classes of all the
       *     plugins.
       */
      public static ClassLoader getPluginClassLoader() {
          return classLoader;
      }
  
      /**
       * Get the list of informational messages, if any, that were generated while
       * loading the plugins.
       *
       * @return The list of informational messages, if any, that were generated
       * while loading the plugins. May be empty, but is never {@code null}.
       */
      public static @NotNull List<String> getMessages() {
          return unmodifiableList(messages);
      }
  
      /**
       * Get the list of errors, if any, that occurred while loading the plugins.
       *
       * @return The list of errors, if any, that occurred while loading the
       * plugins. May be empty, but is never {@code null}.
       */
      public static @NotNull List<String> getErrors() {
          return unmodifiableList(errors);
      }
  
      @SuppressWarnings({"StatementWithEmptyBody", "BooleanMethodIsAlwaysInverted"})
      private static boolean isSigned(JarFile jarFile, PublicKey publicKey) throws IOException {
          for (Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements(); ) {
              // Iterator over all the entries in the jar except directories and
              // signature files
              JarEntry jarEntry = e.nextElement();
              String entryName = jarEntry.getName().toUpperCase();
              if (jarEntry.isDirectory() || entryName.endsWith(".SF") || entryName.endsWith(".DSA") || entryName.endsWith(".EC") || entryName.endsWith(".RSA")) {
                  continue;
              }
  
              // Read the entry fully, otherwise the certificates won't be available
              byte[] buffer = new byte[BUFFER_SIZE];
              try (InputStream in = jarFile.getInputStream(jarEntry)) {
                  while (in.read(buffer) != -1) ;
              }
  
              // Get the signing certificate chain and check if one of them is the
              // WorldPainter plugin signing certificate
              Certificate[] certificates = jarEntry.getCertificates();
              boolean signed = false;
              if (certificates != null) {
                  for (Certificate certificate: certificates) {
                      if (certificate.getPublicKey().equals(publicKey)) {
                          signed = true;
                          break;
                      }
                  }
              }
              if (! signed) {
                  return false;
              }
          }
          return true;
      }
  
      @SuppressWarnings("unchecked") // Guaranteed by isAssignableFrom
      private static <T> void findPlugins(Class<T> type, String descriptorPath, JarFile jarFile, List<T> plugins) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
          Descriptor descriptor = loadDescriptor(jarFile, descriptorPath);
          for (String class_: descriptor.classes) {
              Class<?> pluginType = classLoader.loadClass(class_);
              if (type.isAssignableFrom(pluginType)) {
                  plugins.add(((Class<T>) pluginType).newInstance());
              }
          }
      }
  
      @SuppressWarnings("unchecked") // Guaranteed by isAssignableFrom
      private static <T> void findPlugins(Class<T> type, String descriptorPath, ClassLoader classLoader, List<T> plugins) throws IOException {
          Enumeration<URL> resources = classLoader.getResources(descriptorPath);
          while (resources.hasMoreElements()) {
  