@NotNull
  @Override
  public Collection<String> suggestHomePaths() {
    if (!SystemInfo.isWindows) return Collections.singletonList(suggestHomePath());

    String property = System.getProperty("java.home");
    if (property == null) return Collections.emptyList();

    File javaHome = new File(property).getParentFile(); // actually java.home points to to jre home
    if (javaHome == null || !javaHome.isDirectory() || javaHome.getParentFile() == null) {
      return Collections.emptyList();
    }
    ArrayList<String> result = new ArrayList<>();
    File javasFolder = javaHome.getParentFile();
    scanFolder(javasFolder, result);
    File parentFile = javasFolder.getParentFile();
    File root = parentFile != null ? parentFile.getParentFile() : null;
    String name = parentFile != null ? parentFile.getName() : "";
    if (name.contains("Program Files") && root != null) {
      String x86Suffix = " (x86)";
      boolean x86 = name.endsWith(x86Suffix) && name.length() > x86Suffix.length();
      File anotherJavasFolder;
      if (x86) {
        anotherJavasFolder = new File(root, name.substring(0, name.length() - x86Suffix.length()));
      } else {
        anotherJavasFolder = new File(root, name + x86Suffix);
      }
      if (anotherJavasFolder.isDirectory()) {
        scanFolder(new File(anotherJavasFolder, javasFolder.getName()), result);
      }
    }
    return result;
  }
  @NotNull
  private static List<VirtualFile> findClasses(File file, boolean isJre) {
    List<VirtualFile> result = ContainerUtil.newArrayList();
    VirtualFileManager fileManager = VirtualFileManager.getInstance();

    String path = file.getPath();
    if (JrtFileSystem.isModularJdk(path)) {
      String url =
          VirtualFileManager.constructUrl(
              JrtFileSystem.PROTOCOL,
              FileUtil.toSystemIndependentName(path) + JrtFileSystem.SEPARATOR);
      for (String module : JrtFileSystem.listModules(path)) {
        ContainerUtil.addIfNotNull(result, fileManager.findFileByUrl(url + module));
      }
    }

    for (File root : JavaSdkUtil.getJdkClassesRoots(file, isJre)) {
      String url = VfsUtil.getUrlForLibraryRoot(root);
      ContainerUtil.addIfNotNull(result, fileManager.findFileByUrl(url));
    }

    Collections.sort(result, (o1, o2) -> o1.getPath().compareTo(o2.getPath()));

    return result;
  }
 private void runAutoMake() {
   if (ApplicationManager.getApplication().isUnitTestMode()) {
     return;
   }
   final Project[] openProjects = myProjectManager.getOpenProjects();
   if (openProjects.length > 0) {
     final List<RequestFuture> futures = new ArrayList<RequestFuture>();
     for (final Project project : openProjects) {
       if (project.isDefault() || project.isDisposed()) {
         continue;
       }
       final CompilerWorkspaceConfiguration config =
           CompilerWorkspaceConfiguration.getInstance(project);
       if (!config.useOutOfProcessBuild() || !config.MAKE_PROJECT_ON_SAVE) {
         continue;
       }
       final List<String> emptyList = Collections.emptyList();
       final RequestFuture future =
           scheduleBuild(
               project,
               false,
               true,
               emptyList,
               emptyList,
               emptyList,
               Collections.<String, String>emptyMap(),
               new AutoMakeMessageHandler(project));
       if (future != null) {
         futures.add(future);
         synchronized (myAutomakeFutures) {
           myAutomakeFutures.put(future, project);
         }
       }
     }
     try {
       for (RequestFuture future : futures) {
         future.waitFor();
       }
     } finally {
       synchronized (myAutomakeFutures) {
         myAutomakeFutures.keySet().removeAll(futures);
       }
     }
   }
 }
/**
 * @author Eugene Zhuravlev
 * @since Sep 17, 2004
 */
public class JavaSdkImpl extends JavaSdk {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.openapi.projectRoots.impl.JavaSdkImpl");

  public static final DataKey<Boolean> KEY = DataKey.create("JavaSdk");

  private static final String VM_EXE_NAME =
      "java"; // do not use JavaW.exe for Windows because of issues with encoding
  private static final Pattern VERSION_STRING_PATTERN =
      Pattern.compile("^(.*)java version \"([1234567890_.]*)\"(.*)$");
  private static final String JAVA_VERSION_PREFIX = "java version ";
  private static final String OPENJDK_VERSION_PREFIX = "openjdk version ";

  public JavaSdkImpl(final VirtualFileManager fileManager, final FileTypeManager fileTypeManager) {
    super("JavaSDK");

    fileManager.addVirtualFileListener(
        new VirtualFileAdapter() {
          @Override
          public void fileDeleted(@NotNull VirtualFileEvent event) {
            updateCache(event);
          }

          @Override
          public void contentsChanged(@NotNull VirtualFileEvent event) {
            updateCache(event);
          }

          @Override
          public void fileCreated(@NotNull VirtualFileEvent event) {
            updateCache(event);
          }

          private void updateCache(VirtualFileEvent event) {
            final VirtualFile file = event.getFile();
            if (FileTypes.ARCHIVE.equals(
                fileTypeManager.getFileTypeByFileName(event.getFileName()))) {
              final String filePath = file.getPath();
              synchronized (myCachedVersionStrings) {
                for (String sdkHome : myCachedVersionStrings.keySet()) {
                  if (FileUtil.isAncestor(sdkHome, filePath, false)) {
                    myCachedVersionStrings.remove(sdkHome);
                    break;
                  }
                }
              }
            }
          }
        });
  }

  @NotNull
  @Override
  public String getPresentableName() {
    return ProjectBundle.message("sdk.java.name");
  }

  @Override
  public Icon getIcon() {
    return AllIcons.Nodes.PpJdk;
  }

  @NotNull
  @Override
  public String getHelpTopic() {
    return "reference.project.structure.sdk.java";
  }

  @NotNull
  @Override
  public Icon getIconForAddAction() {
    return AllIcons.General.AddJdk;
  }

  @Override
  @Nullable
  public String getDefaultDocumentationUrl(@NotNull final Sdk sdk) {
    final JavaSdkVersion version = getVersion(sdk);
    if (version == JavaSdkVersion.JDK_1_5) {
      return "http://docs.oracle.com/javase/1.5.0/docs/api/";
    }
    if (version == JavaSdkVersion.JDK_1_6) {
      return "http://docs.oracle.com/javase/6/docs/api/";
    }
    if (version == JavaSdkVersion.JDK_1_7) {
      return "http://docs.oracle.com/javase/7/docs/api/";
    }
    if (version == JavaSdkVersion.JDK_1_8) {
      return "http://docs.oracle.com/javase/8/docs/api";
    }
    return null;
  }

  @Nullable
  @Override
  public String getDownloadSdkUrl() {
    return "http://www.oracle.com/technetwork/java/javase/downloads/index.html";
  }

  @Override
  public AdditionalDataConfigurable createAdditionalDataConfigurable(
      @NotNull SdkModel sdkModel, @NotNull SdkModificator sdkModificator) {
    return null;
  }

  @Override
  public void saveAdditionalData(
      @NotNull SdkAdditionalData additionalData, @NotNull Element additional) {}

  @Override
  @SuppressWarnings("HardCodedStringLiteral")
  public String getBinPath(@NotNull Sdk sdk) {
    return getConvertedHomePath(sdk) + "bin";
  }

  @Override
  public String getToolsPath(@NotNull Sdk sdk) {
    final String versionString = sdk.getVersionString();
    final boolean isJdk1_x =
        versionString != null && (versionString.contains("1.0") || versionString.contains("1.1"));
    return getConvertedHomePath(sdk)
        + "lib"
        + File.separator
        + (isJdk1_x ? "classes.zip" : "tools.jar");
  }

  @Override
  public String getVMExecutablePath(@NotNull Sdk sdk) {
    return getBinPath(sdk) + File.separator + VM_EXE_NAME;
  }

  private static String getConvertedHomePath(Sdk sdk) {
    String homePath = sdk.getHomePath();
    assert homePath != null : sdk;
    String path = FileUtil.toSystemDependentName(homePath);
    if (!path.endsWith(File.separator)) {
      path += File.separator;
    }
    return path;
  }

  @Override
  public String suggestHomePath() {
    if (SystemInfo.isMac) {
      if (new File("/usr/libexec/java_home").canExecute()) {
        String path = ExecUtil.execAndReadLine(new GeneralCommandLine("/usr/libexec/java_home"));
        if (path != null && new File(path).isDirectory()) {
          return path;
        }
      }

      String home =
          checkKnownLocations(
              "/Library/Java/JavaVirtualMachines", "/System/Library/Java/JavaVirtualMachines");
      if (home != null) return home;
    }

    if (SystemInfo.isLinux) {
      String home = checkKnownLocations("/usr/java", "/opt/java", "/usr/lib/jvm");
      if (home != null) return home;
    }

    if (SystemInfo.isSolaris) {
      String home = checkKnownLocations("/usr/jdk");
      if (home != null) return home;
    }

    String property = System.getProperty("java.home");
    if (property != null) {
      File javaHome = new File(property);
      if (javaHome.getName().equals("jre")) {
        javaHome = javaHome.getParentFile();
      }
      if (javaHome != null && javaHome.isDirectory()) {
        return javaHome.getAbsolutePath();
      }
    }

    return null;
  }

  @Nullable
  private static String checkKnownLocations(String... locations) {
    for (String home : locations) {
      if (new File(home).isDirectory()) {
        return home;
      }
    }

    return null;
  }

  @NotNull
  @Override
  public Collection<String> suggestHomePaths() {
    if (!SystemInfo.isWindows) return Collections.singletonList(suggestHomePath());

    String property = System.getProperty("java.home");
    if (property == null) return Collections.emptyList();

    File javaHome = new File(property).getParentFile(); // actually java.home points to to jre home
    if (javaHome == null || !javaHome.isDirectory() || javaHome.getParentFile() == null) {
      return Collections.emptyList();
    }
    ArrayList<String> result = new ArrayList<>();
    File javasFolder = javaHome.getParentFile();
    scanFolder(javasFolder, result);
    File parentFile = javasFolder.getParentFile();
    File root = parentFile != null ? parentFile.getParentFile() : null;
    String name = parentFile != null ? parentFile.getName() : "";
    if (name.contains("Program Files") && root != null) {
      String x86Suffix = " (x86)";
      boolean x86 = name.endsWith(x86Suffix) && name.length() > x86Suffix.length();
      File anotherJavasFolder;
      if (x86) {
        anotherJavasFolder = new File(root, name.substring(0, name.length() - x86Suffix.length()));
      } else {
        anotherJavasFolder = new File(root, name + x86Suffix);
      }
      if (anotherJavasFolder.isDirectory()) {
        scanFolder(new File(anotherJavasFolder, javasFolder.getName()), result);
      }
    }
    return result;
  }

  private static void scanFolder(File javasFolder, List<String> result) {
    @SuppressWarnings("RedundantCast")
    File[] candidates = javasFolder.listFiles((FileFilter) JdkUtil::checkForJdk);
    if (candidates != null) {
      for (File file : candidates) {
        result.add(file.getAbsolutePath());
      }
    }
  }

  @NotNull
  @Override
  public FileChooserDescriptor getHomeChooserDescriptor() {
    final FileChooserDescriptor baseDescriptor = super.getHomeChooserDescriptor();
    final FileChooserDescriptor descriptor =
        new FileChooserDescriptor(baseDescriptor) {
          @Override
          public void validateSelectedFiles(VirtualFile[] files) throws Exception {
            if (files.length > 0 && !JrtFileSystem.isSupported()) {
              String path = files[0].getPath();
              if (JrtFileSystem.isModularJdk(path)
                  || JrtFileSystem.isModularJdk(adjustSelectedSdkHome(path))) {
                throw new Exception(LangBundle.message("jrt.not.available.message"));
              }
            }
            baseDescriptor.validateSelectedFiles(files);
          }
        };
    descriptor.putUserData(KEY, Boolean.TRUE);
    return descriptor;
  }

  @NotNull
  @Override
  public String adjustSelectedSdkHome(@NotNull String homePath) {
    if (SystemInfo.isMac) {
      File home = new File(homePath, "/Home");
      if (home.exists()) return home.getPath();

      home = new File(homePath, "Contents/Home");
      if (home.exists()) return home.getPath();
    }

    return homePath;
  }

  @Override
  public boolean isValidSdkHome(String path) {
    return checkForJdk(new File(path))
        && (!JrtFileSystem.isModularJdk(path) || JrtFileSystem.isSupported());
  }

  @Override
  public String suggestSdkName(String currentSdkName, String sdkHome) {
    final String suggestedName;
    if (currentSdkName != null && !currentSdkName.isEmpty()) {
      final Matcher matcher = VERSION_STRING_PATTERN.matcher(currentSdkName);
      final boolean replaceNameWithVersion = matcher.matches();
      if (replaceNameWithVersion) {
        // user did not change name -> set it automatically
        final String versionString = getVersionString(sdkHome);
        suggestedName =
            versionString == null
                ? currentSdkName
                : matcher.replaceFirst("$1" + versionString + "$3");
      } else {
        suggestedName = currentSdkName;
      }
    } else {
      String versionString = getVersionString(sdkHome);
      suggestedName =
          versionString == null
              ? ProjectBundle.message("sdk.java.unknown.name")
              : getVersionNumber(versionString);
    }
    return suggestedName;
  }

  @NotNull
  private static String getVersionNumber(@NotNull String versionString) {
    if (versionString.startsWith(JAVA_VERSION_PREFIX)
        || versionString.startsWith(OPENJDK_VERSION_PREFIX)) {
      boolean openJdk = versionString.startsWith(OPENJDK_VERSION_PREFIX);
      versionString =
          versionString.substring(
              openJdk ? OPENJDK_VERSION_PREFIX.length() : JAVA_VERSION_PREFIX.length());
      if (versionString.startsWith("\"") && versionString.endsWith("\"")) {
        versionString = versionString.substring(1, versionString.length() - 1);
      }
      int dotIdx = versionString.indexOf('.');
      if (dotIdx > 0) {
        try {
          int major = Integer.parseInt(versionString.substring(0, dotIdx));
          int minorDot = versionString.indexOf('.', dotIdx + 1);
          if (minorDot > 0) {
            int minor = Integer.parseInt(versionString.substring(dotIdx + 1, minorDot));
            versionString = major + "." + minor;
          }
        } catch (NumberFormatException e) {
          // Do nothing. Use original version string if failed to parse according to major.minor
          // pattern.
        }
      }
    }
    return versionString;
  }

  @Override
  @SuppressWarnings("HardCodedStringLiteral")
  public void setupSdkPaths(@NotNull Sdk sdk) {
    String homePath = sdk.getHomePath();
    assert homePath != null : sdk;

    File jdkHome = new File(homePath);
    List<VirtualFile> classes = findClasses(jdkHome, false);
    VirtualFile sources = findSources(jdkHome);
    VirtualFile docs = findDocs(jdkHome, "docs/api");
    SdkModificator sdkModificator = sdk.getSdkModificator();

    Set<VirtualFile> previousRoots =
        new LinkedHashSet<>(Arrays.asList(sdkModificator.getRoots(OrderRootType.CLASSES)));
    sdkModificator.removeRoots(OrderRootType.CLASSES);
    previousRoots.removeAll(new HashSet<>(classes));
    for (VirtualFile aClass : classes) {
      sdkModificator.addRoot(aClass, OrderRootType.CLASSES);
    }
    for (VirtualFile root : previousRoots) {
      sdkModificator.addRoot(root, OrderRootType.CLASSES);
    }

    if (sources != null) {
      sdkModificator.addRoot(sources, OrderRootType.SOURCES);
    }
    VirtualFile javaFxSources = findSources(jdkHome, "javafx-src");
    if (javaFxSources != null) {
      sdkModificator.addRoot(javaFxSources, OrderRootType.SOURCES);
    }

    if (docs != null) {
      sdkModificator.addRoot(docs, JavadocOrderRootType.getInstance());
    } else if (SystemInfo.isMac) {
      VirtualFile commonDocs = findDocs(jdkHome, "docs");
      if (commonDocs == null) {
        commonDocs = findInJar(new File(jdkHome, "docs.jar"), "doc/api");
        if (commonDocs == null) {
          commonDocs = findInJar(new File(jdkHome, "docs.jar"), "docs/api");
        }
      }
      if (commonDocs != null) {
        sdkModificator.addRoot(commonDocs, JavadocOrderRootType.getInstance());
      }

      VirtualFile appleDocs = findDocs(jdkHome, "appledocs");
      if (appleDocs == null) {
        appleDocs = findInJar(new File(jdkHome, "appledocs.jar"), "appledoc/api");
      }
      if (appleDocs != null) {
        sdkModificator.addRoot(appleDocs, JavadocOrderRootType.getInstance());
      }

      if (commonDocs == null && appleDocs == null && sources == null) {
        String url = getDefaultDocumentationUrl(sdk);
        if (url != null) {
          sdkModificator.addRoot(
              VirtualFileManager.getInstance().findFileByUrl(url),
              JavadocOrderRootType.getInstance());
        }
      }
    } else if (getVersion(sdk) == JavaSdkVersion.JDK_1_7) {
      VirtualFile url =
          VirtualFileManager.getInstance().findFileByUrl("http://docs.oracle.com/javafx/2/api/");
      sdkModificator.addRoot(url, JavadocOrderRootType.getInstance());
    }

    attachJdkAnnotations(sdkModificator);

    sdkModificator.commitChanges();
  }

  public static void attachJdkAnnotations(@NotNull SdkModificator modificator) {
    LocalFileSystem lfs = LocalFileSystem.getInstance();
    List<String> pathsChecked = new ArrayList<>();
    // community idea under idea
    String path =
        FileUtil.toSystemIndependentName(PathManager.getHomePath()) + "/java/jdkAnnotations";
    VirtualFile root = lfs.findFileByPath(path);
    pathsChecked.add(path);

    if (root == null) { // idea under idea
      path =
          FileUtil.toSystemIndependentName(PathManager.getHomePath())
              + "/community/java/jdkAnnotations";
      root = lfs.findFileByPath(path);
      pathsChecked.add(path);
    }
    if (root == null) { // build
      String url =
          "jar://"
              + FileUtil.toSystemIndependentName(PathManager.getHomePath())
              + "/lib/jdkAnnotations.jar!/";
      root = VirtualFileManager.getInstance().findFileByUrl(url);
      pathsChecked.add(
          FileUtil.toSystemIndependentName(PathManager.getHomePath()) + "/lib/jdkAnnotations.jar");
    }
    if (root == null) {
      String msg = "Paths checked:\n";
      for (String p : pathsChecked) {
        File file = new File(p);
        msg +=
            "Path: '"
                + p
                + "' "
                + (file.exists() ? "Found" : "Not found")
                + "; directory children: "
                + Arrays.toString(file.getParentFile().listFiles())
                + "\n";
      }
      LOG.error("JDK annotations not found", msg);
      return;
    }

    OrderRootType annoType = AnnotationOrderRootType.getInstance();
    modificator.removeRoot(root, annoType);
    modificator.addRoot(root, annoType);
  }

  private final Map<String, String> myCachedVersionStrings =
      Collections.synchronizedMap(new HashMap<String, String>());

  @Override
  public final String getVersionString(String sdkHome) {
    String versionString = myCachedVersionStrings.get(sdkHome);
    if (versionString == null) {
      versionString = getJdkVersion(sdkHome);
      if (!StringUtil.isEmpty(versionString)) {
        myCachedVersionStrings.put(sdkHome, versionString);
      }
    }
    return versionString;
  }

  @Override
  public int compareTo(@NotNull String versionString, @NotNull String versionNumber) {
    return getVersionNumber(versionString).compareTo(versionNumber);
  }

  @Override
  public JavaSdkVersion getVersion(@NotNull Sdk sdk) {
    String version = sdk.getVersionString();
    if (version == null) return null;
    return JdkVersionUtil.getVersion(version);
  }

  @Override
  @Nullable
  public JavaSdkVersion getVersion(@NotNull String versionString) {
    return JdkVersionUtil.getVersion(versionString);
  }

  @Override
  public boolean isOfVersionOrHigher(@NotNull Sdk sdk, @NotNull JavaSdkVersion version) {
    JavaSdkVersion sdkVersion = getVersion(sdk);
    return sdkVersion != null && sdkVersion.isAtLeast(version);
  }

  @NotNull
  @Override
  public Sdk createJdk(@NotNull String jdkName, @NotNull String home, boolean isJre) {
    ProjectJdkImpl jdk = new ProjectJdkImpl(jdkName, this);
    SdkModificator sdkModificator = jdk.getSdkModificator();

    String path = home.replace(File.separatorChar, '/');
    sdkModificator.setHomePath(path);
    sdkModificator.setVersionString(
        jdkName); // must be set after home path, otherwise setting home path clears the version
                  // string

    File jdkHomeFile = new File(home);
    addClasses(jdkHomeFile, sdkModificator, isJre);
    addSources(jdkHomeFile, sdkModificator);
    addDocs(jdkHomeFile, sdkModificator);
    sdkModificator.commitChanges();

    return jdk;
  }

  @NotNull
  @TestOnly
  public Sdk createMockJdk(@NotNull String jdkName, @NotNull String home, boolean isJre) {
    String homePath = home.replace(File.separatorChar, '/');
    File jdkHomeFile = new File(homePath);
    List<VirtualFile> classes = findClasses(jdkHomeFile, isJre);
    VirtualFile sources = findSources(jdkHomeFile);
    VirtualFile docs = findDocs(jdkHomeFile, "docs/api");
    ProjectRootContainerImpl rootContainer = new ProjectRootContainerImpl(true);
    rootContainer.startChange();
    for (VirtualFile aClass : classes) {
      rootContainer.addRoot(aClass, OrderRootType.CLASSES);
    }
    if (sources != null) {
      rootContainer.addRoot(sources, OrderRootType.SOURCES);
    }
    if (docs != null) {
      rootContainer.addRoot(docs, OrderRootType.DOCUMENTATION);
    }
    rootContainer.finishChange();

    ProjectJdkImpl jdk =
        new ProjectJdkImpl(jdkName, this, homePath, jdkName) {
          @Override
          public void setName(@NotNull String name) {
            throwReadOnly();
          }

          @Override
          public void readExternal(@NotNull Element element) {
            throwReadOnly();
          }

          @Override
          public void readExternal(
              @NotNull Element element, @Nullable ProjectJdkTable projectJdkTable) {
            throwReadOnly();
          }

          @NotNull
          @Override
          public SdkModificator getSdkModificator() {
            throwReadOnly();
            return null;
          }

          @Override
          public void setSdkAdditionalData(SdkAdditionalData data) {
            throwReadOnly();
          }

          @Override
          public void addRoot(VirtualFile root, OrderRootType rootType) {
            throwReadOnly();
          }

          @Override
          public void removeRoot(VirtualFile root, OrderRootType rootType) {
            throwReadOnly();
          }

          @Override
          public void removeRoots(OrderRootType rootType) {
            throwReadOnly();
          }

          @Override
          public void removeAllRoots() {
            throwReadOnly();
          }

          @Override
          public boolean isWritable() {
            return false;
          }

          @Override
          public void update() {
            throwReadOnly();
          }

          @Override
          public VirtualFile[] getRoots(OrderRootType rootType) {
            return rootContainer.getRootFiles(rootType);
          }

          @NotNull
          @Override
          public RootProvider getRootProvider() {
            return new RootProvider() {
              @NotNull
              @Override
              public String[] getUrls(@NotNull OrderRootType rootType) {
                return ContainerUtil.map2Array(
                    getFiles(rootType), String.class, VirtualFile::getUrl);
              }

              @NotNull
              @Override
              public VirtualFile[] getFiles(@NotNull OrderRootType rootType) {
                return getRoots(rootType);
              }

              @Override
              public void addRootSetChangedListener(@NotNull RootSetChangedListener listener) {}

              @Override
              public void addRootSetChangedListener(
                  @NotNull RootSetChangedListener listener, @NotNull Disposable parentDisposable) {}

              @Override
              public void removeRootSetChangedListener(@NotNull RootSetChangedListener listener) {}
            };
          }
        };

    ProjectJdkImpl.copyRoots(rootContainer, jdk);
    return jdk;
  }

  private static void throwReadOnly() {
    throw new IncorrectOperationException(
        "Can't modify, MockJDK is read-only, consider calling .clone() first");
  }

  private static void addClasses(File file, SdkModificator sdkModificator, boolean isJre) {
    for (VirtualFile virtualFile : findClasses(file, isJre)) {
      sdkModificator.addRoot(virtualFile, OrderRootType.CLASSES);
    }
  }

  @NotNull
  private static List<VirtualFile> findClasses(File file, boolean isJre) {
    List<VirtualFile> result = ContainerUtil.newArrayList();
    VirtualFileManager fileManager = VirtualFileManager.getInstance();

    String path = file.getPath();
    if (JrtFileSystem.isModularJdk(path)) {
      String url =
          VirtualFileManager.constructUrl(
              JrtFileSystem.PROTOCOL,
              FileUtil.toSystemIndependentName(path) + JrtFileSystem.SEPARATOR);
      for (String module : JrtFileSystem.listModules(path)) {
        ContainerUtil.addIfNotNull(result, fileManager.findFileByUrl(url + module));
      }
    }

    for (File root : JavaSdkUtil.getJdkClassesRoots(file, isJre)) {
      String url = VfsUtil.getUrlForLibraryRoot(root);
      ContainerUtil.addIfNotNull(result, fileManager.findFileByUrl(url));
    }

    Collections.sort(result, (o1, o2) -> o1.getPath().compareTo(o2.getPath()));

    return result;
  }

  private static void addSources(@NotNull File file, @NotNull SdkModificator sdkModificator) {
    VirtualFile vFile = findSources(file);
    if (vFile != null) {
      sdkModificator.addRoot(vFile, OrderRootType.SOURCES);
    }
  }

  @Nullable
  @SuppressWarnings("HardCodedStringLiteral")
  private static VirtualFile findSources(File file) {
    return findSources(file, "src");
  }

  @Nullable
  @SuppressWarnings("HardCodedStringLiteral")
  private static VirtualFile findSources(File file, final String srcName) {
    File jarFile = new File(file, srcName + ".jar");
    if (!jarFile.exists()) {
      jarFile = new File(file, srcName + ".zip");
    }

    if (jarFile.exists()) {
      VirtualFile vFile = findInJar(jarFile, "src");
      if (vFile != null) return vFile;
      // try 1.4 format
      vFile = findInJar(jarFile, "");
      return vFile;
    } else {
      File srcDir = new File(file, "src");
      if (!srcDir.exists() || !srcDir.isDirectory()) return null;
      String path = srcDir.getAbsolutePath().replace(File.separatorChar, '/');
      return LocalFileSystem.getInstance().findFileByPath(path);
    }
  }

  @SuppressWarnings("HardCodedStringLiteral")
  private static void addDocs(File file, SdkModificator rootContainer) {
    VirtualFile vFile = findDocs(file, "docs/api");
    if (vFile != null) {
      rootContainer.addRoot(vFile, JavadocOrderRootType.getInstance());
    }
  }

  @Nullable
  private static VirtualFile findInJar(File jarFile, String relativePath) {
    if (!jarFile.exists()) return null;
    String url =
        JarFileSystem.PROTOCOL_PREFIX
            + jarFile.getAbsolutePath().replace(File.separatorChar, '/')
            + JarFileSystem.JAR_SEPARATOR
            + relativePath;
    return VirtualFileManager.getInstance().findFileByUrl(url);
  }

  @Nullable
  private static VirtualFile findDocs(@NotNull File file, @NotNull String relativePath) {
    file =
        new File(
            file.getAbsolutePath()
                + File.separator
                + relativePath.replace('/', File.separatorChar));
    if (!file.exists() || !file.isDirectory()) return null;
    String path = file.getAbsolutePath().replace(File.separatorChar, '/');
    return LocalFileSystem.getInstance().findFileByPath(path);
  }

  @Override
  public boolean isRootTypeApplicable(@NotNull OrderRootType type) {
    return type == OrderRootType.CLASSES
        || type == OrderRootType.SOURCES
        || type == JavadocOrderRootType.getInstance()
        || type == AnnotationOrderRootType.getInstance();
  }
}
/** @author Eugene Zhuravlev Date: 9/6/11 */
public class BuildManager implements ApplicationComponent {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.compiler.server.BuildManager");
  private static final String SYSTEM_ROOT = "compile-server";
  private static final String LOGGER_CONFIG = "log.xml";
  private static final String DEFAULT_LOGGER_CONFIG = "defaultLogConfig.xml";
  private static final int MAKE_TRIGGER_DELAY = 5 * 1000 /*5 seconds*/;

  private final File mySystemDirectory;
  private final ProjectManager myProjectManager;

  private final Map<RequestFuture, Project> myAutomakeFutures =
      new HashMap<RequestFuture, Project>();
  private final Map<String, RequestFuture> myBuildsInProgress =
      Collections.synchronizedMap(new HashMap<String, RequestFuture>());
  private final CompileServerClasspathManager myClasspathManager =
      new CompileServerClasspathManager();
  private final Executor myPooledThreadExecutor =
      new Executor() {
        @Override
        public void execute(Runnable command) {
          ApplicationManager.getApplication().executeOnPooledThread(command);
        }
      };
  private final SequentialTaskExecutor myEventsProcessor =
      new SequentialTaskExecutor(myPooledThreadExecutor);

  private final Map<String, ProjectData> myProjectDataMap =
      Collections.synchronizedMap(new HashMap<String, ProjectData>());

  private final ChannelGroup myAllOpenChannels = new DefaultChannelGroup("build-manager");
  private final BuildMessageDispatcher myMessageDispatcher = new BuildMessageDispatcher();
  private int myListenPort = -1;
  private volatile CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings myGlobals;

  public BuildManager(final ProjectManager projectManager) {
    myProjectManager = projectManager;
    final String systemPath = PathManager.getSystemPath();
    File system = new File(systemPath);
    try {
      system = system.getCanonicalFile();
    } catch (IOException e) {
      LOG.info(e);
    }
    mySystemDirectory = system;

    projectManager.addProjectManagerListener(new ProjectWatcher());
    final MessageBusConnection conn = ApplicationManager.getApplication().getMessageBus().connect();
    conn.subscribe(
        VirtualFileManager.VFS_CHANGES,
        new BulkFileListener() {
          private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
          private final AtomicBoolean myAutoMakeInProgress = new AtomicBoolean(false);

          @Override
          public void before(@NotNull List<? extends VFileEvent> events) {}

          @Override
          public void after(@NotNull List<? extends VFileEvent> events) {
            if (shouldTriggerMake(events)) {
              scheduleMake(
                  new Runnable() {
                    @Override
                    public void run() {
                      if (!myAutoMakeInProgress.getAndSet(true)) {
                        try {
                          ApplicationManager.getApplication()
                              .executeOnPooledThread(
                                  new Runnable() {
                                    @Override
                                    public void run() {
                                      try {
                                        runAutoMake();
                                      } finally {
                                        myAutoMakeInProgress.set(false);
                                      }
                                    }
                                  });
                        } catch (RejectedExecutionException ignored) {
                          // we were shut down
                        }
                      } else {
                        scheduleMake(this);
                      }
                    }
                  });
            }
          }

          private void scheduleMake(Runnable runnable) {
            myAlarm.cancelAllRequests();
            myAlarm.addRequest(runnable, MAKE_TRIGGER_DELAY);
          }

          private boolean shouldTriggerMake(List<? extends VFileEvent> events) {
            if (!CompilerWorkspaceConfiguration.useServerlessOutOfProcessBuild()) {
              return false;
            }
            for (VFileEvent event : events) {
              if (event.isFromRefresh() || event.getRequestor() instanceof SavingRequestor) {
                return true;
              }
            }
            return false;
          }
        });

    ShutDownTracker.getInstance()
        .registerShutdownTask(
            new Runnable() {
              @Override
              public void run() {
                stopListening();
              }
            });
  }

  public static BuildManager getInstance() {
    return ApplicationManager.getApplication().getComponent(BuildManager.class);
  }

  public void notifyFilesChanged(final Collection<String> paths) {
    doNotify(paths, false);
  }

  public void notifyFilesDeleted(Collection<String> paths) {
    doNotify(paths, true);
  }

  private void doNotify(final Collection<String> paths, final boolean notifyDeletion) {
    // ensure events processed in the order they arrived
    myEventsProcessor.submit(
        new Runnable() {
          @Override
          public void run() {
            synchronized (myProjectDataMap) {
              for (Map.Entry<String, ProjectData> entry : myProjectDataMap.entrySet()) {
                final ProjectData data = entry.getValue();
                if (notifyDeletion) {
                  data.addDeleted(paths);
                } else {
                  data.addChanged(paths);
                }
                final RequestFuture future = myBuildsInProgress.get(entry.getKey());
                if (future != null && !future.isCancelled() && !future.isDone()) {
                  final UUID sessionId = future.getRequestID();
                  final Channel channel = myMessageDispatcher.getConnectedChannel(sessionId);
                  if (channel != null) {
                    final CmdlineRemoteProto.Message.ControllerMessage message =
                        CmdlineRemoteProto.Message.ControllerMessage.newBuilder()
                            .setType(CmdlineRemoteProto.Message.ControllerMessage.Type.FS_EVENT)
                            .setFsEvent(data.createNextEvent())
                            .build();
                    Channels.write(channel, CmdlineProtoUtil.toMessage(sessionId, message));
                  }
                }
              }
            }
          }
        });
  }

  public void clearState(Project project) {
    myGlobals = null;
    final String projectPath = getProjectPath(project);
    synchronized (myProjectDataMap) {
      final ProjectData data = myProjectDataMap.get(projectPath);
      if (data != null) {
        data.dropChanges();
      }
    }
  }

  @Nullable
  private static String getProjectPath(final Project project) {
    final String path = project.getPresentableUrl();
    if (path == null) {
      return null;
    }
    final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(path);
    return vFile != null ? vFile.getPath() : null;
  }

  private void runAutoMake() {
    if (ApplicationManager.getApplication().isUnitTestMode()) {
      return;
    }
    final Project[] openProjects = myProjectManager.getOpenProjects();
    if (openProjects.length > 0) {
      final List<RequestFuture> futures = new ArrayList<RequestFuture>();
      for (final Project project : openProjects) {
        if (project.isDefault() || project.isDisposed()) {
          continue;
        }
        final CompilerWorkspaceConfiguration config =
            CompilerWorkspaceConfiguration.getInstance(project);
        if (!config.useOutOfProcessBuild() || !config.MAKE_PROJECT_ON_SAVE) {
          continue;
        }
        final List<String> emptyList = Collections.emptyList();
        final RequestFuture future =
            scheduleBuild(
                project,
                false,
                true,
                emptyList,
                emptyList,
                emptyList,
                Collections.<String, String>emptyMap(),
                new AutoMakeMessageHandler(project));
        if (future != null) {
          futures.add(future);
          synchronized (myAutomakeFutures) {
            myAutomakeFutures.put(future, project);
          }
        }
      }
      try {
        for (RequestFuture future : futures) {
          future.waitFor();
        }
      } finally {
        synchronized (myAutomakeFutures) {
          myAutomakeFutures.keySet().removeAll(futures);
        }
      }
    }
  }

  public Collection<RequestFuture> cancelAutoMakeTasks(Project project) {
    final Collection<RequestFuture> futures = new ArrayList<RequestFuture>();
    synchronized (myAutomakeFutures) {
      for (Map.Entry<RequestFuture, Project> entry : myAutomakeFutures.entrySet()) {
        if (entry.getValue().equals(project)) {
          final RequestFuture future = entry.getKey();
          future.cancel(false);
          futures.add(future);
        }
      }
    }
    return futures;
  }

  @Nullable
  public RequestFuture scheduleBuild(
      final Project project,
      final boolean isRebuild,
      final boolean isMake,
      final Collection<String> modules,
      final Collection<String> artifacts,
      final Collection<String> paths,
      final Map<String, String> userData,
      DefaultMessageHandler handler) {

    final String projectPath = getProjectPath(project);
    final UUID sessionId = UUID.randomUUID();
    final CmdlineRemoteProto.Message.ControllerMessage params;
    CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings globals = myGlobals;
    if (globals == null) {
      globals = buildGlobalSettings();
      myGlobals = globals;
    }

    CmdlineRemoteProto.Message.ControllerMessage.FSEvent currentFSChanges = null;
    final SequentialTaskExecutor projectTaskQueue;
    synchronized (myProjectDataMap) {
      ProjectData data = myProjectDataMap.get(projectPath);
      if (data == null) {
        data = new ProjectData(new SequentialTaskExecutor(myPooledThreadExecutor));
        myProjectDataMap.put(projectPath, data);
      }
      if (isRebuild) {
        data.dropChanges();
      }
      currentFSChanges = data.getAndResetRescanFlag() ? null : data.createNextEvent();
      projectTaskQueue = data.taskQueue;
    }

    if (isRebuild) {
      params = CmdlineProtoUtil.createRebuildRequest(projectPath, userData, globals);
    } else {
      params =
          isMake
              ? CmdlineProtoUtil.createMakeRequest(
                  projectPath, modules, artifacts, userData, globals, currentFSChanges)
              : CmdlineProtoUtil.createForceCompileRequest(
                  projectPath, modules, artifacts, paths, userData, globals, currentFSChanges);
    }

    myMessageDispatcher.registerBuildMessageHandler(sessionId, handler, params);

    // ensure server is listening
    if (myListenPort < 0) {
      try {
        myListenPort = startListening();
      } catch (Exception e) {
        myMessageDispatcher.unregisterBuildMessageHandler(sessionId);
        handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), null));
        handler.sessionTerminated();
        return null;
      }
    }

    final RequestFuture<BuilderMessageHandler> future =
        new RequestFuture<BuilderMessageHandler>(
            handler,
            sessionId,
            new RequestFuture.CancelAction<BuilderMessageHandler>() {
              @Override
              public void cancel(RequestFuture<BuilderMessageHandler> future) throws Exception {
                myMessageDispatcher.cancelSession(future.getRequestID());
              }
            });

    projectTaskQueue.submit(
        new Runnable() {
          @Override
          public void run() {
            try {
              if (project.isDisposed()) {
                future.cancel(false);
                return;
              }
              myBuildsInProgress.put(projectPath, future);
              final Process process = launchBuildProcess(project, myListenPort, sessionId);
              final OSProcessHandler processHandler =
                  new OSProcessHandler(process, null) {
                    @Override
                    protected boolean shouldDestroyProcessRecursively() {
                      return true;
                    }
                  };
              final StringBuilder stdErrOutput = new StringBuilder();
              processHandler.addProcessListener(
                  new ProcessAdapter() {
                    @Override
                    public void processTerminated(ProcessEvent event) {
                      final BuilderMessageHandler handler =
                          myMessageDispatcher.unregisterBuildMessageHandler(sessionId);
                      if (handler != null) {
                        handler.sessionTerminated();
                      }
                    }

                    @Override
                    public void onTextAvailable(ProcessEvent event, Key outputType) {
                      // re-translate builder's output to idea.log
                      final String text = event.getText();
                      if (!StringUtil.isEmpty(text)) {
                        LOG.info("BUILDER_PROCESS [" + outputType.toString() + "]: " + text.trim());
                        if (stdErrOutput.length() < 1024
                            && ProcessOutputTypes.STDERR.equals(outputType)) {
                          stdErrOutput.append(text);
                        }
                      }
                    }
                  });
              processHandler.startNotify();
              final boolean terminated = processHandler.waitFor();
              if (terminated) {
                final int exitValue = processHandler.getProcess().exitValue();
                if (exitValue != 0) {
                  final StringBuilder msg = new StringBuilder();
                  msg.append("Abnormal build process termination: ");
                  if (stdErrOutput.length() > 0) {
                    msg.append("\n").append(stdErrOutput);
                  } else {
                    msg.append("unknown error");
                  }
                  future
                      .getMessageHandler()
                      .handleFailure(
                          sessionId, CmdlineProtoUtil.createFailure(msg.toString(), null));
                }
              } else {
                future
                    .getMessageHandler()
                    .handleFailure(
                        sessionId,
                        CmdlineProtoUtil.createFailure("Disconnected from build process", null));
              }
            } catch (ExecutionException e) {
              myMessageDispatcher.unregisterBuildMessageHandler(sessionId);
              future
                  .getMessageHandler()
                  .handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), e));
              future.getMessageHandler().sessionTerminated();
            } finally {
              myBuildsInProgress.remove(projectPath);
              future.setDone();
            }
          }
        });

    return future;
  }

  @Override
  public void initComponent() {}

  @Override
  public void disposeComponent() {
    stopListening();
  }

  @NotNull
  @Override
  public String getComponentName() {
    return "com.intellij.compiler.server.BuildManager";
  }

  private static CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings buildGlobalSettings() {
    final Map<String, String> data = new HashMap<String, String>();

    for (Map.Entry<String, String> entry : PathMacrosImpl.getGlobalSystemMacros().entrySet()) {
      data.put(entry.getKey(), FileUtil.toSystemIndependentName(entry.getValue()));
    }

    final PathMacros pathVars = PathMacros.getInstance();
    for (String name : pathVars.getAllMacroNames()) {
      final String path = pathVars.getValue(name);
      if (path != null) {
        data.put(name, FileUtil.toSystemIndependentName(path));
      }
    }

    final List<GlobalLibrary> globals = new ArrayList<GlobalLibrary>();

    fillSdks(globals);
    fillGlobalLibraries(globals);

    final CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings.Builder cmdBuilder =
        CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings.newBuilder();

    if (!data.isEmpty()) {
      for (Map.Entry<String, String> entry : data.entrySet()) {
        final String var = entry.getKey();
        final String value = entry.getValue();
        if (var != null && value != null) {
          cmdBuilder.addPathVariable(CmdlineProtoUtil.createPair(var, value));
        }
      }
    }

    if (!globals.isEmpty()) {
      for (GlobalLibrary lib : globals) {
        final CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings.GlobalLibrary.Builder
            libBuilder =
                CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings.GlobalLibrary
                    .newBuilder();
        libBuilder.setName(lib.getName()).addAllPath(lib.getPaths());
        if (lib instanceof SdkLibrary) {
          final SdkLibrary sdk = (SdkLibrary) lib;
          libBuilder.setHomePath(sdk.getHomePath());
          libBuilder.setTypeName(sdk.getTypeName());
          final String additional = sdk.getAdditionalDataXml();
          if (additional != null) {
            libBuilder.setAdditionalDataXml(additional);
          }
          final String version = sdk.getVersion();
          if (version != null) {
            libBuilder.setVersion(version);
          }
        }
        cmdBuilder.addGlobalLibrary(libBuilder.build());
      }
    }

    final String defaultCharset = EncodingManager.getInstance().getDefaultCharsetName();
    if (defaultCharset != null) {
      cmdBuilder.setGlobalEncoding(defaultCharset);
    }

    final String ignoredFilesList = FileTypeManager.getInstance().getIgnoredFilesList();
    cmdBuilder.setIgnoredFilesPatterns(ignoredFilesList);
    return cmdBuilder.build();
  }

  private static void fillSdks(List<GlobalLibrary> globals) {
    for (Sdk sdk : ProjectJdkTable.getInstance().getAllJdks()) {
      final String name = sdk.getName();
      final String homePath = sdk.getHomePath();
      if (homePath == null) {
        continue;
      }
      final SdkAdditionalData data = sdk.getSdkAdditionalData();
      final String additionalDataXml;
      final SdkType sdkType = (SdkType) sdk.getSdkType();
      if (data == null) {
        additionalDataXml = null;
      } else {
        final Element element = new Element("additional");
        sdkType.saveAdditionalData(data, element);
        additionalDataXml = JDOMUtil.writeElement(element, "\n");
      }
      final List<String> paths =
          convertToLocalPaths(sdk.getRootProvider().getFiles(OrderRootType.CLASSES));
      String versionString = sdk.getVersionString();
      if (versionString != null && sdkType instanceof JavaSdk) {
        final JavaSdkVersion version = ((JavaSdk) sdkType).getVersion(versionString);
        if (version != null) {
          versionString = version.getDescription();
        }
      }
      globals.add(
          new SdkLibrary(
              name, sdkType.getName(), versionString, homePath, paths, additionalDataXml));
    }
  }

  private static void fillGlobalLibraries(List<GlobalLibrary> globals) {
    final Iterator<Library> iterator =
        LibraryTablesRegistrar.getInstance().getLibraryTable().getLibraryIterator();
    while (iterator.hasNext()) {
      Library library = iterator.next();
      final String name = library.getName();

      if (name != null) {
        final List<String> paths = convertToLocalPaths(library.getFiles(OrderRootType.CLASSES));
        globals.add(new GlobalLibrary(name, paths));
      }
    }
  }

  private static List<String> convertToLocalPaths(VirtualFile[] files) {
    final List<String> paths = new ArrayList<String>();
    for (VirtualFile file : files) {
      if (file.isValid()) {
        paths.add(
            StringUtil.trimEnd(
                FileUtil.toSystemIndependentName(file.getPath()), JarFileSystem.JAR_SEPARATOR));
      }
    }
    return paths;
  }

  private Process launchBuildProcess(Project project, final int port, final UUID sessionId)
      throws ExecutionException {
    // choosing sdk with which the build process should be run
    final Sdk internalJdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk();
    Sdk projectJdk = internalJdk;
    final String versionString = projectJdk.getVersionString();
    JavaSdkVersion sdkVersion =
        versionString != null
            ? ((JavaSdk) projectJdk.getSdkType()).getVersion(versionString)
            : null;
    if (sdkVersion != null) {
      final Set<Sdk> candidates = new HashSet<Sdk>();
      for (Module module : ModuleManager.getInstance(project).getModules()) {
        final Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
        if (sdk != null && sdk.getSdkType() instanceof JavaSdk) {
          candidates.add(sdk);
        }
      }
      // now select the latest version from the sdks that are used in the project, but not older
      // than the internal sdk version
      for (Sdk candidate : candidates) {
        final String vs = candidate.getVersionString();
        if (vs != null) {
          final JavaSdkVersion candidateVersion = ((JavaSdk) candidate.getSdkType()).getVersion(vs);
          if (candidateVersion != null) {
            if (candidateVersion.compareTo(sdkVersion) > 0) {
              sdkVersion = candidateVersion;
              projectJdk = candidate;
            }
          }
        }
      }
    }

    // validate tools.jar presence
    final File compilerPath;
    if (projectJdk.equals(internalJdk)) {
      final JavaCompiler systemCompiler = ToolProvider.getSystemJavaCompiler();
      if (systemCompiler == null) {
        throw new ExecutionException(
            "No system java compiler is provided by the JRE. Make sure tools.jar is present in IntelliJ IDEA classpath.");
      }
      compilerPath = ClasspathBootstrap.getResourcePath(systemCompiler.getClass());
    } else {
      final String path = ((JavaSdk) projectJdk.getSdkType()).getToolsPath(projectJdk);
      if (path == null) {
        throw new ExecutionException(
            "Cannot determine path to 'tools.jar' library for "
                + projectJdk.getName()
                + " ("
                + projectJdk.getHomePath()
                + ")");
      }
      compilerPath = new File(path);
    }

    final GeneralCommandLine cmdLine = new GeneralCommandLine();
    final String vmExecutablePath =
        ((JavaSdkType) projectJdk.getSdkType()).getVMExecutablePath(projectJdk);
    cmdLine.setExePath(vmExecutablePath);
    cmdLine.addParameter("-XX:MaxPermSize=150m");
    cmdLine.addParameter("-XX:ReservedCodeCacheSize=64m");
    final int heapSize = Registry.intValue("compiler.process.heap.size");
    final int xms = heapSize / 2;
    if (xms > 32) {
      cmdLine.addParameter("-Xms" + xms + "m");
    }
    cmdLine.addParameter("-Xmx" + heapSize + "m");

    if (SystemInfo.isMac
        && sdkVersion != null
        && JavaSdkVersion.JDK_1_6.equals(sdkVersion)
        && Registry.is("compiler.process.32bit.vm.on.mac")) {
      // unfortunately -d32 is supported on jdk 1.6 only
      cmdLine.addParameter("-d32");
    }

    cmdLine.addParameter("-Djava.awt.headless=true");

    final String shouldGenerateIndex =
        System.getProperty(GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION);
    if (shouldGenerateIndex != null) {
      cmdLine.addParameter(
          "-D" + GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION + "=" + shouldGenerateIndex);
    }

    final String additionalOptions = Registry.stringValue("compiler.process.vm.options");
    if (!StringUtil.isEmpty(additionalOptions)) {
      final StringTokenizer tokenizer = new StringTokenizer(additionalOptions, " ", false);
      while (tokenizer.hasMoreTokens()) {
        cmdLine.addParameter(tokenizer.nextToken());
      }
    }

    // debugging
    final int debugPort = Registry.intValue("compiler.process.debug.port");
    if (debugPort > 0) {
      cmdLine.addParameter("-XX:+HeapDumpOnOutOfMemoryError");
      cmdLine.addParameter(
          "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=" + debugPort);
    }

    if (Registry.is("compiler.process.use.memory.temp.cache")) {
      cmdLine.addParameter("-D" + GlobalOptions.USE_MEMORY_TEMP_CACHE_OPTION);
    }
    if (Registry.is("compiler.process.use.external.javac")) {
      cmdLine.addParameter("-D" + GlobalOptions.USE_EXTERNAL_JAVAC_OPTION);
    }
    final String host = NetUtils.getLocalHostString();
    cmdLine.addParameter("-D" + GlobalOptions.HOSTNAME_OPTION + "=" + host);

    // javac's VM should use the same default locale that IDEA uses in order for javac to print
    // messages in 'correct' language
    final String lang = System.getProperty("user.language");
    if (lang != null) {
      //noinspection HardCodedStringLiteral
      cmdLine.addParameter("-Duser.language=" + lang);
    }
    final String country = System.getProperty("user.country");
    if (country != null) {
      //noinspection HardCodedStringLiteral
      cmdLine.addParameter("-Duser.country=" + country);
    }
    //noinspection HardCodedStringLiteral
    final String region = System.getProperty("user.region");
    if (region != null) {
      //noinspection HardCodedStringLiteral
      cmdLine.addParameter("-Duser.region=" + region);
    }

    cmdLine.addParameter("-classpath");

    final List<File> cp = ClasspathBootstrap.getBuildProcessApplicationClasspath();
    cp.add(compilerPath);
    cp.addAll(myClasspathManager.getCompileServerPluginsClasspath());

    cmdLine.addParameter(classpathToString(cp));

    cmdLine.addParameter(BuildMain.class.getName());
    cmdLine.addParameter(host);
    cmdLine.addParameter(Integer.toString(port));
    cmdLine.addParameter(sessionId.toString());

    final File workDirectory = new File(mySystemDirectory, SYSTEM_ROOT);
    workDirectory.mkdirs();
    ensureLogConfigExists(workDirectory);

    cmdLine.addParameter(FileUtil.toSystemIndependentName(workDirectory.getPath()));

    cmdLine.setWorkDirectory(workDirectory);

    return cmdLine.createProcess();
  }

  private static void ensureLogConfigExists(File workDirectory) {
    final File logConfig = new File(workDirectory, LOGGER_CONFIG);
    if (!logConfig.exists()) {
      FileUtil.createIfDoesntExist(logConfig);
      try {
        final InputStream in = Server.class.getResourceAsStream("/" + DEFAULT_LOGGER_CONFIG);
        if (in != null) {
          try {
            final FileOutputStream out = new FileOutputStream(logConfig);
            try {
              FileUtil.copy(in, out);
            } finally {
              out.close();
            }
          } finally {
            in.close();
          }
        }
      } catch (IOException e) {
        LOG.error(e);
      }
    }
  }

  public void stopListening() {
    final ChannelGroupFuture closeFuture = myAllOpenChannels.close();
    closeFuture.awaitUninterruptibly();
  }

  private int startListening() throws Exception {
    final ChannelFactory channelFactory =
        new NioServerSocketChannelFactory(myPooledThreadExecutor, myPooledThreadExecutor);
    final SimpleChannelUpstreamHandler channelRegistrar =
        new SimpleChannelUpstreamHandler() {
          public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
            myAllOpenChannels.add(e.getChannel());
            super.channelOpen(ctx, e);
          }

          @Override
          public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
              throws Exception {
            myAllOpenChannels.remove(e.getChannel());
            super.channelClosed(ctx, e);
          }
        };
    ChannelPipelineFactory pipelineFactory =
        new ChannelPipelineFactory() {
          public ChannelPipeline getPipeline() throws Exception {
            return Channels.pipeline(
                channelRegistrar,
                new ProtobufVarint32FrameDecoder(),
                new ProtobufDecoder(CmdlineRemoteProto.Message.getDefaultInstance()),
                new ProtobufVarint32LengthFieldPrepender(),
                new ProtobufEncoder(),
                myMessageDispatcher);
          }
        };
    final ServerBootstrap bootstrap = new ServerBootstrap(channelFactory);
    bootstrap.setPipelineFactory(pipelineFactory);
    bootstrap.setOption("child.tcpNoDelay", true);
    bootstrap.setOption("child.keepAlive", true);
    final int listenPort = NetUtils.findAvailableSocketPort();
    final Channel serverChannel = bootstrap.bind(new InetSocketAddress(listenPort));
    myAllOpenChannels.add(serverChannel);
    return listenPort;
  }

  private static String classpathToString(List<File> cp) {
    StringBuilder builder = new StringBuilder();
    for (File file : cp) {
      if (builder.length() > 0) {
        builder.append(File.pathSeparator);
      }
      builder.append(FileUtil.toCanonicalPath(file.getPath()));
    }
    return builder.toString();
  }

  private class ProjectWatcher extends ProjectManagerAdapter {
    private final Map<Project, MessageBusConnection> myConnections =
        new HashMap<Project, MessageBusConnection>();

    @Override
    public void projectOpened(final Project project) {
      final MessageBusConnection conn = project.getMessageBus().connect();
      myConnections.put(project, conn);
      conn.subscribe(
          ProjectTopics.PROJECT_ROOTS,
          new ModuleRootAdapter() {
            @Override
            public void rootsChanged(final ModuleRootEvent event) {
              final Object source = event.getSource();
              if (source instanceof Project) {
                clearState((Project) source);
              }
            }
          });
    }

    @Override
    public boolean canCloseProject(Project project) {
      cancelAutoMakeTasks(project);
      return super.canCloseProject(project);
    }

    @Override
    public void projectClosing(Project project) {
      for (RequestFuture future : cancelAutoMakeTasks(project)) {
        future.waitFor(500, TimeUnit.MILLISECONDS);
      }
    }

    @Override
    public void projectClosed(Project project) {
      myProjectDataMap.remove(getProjectPath(project));
      final MessageBusConnection conn = myConnections.remove(project);
      if (conn != null) {
        conn.disconnect();
      }
    }
  }

  private static class ProjectData {
    final SequentialTaskExecutor taskQueue;
    private final Set<String> myChanged = new HashSet<String>();
    private final Set<String> myDeleted = new HashSet<String>();
    private long myNextEventOrdinal = 0L;
    private boolean myNeedRescan = true;

    private ProjectData(SequentialTaskExecutor taskQueue) {
      this.taskQueue = taskQueue;
    }

    public void addChanged(Collection<String> paths) {
      if (!myNeedRescan) {
        myDeleted.removeAll(paths);
        myChanged.addAll(paths);
      }
    }

    public void addDeleted(Collection<String> paths) {
      if (!myNeedRescan) {
        myChanged.removeAll(paths);
        myDeleted.addAll(paths);
      }
    }

    public CmdlineRemoteProto.Message.ControllerMessage.FSEvent createNextEvent() {
      final CmdlineRemoteProto.Message.ControllerMessage.FSEvent.Builder builder =
          CmdlineRemoteProto.Message.ControllerMessage.FSEvent.newBuilder();
      builder.setOrdinal(++myNextEventOrdinal);
      builder.addAllChangedPaths(myChanged);
      myChanged.clear();
      builder.addAllDeletedPaths(myDeleted);
      myDeleted.clear();
      return builder.build();
    }

    public boolean getAndResetRescanFlag() {
      final boolean rescan = myNeedRescan;
      myNeedRescan = false;
      return rescan;
    }

    public void dropChanges() {
      myNeedRescan = true;
      myNextEventOrdinal = 0L;
      myChanged.clear();
      myDeleted.clear();
    }
  }
}