diff --git a/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/codemining/ParameterNamesCodeMiningTest.java b/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/codemining/ParameterNamesCodeMiningTest.java index b56101094bd..628246595ae 100644 --- a/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/codemining/ParameterNamesCodeMiningTest.java +++ b/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/codemining/ParameterNamesCodeMiningTest.java @@ -102,11 +102,11 @@ public void tearDown() throws Exception { } } - private void waitReconciled(JavaSourceViewer viewer) { + private void waitReconciled(JavaEditor editor, JavaSourceViewer viewer) { assertTrue(new DisplayHelper() { @Override protected boolean condition() { - return JavaCodeMiningReconciler.isReconciled(viewer); + return JavaCodeMiningReconciler.getFuture(editor).isDone(); } }.waitForCondition(viewer.getTextWidget().getDisplay(), 2000), "Editor not reconciled"); } @@ -122,7 +122,7 @@ public class Foo { JavaEditor editor= (JavaEditor) EditorUtility.openInEditor(compilationUnit); fParameterNameCodeMiningProvider.setContext(editor); JavaSourceViewer viewer= (JavaSourceViewer)editor.getViewer(); - waitReconciled(viewer); + waitReconciled(editor, viewer); assertEquals(3, fParameterNameCodeMiningProvider.provideCodeMinings(viewer, new NullProgressMonitor()).get().size()); } @@ -138,7 +138,7 @@ public class Foo { JavaEditor editor= (JavaEditor) EditorUtility.openInEditor(compilationUnit); fParameterNameCodeMiningProvider.setContext(editor); JavaSourceViewer viewer= (JavaSourceViewer)editor.getViewer(); - waitReconciled(viewer); + waitReconciled(editor, viewer); assertEquals(2, fParameterNameCodeMiningProvider.provideCodeMinings(viewer, new NullProgressMonitor()).get().size()); } @@ -162,7 +162,7 @@ public void foo() { viewer.setCodeMiningProviders(new ICodeMiningProvider[] { fParameterNameCodeMiningProvider }); - waitReconciled(viewer); + waitReconciled(editor, viewer); StyledText widget= viewer.getTextWidget(); // assertEquals(2, fParameterNameCodeMiningProvider.provideCodeMinings(viewer, new NullProgressMonitor()).get().size()); @@ -192,7 +192,7 @@ public void mehod() { JavaEditor editor= (JavaEditor) EditorUtility.openInEditor(compilationUnit); fParameterNameCodeMiningProvider.setContext(editor); JavaSourceViewer viewer= (JavaSourceViewer)editor.getViewer(); - waitReconciled(viewer); + waitReconciled(editor, viewer); // Only code mining on "printf" parameters assertEquals(2, fParameterNameCodeMiningProvider.provideCodeMinings(viewer, new NullProgressMonitor()).get().size()); } @@ -221,7 +221,7 @@ public class Foo { viewer.setCodeMiningProviders(new ICodeMiningProvider[] { fParameterNameCodeMiningProvider }); - waitReconciled(viewer); + waitReconciled(editor, viewer); AtomicReference errorInLog= new AtomicReference<>(); ILogListener logListener= (status, plugin) -> { @@ -254,7 +254,7 @@ public class Foo { viewer.setCodeMiningProviders(new ICodeMiningProvider[] { fParameterNameCodeMiningProvider }); - waitReconciled(viewer); + waitReconciled(editor, viewer); // StyledText widget= viewer.getTextWidget(); int charWidth= widget.getTextBounds(0, 1).width; @@ -324,7 +324,7 @@ public void testBug547232() throws Exception { JavaEditor editor= (JavaEditor) EditorUtility.openInEditor(compilationUnit); fParameterNameCodeMiningProvider.setContext(editor); JavaSourceViewer viewer= (JavaSourceViewer)editor.getViewer(); - waitReconciled(viewer); + waitReconciled(editor, viewer); assertEquals(2, fParameterNameCodeMiningProvider.provideCodeMinings(viewer, new NullProgressMonitor()).get().size()); } @@ -351,7 +351,7 @@ public void testBug549023() throws Exception { JavaEditor editor= (JavaEditor) EditorUtility.openInEditor(compilationUnit); fParameterNameCodeMiningProvider.setContext(editor); JavaSourceViewer viewer= (JavaSourceViewer)editor.getViewer(); - waitReconciled(viewer); + waitReconciled(editor, viewer); assertEquals(2, fParameterNameCodeMiningProvider.provideCodeMinings(viewer, new NullProgressMonitor()).get().size()); } @@ -374,7 +374,7 @@ public void testBug549126() throws Exception { JavaEditor editor= (JavaEditor) EditorUtility.openInEditor(compilationUnit); fParameterNameCodeMiningProvider.setContext(editor); JavaSourceViewer viewer= (JavaSourceViewer)editor.getViewer(); - waitReconciled(viewer); + waitReconciled(editor, viewer); assertEquals(2, fParameterNameCodeMiningProvider.provideCodeMinings(viewer, new NullProgressMonitor()).get().size()); } @@ -405,7 +405,7 @@ record Ca (int size){ JavaEditor editor= (JavaEditor) EditorUtility.openInEditor(compilationUnit); fParameterNameCodeMiningProvider.setContext(editor); JavaSourceViewer viewer= (JavaSourceViewer)editor.getViewer(); - waitReconciled(viewer); + waitReconciled(editor, viewer); assertEquals(expectedShownNames, fParameterNameCodeMiningProvider.provideCodeMinings(viewer, new NullProgressMonitor()).get().size()); } @@ -440,7 +440,7 @@ public void test () { JavaEditor editor= (JavaEditor) EditorUtility.openInEditor(compilationUnit); fParameterNameCodeMiningProvider.setContext(editor); JavaSourceViewer viewer= (JavaSourceViewer)editor.getViewer(); - waitReconciled(viewer); + waitReconciled(editor, viewer); // 6 parameter names from Edge // 4 parameter names from the 2 Map.entry(int, int) calls @@ -484,7 +484,7 @@ public void foo() { JavaEditor editor= (JavaEditor) EditorUtility.openInEditor(compilationUnit); fParameterNameCodeMiningProvider.setContext(editor); JavaSourceViewer viewer= (JavaSourceViewer)editor.getViewer(); - waitReconciled(viewer); + waitReconciled(editor,viewer); assertEquals(0, fParameterNameCodeMiningProvider.provideCodeMinings(viewer, new NullProgressMonitor()).get().size()); } diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaCodeMiningReconciler.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaCodeMiningReconciler.java index a2cc8a608e0..fe8aae7c523 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaCodeMiningReconciler.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaCodeMiningReconciler.java @@ -13,14 +13,18 @@ *******************************************************************************/ package org.eclipse.jdt.internal.ui.javaeditor; -import java.util.HashSet; -import java.util.Set; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.ISourceViewerExtension5; +import org.eclipse.ui.texteditor.ITextEditor; + +import org.eclipse.jdt.core.ITypeRoot; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.internal.ui.text.java.IJavaReconcilingListener; @@ -31,10 +35,12 @@ public class JavaCodeMiningReconciler implements IJavaReconcilingListener { /** - * Stores the set of viewers for which source is reconciled and requests - * for references can be performed. + * Maps Java editors to futures representing their associated {@link ITypeRoot}. + *

+ * The future is completed when a reconciled {@link ITypeRoot} becomes available for the editor, + * or cancelled/replaced when a new reconciliation cycle starts. */ - private static final Set reconciledViewers= new HashSet<>(); + private static final Map> typeRootFutureByEditor= new ConcurrentHashMap<>(); /** The Java editor this Java code mining reconciler is installed on */ private JavaEditor fEditor; @@ -45,17 +51,45 @@ public class JavaCodeMiningReconciler implements IJavaReconcilingListener { @Override public void reconciled(CompilationUnit ast, boolean forced, IProgressMonitor progressMonitor) { - final ISourceViewerExtension5 sourceViewer= fSourceViewer; // take a copy as this can be null-ed in the meantime - if (sourceViewer != null) { - reconciledViewers.add(sourceViewer); + final JavaEditor editor= fEditor; // take a copy as this can be null-ed in the meantime + final ISourceViewerExtension5 sourceViewer= fSourceViewer; + if (editor != null && sourceViewer != null) { sourceViewer.updateCodeMinings(); + CompletableFuture future= typeRootFutureByEditor.get(editor); + if (future != null && !future.isDone()) { + if (ast != null && ast.getTypeRoot() != null) { + future.complete(ast.getTypeRoot()); + } else { + future.cancel(false); + } + } + } + } + + public static CompletableFuture getFuture(ITextEditor editor) { + return typeRootFutureByEditor.computeIfAbsent(editor, JavaCodeMiningReconciler::typeRootFor); + } + + private static CompletableFuture typeRootFor(ITextEditor editor) { + CompletableFuture future= new CompletableFuture<>(); + ITypeRoot unit= EditorUtility.getEditorInputJavaElement(editor, true); + if (unit != null) { + future.complete(unit); } + return future; } @Override public void aboutToBeReconciled() { - // interrupt code minings if modification occurs - reconciledViewers.remove(fSourceViewer); + if (fEditor == null) { + return; + } + typeRootFutureByEditor.compute(fEditor, (editor, existingFuture) -> { + if (existingFuture != null) { + existingFuture.cancel(false); + } + return new CompletableFuture(); + }); } /** @@ -83,16 +117,14 @@ public void install(JavaEditor editor, ISourceViewer sourceViewer) { * Uninstall this reconciler from the editor. */ public void uninstall() { - reconciledViewers.remove(fSourceViewer); + CompletableFuture future= typeRootFutureByEditor.remove(fEditor); + if (future != null) { + future.cancel(false); + } if (fEditor instanceof CompilationUnitEditor) { ((CompilationUnitEditor) fEditor).removeReconcileListener(this); } fEditor= null; fSourceViewer= null; } - - public static boolean isReconciled(ISourceViewerExtension5 viewer) { - return reconciledViewers.contains(viewer); - } - } diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/codemining/JavaElementCodeMiningProvider.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/codemining/JavaElementCodeMiningProvider.java index 6f311f16841..38489dbf9fc 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/codemining/JavaElementCodeMiningProvider.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/codemining/JavaElementCodeMiningProvider.java @@ -16,7 +16,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.TimeUnit; import org.eclipse.core.runtime.IProgressMonitor; @@ -24,7 +27,6 @@ import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.codemining.AbstractCodeMiningProvider; import org.eclipse.jface.text.codemining.ICodeMining; -import org.eclipse.jface.text.source.ISourceViewerExtension5; import org.eclipse.ui.texteditor.ITextEditor; @@ -36,7 +38,6 @@ import org.eclipse.jdt.ui.PreferenceConstants; -import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility; import org.eclipse.jdt.internal.ui.javaeditor.JavaCodeMiningReconciler; import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; import org.eclipse.jdt.internal.ui.preferences.JavaPreferencesPropertyTester; @@ -83,40 +84,34 @@ public CompletableFuture> provideCodeMinings(ITextVi if (!editorEnabled) { return CompletableFuture.completedFuture(Collections.emptyList()); } - if (viewer instanceof ISourceViewerExtension5) { - ISourceViewerExtension5 codeMiningViewer = (ISourceViewerExtension5)viewer; - if (!JavaCodeMiningReconciler.isReconciled(codeMiningViewer)) { - // the provider isn't able to return code minings for non-reconciled viewers - return CompletableFuture.completedFuture(Collections.emptyList()); + ITextEditor textEditor= super.getAdapter(ITextEditor.class); + CompletableFuture future= JavaCodeMiningReconciler.getFuture(textEditor); + return future.thenApplyAsync(typeRoot -> { + return computeCodeMinings(viewer, textEditor, monitor, typeRoot); + }).orTimeout(15, TimeUnit.SECONDS).handle((result, ex) -> { + if (ex instanceof CompletionException ce && + ce.getCause() instanceof CancellationException) { + monitor.setCanceled(true); } - } - return CompletableFuture.supplyAsync(() -> { - monitor.isCanceled(); - ITextEditor textEditor= super.getAdapter(ITextEditor.class); - ITypeRoot unit= EditorUtility.getEditorInputJavaElement(textEditor, true); - if (unit == null) { - return Collections.emptyList(); - } - try { - IJavaElement[] elements= unit.getChildren(); - List minings= new ArrayList<>(elements.length); - collectMinings(unit, textEditor, unit.getChildren(), minings, viewer, monitor); - // interrupt if editor was marked to be reconciled in the meantime - if (viewer instanceof ISourceViewerExtension5) { - ISourceViewerExtension5 codeMiningViewer= (ISourceViewerExtension5)viewer; - if (!JavaCodeMiningReconciler.isReconciled(codeMiningViewer)) { - monitor.setCanceled(true); - } - } - monitor.isCanceled(); - return minings; - } catch (JavaModelException e) { - // Should never occur - } - return Collections.emptyList(); + return result; }); } + private List computeCodeMinings(ITextViewer viewer, ITextEditor textEditor, IProgressMonitor monitor, ITypeRoot unit) { + if (unit == null) { + return Collections.emptyList(); + } + try { + IJavaElement[] elements= unit.getChildren(); + List minings= new ArrayList<>(elements.length); + collectMinings(unit, textEditor, unit.getChildren(), minings, viewer, monitor); + return minings; + } catch (JavaModelException e) { + // Should never occur + } + return Collections.emptyList(); + } + /** * Collect java code minings. * diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/codemining/JavaMethodParameterCodeMiningProvider.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/codemining/JavaMethodParameterCodeMiningProvider.java index ba08d15020c..5285edabfda 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/codemining/JavaMethodParameterCodeMiningProvider.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/codemining/JavaMethodParameterCodeMiningProvider.java @@ -16,14 +16,16 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.TimeUnit; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.codemining.AbstractCodeMiningProvider; import org.eclipse.jface.text.codemining.ICodeMining; -import org.eclipse.jface.text.source.ISourceViewerExtension5; import org.eclipse.ui.texteditor.ITextEditor; @@ -36,7 +38,6 @@ import org.eclipse.jdt.internal.corext.dom.IASTSharedValues; import org.eclipse.jdt.internal.ui.JavaPlugin; -import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility; import org.eclipse.jdt.internal.ui.javaeditor.JavaCodeMiningReconciler; /** @@ -48,40 +49,34 @@ public class JavaMethodParameterCodeMiningProvider extends AbstractCodeMiningPro @Override public CompletableFuture> provideCodeMinings(ITextViewer viewer, IProgressMonitor monitor) { - if (viewer instanceof ISourceViewerExtension5) { - ISourceViewerExtension5 codeMiningViewer= (ISourceViewerExtension5)viewer; - if (!JavaCodeMiningReconciler.isReconciled(codeMiningViewer)) { - // the provider isn't able to return code minings for non-reconciled viewers - return CompletableFuture.completedFuture(Collections.emptyList()); + ITextEditor textEditor= super.getAdapter(ITextEditor.class); + CompletableFuture future= JavaCodeMiningReconciler.getFuture(textEditor); + return future.thenApplyAsync(typeRoot -> { + return computeCodeMinings(typeRoot); + }).orTimeout(15, TimeUnit.SECONDS).handle((result, ex) -> { + if (ex instanceof CompletionException ce && + ce.getCause() instanceof CancellationException) { + monitor.setCanceled(true); } - } - return CompletableFuture.supplyAsync(() -> { - monitor.isCanceled(); - ITextEditor textEditor= super.getAdapter(ITextEditor.class); - ITypeRoot unit= EditorUtility.getEditorInputJavaElement(textEditor, true); - if (unit == null) { - return null; - } - try { - IJavaElement[] elements= unit.getChildren(); - List minings= new ArrayList<>(elements.length); - collectLineContentCodeMinings(unit, minings); - if (viewer instanceof ISourceViewerExtension5) { - ISourceViewerExtension5 codeMiningViewer= (ISourceViewerExtension5)viewer; - if (!JavaCodeMiningReconciler.isReconciled(codeMiningViewer)) { - // the provider isn't able to return code minings for non-reconciled viewers - monitor.setCanceled(true); - } - } - monitor.isCanceled(); - return minings; - } catch (JavaModelException e) { - // TODO: what should we done when there are some errors? - } - return null; + return result; }); } + private List computeCodeMinings(ITypeRoot unit) { + if (unit == null) { + return Collections.emptyList(); + } + try { + IJavaElement[] elements= unit.getChildren(); + List minings= new ArrayList<>(elements.length); + collectLineContentCodeMinings(unit, minings); + return minings; + } catch (JavaModelException e) { + // TODO: what should we done when there are some errors? + } + return Collections.emptyList(); + } + private void collectLineContentCodeMinings(ITypeRoot unit, List minings) { CompilationUnit cu= getCompilationUnitNode(unit, true); CalleeJavaMethodParameterVisitor visitor= new CalleeJavaMethodParameterVisitor(minings, this);