From 9f65147dcf90b55963790328af4096aafcb5eb72 Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 15 Mar 2017 18:13:15 +0100 Subject: [PATCH 01/11] no PyUniversalTestLegacyInterop in default project --- .../testing/universalTests/PyUniversalTestLegacyInterop.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/python/src/com/jetbrains/python/testing/universalTests/PyUniversalTestLegacyInterop.kt b/python/src/com/jetbrains/python/testing/universalTests/PyUniversalTestLegacyInterop.kt index 6d4f69990ae9..8fd13cda8462 100644 --- a/python/src/com/jetbrains/python/testing/universalTests/PyUniversalTestLegacyInterop.kt +++ b/python/src/com/jetbrains/python/testing/universalTests/PyUniversalTestLegacyInterop.kt @@ -54,6 +54,7 @@ class PyUniversalTestLegacyInteropInitializer { // Delegate to project initialization ApplicationManager.getApplication().messageBus.connect().subscribe(ProjectLifecycleListener.TOPIC, object : ProjectLifecycleListener { override fun projectComponentsInitialized(project: Project) { + if (project.isDefault) return if (project.isInitialized) { projectInitialized(project) return From e1dfc67624027413307b5f0d1ff61bf36461a507 Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 15 Mar 2017 18:13:20 +0100 Subject: [PATCH 02/11] no dumb tasks in default project --- .../src/com/intellij/openapi/project/DumbServiceImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java index 487038ac0a1c..65ba2275adee 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java @@ -169,8 +169,9 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica } @Override - public void queueTask(@NotNull final DumbModeTask task) { + public void queueTask(@NotNull DumbModeTask task) { if (LOG.isDebugEnabled()) LOG.debug("Scheduling task " + task); + LOG.assertTrue(!myProject.isDefault(), "No indexing tasks should be created for default project: " + task); final Application application = ApplicationManager.getApplication(); if (application.isUnitTestMode() || application.isHeadlessEnvironment()) { From 981167650ebf9d22d77cd55c3ab8b70fd92f1368 Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 15 Mar 2017 18:14:12 +0100 Subject: [PATCH 03/11] prohibit scheduling startup activities in default project they won't be run anyway --- .../openapi/roots/impl/ProjectRootManagerComponent.java | 4 +++- .../intellij/ide/startup/impl/StartupManagerImpl.java | 9 ++++++++- .../intellij/openapi/vcs/impl/FileStatusManagerImpl.java | 9 +++------ .../com/intellij/openapi/vcs/impl/VcsInitialization.java | 4 ++-- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/impl/ProjectRootManagerComponent.java b/platform/lang-impl/src/com/intellij/openapi/roots/impl/ProjectRootManagerComponent.java index af5013b3bce3..ae34a462b50d 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/impl/ProjectRootManagerComponent.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/impl/ProjectRootManagerComponent.java @@ -91,7 +91,9 @@ public class ProjectRootManagerComponent extends ProjectRootManagerImpl implemen } }, project); - startupManager.registerStartupActivity(() -> myStartupActivityPerformed = true); + if (!myProject.isDefault()) { + startupManager.registerStartupActivity(() -> myStartupActivityPerformed = true); + } myHandler = new BatchUpdateListener() { @Override diff --git a/platform/platform-impl/src/com/intellij/ide/startup/impl/StartupManagerImpl.java b/platform/platform-impl/src/com/intellij/ide/startup/impl/StartupManagerImpl.java index 754b1ad0c1c3..22509e56d1f4 100644 --- a/platform/platform-impl/src/com/intellij/ide/startup/impl/StartupManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/ide/startup/impl/StartupManagerImpl.java @@ -77,20 +77,27 @@ public class StartupManagerImpl extends StartupManagerEx { myProject = project; } + private void checkNonDefaultProject() { + LOG.assertTrue(!myProject.isDefault(), "Please don't register startup activities for the default project: they won't ever be run"); + } + @Override public void registerPreStartupActivity(@NotNull Runnable runnable) { + checkNonDefaultProject(); LOG.assertTrue(!myPreStartupActivitiesPassed, "Registering pre startup activity that will never be run"); myPreStartupActivities.add(runnable); } @Override public void registerStartupActivity(@NotNull Runnable runnable) { + checkNonDefaultProject(); LOG.assertTrue(!myStartupActivitiesPassed, "Registering startup activity that will never be run"); myStartupActivities.add(runnable); } @Override public synchronized void registerPostStartupActivity(@NotNull Runnable runnable) { + checkNonDefaultProject(); LOG.assertTrue(!myPostStartupActivitiesPassed, "Registering post-startup activity that will never be run:" + " disposed=" + myProject.isDisposed() + "; open=" + myProject.isOpen() + "; passed=" + myStartupActivitiesPassed); (DumbService.isDumbAware(runnable) ? myDumbAwarePostStartupActivities : myNotDumbAwarePostStartupActivities).add(runnable); @@ -372,7 +379,7 @@ public class StartupManagerImpl extends StartupManagerEx { synchronized (this) { // in tests which simulate project opening, post-startup activities could have been run already. // Then we should act as if the project was initialized - boolean initialized = myProject.isInitialized() || application.isUnitTestMode() && myPostStartupActivitiesPassed; + boolean initialized = myProject.isInitialized() || myProject.isDefault() || application.isUnitTestMode() && myPostStartupActivitiesPassed; if (!initialized) { registerPostStartupActivity(action); return; diff --git a/platform/platform-impl/src/com/intellij/openapi/vcs/impl/FileStatusManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/vcs/impl/FileStatusManagerImpl.java index fe31c2f1b194..7bc9d843e3e5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vcs/impl/FileStatusManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vcs/impl/FileStatusManagerImpl.java @@ -106,6 +106,8 @@ public class FileStatusManagerImpl extends FileStatusManager implements ProjectC } }); + if (project.isDefault()) return; + startupManager.registerPreStartupActivity(() -> { DocumentAdapter documentListener = new DocumentAdapter() { @Override @@ -123,12 +125,7 @@ public class FileStatusManagerImpl extends FileStatusManager implements ProjectC factory.getEventMulticaster().addDocumentListener(documentListener, myProject); } }); - startupManager.registerPostStartupActivity(new DumbAwareRunnable() { - @Override - public void run() { - fileStatusesChanged(); - } - }); + startupManager.registerPostStartupActivity((DumbAwareRunnable)() -> fileStatusesChanged()); } public void setFileStatusProvider(final FileStatusProvider fileStatusProvider) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsInitialization.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsInitialization.java index 7afe0b0c6208..2b36da508f05 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsInitialization.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsInitialization.java @@ -43,7 +43,7 @@ public class VcsInitialization implements Disposable { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.impl.VcsInitialization"); private final List> myList = new ArrayList<>(); - private final Object myLock; + private final Object myLock = new Object(); @NotNull private final Project myProject; // the initialization lifecycle: IDLE -(on startup completion)-> RUNNING -(on all tasks executed or project canceled)-> FINISHED @@ -55,7 +55,7 @@ public class VcsInitialization implements Disposable { VcsInitialization(@NotNull final Project project) { myProject = project; - myLock = new Object(); + if (project.isDefault()) return; StartupManager.getInstance(project).registerPostStartupActivity((DumbAwareRunnable)() -> { if (project.isDisposed()) return; From 09893539ee9876ea4e0ae98587796ff0940f75c7 Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 15 Mar 2017 18:19:01 +0100 Subject: [PATCH 04/11] add TransactionGuard#assertWriteSafeContext to catch write-unsafe progresses earlier (EA-97652, EA-97653, EA-97854) --- .../openapi/application/TransactionGuard.java | 7 ++++++ .../application/TransactionGuardImpl.java | 23 ++++++++++++++----- .../util/ExternalSystemApiUtil.java | 5 ++-- .../git4idea/merge/GitConflictResolver.java | 3 +++ .../python/sdk/PythonSdkUpdater.java | 6 ++--- 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/platform/core-api/src/com/intellij/openapi/application/TransactionGuard.java b/platform/core-api/src/com/intellij/openapi/application/TransactionGuard.java index 3aefe326178a..152951b5d358 100644 --- a/platform/core-api/src/com/intellij/openapi/application/TransactionGuard.java +++ b/platform/core-api/src/com/intellij/openapi/application/TransactionGuard.java @@ -122,6 +122,13 @@ public abstract class TransactionGuard { guard.submitTransaction(parentDisposable, guard.getContextTransaction(), transaction); } + /** + * Logs an error if the given modality state was created in a write-unsafe context. For modalities created in write-safe contexts, + * {@link Application#invokeLater(Runnable, ModalityState)} and similar calls will be guaranteed to also run in a write-safe context. + * {@link ModalityState#NON_MODAL} is always write-safe, {@link ModalityState#any()} is always write-unsafe. + */ + public abstract void assertWriteSafeContext(@NotNull ModalityState modality); + /** * Schedules a given runnable to be executed inside a transaction later on Swing thread. * Same as {@link #submitTransaction(Disposable, Runnable)}, but the runnable is never executed immediately. diff --git a/platform/core-impl/src/com/intellij/openapi/application/TransactionGuardImpl.java b/platform/core-impl/src/com/intellij/openapi/application/TransactionGuardImpl.java index 18c537f28050..b1a20d56d1b6 100644 --- a/platform/core-impl/src/com/intellij/openapi/application/TransactionGuardImpl.java +++ b/platform/core-impl/src/com/intellij/openapi/application/TransactionGuardImpl.java @@ -226,17 +226,28 @@ public class TransactionGuardImpl extends TransactionGuard { public void assertWriteActionAllowed() { ApplicationManager.getApplication().assertIsDispatchThread(); if (areAssertionsEnabled() && !myWritingAllowed && !myErrorReported) { - String message = "Write access is allowed from write-safe contexts only. " + - "Please ensure you're using invokeLater/invokeAndWait with a correct modality state (not \"any\"). " + - "See TransactionGuard documentation for details." + - "\n current modality=" + ModalityState.current() + - "\n known modalities=" + myWriteSafeModalities; // please assign exceptions here to Peter - LOG.error(message); + LOG.error(reportWriteUnsafeContext(ModalityState.current())); myErrorReported = true; } } + private String reportWriteUnsafeContext(@NotNull ModalityState modality) { + return "Write-unsafe context! Model changes are allowed from write-safe contexts only. " + + "Please ensure you're using invokeLater/invokeAndWait with a correct modality state (not \"any\"). " + + "See TransactionGuard documentation for details." + + "\n current modality=" + modality + + "\n known modalities=" + myWriteSafeModalities; + } + + @Override + public void assertWriteSafeContext(@NotNull ModalityState modality) { + if (!isWriteSafeModality(modality) && areAssertionsEnabled()) { + // please assign exceptions here to Peter + LOG.error(reportWriteUnsafeContext(modality)); + } + } + private static boolean areAssertionsEnabled() { Application app = ApplicationManager.getApplication(); if (app.isUnitTestMode() && !ourTestingTransactions) { diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemApiUtil.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemApiUtil.java index 5bd165bd5bb1..7d08f1a437e9 100644 --- a/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemApiUtil.java +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemApiUtil.java @@ -17,9 +17,7 @@ package com.intellij.openapi.externalSystem.util; import com.intellij.execution.rmi.RemoteUtil; import com.intellij.ide.util.PropertiesComponent; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.application.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.externalSystem.ExternalSystemAutoImportAware; import com.intellij.openapi.externalSystem.ExternalSystemManager; @@ -455,6 +453,7 @@ public class ExternalSystemApiUtil { } public static void executeProjectChangeAction(boolean synchronous, @NotNull final DisposeAwareProjectChange task) { + TransactionGuard.getInstance().assertWriteSafeContext(ModalityState.defaultModalityState()); executeOnEdt(synchronous, () -> ApplicationManager.getApplication().runWriteAction(() -> task.run())); } diff --git a/plugins/git4idea/src/git4idea/merge/GitConflictResolver.java b/plugins/git4idea/src/git4idea/merge/GitConflictResolver.java index 3c6c3fb658e9..56f2dd6c6573 100644 --- a/plugins/git4idea/src/git4idea/merge/GitConflictResolver.java +++ b/plugins/git4idea/src/git4idea/merge/GitConflictResolver.java @@ -18,6 +18,8 @@ package git4idea.merge; import com.intellij.notification.Notification; import com.intellij.notification.NotificationListener; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.TransactionGuard; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; @@ -231,6 +233,7 @@ public class GitConflictResolver { } private void showMergeDialog(@NotNull final Collection initiallyUnmergedFiles) { + TransactionGuard.getInstance().assertWriteSafeContext(ModalityState.defaultModalityState()); ApplicationManager.getApplication().invokeAndWait(() -> { MergeProvider mergeProvider = new GitMergeProvider(myProject, myParams.reverse); myVcsHelper.showMergeDialog(new ArrayList<>(initiallyUnmergedFiles), mergeProvider, myParams.myMergeDialogCustomizer); diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java b/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java index 86e360a9e69f..94500f33a9d9 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java @@ -19,10 +19,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.intellij.execution.ExecutionException; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.application.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; @@ -426,6 +423,7 @@ public class PythonSdkUpdater implements StartupActivity { final SdkModificator modificatorToGetRoots = sdkModificator != null ? sdkModificator : sdk.getSdkModificator(); final List currentSdkPaths = Arrays.asList(modificatorToGetRoots.getRoots(OrderRootType.CLASSES)); if (forceCommit || !Sets.newHashSet(sdkPaths).equals(Sets.newHashSet(currentSdkPaths))) { + TransactionGuard.getInstance().assertWriteSafeContext(ModalityState.defaultModalityState()); ApplicationManager.getApplication().invokeAndWait(() -> { final Sdk sdkInsideInvoke = PythonSdkType.findSdkByKey(key); final SdkModificator modificatorToCommit = sdkModificator != null ? sdkModificator : From 554f0340ed983977e326fe8cdfa5508cd7b1a201 Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 15 Mar 2017 19:45:50 +0100 Subject: [PATCH 05/11] remove modal invokeLater from bg document commit (IDEA-169201) --- .../core/MockDocumentCommitProcessor.java | 4 +-- .../psi/impl/DocumentCommitProcessor.java | 9 ++--- .../psi/impl/DocumentCommitThread.java | 36 +++++++++---------- .../psi/impl/PsiDocumentManagerBase.java | 4 +-- .../psi/impl/PsiDocumentManagerImpl.java | 3 +- .../psi/impl/PsiDocumentManagerImplTest.java | 19 ++++++++++ 6 files changed, 48 insertions(+), 27 deletions(-) diff --git a/platform/core-impl/src/com/intellij/core/MockDocumentCommitProcessor.java b/platform/core-impl/src/com/intellij/core/MockDocumentCommitProcessor.java index aef33bbd9aef..92784387ab48 100644 --- a/platform/core-impl/src/com/intellij/core/MockDocumentCommitProcessor.java +++ b/platform/core-impl/src/com/intellij/core/MockDocumentCommitProcessor.java @@ -15,7 +15,7 @@ */ package com.intellij.core; -import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.TransactionId; import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiFile; @@ -35,6 +35,6 @@ class MockDocumentCommitProcessor implements DocumentCommitProcessor { public void commitAsynchronously(@NotNull Project project, @NotNull Document document, @NonNls @NotNull Object reason, - @NotNull ModalityState currentModalityState) { + @NotNull TransactionId context) { } } diff --git a/platform/core-impl/src/com/intellij/psi/impl/DocumentCommitProcessor.java b/platform/core-impl/src/com/intellij/psi/impl/DocumentCommitProcessor.java index ca04c2cfaeb4..1fd9254724bb 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/DocumentCommitProcessor.java +++ b/platform/core-impl/src/com/intellij/psi/impl/DocumentCommitProcessor.java @@ -15,17 +15,18 @@ */ package com.intellij.psi.impl; -import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.TransactionId; import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public interface DocumentCommitProcessor { void commitSynchronously(@NotNull Document document, @NotNull Project project, @NotNull PsiFile psiFile); void commitAsynchronously(@NotNull final Project project, - @NotNull final Document document, - @NonNls @NotNull Object reason, - @NotNull ModalityState currentModalityState); + @NotNull final Document document, + @NonNls @NotNull Object reason, + @Nullable TransactionId context); } diff --git a/platform/core-impl/src/com/intellij/psi/impl/DocumentCommitThread.java b/platform/core-impl/src/com/intellij/psi/impl/DocumentCommitThread.java index 7c87ef4ccdd0..309c06cbbb84 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/DocumentCommitThread.java +++ b/platform/core-impl/src/com/intellij/psi/impl/DocumentCommitThread.java @@ -152,25 +152,25 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi public void commitAsynchronously(@NotNull final Project project, @NotNull final Document document, @NonNls @NotNull Object reason, - @NotNull ModalityState currentModalityState) { + @Nullable TransactionId context) { assert !isDisposed : "already disposed"; if (!project.isInitialized()) return; PsiFile psiFile = PsiDocumentManager.getInstance(project).getCachedPsiFile(document); if (psiFile == null) return; - doQueue(project, document, getAllFileNodes(psiFile), reason, currentModalityState, + doQueue(project, document, getAllFileNodes(psiFile), reason, context, PsiDocumentManager.getInstance(project).getLastCommittedText(document)); } private void doQueue(@NotNull Project project, - @NotNull Document document, - @NotNull List> oldFileNodes, - @NotNull Object reason, - @NotNull ModalityState currentModalityState, - @NotNull CharSequence lastCommittedText) { + @NotNull Document document, + @NotNull List> oldFileNodes, + @NotNull Object reason, + @Nullable TransactionId context, + @NotNull CharSequence lastCommittedText) { synchronized (lock) { if (!project.isInitialized()) return; // check the project is disposed under lock. - CommitTask newTask = createNewTaskAndCancelSimilar(project, document, oldFileNodes, reason, currentModalityState, + CommitTask newTask = createNewTaskAndCancelSimilar(project, document, oldFileNodes, reason, context, lastCommittedText); documentsToCommit.offer(newTask); @@ -185,13 +185,13 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi @NotNull Document document, @NotNull List> oldFileNodes, @NotNull Object reason, - @NotNull ModalityState currentModalityState, + @Nullable TransactionId context, @NotNull CharSequence lastCommittedText) { synchronized (lock) { for (Pair pair : oldFileNodes) { assert pair.first.getProject() == project; } - CommitTask newTask = new CommitTask(project, document, oldFileNodes, createProgressIndicator(), reason, currentModalityState, + CommitTask newTask = new CommitTask(project, document, oldFileNodes, createProgressIndicator(), reason, context, lastCommittedText); cancelAndRemoveFromDocsToCommit(newTask, reason); cancelAndRemoveCurrentTask(newTask, reason); @@ -380,7 +380,7 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi if (success) { assert !myApplication.isDispatchThread(); TransactionGuardImpl guard = (TransactionGuardImpl)TransactionGuard.getInstance(); - guard.submitTransaction(project, guard.getModalityTransaction(task.myCreationModalityState), finishRunnable); + guard.submitTransaction(project, task.myCreationContext, finishRunnable); } } } @@ -408,7 +408,7 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi List> oldFileNodes = file == null ? null : getAllFileNodes(file); if (oldFileNodes != null) { doQueue(finalProject, finalDocument, oldFileNodes, "re-added on failure: " + finalFailureReason, - finalTask.myCreationModalityState, + finalTask.myCreationContext, lastCommittedText); } }); @@ -442,7 +442,7 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi CommitTask task; synchronized (lock) { // synchronized to ensure no new similar tasks can start before we hold the document's lock - task = createNewTaskAndCancelSimilar(project, document, allFileNodes, SYNC_COMMIT_REASON, ModalityState.current(), + task = createNewTaskAndCancelSimilar(project, document, allFileNodes, SYNC_COMMIT_REASON, TransactionGuard.getInstance().getContextTransaction(), PsiDocumentManager.getInstance(project).getLastCommittedText(document)); documentLock.lock(); } @@ -541,7 +541,7 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi throw new PsiInvalidElementAccessException(file, "File " + file + " invalidated during sync commit"); } commitAsynchronously(project, document, "File " + file + " invalidated during background commit; task: "+task, - task.myCreationModalityState); + task.myCreationContext); } } } @@ -605,7 +605,7 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi } else { // add document back to the queue - commitAsynchronously(project, document, "Re-added back", task.myCreationModalityState); + commitAsynchronously(project, document, "Re-added back", task.myCreationContext); } }; } @@ -655,7 +655,7 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi // when failed it's canceled @NotNull final ProgressIndicator indicator; // progress to commit this doc under. @NotNull final Object reason; - @NotNull final ModalityState myCreationModalityState; + @Nullable final TransactionId myCreationContext; private final CharSequence myLastCommittedText; @NotNull final List> myOldFileNodes; @@ -664,13 +664,13 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi @NotNull final List> oldFileNodes, @NotNull ProgressIndicator indicator, @NotNull Object reason, - @NotNull ModalityState currentModalityState, + @Nullable TransactionId context, @NotNull CharSequence lastCommittedText) { this.document = document; this.project = project; this.indicator = indicator; this.reason = reason; - myCreationModalityState = currentModalityState; + myCreationContext = context; myLastCommittedText = lastCommittedText; myOldFileNodes = oldFileNodes; modificationSequence = ((DocumentEx)document).getModificationSequence(); diff --git a/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java b/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java index 54d78819c4ec..0a13546a7dcc 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java +++ b/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java @@ -544,7 +544,7 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen } actions.add(action); - ModalityState current = ModalityState.current(); + TransactionId current = TransactionGuard.getInstance().getContextTransaction(); if (current != ModalityState.NON_MODAL) { // re-add all uncommitted documents into the queue with this new modality // because this client obviously expects them to commit even inside modal dialog @@ -846,7 +846,7 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen commitDocument(document); } else if (!((DocumentEx)document).isInBulkUpdate() && myPerformBackgroundCommit) { - myDocumentCommitProcessor.commitAsynchronously(myProject, document, event, ApplicationManager.getApplication().getCurrentModalityState()); + myDocumentCommitProcessor.commitAsynchronously(myProject, document, event, TransactionGuard.getInstance().getContextTransaction()); } } else { diff --git a/platform/lang-impl/src/com/intellij/psi/impl/PsiDocumentManagerImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/PsiDocumentManagerImpl.java index ccbefe64ab09..6ad90f8e91be 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/PsiDocumentManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/PsiDocumentManagerImpl.java @@ -22,6 +22,7 @@ import com.intellij.injected.editor.DocumentWindowImpl; import com.intellij.injected.editor.EditorWindowImpl; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.application.TransactionGuard; import com.intellij.openapi.components.SettingsSavingComponent; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.EditorFactory; @@ -78,7 +79,7 @@ public class PsiDocumentManagerImpl extends PsiDocumentManagerBase implements Se connection.subscribe(DocumentBulkUpdateListener.TOPIC, new DocumentBulkUpdateListener.Adapter() { @Override public void updateFinished(@NotNull Document doc) { - documentCommitThread.commitAsynchronously(project, doc, "Bulk update finished", ApplicationManager.getApplication().getDefaultModalityState()); + documentCommitThread.commitAsynchronously(project, doc, "Bulk update finished", TransactionGuard.getInstance().getContextTransaction()); } }); Disposer.register(project, () -> ((DocumentCommitThread)myDocumentCommitThread).cancelTasksOnProjectDispose(project)); diff --git a/platform/platform-tests/testSrc/com/intellij/psi/impl/PsiDocumentManagerImplTest.java b/platform/platform-tests/testSrc/com/intellij/psi/impl/PsiDocumentManagerImplTest.java index b10c3cbad19c..3ae54d80c957 100644 --- a/platform/platform-tests/testSrc/com/intellij/psi/impl/PsiDocumentManagerImplTest.java +++ b/platform/platform-tests/testSrc/com/intellij/psi/impl/PsiDocumentManagerImplTest.java @@ -21,6 +21,7 @@ import com.intellij.mock.MockDocument; import com.intellij.mock.MockPsiFile; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.TransactionGuard; import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.Document; @@ -563,6 +564,24 @@ public class PsiDocumentManagerImplTest extends PlatformTestCase { assertTrue(getPsiDocumentManager().isCommitted(document)); } + public void testBackgroundCommitInDialogInTransaction() throws IOException { + VirtualFile vFile = getVirtualFile(createTempFile("a.txt", "abc")); + PsiFile psiFile = findFile(vFile); + Document document = getDocument(psiFile); + + TransactionGuard.submitTransaction(myProject, () -> { + WriteCommandAction.runWriteCommandAction(myProject, () -> { + document.insertString(0, "x"); + LaterInvocator.enterModal(new Object()); + assertFalse(getPsiDocumentManager().isCommitted(document)); + }); + + waitTenSecondsForCommit(document); + assertTrue(getPsiDocumentManager().isCommitted(document)); + }); + UIUtil.dispatchAllInvocationEvents(); + } + public void testChangeDocumentThenEnterModalDialogThenCallPerformWhenAllCommittedShouldFireWhileInsideModal() throws IOException { VirtualFile vFile = getVirtualFile(createTempFile("a.txt", "abc")); PsiFile psiFile = findFile(vFile); From e145842fd43261e4046f83a7bc0f77920d21867a Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 15 Mar 2017 19:47:59 +0100 Subject: [PATCH 06/11] remove modal invokeLater from VFS refresh, use only transaction API for event firing --- .../openapi/project/DumbServiceImpl.java | 17 +----------- .../openapi/vfs/newvfs/RefreshQueueImpl.java | 14 ++++------ .../vfs/newvfs/RefreshSessionImpl.java | 27 ++++++++----------- 3 files changed, 17 insertions(+), 41 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java index 65ba2275adee..1e93115d23be 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java @@ -62,7 +62,6 @@ import java.util.concurrent.atomic.AtomicReference; public class DumbServiceImpl extends DumbService implements Disposable, ModificationTracker { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.project.DumbServiceImpl"); - private static Throwable ourForcedTrace; private final AtomicReference myState = new AtomicReference<>(State.SMART); private volatile Throwable myDumbStart; private volatile TransactionId myDumbStartTransaction; @@ -197,7 +196,7 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica @VisibleForTesting void queueAsynchronousTask(@NotNull DumbModeTask task) { - Throwable trace = ourForcedTrace != null ? ourForcedTrace : new Throwable(); // please report exceptions here to peter + Throwable trace = new Throwable(); // please report exceptions here to peter TransactionId contextTransaction = TransactionGuard.getInstance().getContextTransaction(); Runnable runnable = () -> queueTaskOnEdt(task, contextTransaction, trace); if (ApplicationManager.getApplication().isDispatchThread()) { @@ -253,20 +252,6 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica } } - @NotNull - public static AccessToken forceDumbModeStartTrace(@NotNull Throwable trace) { - ApplicationManager.getApplication().assertIsDispatchThread(); - final Throwable prev = ourForcedTrace; - ourForcedTrace = trace; - return new AccessToken() { - @Override - public void finish() { - //noinspection AssignmentToStaticFieldFromInstanceMethod - ourForcedTrace = prev; - } - }; - } - private void queueUpdateFinished() { if (myState.compareAndSet(State.RUNNING_DUMB_TASKS, State.WAITING_FOR_FINISH)) { StartupManager.getInstance(myProject).runWhenProjectIsInitialized( diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshQueueImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshQueueImpl.java index c5e5a9d95e54..c6db8cbeffff 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshQueueImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshQueueImpl.java @@ -46,7 +46,7 @@ public class RefreshQueueImpl extends RefreshQueue implements Disposable { public void execute(@NotNull RefreshSessionImpl session) { if (session.isAsynchronous()) { - queueSession(session, session.getModalityState(), session.getTransaction()); + queueSession(session, session.getTransaction()); } else { Application app = ApplicationManager.getApplication(); @@ -61,13 +61,13 @@ public class RefreshQueueImpl extends RefreshQueue implements Disposable { "this will cause a deadlock if there are any events to fire."); return; } - queueSession(session, ModalityState.defaultModalityState(), TransactionGuard.getInstance().getContextTransaction()); + queueSession(session, TransactionGuard.getInstance().getContextTransaction()); session.waitFor(); } } } - private void queueSession(@NotNull final RefreshSessionImpl session, @NotNull final ModalityState modality, @Nullable TransactionId transaction) { + private void queueSession(@NotNull RefreshSessionImpl session, @Nullable TransactionId transaction) { myQueue.submit(() -> { myRefreshIndicator.start(); try (AccessToken ignored = HeavyProcessLatch.INSTANCE.processStarted("Doing file refresh. " + session)) { @@ -75,11 +75,7 @@ public class RefreshQueueImpl extends RefreshQueue implements Disposable { } finally { myRefreshIndicator.stop(); - Application app = ApplicationManager.getApplication(); - // invokeLater might be not necessary once transactions are enforced - app.invokeLater( - () -> TransactionGuard.getInstance().submitTransaction(app, transaction, session::fireEvents), - modality); + TransactionGuard.getInstance().submitTransaction(ApplicationManager.getApplication(), transaction, session::fireEvents); } }); myEventCounter.eventHappened(session); @@ -123,7 +119,7 @@ public class RefreshQueueImpl extends RefreshQueue implements Disposable { @NotNull @Override public RefreshSession createSession(boolean async, boolean recursively, @Nullable Runnable finishRunnable, @NotNull ModalityState state) { - return new RefreshSessionImpl(async, recursively, finishRunnable, state); + return new RefreshSessionImpl(async, recursively, finishRunnable, ((TransactionGuardImpl)TransactionGuard.getInstance()).getModalityTransaction(state)); } @Override diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java index 73f55d98c98c..2704496a7ab0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java @@ -16,9 +16,11 @@ package com.intellij.openapi.vfs.newvfs; import com.intellij.codeInsight.daemon.impl.FileStatusMap; -import com.intellij.openapi.application.*; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.TransactionId; +import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.DumbServiceImpl; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; @@ -46,7 +48,6 @@ public class RefreshSessionImpl extends RefreshSession { private final boolean myIsAsync; private final boolean myIsRecursive; private final Runnable myFinishRunnable; - private final ModalityState myModalityState; private final Throwable myStartTrace; private final Semaphore mySemaphore = new Semaphore(); @@ -57,13 +58,12 @@ public class RefreshSessionImpl extends RefreshSession { private volatile boolean myCancelled; private final TransactionId myTransaction; - public RefreshSessionImpl(boolean async, boolean recursive, @Nullable Runnable finishRunnable, @NotNull ModalityState modalityState) { + RefreshSessionImpl(boolean async, boolean recursive, @Nullable Runnable finishRunnable, @Nullable TransactionId context) { myIsAsync = async; myIsRecursive = recursive; myFinishRunnable = finishRunnable; - myModalityState = modalityState; - myTransaction = ((TransactionGuardImpl)TransactionGuard.getInstance()).getModalityTransaction(modalityState); - LOG.assertTrue(modalityState == ModalityState.NON_MODAL || modalityState != ModalityState.any(), "Refresh session should have a specific modality"); + myTransaction = context; + LOG.assertTrue(context == ModalityState.NON_MODAL || context != ModalityState.any(), "Refresh session should have a specific modality"); myStartTrace = rememberStartTrace(); } @@ -72,11 +72,11 @@ public class RefreshSessionImpl extends RefreshSession { (myIsAsync || !ApplicationManager.getApplication().isDispatchThread())) { return new Throwable(); } - return myModalityState == ModalityState.NON_MODAL ? null : new Throwable(); + return null; } - public RefreshSessionImpl(@NotNull List events) { - this(false, false, null, ModalityState.NON_MODAL); + RefreshSessionImpl(@NotNull List events) { + this(false, false, null, null); myEvents.addAll(events); } @@ -178,7 +178,7 @@ public class RefreshSessionImpl extends RefreshSession { return; } - try (AccessToken ignore = myStartTrace == null ? null : DumbServiceImpl.forceDumbModeStartTrace(myStartTrace)) { + try { if (LOG.isDebugEnabled()) LOG.debug("events are about to fire: " + myEvents); WriteAction.run(this::fireEventsInWriteAction); } @@ -226,11 +226,6 @@ public class RefreshSessionImpl extends RefreshSession { return events; } - @NotNull - ModalityState getModalityState() { - return myModalityState; - } - @Nullable TransactionId getTransaction() { return myTransaction; From 72f51475b8d5f1de6ce395fcbdef9579fc6ca8d5 Mon Sep 17 00:00:00 2001 From: Shaverdova Elena Date: Wed, 15 Mar 2017 20:00:31 +0100 Subject: [PATCH 07/11] WI-35344 New Project dialog reuses previous state instead of default project settings --- .../ide/util/projectWizard/ProjectSettingsStepBase.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/platform/lang-impl/src/com/intellij/ide/util/projectWizard/ProjectSettingsStepBase.java b/platform/lang-impl/src/com/intellij/ide/util/projectWizard/ProjectSettingsStepBase.java index 9ac11580f2f3..3de5ba365954 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/projectWizard/ProjectSettingsStepBase.java +++ b/platform/lang-impl/src/com/intellij/ide/util/projectWizard/ProjectSettingsStepBase.java @@ -309,5 +309,9 @@ public class ProjectSettingsStepBase extends AbstractActionWithPanel implements } @Override - public void dispose() {} + public void dispose() { + if (myProjectGenerator instanceof WebProjectTemplate) { + ((WebProjectTemplate)myProjectGenerator).reset(); + } + } } From 1ff37a0c307a13c99d7f560c0cbef7991c5aa457 Mon Sep 17 00:00:00 2001 From: "Anna.Kozlova" Date: Wed, 15 Mar 2017 17:24:03 +0100 Subject: [PATCH 08/11] usage of inaccessible api: show language level where the api appeared; quickfix to increase language level (IDEA-168695) --- .../Java15APIUsageInspectionBase.java | 50 +++++++++++-------- .../FunctionalInterfaceSuggester.java | 2 +- .../completion/JavaCompletionUtil.java | 2 +- .../analysis/IncreaseLanguageLevelFix.java | 22 ++++++-- .../usage1.5/overrideAnnotation/expected.xml | 2 +- .../inspections/Java15FormInspection.java | 2 +- 6 files changed, 50 insertions(+), 30 deletions(-) diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/java15api/Java15APIUsageInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/java15api/Java15APIUsageInspectionBase.java index 7d3f82a2212b..1e1edf8938d8 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/java15api/Java15APIUsageInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/java15api/Java15APIUsageInspectionBase.java @@ -21,6 +21,7 @@ import com.intellij.codeInsight.daemon.GroupNames; import com.intellij.codeInsight.intention.QuickFixFactory; import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool; import com.intellij.codeInspection.InspectionsBundle; +import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.EffectiveLanguageLevelUtil; @@ -233,7 +234,8 @@ public class Java15APIUsageInspectionBase extends BaseJavaBatchLocalInspectionTo final Module module = ModuleUtilCore.findModuleForPsiElement(reference.getElement()); if (module != null) { final LanguageLevel languageLevel = getEffectiveLanguageLevel(module); - if (isForbiddenApiUsage((PsiMember)resolved, languageLevel)) { + LanguageLevel sinceLanguageLevel = getLastIncompatibleLanguageLevel((PsiMember)resolved, languageLevel); + if (sinceLanguageLevel != null) { PsiClass psiClass = null; final PsiElement qualifier = reference.getQualifier(); if (qualifier != null) { @@ -250,7 +252,7 @@ public class Java15APIUsageInspectionBase extends BaseJavaBatchLocalInspectionTo if (isIgnored(superClass)) return; } } - registerError(reference, languageLevel); + registerError(reference, sinceLanguageLevel); } else if (resolved instanceof PsiClass && isInProject(reference)&& !languageLevel.isAtLeast(LanguageLevel.JDK_1_7)) { final PsiReferenceParameterList parameterList = reference.getParameterList(); if (parameterList != null && parameterList.getTypeParameterElements().length > 0) { @@ -296,8 +298,9 @@ public class Java15APIUsageInspectionBase extends BaseJavaBatchLocalInspectionTo if (module != null) { final LanguageLevel languageLevel = getEffectiveLanguageLevel(module); if (constructor instanceof PsiCompiledElement) { - if (isForbiddenApiUsage(constructor, languageLevel)) { - registerError(expression.getClassReference(), languageLevel); + LanguageLevel sinceLanguageLevel = getLastIncompatibleLanguageLevel(constructor, languageLevel); + if (sinceLanguageLevel != null) { + registerError(expression.getClassReference(), sinceLanguageLevel); } } } @@ -309,12 +312,14 @@ public class Java15APIUsageInspectionBase extends BaseJavaBatchLocalInspectionTo PsiAnnotation annotation = !method.isConstructor() ? AnnotationUtil.findAnnotation(method, CommonClassNames.JAVA_LANG_OVERRIDE) : null; if (annotation != null) { final Module module = ModuleUtilCore.findModuleForPsiElement(annotation); + LanguageLevel sinceLanguageLevel = null; if (module != null) { final LanguageLevel languageLevel = getEffectiveLanguageLevel(module); final PsiMethod[] methods = method.findSuperMethods(); for (PsiMethod superMethod : methods) { if (superMethod instanceof PsiCompiledElement) { - if (!isForbiddenApiUsage(superMethod, languageLevel)) { + sinceLanguageLevel = getLastIncompatibleLanguageLevel(superMethod, languageLevel); + if (sinceLanguageLevel == null) { return; } } @@ -323,7 +328,7 @@ public class Java15APIUsageInspectionBase extends BaseJavaBatchLocalInspectionTo } } if (methods.length > 0) { - registerError(annotation.getNameReferenceElement(), languageLevel); + registerError(annotation.getNameReferenceElement(), sinceLanguageLevel); } } } @@ -337,7 +342,9 @@ public class Java15APIUsageInspectionBase extends BaseJavaBatchLocalInspectionTo private void registerError(PsiJavaCodeReferenceElement reference, LanguageLevel api) { if (reference != null && isInProject(reference)) { //noinspection DialogTitleCapitalization - myHolder.registerProblem(reference, InspectionsBundle.message("inspection.1.5.problem.descriptor", getShortName(api))); + myHolder.registerProblem(reference, + InspectionsBundle.message("inspection.1.5.problem.descriptor", getShortName(api)), + myOnTheFly ? new LocalQuickFix[] {(LocalQuickFix)QuickFixFactory.getInstance().createIncreaseLanguageLevelFix(LanguageLevel.values()[api.ordinal() + 1])} : null); } } } @@ -347,33 +354,32 @@ public class Java15APIUsageInspectionBase extends BaseJavaBatchLocalInspectionTo return presentableText.substring(0, presentableText.indexOf(' ')); } - public static boolean isForbiddenApiUsage(@NotNull PsiMember member, @NotNull LanguageLevel languageLevel) { - if (member instanceof PsiAnonymousClass) return false; + public static LanguageLevel getLastIncompatibleLanguageLevel(@NotNull PsiMember member, @NotNull LanguageLevel languageLevel) { + if (member instanceof PsiAnonymousClass) return null; PsiClass containingClass = member.getContainingClass(); - if (containingClass instanceof PsiAnonymousClass) return false; - if (member instanceof PsiClass && !(member.getParent() instanceof PsiClass || member.getParent() instanceof PsiFile)) return false; + if (containingClass instanceof PsiAnonymousClass) return null; + if (member instanceof PsiClass && !(member.getParent() instanceof PsiClass || member.getParent() instanceof PsiFile)) return null; - return isForbiddenSignature(member, languageLevel) || - containingClass != null && isForbiddenApiUsage(containingClass, languageLevel); - - } - - private static boolean isForbiddenSignature(@NotNull PsiMember member, @NotNull LanguageLevel languageLevel) { Set forbiddenApi = getForbiddenApi(languageLevel); String signature = getSignature(member); - return forbiddenApi != null && signature != null && isForbiddenSignature(signature, languageLevel, forbiddenApi); + if (forbiddenApi != null && signature != null) { + LanguageLevel lastIncompatibleLanguageLevel = getLastIncompatibleLanguageLevelForSignature(signature, languageLevel, forbiddenApi); + if (lastIncompatibleLanguageLevel != null) return lastIncompatibleLanguageLevel; + } + return containingClass != null ? getLastIncompatibleLanguageLevel(containingClass, languageLevel) : null; + } - private static boolean isForbiddenSignature(@NotNull String signature, @NotNull LanguageLevel languageLevel, @NotNull Set forbiddenApi) { + private static LanguageLevel getLastIncompatibleLanguageLevelForSignature(@NotNull String signature, @NotNull LanguageLevel languageLevel, @NotNull Set forbiddenApi) { if (forbiddenApi.contains(signature)) { - return true; + return languageLevel; } if (languageLevel.compareTo(ourHighestKnownLanguage) == 0) { - return false; + return null; } LanguageLevel nextLanguageLevel = LanguageLevel.values()[languageLevel.ordinal() + 1]; Set nextForbiddenApi = getForbiddenApi(nextLanguageLevel); - return nextForbiddenApi != null && isForbiddenSignature(signature, nextLanguageLevel, nextForbiddenApi); + return nextForbiddenApi != null ? getLastIncompatibleLanguageLevelForSignature(signature, nextLanguageLevel, nextForbiddenApi) : null; } /** diff --git a/java/java-impl/src/com/intellij/codeInsight/FunctionalInterfaceSuggester.java b/java/java-impl/src/com/intellij/codeInsight/FunctionalInterfaceSuggester.java index 27c06e42df1c..be15abead8b3 100644 --- a/java/java-impl/src/com/intellij/codeInsight/FunctionalInterfaceSuggester.java +++ b/java/java-impl/src/com/intellij/codeInsight/FunctionalInterfaceSuggester.java @@ -144,7 +144,7 @@ public class FunctionalInterfaceSuggester { final Project project = element.getProject(); final Set types = new HashSet<>(); final Processor consumer = member -> { - if (member instanceof PsiClass && !Java15APIUsageInspectionBase.isForbiddenApiUsage(member, PsiUtil.getLanguageLevel(element))) { + if (member instanceof PsiClass && Java15APIUsageInspectionBase.getLastIncompatibleLanguageLevel(member, PsiUtil.getLanguageLevel(element)) == null) { if (!JavaResolveUtil.isAccessible(member, null, member.getModifierList(), element, null, null)) { return true; } diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java index 0f3714a768a1..a450028e9869 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java @@ -481,7 +481,7 @@ public class JavaCompletionUtil { private static boolean shouldMarkRed(@NotNull Object object, @NotNull PsiElement place) { if (!(object instanceof PsiMember)) return false; - if (Java15APIUsageInspectionBase.isForbiddenApiUsage((PsiMember)object, PsiUtil.getLanguageLevel(place))) return true; + if (Java15APIUsageInspectionBase.getLastIncompatibleLanguageLevel((PsiMember)object, PsiUtil.getLanguageLevel(place)) != null) return true; if (object instanceof PsiEnumConstant) { return findConstantsUsedInSwitch(place).contains(CompletionUtil.getOriginalOrSelf((PsiEnumConstant)object)); diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/IncreaseLanguageLevelFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/IncreaseLanguageLevelFix.java index 760ad0fa4959..bc8ccb263837 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/IncreaseLanguageLevelFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/IncreaseLanguageLevelFix.java @@ -17,6 +17,8 @@ package com.intellij.codeInsight.daemon.impl.analysis; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.module.Module; @@ -29,13 +31,14 @@ import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * @author cdr */ -public class IncreaseLanguageLevelFix implements IntentionAction { +public class IncreaseLanguageLevelFix implements IntentionAction, LocalQuickFix { private static final Logger LOG = Logger.getInstance("#" + IncreaseLanguageLevelFix.class.getName()); private final LanguageLevel myLevel; @@ -50,12 +53,25 @@ public class IncreaseLanguageLevelFix implements IntentionAction { return CodeInsightBundle.message("set.language.level.to.0", myLevel.getPresentableText()); } + @Nls + @NotNull + @Override + public String getName() { + return getText(); + } + @Override @NotNull public String getFamilyName() { return CodeInsightBundle.message("set.language.level"); } + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiElement element = descriptor.getPsiElement(); + invoke(project, null, element.getContainingFile()); + } + @Override public boolean isAvailable(@NotNull final Project project, final Editor editor, final PsiFile file) { final VirtualFile virtualFile = file.getVirtualFile(); @@ -67,9 +83,7 @@ public class IncreaseLanguageLevelFix implements IntentionAction { @Override public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { - final VirtualFile virtualFile = file.getVirtualFile(); - LOG.assertTrue(virtualFile != null); - final Module module = ModuleUtilCore.findModuleForFile(virtualFile, project); + final Module module = ModuleUtilCore.findModuleForPsiElement(file); if (module == null) return; JavaProjectModelModificationService.getInstance(project).changeLanguageLevel(module, myLevel); diff --git a/java/java-tests/testData/inspection/usage1.5/overrideAnnotation/expected.xml b/java/java-tests/testData/inspection/usage1.5/overrideAnnotation/expected.xml index bd67b3f7a82c..6502e2c42b21 100644 --- a/java/java-tests/testData/inspection/usage1.5/overrideAnnotation/expected.xml +++ b/java/java-tests/testData/inspection/usage1.5/overrideAnnotation/expected.xml @@ -4,7 +4,7 @@ Test.java 4 Usages of API documented as @since 1.5 (1.6|1.7) - Usage of API documented as @since 1.7+ + Usage of API documented as @since 1.8+ \ No newline at end of file diff --git a/plugins/ui-designer/src/com/intellij/uiDesigner/inspections/Java15FormInspection.java b/plugins/ui-designer/src/com/intellij/uiDesigner/inspections/Java15FormInspection.java index 53ea8102d5b6..ffeab244f131 100644 --- a/plugins/ui-designer/src/com/intellij/uiDesigner/inspections/Java15FormInspection.java +++ b/plugins/ui-designer/src/com/intellij/uiDesigner/inspections/Java15FormInspection.java @@ -58,7 +58,7 @@ public class Java15FormInspection extends BaseFormInspection { final PsiMethod getter = PropertyUtil.findPropertyGetter(aClass, prop.getName(), false, true); if (getter == null) continue; final LanguageLevel languageLevel = LanguageLevelUtil.getEffectiveLanguageLevel(module); - if (Java15APIUsageInspection.isForbiddenApiUsage(getter, languageLevel)) { + if (Java15APIUsageInspection.getLastIncompatibleLanguageLevel(getter, languageLevel) != null) { registerError(component, collector, prop, "@since " + Java15APIUsageInspection.getShortName(languageLevel)); } } From 02d25ac5f60be6a7975c57f7d584c0b11b3855aa Mon Sep 17 00:00:00 2001 From: "Anna.Kozlova" Date: Wed, 15 Mar 2017 19:08:13 +0100 Subject: [PATCH 09/11] invert if: pass context to resolve to correct inner/local class (IDEA-169396) --- .../intention/impl/InvertIfConditionAction.java | 12 ++++++------ .../afterConflictWithJavaLangClass.java | 16 ++++++++++++++++ .../beforeConflictWithJavaLangClass.java | 14 ++++++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/invertIfCondition/afterConflictWithJavaLangClass.java create mode 100644 java/java-tests/testData/codeInsight/invertIfCondition/beforeConflictWithJavaLangClass.java diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/InvertIfConditionAction.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/InvertIfConditionAction.java index 71046a572197..87ac4653c2aa 100644 --- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/InvertIfConditionAction.java +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/InvertIfConditionAction.java @@ -102,7 +102,7 @@ public class InvertIfConditionAction extends PsiElementBaseIntentionAction { final CodeStyleManager codeStyle = CodeStyleManager.getInstance(project); - PsiBlockStatement codeBlock = (PsiBlockStatement)factory.createStatementFromText("{}", null); + PsiBlockStatement codeBlock = (PsiBlockStatement)factory.createStatementFromText("{}", ifStatement); codeBlock = (PsiBlockStatement)codeStyle.reformat(codeBlock); ifStatement.getThenBranch().replace(codeBlock); @@ -182,7 +182,7 @@ public class InvertIfConditionAction extends PsiElementBaseIntentionAction { final CodeStyleManager codeStyle = CodeStyleManager.getInstance(project); if (flow.getSize() == 0) { ifStatement.setElseBranch(thenBranch); - PsiStatement statement = factory.createStatementFromText("{}", null); + PsiStatement statement = factory.createStatementFromText("{}", ifStatement); statement = (PsiStatement) codeStyle.reformat(statement); statement = (PsiStatement) ifStatement.getThenBranch().replace(statement); codeStyle.reformat(statement); @@ -194,7 +194,7 @@ public class InvertIfConditionAction extends PsiElementBaseIntentionAction { LOG.assertTrue(endOffset >= 0); if (endOffset >= flow.getSize()) { - PsiStatement statement = factory.createStatementFromText("return;", null); + PsiStatement statement = factory.createStatementFromText("return;", ifStatement); statement = (PsiStatement) codeStyle.reformat(statement); if (thenBranch instanceof PsiBlockStatement) { PsiStatement[] statements = ((PsiBlockStatement) thenBranch).getCodeBlock().getStatements(); @@ -219,7 +219,7 @@ public class InvertIfConditionAction extends PsiElementBaseIntentionAction { if (element != null && element.getParent() instanceof PsiForStatement && ((PsiForStatement)element.getParent()).getUpdate() == element || element instanceof PsiWhileStatement && flow.getStartOffset(element) == endOffset || element instanceof PsiForeachStatement && flow.getStartOffset(element) + 1 == endOffset) { - PsiStatement statement = factory.createStatementFromText("continue;", null); + PsiStatement statement = factory.createStatementFromText("continue;", ifStatement); statement = (PsiStatement)codeStyle.reformat(statement); ifStatement = addAfterWithinCodeBlock(ifStatement, thenBranch); ifStatement.getThenBranch().replace(statement); @@ -267,7 +267,7 @@ public class InvertIfConditionAction extends PsiElementBaseIntentionAction { last = last.getPrevSibling(); - PsiBlockStatement codeBlock = (PsiBlockStatement) factory.createStatementFromText("{}", null); + PsiBlockStatement codeBlock = (PsiBlockStatement) factory.createStatementFromText("{}", ifStatement); codeBlock.getCodeBlock().addRange(first, last); first.getParent().deleteChildRange(first, last); ifStatement.getThenBranch().replace(codeBlock); @@ -277,7 +277,7 @@ public class InvertIfConditionAction extends PsiElementBaseIntentionAction { } setElseBranch(ifStatement, thenBranch, flow); - PsiStatement statement = factory.createStatementFromText("{}", null); + PsiStatement statement = factory.createStatementFromText("{}", ifStatement); statement = (PsiStatement) codeStyle.reformat(statement); statement = (PsiStatement) ifStatement.getThenBranch().replace(statement); codeStyle.reformat(statement); diff --git a/java/java-tests/testData/codeInsight/invertIfCondition/afterConflictWithJavaLangClass.java b/java/java-tests/testData/codeInsight/invertIfCondition/afterConflictWithJavaLangClass.java new file mode 100644 index 000000000000..e6b801d124b6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/invertIfCondition/afterConflictWithJavaLangClass.java @@ -0,0 +1,16 @@ +// "Invert 'if' condition" "true" +class Main2 { + private static class Process { } + + public static void main(String[] args) { + List processList = null; + if (processList != null) { + for (Process process : processList) { + System.out.println("process = " + process); + } + } + else { + return; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/invertIfCondition/beforeConflictWithJavaLangClass.java b/java/java-tests/testData/codeInsight/invertIfCondition/beforeConflictWithJavaLangClass.java new file mode 100644 index 000000000000..b69a64f15fd4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/invertIfCondition/beforeConflictWithJavaLangClass.java @@ -0,0 +1,14 @@ +// "Invert 'if' condition" "true" +class Main2 { + private static class Process { } + + public static void main(String[] args) { + List processList = null; + if (processList == null) { + return; + } + for (Process process : processList) { + System.out.println("process = " + process); + } + } +} \ No newline at end of file From d688a9a7a424d7fa2c31e41590b5748c9a0a1aa0 Mon Sep 17 00:00:00 2001 From: "Anna.Kozlova" Date: Wed, 15 Mar 2017 20:05:16 +0100 Subject: [PATCH 10/11] guess expected type like for completion if on the left side of assignment to functional expression which provides in this case no expected type (IDEA-169382) --- .../daemon/impl/quickfix/CreateFromUsageUtils.java | 5 ++++- ...ssignmentToFunctionalExpressionInArgumentsList.java | 10 ++++++++++ ...ssignmentToFunctionalExpressionInArgumentsList.java | 9 +++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalFromUsage/afterAssignmentToFunctionalExpressionInArgumentsList.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalFromUsage/beforeAssignmentToFunctionalExpressionInArgumentsList.java diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateFromUsageUtils.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateFromUsageUtils.java index d7b299e9a0f3..1285b99ebf95 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateFromUsageUtils.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateFromUsageUtils.java @@ -575,7 +575,10 @@ public class CreateFromUsageUtils { PsiElement parent = expr.getParent(); if (!(parent instanceof PsiReferenceExpression)) { - ExpectedTypeInfo[] someExpectedTypes = ExpectedTypesProvider.getExpectedTypes(expr, PsiUtil.skipParenthesizedExprUp(parent) instanceof PsiExpressionList); + boolean isAssignmentToFunctionalExpression = PsiUtil.isOnAssignmentLeftHand(expr) && + ((PsiAssignmentExpression)PsiUtil.skipParenthesizedExprUp(parent)).getRExpression() instanceof PsiFunctionalExpression; + boolean forCompletion = PsiUtil.skipParenthesizedExprUp(isAssignmentToFunctionalExpression ? parent.getParent() : parent) instanceof PsiExpressionList; + ExpectedTypeInfo[] someExpectedTypes = ExpectedTypesProvider.getExpectedTypes(expr, forCompletion); if (someExpectedTypes.length > 0) { Arrays.sort(someExpectedTypes, (o1, o2) -> compareExpectedTypes(o1, o2, expression)); types.add(someExpectedTypes); diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalFromUsage/afterAssignmentToFunctionalExpressionInArgumentsList.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalFromUsage/afterAssignmentToFunctionalExpressionInArgumentsList.java new file mode 100644 index 000000000000..8145e314de71 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalFromUsage/afterAssignmentToFunctionalExpressionInArgumentsList.java @@ -0,0 +1,10 @@ +// "Create local variable 'lf'" "true" +import java.util.function.Function; + +class Main2 { + void f(Function g) {} + { + Function lf; + f(lf = c -> c); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalFromUsage/beforeAssignmentToFunctionalExpressionInArgumentsList.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalFromUsage/beforeAssignmentToFunctionalExpressionInArgumentsList.java new file mode 100644 index 000000000000..36e822ddf2c8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalFromUsage/beforeAssignmentToFunctionalExpressionInArgumentsList.java @@ -0,0 +1,9 @@ +// "Create local variable 'lf'" "true" +import java.util.function.Function; + +class Main2 { + void f(Function g) {} + { + f(lf = c -> c); + } +} From ab5ce52f58ee143efa47dc63c7d604535881cfff Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 15 Mar 2017 22:44:14 +0300 Subject: [PATCH 11/11] Allow search for Python stubs for third-party libraries in tests --- .../src/com/jetbrains/python/codeInsight/typing/PyTypeShed.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypeShed.kt b/python/src/com/jetbrains/python/codeInsight/typing/PyTypeShed.kt index ed3f86b4f8b5..948f9e71b7c0 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypeShed.kt +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypeShed.kt @@ -15,6 +15,7 @@ */ package com.jetbrains.python.codeInsight.typing +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.PathManager import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.vfs.StandardFileSystems @@ -58,6 +59,9 @@ object PyTypeShed { return true } if (isInThirdPartyLibraries(root)) { + if (ApplicationManager.getApplication().isUnitTestMode) { + return true + } val pyPIPackage = PyPIPackageUtil.PACKAGES_TOPLEVEL[topLevelPackage] ?: topLevelPackage val packages = PyPackageManagers.getInstance().forSdk(sdk).packages ?: return true return PyPackageUtil.findPackage(packages, pyPIPackage) != null