From 6d2a728672c7dd5ad438c650f72224b7cdbb81c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Thu, 24 Jul 2025 06:42:08 +0200 Subject: [PATCH] Restore original type from class file input in background If ClassFileEditor is restored, underlined class file might be moved in the meantime (e.g. library on classpath changed version so the type is still there but the absolute class file path changed). To check if the type is still on the classpath of the project might freeze UI for some time, so move this check to a job. If the job is running very early on startup, project classpath might be not yet updated (see RequiredPluginsInitializer and UpdateClasspathsJob in PDE which initialize classpath container lazily). In the case the type is not found, listen to JDT model changes and refresh the input in case the project classpath is updated. Fixes https://github.com/eclipse-jdt/eclipse.jdt.ui/issues/2245 --- .../javaeditor/ClassFileDocumentProvider.java | 11 +- .../ui/javaeditor/ClassFileEditor.java | 138 +++++++++++------- .../InternalClassFileEditorInput.java | 11 +- .../internal/ui/javaeditor/JavaEditor.java | 3 +- 4 files changed, 106 insertions(+), 57 deletions(-) diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/ClassFileDocumentProvider.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/ClassFileDocumentProvider.java index e626f3be29a..d1e2dedf928 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/ClassFileDocumentProvider.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/ClassFileDocumentProvider.java @@ -209,7 +209,7 @@ public ClassFileDocumentProvider() { protected boolean setDocumentContent(IDocument document, IEditorInput editorInput, String encoding) throws CoreException { if (editorInput instanceof IClassFileEditorInput) { IClassFile classFile= ((IClassFileEditorInput) editorInput).getClassFile(); - String source= classFile.getSource(); + String source= getSourceIfPresent(classFile); if (source == null) source= ""; //$NON-NLS-1$ document.set(source); @@ -218,6 +218,15 @@ protected boolean setDocumentContent(IDocument document, IEditorInput editorInpu return super.setDocumentContent(document, editorInput, encoding); } + private String getSourceIfPresent(IClassFile classFile) { + try { + return classFile.getSource(); + } catch (JavaModelException e) { + //Assume no source... + return null; + } + } + /** * Creates an annotation model derived from the given class file editor input. * diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/ClassFileEditor.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/ClassFileEditor.java index 6c54d42df4d..5be307c34c2 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/ClassFileEditor.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/ClassFileEditor.java @@ -73,15 +73,18 @@ import org.eclipse.ui.texteditor.ITextEditorActionConstants; import org.eclipse.jdt.core.ClasspathContainerInitializer; +import org.eclipse.jdt.core.ElementChangedEvent; import org.eclipse.jdt.core.IClassFile; import org.eclipse.jdt.core.IClasspathAttribute; import org.eclipse.jdt.core.IClasspathContainer; import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IElementChangedListener; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaModelStatusConstants; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IOrdinaryClassFile; import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.ISourceReference; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; @@ -111,6 +114,75 @@ * Java specific text editor. */ public class ClassFileEditor extends JavaEditor implements ClassFileDocumentProvider.InputChangeListener { + + private final class LoadJob extends Job implements IElementChangedListener { + private final IJavaElement fElement; + private volatile boolean continueListening; + + private LoadJob(IJavaElement element) { + super("Restoring editor input for " + element.getElementName()); //$NON-NLS-1$ + fElement= element; + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + if(monitor.isCanceled() || disposed) { + continueListening = false; + return Status.CANCEL_STATUS; + } + if (!(fElement instanceof IOrdinaryClassFile) || fElement.exists()) { + return Status.OK_STATUS; + } + /* + * Let's try to find the class file, + * see https://bugs.eclipse.org/bugs/show_bug.cgi?id=83221 + */ + IOrdinaryClassFile cf= (IOrdinaryClassFile)fElement; + IType type= cf.getType(); + IJavaProject project= fElement.getJavaProject(); + if (project != null) { + type= project.findType(type.getFullyQualifiedName()); + if (type != null) { + continueListening = false; + IJavaElement javaElement= type.getParent(); + IEditorInput editorInput= EditorUtility.getEditorInput(javaElement); + PlatformUI.getWorkbench().getDisplay().asyncExec(() -> { + if (!disposed) { + setInput(editorInput); + if(javaElement instanceof ISourceReference ref) { + setBreadcrumbInput(ref); + } + } + }); + } else { + if (!continueListening) { + continueListening= true; + JavaCore.addElementChangedListener(this); + } + } + } + } catch (CoreException e) { + return e.getStatus(); + } finally { + if (!continueListening) { + JavaCore.removeElementChangedListener(this); + } + } + return Status.OK_STATUS; + } + + @Override + public boolean belongsTo(Object family) { + return family == ClassFileEditor.class; + } + + @Override + public void elementChanged(ElementChangedEvent event) { + schedule(100); + } + } + /** * A form to attach source to a class file. */ @@ -409,7 +481,9 @@ private void updateCodeView(StyledText styledText, IClassFile classFile) { try { content= disassembler.disassemble(classFile.getBytes(), "\n", ClassFileBytesDisassembler.DETAILED); //$NON-NLS-1$ } catch (JavaModelException ex) { - JavaPlugin.log(ex.getStatus()); + if (!ex.isDoesNotExist()) { + JavaPlugin.log(ex.getStatus()); + } } catch (ClassFormatException ex) { JavaPlugin.log(ex); } @@ -511,6 +585,7 @@ private boolean isEqualInput(IEditorInput input1, IEditorInput input2) { * @since 3.3 */ private StyledText fNoSourceTextWidget; + private volatile boolean disposed; /** * Default constructor. @@ -644,21 +719,8 @@ public boolean isEditorInputReadOnly() { protected IEditorInput transformEditorInput(IEditorInput input) throws CoreException{ if (input instanceof HandleEditorInput handle) { IJavaElement element= handle.getElement(); - if (!element.exists() && element instanceof IOrdinaryClassFile) { - /* - * Let's try to find the class file, - * see https://bugs.eclipse.org/bugs/show_bug.cgi?id=83221 - */ - IOrdinaryClassFile cf= (IOrdinaryClassFile)element; - IType type= cf.getType(); - IJavaProject project= element.getJavaProject(); - if (project != null) { - type= project.findType(type.getFullyQualifiedName()); - if (type == null) - return null; - element= type.getParent(); - } - } + LoadJob job= new LoadJob(element); + job.schedule(); input= EditorUtility.getEditorInput(element); } if (input instanceof IFileEditorInput) { @@ -694,21 +756,6 @@ private void doSetInputCached(IEditorInput input) throws CoreException { message, null)); } - - JavaModelException e= probeInputForSource(input); - if (e != null) { - IClassFileEditorInput classFileEditorInput= (IClassFileEditorInput) input; - IClassFile file= classFileEditorInput.getClassFile(); - IJavaProject javaProject= file.getJavaProject(); - if (!javaProject.exists() || !javaProject.isOnClasspath(file)) { - throw new CoreException(JavaUIStatus.createError( - IJavaModelStatusConstants.INVALID_RESOURCE, - JavaEditorMessages.ClassFileEditor_error_classfile_not_on_classpath, - null)); - } - throw e; - } - IDocumentProvider documentProvider= getDocumentProvider(); if (documentProvider instanceof ClassFileDocumentProvider) { ((ClassFileDocumentProvider) documentProvider).removeInputChangeListener(this); @@ -799,23 +846,6 @@ private void createPartControlCached(Composite parent) { } } - private JavaModelException probeInputForSource(IEditorInput input) { - if (input == null) { - return null; - } - - IClassFileEditorInput classFileEditorInput= (IClassFileEditorInput) input; - IClassFile file= classFileEditorInput.getClassFile(); - - try { - file.getSourceRange(); - } catch (JavaModelException e) { - return e; - } - - return null; - } - /** * Checks if the class file input has no source attached. If so, a source attachment form is shown. * @@ -836,7 +866,7 @@ private void verifyInput(IEditorInput input) throws JavaModelException { boolean wasUsingSourceCopyAction= fSourceCopyAction == getAction(ITextEditorActionConstants.COPY); // show source attachment form if no source found - if (file.getSourceRange() == null) { + if (!hasSource(file)) { // dispose old source attachment form if (fSourceAttachmentForm != null) { fSourceAttachmentForm.dispose(); @@ -925,6 +955,15 @@ public void run() { } } + private static boolean hasSource(IClassFile file) { + try { + return file.getSourceRange() != null; + } catch (JavaModelException e) { + //assume no source then... + return false; + } + } + /* * @see ClassFileDocumentProvider.InputChangeListener#inputChanged(IClassFileEditorInput) */ @@ -973,6 +1012,7 @@ public void doOperation(int operation) { */ @Override public void dispose() { + disposed = true; // http://bugs.eclipse.org/bugs/show_bug.cgi?id=18510 IDocumentProvider documentProvider= getDocumentProvider(); if (documentProvider instanceof ClassFileDocumentProvider) { diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/InternalClassFileEditorInput.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/InternalClassFileEditorInput.java index 0cb974c8d71..c92f2420d07 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/InternalClassFileEditorInput.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/InternalClassFileEditorInput.java @@ -18,7 +18,6 @@ import java.io.IOException; import java.nio.file.Files; -import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; @@ -45,7 +44,7 @@ public class InternalClassFileEditorInput implements IClassFileEditorInput, IPer private IClassFile fClassFile; - private IPath fPath; + private volatile IPath fPath; public InternalClassFileEditorInput(IClassFile classFile) { fClassFile= classFile; @@ -182,10 +181,12 @@ private static IPath writeToTempFile(IClassFile classFile) { return new Path(file.toString()); } catch (IOException e) { JavaPlugin.log(e); - } catch (CoreException e) { - JavaPlugin.log(e.getStatus()); + } catch (JavaModelException ex) { + if (!ex.isDoesNotExist()) { + JavaPlugin.log(ex.getStatus()); + } } - throw new IllegalArgumentException("Could not create temporary file."); //$NON-NLS-1$ + return IPath.EMPTY; } } diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaEditor.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaEditor.java index 937dd308d9e..ede85da4205 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaEditor.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaEditor.java @@ -1999,9 +1999,8 @@ private void hideBreadcrumb() { /** * Sets the breadcrumb input to the given element. * @param element the element to use as input for the breadcrumb - * @since 3.4 */ - private void setBreadcrumbInput(ISourceReference element) { + protected void setBreadcrumbInput(ISourceReference element) { if (fBreadcrumb == null) return;