@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(); } } }