private DefaultPlatformManager(VertxInternal vertx) {
   this.platformClassLoader = Thread.currentThread().getContextClassLoader();
   this.vertx = vertx;
   String modDir = System.getProperty(MODS_DIR_PROP_NAME);
   if (modDir != null && !modDir.trim().equals("")) {
     modRoot = new File(modDir);
   } else {
     // Default to local module directory
     modRoot = new File(LOCAL_MODS_DIR);
   }
   String vertxHome = System.getProperty(VERTX_HOME_SYS_PROP);
   if (vertxHome == null || modDir != null) {
     systemModRoot = modRoot;
   } else {
     systemModRoot = new File(vertxHome, SYS_MODS_DIR);
   }
   this.redeployer = new Redeployer(vertx, modRoot, this);
   loadLanguageMappings();
   loadRepos();
 }
  private void loadLanguageMappings() {
    // The only language that Vert.x understands out of the box is Java, so we add the default
    // runtime and
    // extension mapping for that. This can be overridden in langs.properties
    languageImpls.put(
        "java",
        new LanguageImplInfo(null, "org.vertx.java.platform.impl.java.JavaVerticleFactory"));
    extensionMappings.put("java", "java");
    extensionMappings.put("class", "java");
    defaultLanguageImplName = "java";

    // First try loading mappings from the LANG_PROPS_FILE_NAMEs file
    // This file is structured as follows:
    //   It should contain one line for every language implementation that is to be used with Vert.x
    //     That line should be structured as follows:
    //       <lang_impl_name>=[module_name:]<factory_name>
    //     Where:
    //       <lang_impl_name> is the name you want to give to the language implementation, e.g.
    // 'jython'
    //       module_name is the (optional) name of a module that contains the language
    // implementation
    //         if ommitted it will be assumed the language implementation is included as part of the
    // Vert.x installation
    //         - this is only true for the Java implementation
    //         if included the module_name should be followed by a colon
    //       factory_name is the FQCN of a VerticleFactory for the language implementation
    //     Examples:
    //       rhino=vertx.lang-rhino-v1.0.0:org.vertx.java.platform.impl.rhino.RhinoVerticleFactory
    //       java=org.vertx.java.platform.impl.java.JavaVerticleFactory
    //   The file should also contain one line for every extension mapping - this maps a file
    // extension to
    //   a <lang_impl_name> as specified above
    //     Examples:
    //       .js=rhino
    //       .rb=jruby
    //   The file can also contain a line representing the default language runtime to be used when
    // no extension or
    //   prefix maps, e.g.
    //     .=java

    try (InputStream is = getClass().getClassLoader().getResourceAsStream(LANG_PROPS_FILE_NAME)) {
      if (is != null) {
        Properties props = new Properties();
        props.load(new BufferedInputStream(is));
        loadLanguageMappings(props);
      }
    } catch (IOException e) {
      log.error("Failed to load " + LANG_PROPS_FILE_NAME + " " + e.getMessage());
    }

    // Then override any with system properties
    Properties sysProps = new Properties();
    Set<String> propertyNames = System.getProperties().stringPropertyNames();
    for (String propertyName : propertyNames) {
      if (propertyName.startsWith(LANG_IMPLS_SYS_PROP_ROOT)) {
        String lang = propertyName.substring(LANG_IMPLS_SYS_PROP_ROOT.length());
        String value = System.getProperty(propertyName);
        sysProps.put(lang, value);
      }
    }
    loadLanguageMappings(sysProps);
  }
/**
 * This class could benefit from some refactoring
 *
 * @author <a href="http://tfox.org">Tim Fox</a>
 */
public class DefaultPlatformManager implements PlatformManagerInternal, ModuleReloader {

  private static final Logger log = LoggerFactory.getLogger(DefaultPlatformManager.class);
  private static final int BUFFER_SIZE = 4096;
  private static final String MODS_DIR_PROP_NAME = "vertx.mods";
  private static final char COLON = ':';
  private static final String LANG_IMPLS_SYS_PROP_ROOT = "vertx.langs.";
  private static final String LANG_PROPS_FILE_NAME = "langs.properties";
  private static final String REPOS_FILE_NAME = "repos.txt";
  private static final String LOCAL_MODS_DIR = "mods";
  private static final String SYS_MODS_DIR = "sys-mods";
  private static final String VERTX_HOME_SYS_PROP = "vertx.home";
  private static final String TEMP_DIR = System.getProperty("java.io.tmpdir");
  private static final String FILE_SEP = System.getProperty("file.separator");

  private final VertxInternal vertx;
  // deployment name --> deployment
  private final Map<String, Deployment> deployments = new ConcurrentHashMap<>();
  // The user mods dir
  private final File modRoot;
  private final File systemModRoot;
  private final ConcurrentMap<String, ModuleReference> moduleRefs = new ConcurrentHashMap<>();
  private final Redeployer redeployer;
  private Map<String, LanguageImplInfo> languageImpls = new ConcurrentHashMap<>();
  private Map<String, String> extensionMappings = new ConcurrentHashMap<>();
  private String defaultLanguageImplName;
  private List<RepoResolver> repos = new ArrayList<>();
  private Handler<Void> exitHandler;
  private final ClassLoader platformClassLoader;

  DefaultPlatformManager() {
    this(new DefaultVertx());
  }

  DefaultPlatformManager(String hostname) {
    this(new DefaultVertx(hostname));
  }

  DefaultPlatformManager(int port, String hostname) {
    this(new DefaultVertx(port, hostname));
  }

  private DefaultPlatformManager(VertxInternal vertx) {
    this.platformClassLoader = Thread.currentThread().getContextClassLoader();
    this.vertx = vertx;
    String modDir = System.getProperty(MODS_DIR_PROP_NAME);
    if (modDir != null && !modDir.trim().equals("")) {
      modRoot = new File(modDir);
    } else {
      // Default to local module directory
      modRoot = new File(LOCAL_MODS_DIR);
    }
    String vertxHome = System.getProperty(VERTX_HOME_SYS_PROP);
    if (vertxHome == null || modDir != null) {
      systemModRoot = modRoot;
    } else {
      systemModRoot = new File(vertxHome, SYS_MODS_DIR);
    }
    this.redeployer = new Redeployer(vertx, modRoot, this);
    loadLanguageMappings();
    loadRepos();
  }

  public void registerExitHandler(Handler<Void> handler) {
    this.exitHandler = handler;
  }

  public void deployVerticle(
      String main,
      JsonObject config,
      URL[] classpath,
      int instances,
      String includes,
      Handler<String> doneHandler) {
    if (main == null) {
      throw new NullPointerException("main cannot be null");
    }
    deployVerticle(false, false, main, config, classpath, instances, includes, doneHandler);
  }

  public void deployWorkerVerticle(
      boolean multiThreaded,
      String main,
      JsonObject config,
      URL[] classpath,
      int instances,
      String includes,
      Handler<String> doneHandler) {
    deployVerticle(true, multiThreaded, main, config, classpath, instances, includes, doneHandler);
  }

  public void deployModule(
      final String moduleName,
      final JsonObject config,
      final int instances,
      final Handler<String> doneHandler) {
    final ModuleIdentifier modID = new ModuleIdentifier(moduleName);
    final File currentModDir = getDeploymentModDir();
    BlockingAction<Void> deployModuleAction =
        new BlockingAction<Void>(vertx, createHandler(doneHandler)) {

          @Override
          public Void action() throws Exception {
            doDeployMod(
                false, null, modID, config, instances, currentModDir, wrapDoneHandler(doneHandler));
            return null;
          }
        };
    deployModuleAction.run();
  }

  public synchronized void undeploy(String deploymentID, final Handler<Void> doneHandler) {
    if (deploymentID == null) {
      throw new NullPointerException("deploymentID is null");
    }
    final Deployment dep = deployments.get(deploymentID);
    if (dep == null) {
      throw new IllegalArgumentException("There is no deployment with id " + deploymentID);
    }
    Handler<Void> wrappedHandler =
        new SimpleHandler() {
          public void handle() {
            if (dep.modID != null && dep.autoRedeploy) {
              redeployer.moduleUndeployed(dep);
            }
            if (doneHandler != null) {
              doneHandler.handle(null);
            }
          }
        };
    doUndeploy(deploymentID, wrappedHandler);
  }

  public synchronized void undeployAll(final Handler<Void> doneHandler) {
    final CountingCompletionHandler count = new CountingCompletionHandler(vertx);
    if (!deployments.isEmpty()) {
      // We do it this way since undeploy is itself recursive - we don't want
      // to attempt to undeploy the same verticle twice if it's a child of
      // another
      while (!deployments.isEmpty()) {
        String name = deployments.keySet().iterator().next();
        count.incRequired();
        undeploy(
            name,
            new SimpleHandler() {
              public void handle() {
                count.complete();
              }
            });
      }
    }
    count.setHandler(doneHandler);
  }

  public Map<String, Integer> listInstances() {
    Map<String, Integer> map = new HashMap<>();
    for (Map.Entry<String, Deployment> entry : deployments.entrySet()) {
      map.put(entry.getKey(), entry.getValue().verticles.size());
    }
    return map;
  }

  public void installModule(final String moduleName) {
    final CountDownLatch latch = new CountDownLatch(1);
    final AtomicReference<Exception> result = new AtomicReference<>();
    final ModuleIdentifier modID = new ModuleIdentifier(moduleName);
    AsyncResultHandler<Void> handler =
        new AsyncResultHandler<Void>() {
          public void handle(AsyncResult<Void> res) {
            result.set(res.exception);
            latch.countDown();
          }
        };

    BlockingAction<Void> installModuleAction =
        new BlockingAction<Void>(vertx, handler) {
          @Override
          public Void action() throws Exception {
            doInstallMod(modID);
            return null;
          }
        };

    installModuleAction.run();

    while (true) {
      try {
        if (!latch.await(300, TimeUnit.SECONDS)) {
          throw new IllegalStateException("Timed out waiting to install module");
        }
        break;
      } catch (InterruptedException ignore) {
      }
    }
    Exception e = result.get();
    if (e != null) {
      log.error("Failed to install module", e);
    }
  }

  public synchronized void uninstallModule(String moduleName) {
    log.info("Uninstalling module " + moduleName + " from directory " + modRoot);
    File modDir = new File(modRoot, moduleName);
    if (!modDir.exists()) {
      log.error("Cannot find module to uninstall");
    } else {
      try {
        vertx.fileSystem().deleteSync(modDir.getAbsolutePath(), true);
        log.info("Module " + moduleName + " successfully uninstalled");
      } catch (Exception e) {
        log.error("Failed to delete directory: " + e.getMessage());
      }
    }
  }

  public boolean pullInDependencies(String moduleName) {
    log.info("Attempting to pull in dependencies for module: " + moduleName);
    ModuleIdentifier modID = new ModuleIdentifier(moduleName);
    try {
      return doPullInDependencies(modRoot, modID);
    } catch (Exception e) {
      log.error("Failed to pull in dependencies", e);
      return false;
    }
  }

  public void reloadModules(final Set<Deployment> deps) {
    for (final Deployment deployment : deps) {
      if (deployments.containsKey(deployment.name)) {
        doUndeploy(
            deployment.name,
            new SimpleHandler() {
              public void handle() {
                redeploy(deployment);
              }
            });
      } else {
        // This will be the case if the previous deployment failed, e.g.
        // a code error in a user verticle
        redeploy(deployment);
      }
    }
  }

  public Vertx getVertx() {
    return this.vertx;
  }

  public void deployModuleFromZip(
      String zipFileName, JsonObject config, int instances, Handler<String> doneHandler) {
    final String modName = zipFileName.substring(0, zipFileName.length() - 4);
    ModuleIdentifier modID = new ModuleIdentifier("__vertx_tmp#" + modName + "#__vertx_tmp");
    if (unzipModule(modID, new ModuleZipInfo(false, zipFileName), false)) {
      deployModule(modID.toString(), config, instances, doneHandler);
    } else {
      doneHandler.handle(null);
    }
  }

  public void exit() {
    if (exitHandler != null) {
      exitHandler.handle(null);
    }
  }

  public JsonObject getConfig() {
    VerticleHolder holder = getVerticleHolder();
    return holder == null ? null : holder.config;
  }

  public Logger getLogger() {
    VerticleHolder holder = getVerticleHolder();
    return holder == null ? null : holder.logger;
  }

  private void deployVerticle(
      final boolean worker,
      final boolean multiThreaded,
      final String main,
      final JsonObject config,
      URL[] classpath,
      final int instances,
      final String includes,
      final Handler<String> doneHandler) {
    final File currentModDir = getDeploymentModDir();
    final URL[] cp;
    if (classpath == null) {
      // Use the current moduleRefs/verticle's classpath
      cp = getDeploymentURLs();
    } else {
      cp = classpath;
    }
    BlockingAction<Void> deployModuleAction =
        new BlockingAction<Void>(vertx, createHandler(doneHandler)) {
          @Override
          public Void action() throws Exception {
            doDeployVerticle(
                worker,
                multiThreaded,
                main,
                config,
                cp,
                instances,
                currentModDir,
                includes,
                wrapDoneHandler(doneHandler));
            return null;
          }
        };
    deployModuleAction.run();
  }

  private String getDeploymentName() {
    VerticleHolder holder = getVerticleHolder();
    return holder == null ? null : holder.deployment.name;
  }

  private URL[] getDeploymentURLs() {
    VerticleHolder holder = getVerticleHolder();
    return holder == null ? null : holder.deployment.urls;
  }

  private File getDeploymentModDir() {
    VerticleHolder holder = getVerticleHolder();
    return holder == null ? null : holder.deployment.modDir;
  }

  private boolean doPullInDependencies(File modRoot, ModuleIdentifier modID) {
    File modDir = new File(modRoot, modID.toString());
    if (!modDir.exists()) {
      log.error("Cannot find module to uninstall");
    }
    JsonObject conf = loadModuleConfig(modID, modDir);
    if (conf == null) {
      log.error("Module " + modID + " does not contain a mod.json");
    }
    ModuleFields fields = new ModuleFields(conf);
    List<String> mods = new ArrayList<>();
    String includes = fields.getIncludes();
    if (includes != null) {
      mods.addAll(Arrays.asList(parseIncludeString(includes)));
    }
    String deploys = fields.getDeploys();
    if (deploys != null) {
      mods.addAll(Arrays.asList(parseIncludeString(deploys)));
    }
    if (!mods.isEmpty()) {
      File internalModsDir = new File(modDir, "mods");
      if (!internalModsDir.exists()) {
        internalModsDir.mkdir();
      }
      for (String modName : mods) {
        File internalModDir = new File(internalModsDir, modName);
        if (!internalModDir.exists()) {
          ModuleIdentifier theModID = new ModuleIdentifier(modName);
          ModuleZipInfo zipInfo = getModule(theModID);
          if (zipInfo.filename != null) {
            internalModDir.mkdir();
            if (!unzipModuleData(internalModDir, zipInfo, true)) {
              return false;
            } else {
              log.info("Module " + modName + " successfully installed in mods dir of " + modName);
              // Now recurse so we bring in all of the deps
              doPullInDependencies(internalModsDir, theModID);
            }
          }
        }
      }
    }
    return true;
  }

  private AsyncResultHandler<Void> createHandler(final Handler<String> doneHandler) {
    return new AsyncResultHandler<Void>() {
      @Override
      public void handle(AsyncResult<Void> ar) {
        if (ar.failed()) {
          log.error("Failed to deploy verticle", ar.exception);
          if (doneHandler != null) {
            doneHandler.handle(null);
          }
        }
      }
    };
  }

  private Handler<String> wrapDoneHandler(final Handler<String> doneHandler) {
    if (doneHandler == null) {
      return null;
    }
    final Context context = vertx.getContext();
    return new Handler<String>() {
      @Override
      public void handle(final String deploymentID) {
        if (context == null) {
          doneHandler.handle(deploymentID);
        } else {
          context.execute(
              new Runnable() {
                public void run() {
                  doneHandler.handle(deploymentID);
                }
              });
        }
      }
    };
  }

  // Recurse up through the parent deployments and return the the module name for the first one
  // which has one, or if there is no enclosing module just use the deployment name
  private ModuleIdentifier getEnclosingModID() {
    VerticleHolder holder = getVerticleHolder();
    Deployment dep = holder == null ? null : holder.deployment;
    while (dep != null) {
      if (dep.modID != null) {
        return dep.modID;
      } else {
        String parentDepName = dep.parentDeploymentName;
        if (parentDepName != null) {
          dep = deployments.get(parentDepName);
        } else {
          // Top level - deployed as verticle not module
          // Just use the deployment name
          return createInternalModIDForVerticle(dep.name);
        }
      }
    }
    return null; // We are at the top level already
  }

  private ModuleIdentifier createInternalModIDForVerticle(String depName) {
    return new ModuleIdentifier("__vertx#" + depName + "#__vertx");
  }

  private void doDeployVerticle(
      boolean worker,
      boolean multiThreaded,
      final String main,
      final JsonObject config,
      final URL[] urls,
      int instances,
      File currentModDir,
      String includes,
      Handler<String> doneHandler) {
    checkWorkerContext();

    // There is one module class loader per enclosing module + the name of the verticle.
    // If there is no enclosing module, there is one per top level verticle deployment
    // E.g. if a module A deploys "foo.js" as a verticle then all instances of foo.js deployed by
    // the enclosing
    // module will share a module class loader
    String depName = genDepName();
    ModuleIdentifier enclosingModName = getEnclosingModID();
    String moduleKey;
    if (enclosingModName == null) {
      // We are at the top level - just use the deployment name as the key
      moduleKey = createInternalModIDForVerticle(depName).toString();
    } else {
      // Use the enclosing module name / or enclosing verticle PLUS the main
      moduleKey = enclosingModName.toString() + "#" + main;
    }

    ModuleReference mr = moduleRefs.get(moduleKey);
    if (mr == null) {
      mr =
          new ModuleReference(
              this, moduleKey, new ModuleClassLoader(platformClassLoader, urls), false);
      ModuleReference prev = moduleRefs.putIfAbsent(moduleKey, mr);
      if (prev != null) {
        mr = prev;
      }
    }

    if (includes != null) {
      loadIncludedModules(currentModDir, mr, includes);
    }
    doDeploy(
        depName,
        false,
        worker,
        multiThreaded,
        main,
        null,
        config,
        urls,
        instances,
        currentModDir,
        mr,
        doneHandler);
  }

  private void checkWorkerContext() {
    Thread t = Thread.currentThread();
    if (!t.getName().startsWith("vert.x-worker-thread")) {
      throw new IllegalStateException("Not a worker thread");
    }
  }

  private void loadLanguageMappings() {
    // The only language that Vert.x understands out of the box is Java, so we add the default
    // runtime and
    // extension mapping for that. This can be overridden in langs.properties
    languageImpls.put(
        "java",
        new LanguageImplInfo(null, "org.vertx.java.platform.impl.java.JavaVerticleFactory"));
    extensionMappings.put("java", "java");
    extensionMappings.put("class", "java");
    defaultLanguageImplName = "java";

    // First try loading mappings from the LANG_PROPS_FILE_NAMEs file
    // This file is structured as follows:
    //   It should contain one line for every language implementation that is to be used with Vert.x
    //     That line should be structured as follows:
    //       <lang_impl_name>=[module_name:]<factory_name>
    //     Where:
    //       <lang_impl_name> is the name you want to give to the language implementation, e.g.
    // 'jython'
    //       module_name is the (optional) name of a module that contains the language
    // implementation
    //         if ommitted it will be assumed the language implementation is included as part of the
    // Vert.x installation
    //         - this is only true for the Java implementation
    //         if included the module_name should be followed by a colon
    //       factory_name is the FQCN of a VerticleFactory for the language implementation
    //     Examples:
    //       rhino=vertx.lang-rhino-v1.0.0:org.vertx.java.platform.impl.rhino.RhinoVerticleFactory
    //       java=org.vertx.java.platform.impl.java.JavaVerticleFactory
    //   The file should also contain one line for every extension mapping - this maps a file
    // extension to
    //   a <lang_impl_name> as specified above
    //     Examples:
    //       .js=rhino
    //       .rb=jruby
    //   The file can also contain a line representing the default language runtime to be used when
    // no extension or
    //   prefix maps, e.g.
    //     .=java

    try (InputStream is = getClass().getClassLoader().getResourceAsStream(LANG_PROPS_FILE_NAME)) {
      if (is != null) {
        Properties props = new Properties();
        props.load(new BufferedInputStream(is));
        loadLanguageMappings(props);
      }
    } catch (IOException e) {
      log.error("Failed to load " + LANG_PROPS_FILE_NAME + " " + e.getMessage());
    }

    // Then override any with system properties
    Properties sysProps = new Properties();
    Set<String> propertyNames = System.getProperties().stringPropertyNames();
    for (String propertyName : propertyNames) {
      if (propertyName.startsWith(LANG_IMPLS_SYS_PROP_ROOT)) {
        String lang = propertyName.substring(LANG_IMPLS_SYS_PROP_ROOT.length());
        String value = System.getProperty(propertyName);
        sysProps.put(lang, value);
      }
    }
    loadLanguageMappings(sysProps);
  }

  private void loadLanguageMappings(Properties props) {
    Enumeration<?> en = props.propertyNames();
    while (en.hasMoreElements()) {
      String propName = (String) en.nextElement();
      String propVal = props.getProperty(propName);
      if (propName.startsWith(".")) {
        // This is an extension mapping
        if (propName.equals(".")) {
          // The default mapping
          defaultLanguageImplName = propVal;
        } else {
          propName = propName.substring(1);
          extensionMappings.put(propName, propVal);
        }
      } else {
        // value is made up of an optional module name followed by colon followed by the
        // FQCN of the factory
        int colonIndex = propVal.lastIndexOf(COLON);
        String moduleName;
        String factoryName;
        if (colonIndex != -1) {
          moduleName = propVal.substring(0, colonIndex);
          factoryName = propVal.substring(colonIndex + 1);
        } else {
          throw new IllegalArgumentException(
              "Language mapping: " + propVal + " does not specify an implementing module");
        }
        LanguageImplInfo langImpl = new LanguageImplInfo(moduleName, factoryName);
        languageImpls.put(propName, langImpl);
        extensionMappings.put(propName, propName); // automatically register the name as a mapping
      }
    }
  }

  private File locateModule(File currentModDir, ModuleIdentifier modID) {
    if (currentModDir != null) {
      // Nested moduleRefs - look inside current module dir
      File modDir = new File(new File(currentModDir, LOCAL_MODS_DIR), modID.toString());
      if (modDir.exists()) {
        return modDir;
      }
    }
    File modDir = new File(modRoot, modID.toString());
    if (modDir.exists()) {
      return modDir;
    } else if (!systemModRoot.equals(modRoot)) {
      modDir = new File(systemModRoot, modID.toString());
      if (modDir.exists()) {
        return modDir;
      }
    }
    return null;
  }

  private void doDeployMod(
      final boolean redeploy,
      final String depName,
      final ModuleIdentifier modID,
      final JsonObject config,
      final int instances,
      final File currentModDir,
      final Handler<String> doneHandler) {
    checkWorkerContext();
    File modDir = locateModule(currentModDir, modID);
    if (modDir != null) {
      JsonObject conf = loadModuleConfig(modID, modDir);
      ModuleFields fields = new ModuleFields(conf);
      String main = fields.getMain();
      if (main == null) {
        log.error("Runnable module " + modID + " mod.json must contain a \"main\" field");
        callDoneHandler(doneHandler, null);
        return;
      }
      boolean worker = fields.isWorker();
      boolean multiThreaded = fields.isMultiThreaded();
      if (multiThreaded && !worker) {
        throw new IllegalArgumentException("Multi-threaded modules must be workers");
      }
      boolean preserveCwd = fields.isPreserveCurrentWorkingDirectory();

      // If preserveCwd then use the current module directory instead, or the cwd if not in a module
      File modDirToUse = preserveCwd ? currentModDir : modDir;

      List<URL> urls = getModuleClasspath(modDir);
      if (urls == null) {
        callDoneHandler(doneHandler, null);
        return;
      }

      ModuleReference mr = moduleRefs.get(modID.toString());
      if (mr == null) {
        boolean res = fields.isResident();
        mr =
            new ModuleReference(
                this,
                modID.toString(),
                new ModuleClassLoader(platformClassLoader, urls.toArray(new URL[urls.size()])),
                res);
        ModuleReference prev = moduleRefs.putIfAbsent(modID.toString(), mr);
        if (prev != null) {
          mr = prev;
        }
      }
      ModuleIdentifier enclosingModID = getEnclosingModID();
      if (enclosingModID != null) {
        // If enclosed in another module then the enclosing module classloader becomes a parent of
        // this one
        ModuleReference parentRef = moduleRefs.get(enclosingModID.toString());
        mr.mcl.addParent(parentRef);
        parentRef.incRef();
      }

      // Now load any included moduleRefs
      String includes = fields.getIncludes();
      if (includes != null) {
        if (!loadIncludedModules(modDir, mr, includes)) {
          callDoneHandler(doneHandler, null);
          return;
        }
      }

      final boolean autoRedeploy = fields.isAutoRedeploy();

      doDeploy(
          depName,
          autoRedeploy,
          worker,
          multiThreaded,
          main,
          modID,
          config,
          urls.toArray(new URL[urls.size()]),
          instances,
          modDirToUse,
          mr,
          new Handler<String>() {
            @Override
            public void handle(String deploymentID) {
              if (deploymentID != null && !redeploy && autoRedeploy) {
                redeployer.moduleDeployed(deployments.get(deploymentID));
              }
              callDoneHandler(doneHandler, deploymentID);
            }
          });
    } else {
      if (doInstallMod(modID)) {
        doDeployMod(redeploy, depName, modID, config, instances, currentModDir, doneHandler);
      } else {
        callDoneHandler(doneHandler, null);
      }
    }
  }

  private JsonObject loadModuleConfig(ModuleIdentifier modID, File modDir) {
    // Checked the byte code produced, .close() is called correctly, so the warning can be
    // suppressed
    try (@SuppressWarnings("resource")
        Scanner scanner = new Scanner(new File(modDir, "mod.json")).useDelimiter("\\A")) {
      String conf = scanner.next();
      return new JsonObject(conf);
    } catch (FileNotFoundException e) {
      throw new IllegalStateException("Module " + modID + " does not contain a mod.json file");
    } catch (NoSuchElementException e) {
      throw new IllegalStateException("Module " + modID + " contains an empty mod.json file");
    } catch (DecodeException e) {
      throw new IllegalStateException("Module " + modID + " mod.json contains invalid json");
    }
  }

  private boolean loadIncludedModules(
      File currentModuleDir, ModuleReference mr, String includesString) {
    checkWorkerContext();
    for (String moduleName : parseIncludeString(includesString)) {
      ModuleIdentifier modID = new ModuleIdentifier(moduleName);
      ModuleReference includedMr = moduleRefs.get(moduleName);
      if (includedMr == null) {
        File modDir = locateModule(currentModuleDir, modID);
        if (modDir == null) {
          if (!doInstallMod(modID)) {
            return false;
          }
        }
        modDir = locateModule(currentModuleDir, modID);
        List<URL> urls = getModuleClasspath(modDir);
        JsonObject conf = loadModuleConfig(modID, modDir);
        ModuleFields fields = new ModuleFields(conf);

        boolean res = fields.isResident();
        includedMr =
            new ModuleReference(
                this,
                moduleName,
                new ModuleClassLoader(platformClassLoader, urls.toArray(new URL[urls.size()])),
                res);
        ModuleReference prev = moduleRefs.putIfAbsent(moduleName, includedMr);
        if (prev != null) {
          includedMr = prev;
        }
        String includes = fields.getIncludes();
        if (includes != null) {
          loadIncludedModules(modDir, includedMr, includes);
        }
      }
      includedMr.incRef();
      mr.mcl.addParent(includedMr);
    }
    return true;
  }

  private List<URL> getModuleClasspath(File modDir) {
    List<URL> urls = new ArrayList<>();
    // Add the urls for this module
    try {
      urls.add(modDir.toURI().toURL());
      File libDir = new File(modDir, "lib");
      if (libDir.exists()) {
        File[] jars = libDir.listFiles();
        for (File jar : jars) {
          URL jarURL = jar.toURI().toURL();
          urls.add(jarURL);
        }
      }
      return urls;
    } catch (MalformedURLException e) {
      // Won't happen
      log.error("malformed url", e);
      return null;
    }
  }

  private String[] parseIncludeString(String sincludes) {
    sincludes = sincludes.trim();
    if ("".equals(sincludes)) {
      log.error("Empty include string");
      return null;
    }
    String[] arr = sincludes.split(",");
    if (arr != null) {
      for (int i = 0; i < arr.length; i++) {
        arr[i] = arr[i].trim();
      }
    }
    return arr;
  }

  private void loadRepos() {
    try (InputStream is = getClass().getClassLoader().getResourceAsStream(REPOS_FILE_NAME)) {
      if (is != null) {
        BufferedReader rdr = new BufferedReader(new InputStreamReader(is));
        String line;
        while ((line = rdr.readLine()) != null) {
          line = line.trim();
          if (line.isEmpty() || line.startsWith("#")) {
            // blank line or comment
            continue;
          }
          int colonPos = line.indexOf(':');
          if (colonPos == -1 || colonPos == line.length() - 1) {
            throw new IllegalArgumentException("Invalid repo: " + line);
          }
          String type = line.substring(0, colonPos);
          String repoID = line.substring(colonPos + 1);
          RepoResolver resolver;
          switch (type) {
            case "mavenLocal":
              resolver = new MavenLocalRepoResolver(repoID);
              type = "maven";
              break;
            case "maven":
              resolver = new MavenRepoResolver(vertx, repoID);
              break;
            case "bintray":
              resolver = new BintrayRepoResolver(vertx, repoID);
              break;
            case "old":
              resolver = new OldRepoResolver(vertx, repoID);
              break;
            default:
              throw new IllegalArgumentException("Unknown repo type: " + type);
          }
          repos.add(resolver);
        }
      }
    } catch (IOException e) {
      log.error("Failed to load " + LANG_PROPS_FILE_NAME + " " + e.getMessage());
    }
  }

  private boolean doInstallMod(final ModuleIdentifier modID) {
    checkWorkerContext();
    if (repos.isEmpty()) {
      log.warn("No repositories configured!");
      return false;
    }
    if (locateModule(null, modID) != null) {
      log.error("Module is already installed");
      return false;
    }
    ModuleZipInfo info = getModule(modID);
    if (info != null) {
      return unzipModule(modID, info, true);
    }
    return false;
  }

  private ModuleZipInfo getModule(ModuleIdentifier modID) {
    String fileName = generateTmpFileName() + ".zip";
    for (RepoResolver resolver : repos) {
      if (resolver.getModule(fileName, modID)) {
        return new ModuleZipInfo(resolver.isOldStyle(), fileName);
      }
    }
    log.error("Module " + modID + " not found in any repositories");
    return null;
  }

  private String generateTmpFileName() {
    return TEMP_DIR + FILE_SEP + "vertx-" + UUID.randomUUID().toString();
  }

  private File unzipIntoTmpDir(ModuleZipInfo zipInfo, boolean deleteZip) {
    String tdir = generateTmpFileName();
    File tdest = new File(tdir);
    tdest.mkdir();

    if (!unzipModuleData(tdest, zipInfo, deleteZip)) {
      return null;
    } else {
      return tdest;
    }
  }

  private boolean checkModDirs() {
    if (!modRoot.exists()) {
      if (!modRoot.mkdir()) {
        log.error("Failed to create mods dir " + modRoot);
        return false;
      }
    }
    if (!systemModRoot.exists()) {
      if (!systemModRoot.mkdir()) {
        log.error("Failed to create sys mods dir " + modRoot);
        return false;
      }
    }
    return true;
  }

  private boolean unzipModule(
      final ModuleIdentifier modID, final ModuleZipInfo zipInfo, boolean deleteZip) {
    // We synchronize to prevent a race whereby it tries to unzip the same module at the
    // same time (e.g. deployModule for the same module name has been called in parallel)
    String modName = modID.toString();
    synchronized (modName.intern()) {
      if (!checkModDirs()) {
        return false;
      }

      File fdest = new File(modRoot, modName);
      File sdest = new File(systemModRoot, modName);
      if (fdest.exists() || sdest.exists()) {
        // This can happen if the same module is requested to be installed
        // at around the same time
        // It's ok if this happens
        log.warn("Module " + modID + " is already installed");
        return true;
      }

      // Unzip into temp dir first
      File tdest = unzipIntoTmpDir(zipInfo, deleteZip);
      if (tdest == null) {
        return false;
      }

      // Check if it's a system module
      JsonObject conf = loadModuleConfig(modID, tdest);
      ModuleFields fields = new ModuleFields(conf);

      boolean system = fields.isSystem();

      // Now copy it to the proper directory
      String moveFrom = tdest.getAbsolutePath();
      try {
        vertx
            .fileSystem()
            .moveSync(moveFrom, system ? sdest.getAbsolutePath() : fdest.getAbsolutePath());
      } catch (Exception e) {
        log.error("Failed to move module", e);
        return false;
      }

      log.info("Module " + modID + " successfully installed");
      return true;
    }
  }

  private String removeTopDir(String entry) {
    int pos = entry.indexOf(FILE_SEP);
    if (pos != -1) {
      entry = entry.substring(pos + 1);
    }
    return entry;
  }

  private boolean unzipModuleData(
      final File directory, final ModuleZipInfo zipinfo, boolean deleteZip) {
    try (InputStream is = new BufferedInputStream(new FileInputStream(zipinfo.filename));
        ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is))) {
      ZipEntry entry;
      while ((entry = zis.getNextEntry()) != null) {
        String entryName = zipinfo.oldStyle ? removeTopDir(entry.getName()) : entry.getName();
        if (!entryName.isEmpty()) {
          if (entry.isDirectory()) {
            new File(directory, entryName).mkdir();
          } else {
            int count;
            byte[] buff = new byte[BUFFER_SIZE];
            BufferedOutputStream dest = null;
            try {
              OutputStream fos = new FileOutputStream(new File(directory, entryName));
              dest = new BufferedOutputStream(fos, BUFFER_SIZE);
              while ((count = zis.read(buff, 0, BUFFER_SIZE)) != -1) {
                dest.write(buff, 0, count);
              }
              dest.flush();
            } finally {
              if (dest != null) {
                dest.close();
              }
            }
          }
        }
      }
    } catch (Exception e) {
      log.error("Failed to unzip module", e);
      return false;
    } finally {
      directory.delete();
      if (deleteZip) {
        new File(zipinfo.filename).delete();
      }
    }
    return true;
  }

  // We calculate a path adjustment that can be used by the fileSystem object
  // so that the *effective* working directory can be the module directory
  // this allows moduleRefs to read and write the file system as if they were
  // in the module dir, even though the actual working directory will be
  // wherever vertx run or vertx start was called from
  private void setPathAdjustment(File modDir) {
    Path cwd = Paths.get(".").toAbsolutePath().getParent();
    Path pmodDir = Paths.get(modDir.getAbsolutePath());
    Path relative = cwd.relativize(pmodDir);
    vertx.getContext().setPathAdjustment(relative);
  }

  private void callDoneHandler(Handler<String> doneHandler, String deploymentID) {
    if (doneHandler != null) {
      doneHandler.handle(deploymentID);
    }
  }

  private String genDepName() {
    return "deployment-" + UUID.randomUUID().toString();
  }

  private void doDeploy(
      final String depName,
      boolean autoRedeploy,
      boolean worker,
      boolean multiThreaded,
      String theMain,
      final ModuleIdentifier modID,
      final JsonObject config,
      final URL[] urls,
      int instances,
      final File modDir,
      final ModuleReference mr,
      final Handler<String> doneHandler) {
    checkWorkerContext();

    final String deploymentName = depName != null ? depName : genDepName();

    log.debug(
        "Deploying name : " + deploymentName + " main: " + theMain + " instances: " + instances);

    // How we determine which language implementation to use:
    // 1. Look for a prefix on the main, e.g. 'groovy:org.foo.myproject.MyGroovyMain' would force
    // the groovy
    //    language impl to be used
    // 2. If there is no prefix, then look at the extension, if any. If there is an extension
    // mapping for that
    //    extension, use that.
    // 3. No prefix and no extension mapping - use the default runtime

    LanguageImplInfo langImplInfo = null;

    final String main;
    // Look for a prefix
    int prefixMarker = theMain.indexOf(COLON);
    if (prefixMarker != -1) {
      String prefix = theMain.substring(0, prefixMarker);
      langImplInfo = languageImpls.get(prefix);
      if (langImplInfo == null) {
        throw new IllegalStateException("No language implementation known for prefix " + prefix);
      }
      main = theMain.substring(prefixMarker + 1);
    } else {
      main = theMain;
    }
    if (langImplInfo == null) {
      // No prefix - now look at the extension
      int extensionMarker = main.lastIndexOf('.');
      if (extensionMarker != -1) {
        String extension = main.substring(extensionMarker + 1);
        String langImplName = extensionMappings.get(extension);
        if (langImplName != null) {
          langImplInfo = languageImpls.get(langImplName);
          if (langImplInfo == null) {
            throw new IllegalStateException(
                "Extension mapping for "
                    + extension
                    + " specified as "
                    + langImplName
                    + ", but no language implementation known for that name");
          }
        }
      }
    }
    if (langImplInfo == null) {
      // Use the default
      langImplInfo = languageImpls.get(defaultLanguageImplName);
      if (langImplInfo == null) {
        throw new IllegalStateException(
            "Default language implementation is "
                + defaultLanguageImplName
                + " but no language implementation known for that name");
      }
    }

    // Include the language impl module as a parent of the classloader
    if (langImplInfo.moduleName != null) {
      if (!loadIncludedModules(modDir, mr, langImplInfo.moduleName)) {
        log.error("Failed to load module: " + langImplInfo.moduleName);
        doneHandler.handle(null);
        return;
      }
    }

    final VerticleFactory verticleFactory;

    final Container container = new DefaultContainer(this);

    try {
      // TODO not one verticle factory per module ref, but one per language per module ref
      verticleFactory = mr.getVerticleFactory(langImplInfo.factoryName, vertx, container);
    } catch (Exception e) {
      log.error("Failed to instantiate verticle factory", e);
      doneHandler.handle(null);
      return;
    }

    final int instCount = instances;

    class AggHandler {
      AtomicInteger count = new AtomicInteger(0);
      boolean failed;

      void done(boolean res) {
        if (!res) {
          failed = true;
        }
        if (count.incrementAndGet() == instCount) {
          String deploymentID = failed ? null : deploymentName;
          callDoneHandler(doneHandler, deploymentID);
        }
      }
    }

    final AggHandler aggHandler = new AggHandler();

    String parentDeploymentName = getDeploymentName();
    final Deployment deployment =
        new Deployment(
            deploymentName,
            main,
            modID,
            instances,
            config == null ? new JsonObject() : config.copy(),
            urls,
            modDir,
            parentDeploymentName,
            mr,
            autoRedeploy);
    mr.incRef();
    deployments.put(deploymentName, deployment);

    if (parentDeploymentName != null) {
      Deployment parentDeployment = deployments.get(parentDeploymentName);
      parentDeployment.childDeployments.add(deploymentName);
    }

    ClassLoader oldTCCL = Thread.currentThread().getContextClassLoader();
    Thread.currentThread().setContextClassLoader(mr.mcl);
    try {

      for (int i = 0; i < instances; i++) {

        // Launch the verticle instance

        Runnable runner =
            new Runnable() {
              public void run() {

                Verticle verticle = null;
                boolean error = true;

                try {
                  verticle = verticleFactory.createVerticle(main);
                  error = false;
                } catch (ClassNotFoundException e) {
                  log.error(
                      "Cannot find verticle "
                          + main
                          + " in "
                          + verticleFactory.getClass().getName(),
                      e);
                } catch (Throwable t) {
                  log.error(
                      "Failed to create verticle "
                          + main
                          + " in "
                          + verticleFactory.getClass().getName(),
                      t);
                }

                if (error) {
                  doUndeploy(
                      deploymentName,
                      new SimpleHandler() {
                        public void handle() {
                          aggHandler.done(false);
                        }
                      });
                  return;
                }
                verticle.setContainer(container);
                verticle.setVertx(vertx);

                try {
                  addVerticle(deployment, verticle, verticleFactory);
                  if (modDir != null) {
                    setPathAdjustment(modDir);
                  }
                  VoidResult vr = new VoidResult();
                  verticle.start(vr);
                  vr.setHandler(
                      new AsyncResultHandler<Void>() {
                        @Override
                        public void handle(AsyncResult<Void> ar) {
                          if (ar.succeeded()) {
                            aggHandler.done(true);
                          } else {
                            log.error(
                                "Failed to deploy verticle "
                                    + main
                                    + " in "
                                    + verticleFactory.getClass().getName(),
                                ar.exception);
                            aggHandler.done(false);
                          }
                        }
                      });
                } catch (Throwable t) {
                  t.printStackTrace();
                  vertx.reportException(t);
                  doUndeploy(
                      deploymentName,
                      new SimpleHandler() {
                        public void handle() {
                          aggHandler.done(false);
                        }
                      });
                }
              }
            };

        if (worker) {
          vertx.startInBackground(runner, multiThreaded);
        } else {
          vertx.startOnEventLoop(runner);
        }
      }
    } finally {
      Thread.currentThread().setContextClassLoader(oldTCCL);
    }
  }

  // Must be synchronized since called directly from different thread
  private void addVerticle(Deployment deployment, Verticle verticle, VerticleFactory factory) {
    String loggerName =
        "org.vertx.deployments." + deployment.name + "-" + deployment.verticles.size();
    Logger logger = LoggerFactory.getLogger(loggerName);
    Context context = vertx.getContext();
    VerticleHolder holder =
        new VerticleHolder(
            deployment, context, verticle, loggerName, logger, deployment.config, factory);
    deployment.verticles.add(holder);
    context.setDeploymentHandle(holder);
  }

  private VerticleHolder getVerticleHolder() {
    Context context = vertx.getContext();
    if (context != null) {
      VerticleHolder holder = (VerticleHolder) context.getDeploymentHandle();
      return holder;
    } else {
      return null;
    }
  }

  private void doUndeploy(String name, final Handler<Void> doneHandler) {
    CountingCompletionHandler count = new CountingCompletionHandler(vertx);
    doUndeploy(name, count);
    if (doneHandler != null) {
      count.setHandler(doneHandler);
    }
  }

  private void doUndeploy(String name, final CountingCompletionHandler parentCount) {
    if (name == null) {
      throw new NullPointerException("deployment id is null");
    }

    final Deployment deployment = deployments.remove(name);
    final CountingCompletionHandler count = new CountingCompletionHandler(vertx);
    parentCount.incRequired();

    // Depth first - undeploy children first
    for (String childDeployment : deployment.childDeployments) {
      doUndeploy(childDeployment, count);
    }

    if (!deployment.verticles.isEmpty()) {
      for (final VerticleHolder holder : deployment.verticles) {
        count.incRequired();
        holder.context.execute(
            new Runnable() {
              public void run() {
                try {
                  holder.verticle.stop();
                } catch (Throwable t) {
                  vertx.reportException(t);
                }
                LoggerFactory.removeLogger(holder.loggerName);
                holder.context.runCloseHooks();
                holder.context.close();
                count.complete();
              }
            });
      }
    }

    if (deployment.parentDeploymentName != null) {
      Deployment parent = deployments.get(deployment.parentDeploymentName);
      if (parent != null) {
        parent.childDeployments.remove(name);
      }
    }

    count.setHandler(
        new SimpleHandler() {
          protected void handle() {
            deployment.moduleReference.decRef();
            parentCount.complete();
          }
        });
  }

  private void redeploy(final Deployment deployment) {
    // Has to occur on a worker thread
    AsyncResultHandler<String> handler =
        new AsyncResultHandler<String>() {
          public void handle(AsyncResult<String> res) {
            if (!res.succeeded()) {
              log.error("Failed to redeploy", res.exception);
            }
          }
        };
    BlockingAction<Void> redeployAction =
        new BlockingAction<Void>(vertx, handler) {
          @Override
          public Void action() throws Exception {
            doDeployMod(
                true,
                deployment.name,
                deployment.modID,
                deployment.config,
                deployment.instances,
                null,
                null);
            return null;
          }
        };
    redeployAction.run();
  }

  public void stop() {
    redeployer.close();
  }

  // For debug only
  public int checkNoModules() {
    int count = 0;
    for (Map.Entry<String, ModuleReference> entry : moduleRefs.entrySet()) {
      if (!entry.getValue().resident) {
        System.out.println("Module remains: " + entry.getKey());
        count++;
      }
    }
    return count;
  }

  public void removeModule(String moduleKey) {
    moduleRefs.remove(moduleKey);
  }

  private static class LanguageImplInfo {
    final String moduleName;
    final String factoryName;

    private LanguageImplInfo(String moduleName, String factoryName) {
      this.moduleName = moduleName;
      this.factoryName = factoryName;
    }

    public String toString() {
      return (moduleName == null ? ":" : (moduleName + ":")) + factoryName;
    }
  }

  private static final class ModuleZipInfo {
    final boolean oldStyle;
    final String filename;

    private ModuleZipInfo(boolean oldStyle, String filename) {
      this.oldStyle = oldStyle;
      this.filename = filename;
    }
  }
}