IJPL-158075 refactor GeneratedSourceFileChangeTrackerImpl

GitOrigin-RevId: 8c7ab67b0fd9599b815166b68ab4bc47993615e3
This commit is contained in:
Vladimir Krivosheev
2024-07-13 17:25:39 +00:00
committed by intellij-monorepo-bot
parent 71e7ebcfe4
commit 902cd2e1fb
@@ -1,183 +1,177 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide;
package com.intellij.ide
import com.intellij.ide.impl.ProjectUtilCore;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileDocumentManagerListener;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.FileEditorManagerListener;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManagerListener;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.ui.EditorNotifications;
import com.intellij.util.SingleAlarm;
import com.intellij.util.messages.MessageBusConnection;
import kotlinx.coroutines.CoroutineScope;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
import com.intellij.ide.impl.ProjectUtilCore
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileEditor.FileDocumentManagerListener
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.FileEditorManagerListener
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManagerListener
import com.intellij.openapi.roots.*
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.testFramework.LightVirtualFile
import com.intellij.ui.EditorNotifications
import com.intellij.util.SingleAlarm
import com.intellij.util.SingleAlarm.Companion.singleAlarm
import kotlinx.coroutines.CoroutineScope
import org.jetbrains.annotations.TestOnly
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.*;
import java.util.concurrent.TimeUnit;
class GeneratedSourceFileChangeTrackerImpl(
private val project: Project,
private val coroutineScope: CoroutineScope
) : GeneratedSourceFileChangeTracker() {
private val checkingQueue: SingleAlarm
private val filesToCheck: MutableSet<VirtualFile> = Collections.synchronizedSet(HashSet())
private val editedGeneratedFiles: MutableSet<VirtualFile> = Collections.synchronizedSet(HashSet())
public final class GeneratedSourceFileChangeTrackerImpl extends GeneratedSourceFileChangeTracker {
private final Project myProject;
private final SingleAlarm myCheckingQueue;
private final Set<VirtualFile> myFilesToCheck = Collections.synchronizedSet(new HashSet<>());
private final Set<VirtualFile> myEditedGeneratedFiles = Collections.synchronizedSet(new HashSet<>());
init {
checkingQueue = singleAlarm(500, coroutineScope, Runnable { this.checkFiles() })
}
@SuppressWarnings("StaticNonFinalField")
// static non-final by design
public static boolean IN_TRACKER_TEST;
companion object {
// static non-final by design
var IN_TRACKER_TEST: Boolean = false
public GeneratedSourceFileChangeTrackerImpl(@NotNull Project project, @NotNull CoroutineScope coroutineScope) {
myProject = project;
myCheckingQueue = SingleAlarm.singleAlarm(500, coroutineScope, this::checkFiles);
private fun isListenerInactive(): Boolean = !IN_TRACKER_TEST && ApplicationManager.getApplication().isUnitTestMode
}
@TestOnly
void waitForAlarm(long timeout, @NotNull TimeUnit timeUnit) {
if (ApplicationManager.getApplication().isWriteAccessAllowed()) {
throw new IllegalStateException("Must not wait for the alarm under write action");
}
myCheckingQueue.waitForAllExecuted(timeout, timeUnit);
fun waitForAlarm(timeout: Long, timeUnit: TimeUnit) {
check(!ApplicationManager.getApplication().isWriteAccessAllowed) { "Must not wait for the alarm under write action" }
checkingQueue.waitForAllExecuted(timeout, timeUnit)
}
@TestOnly
public void cancelAllAndWait(long timeout, @NotNull TimeUnit timeUnit) throws Exception {
myFilesToCheck.clear();
myCheckingQueue.cancel();
waitForAlarm(timeout, timeUnit);
@Throws(Exception::class)
fun cancelAllAndWait(timeout: Long, timeUnit: TimeUnit) {
filesToCheck.clear()
checkingQueue.cancel()
waitForAlarm(timeout, timeUnit)
}
@Override
public boolean isEditedGeneratedFile(@NotNull VirtualFile file) {
return myEditedGeneratedFiles.contains(file);
}
override fun isEditedGeneratedFile(file: VirtualFile): Boolean = editedGeneratedFiles.contains(file)
static final class MyDocumentListener implements DocumentListener {
@Override
public void documentChanged(@NotNull DocumentEvent event) {
internal class MyDocumentListener : DocumentListener {
override fun documentChanged(event: DocumentEvent) {
if (isListenerInactive()) {
return;
return
}
Project[] openProjects = ProjectUtilCore.getOpenProjects();
if (openProjects.length == 0) {
return;
val openProjects = ProjectUtilCore.getOpenProjects()
if (openProjects.isEmpty()) {
return
}
VirtualFile file = FileDocumentManager.getInstance().getFile(event.getDocument());
val file = FileDocumentManager.getInstance().getFile(event.document)
if (file == null || LightVirtualFile.shouldSkipEventSystem(file)) {
return;
return
}
for (Project project : openProjects) {
if (project.isDisposed()) {
continue;
for (project in openProjects) {
if (project.isDisposed) {
continue
}
// It's too costly to synchronously check whether the file is under the project, so delegate the check to
// the background activities of all projects
GeneratedSourceFileChangeTrackerImpl fileChangeTracker = (GeneratedSourceFileChangeTrackerImpl)getInstance(project);
fileChangeTracker.myFilesToCheck.add(file);
fileChangeTracker.myCheckingQueue.cancelAndRequest();
val fileChangeTracker = getInstance(project) as GeneratedSourceFileChangeTrackerImpl
fileChangeTracker.filesToCheck.add(file)
fileChangeTracker.checkingQueue.cancelAndRequest()
}
}
}
private static boolean isListenerInactive() {
return !IN_TRACKER_TEST && ApplicationManager.getApplication().isUnitTestMode();
}
static final class MyProjectManagerListener implements ProjectManagerListener {
@Override
public void projectOpened(@NotNull Project project) {
internal class MyProjectManagerListener : ProjectManagerListener {
@Suppress("removal", "OVERRIDE_DEPRECATION")
override fun projectOpened(project: Project) {
if (isListenerInactive()) {
return;
return
}
((GeneratedSourceFileChangeTrackerImpl)getInstance(project)).projectOpened();
(getInstance(project) as GeneratedSourceFileChangeTrackerImpl).projectOpened()
}
}
private void projectOpened() {
MessageBusConnection connection = myProject.getMessageBus().connect();
connection.subscribe(FileDocumentManagerListener.TOPIC, new FileDocumentManagerListener() {
@Override
public void fileContentReloaded(@NotNull VirtualFile file, @NotNull Document document) {
myFilesToCheck.remove(file);
if (myEditedGeneratedFiles.remove(file)) {
EditorNotifications.getInstance(myProject).updateNotifications(file);
private fun projectOpened() {
val connection = project.messageBus.connect(coroutineScope)
connection.subscribe(FileDocumentManagerListener.TOPIC, object : FileDocumentManagerListener {
override fun fileContentReloaded(file: VirtualFile, document: Document) {
filesToCheck.remove(file)
if (editedGeneratedFiles.remove(file)) {
EditorNotifications.getInstance(project).updateNotifications(file)
}
}
});
connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() {
@Override
public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
myEditedGeneratedFiles.remove(file);
})
connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, object : FileEditorManagerListener {
override fun fileClosed(source: FileEditorManager, file: VirtualFile) {
editedGeneratedFiles.remove(file)
}
});
connection.subscribe(ModuleRootListener.TOPIC, new ModuleRootListener() {
@Override
public void rootsChanged(@NotNull ModuleRootEvent event) {
resetOnRootsChanged();
})
connection.subscribe(ModuleRootListener.TOPIC, object : ModuleRootListener {
override fun rootsChanged(event: ModuleRootEvent) {
resetOnRootsChanged()
}
});
})
connection.subscribe(AdditionalLibraryRootsListener.TOPIC,
(presentableLibraryName, oldRoots, newRoots, libraryNameForDebug) -> resetOnRootsChanged());
AdditionalLibraryRootsListener { _: String?, _: Collection<VirtualFile?>?, _: Collection<VirtualFile?>?, _: String? -> resetOnRootsChanged() })
}
private void resetOnRootsChanged() {
myFilesToCheck.addAll(myEditedGeneratedFiles);
myEditedGeneratedFiles.clear();
myCheckingQueue.cancelAndRequest();
private fun resetOnRootsChanged() {
filesToCheck.addAll(editedGeneratedFiles)
editedGeneratedFiles.clear()
checkingQueue.cancelAndRequest()
}
private void checkFiles() {
final VirtualFile[] files;
synchronized (myFilesToCheck) {
files = myFilesToCheck.toArray(VirtualFile.EMPTY_ARRAY);
myFilesToCheck.clear();
private fun checkFiles() {
val files = synchronized(filesToCheck) {
filesToCheck.toList().also {
filesToCheck.clear()
}
}
if (files.isEmpty()) {
return
}
if (files.length == 0) return;
final int[] i = {0};
ProjectFileIndex fileIndex = ProjectFileIndex.getInstance(myProject);
ReadAction.nonBlocking(() -> {
if (myProject.isDisposed()) return;
final List<VirtualFile> newEditedGeneratedFiles = new ArrayList<>();
val i = intArrayOf(0)
val fileIndex = ProjectFileIndex.getInstance(project)
ReadAction.nonBlocking {
if (project.isDisposed) {
return@nonBlocking
}
val newEditedGeneratedFiles: MutableList<VirtualFile> = ArrayList()
try {
while (i[0] < files.length) {
ProgressManager.checkCanceled();
while (i[0] < files.size) {
ProgressManager.checkCanceled()
VirtualFile file = files[i[0]];
val file = files[i[0]]
if (fileIndex.isInContent(file) || fileIndex.isInLibrary(file)) {
if (GeneratedSourcesFilter.isGeneratedSourceByAnyFilter(file, myProject)) {
newEditedGeneratedFiles.add(file);
if (GeneratedSourcesFilter.isGeneratedSourceByAnyFilter(file, project)) {
newEditedGeneratedFiles.add(file)
}
}
i[0]++;
i[0]++
}
}
finally {
// In case of canceled exception or (really) any other exception
// some files could be already processed
// let's not wait for the next available read lock slot
// In case of canceled exception or (really) any other exception,
// some files could be already processed let's not wait for the next available read lock slot
if (!newEditedGeneratedFiles.isEmpty()) {
myEditedGeneratedFiles.addAll(newEditedGeneratedFiles);
EditorNotifications.getInstance(myProject).updateAllNotifications();
editedGeneratedFiles.addAll(newEditedGeneratedFiles)
EditorNotifications.getInstance(project).updateAllNotifications()
}
}
}).executeSynchronously();
}.executeSynchronously()
}
}