blob: 2f3f84ebb9665566cdc3d5c57e7543b6878e0d86 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2020 1C-Soft LLC.
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Vladimir Piskarev (1C) - initial API and implementation
*******************************************************************************/
package org.eclipse.handly.ui.texteditor;
import static org.eclipse.handly.context.Contexts.EMPTY_CONTEXT;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.handly.buffer.IBuffer;
import org.eclipse.handly.model.Elements;
import org.eclipse.handly.model.ISourceFile;
import org.eclipse.handly.model.impl.ISourceFileImplExtension;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ui.editors.text.TextFileDocumentProvider;
import org.eclipse.ui.texteditor.IDocumentProvider;
/**
* In contrast to {@link SourceFileDocumentProvider}, which acquires a working
* copy in the calling thread, this class defers working copy acquisition to a
* worker thread.
*
* @since 1.5
*/
public abstract class DeferredSourceFileDocumentProvider
extends TextFileDocumentProvider
{
private static final ISourceFile[] NO_SOURCE_FILES = new ISourceFile[0];
/**
* Creates a new source file document provider with no parent.
*/
public DeferredSourceFileDocumentProvider()
{
this(null);
}
/**
* Creates a new source file document provider with the given parent.
*
* @param parent the parent document provider
*/
public DeferredSourceFileDocumentProvider(IDocumentProvider parent)
{
super(parent);
}
/**
* Returns the source file managed for the given element,
* or <code>null</code> if this provider does not currently manage
* a source file for the element.
* <p>
* This implementation returns the source file retained by the file info for
* the given element. The file info is obtained via {@link #getFileInfo(Object)}.
* </p>
*
* @param element the element for which to find the source file,
* or <code>null</code>
* @return the source file managed for the given element,
* or <code>null</code> if none
*/
public ISourceFile getConnectedSourceFile(Object element)
{
FileInfo info = getFileInfo(element);
if (info instanceof SourceFileInfo)
return ((SourceFileInfo)info).sourceFile;
return null;
}
/**
* Returns the source file managed for the given document,
* or <code>null</code> if this provider does not currently manage
* a source file for the document.
* <p>
* <b>Note:</b> An implementation of this method may go through the list
* of source files and test whether the source file buffer's document
* equals the given document. Therefore, this method should not be used
* in performance critical code.
* </p>
* <p>
* This implementation returns the source file retained by the file info
* for the given document. The file info is found by iterating over
* this provider's file info objects via {@link #getFileInfosIterator()}
* and testing whether the document of the file info's text file buffer
* equals the given document.
* </p>
*
* @param document the document for which to find the source file,
* or <code>null</code>
* @return the source file managed for the given document,
* or <code>null</code> if none
*/
public ISourceFile getConnectedSourceFile(IDocument document)
{
Iterator<FileInfo> it = getFileInfosIterator();
while (it.hasNext())
{
FileInfo info = it.next();
IDocument infoDocument = null;
if (info.fTextFileBuffer != null)
infoDocument = info.fTextFileBuffer.getDocument();
if (infoDocument != null && infoDocument.equals(document))
{
if (info instanceof SourceFileInfo)
return ((SourceFileInfo)info).sourceFile;
}
}
return null;
}
/**
* Returns all source files that are currently managed by this provider.
* <p>
* This implementation iterates over this provider's file info objects
* via {@link #getFileInfosIterator()} and collects the source files
* they retain.
* </p>
*
* @return the source files currently managed by this provider
* (never <code>null</code>)
*/
public ISourceFile[] getConnectedSourceFiles()
{
List<ISourceFile> result = new ArrayList<>();
Iterator<FileInfo> it = getFileInfosIterator();
while (it.hasNext())
{
FileInfo info = it.next();
if (info instanceof SourceFileInfo)
{
ISourceFile sourceFile = ((SourceFileInfo)info).sourceFile;
if (sourceFile != null)
result.add(sourceFile);
}
}
return result.toArray(NO_SOURCE_FILES);
}
/**
* Returns the source file that corresponds to the given element.
*
* @param element the element
* @return the source file that corresponds to the given element,
* or <code>null</code> if none
*/
protected abstract ISourceFile getSourceFile(Object element);
/**
* {@inheritDoc}
* <p>
* This implementation returns a new instance of
* {@link DeferredSourceFileDocumentProvider.SourceFileInfo SourceFileInfo}.
* </p>
*/
@Override
protected FileInfo createEmptyFileInfo()
{
return new SourceFileInfo();
}
/**
* {@inheritDoc}
* <p>
* This implementation invokes the superclass implementation to create the
* file info object. If the created object is an instance of {@link
* DeferredSourceFileDocumentProvider.SourceFileInfo SourceFileInfo}, it stores
* a reference to the corresponding {@link #getSourceFile(Object) source file}
* in the created file info, and then attempts to {@link #acquireWorkingCopy
* acquire} a working copy for the source file asynchronously.
* </p>
*/
@Override
protected FileInfo createFileInfo(Object element) throws CoreException
{
FileInfo info = super.createFileInfo(element);
if (info instanceof SourceFileInfo)
{
setUpSourceFileInfo(element, (SourceFileInfo)info);
}
return info;
}
/**
* {@inheritDoc}
* <p>
* This implementation invokes the superclass implementation after trying to
* {@link #releaseWorkingCopy(ISourceFile, Object, TextFileDocumentProvider.FileInfo)
* release} the working copy retained by the given file info object.
* </p>
*/
@Override
protected void disposeFileInfo(Object element, FileInfo info)
{
try
{
if (info instanceof SourceFileInfo)
disposeSourceFileInfo(element, (SourceFileInfo)info);
}
finally
{
super.disposeFileInfo(element, info);
}
}
/**
* Attempts to acquire a working copy for the given source file. The working
* copy acquired by this method <b>must</b> be released eventually via a call
* to {@link #releaseWorkingCopy releaseWorkingCopy}.
* <p>
* If the given source file implements {@link ISourceFileImplExtension}, this
* implementation invokes <code>{@link ISourceFileImplExtension#becomeWorkingCopy_
* becomeWorkingCopy_}(EMPTY_CONTEXT, monitor)</code> on it and returns
* <code>true</code>. Otherwise, <code>false</code> is returned.
* </p>
*
* @param sourceFile the source file
* @param element the element
* @param info the element info
* @param monitor a progress monitor, or <code>null</code>
* if progress reporting is not desired. The caller must not rely on
* {@link IProgressMonitor#done()} having been called by the receiver
* @return <code>true</code> if a working copy has been acquired;
* <code>false</code> if no working copy can be acquired for
* the given source file
* @throws CoreException if the working copy could not be acquired successfully
* @throws OperationCanceledException if this method is canceled
*/
protected boolean acquireWorkingCopy(ISourceFile sourceFile, Object element,
FileInfo info, IProgressMonitor monitor) throws CoreException
{
if (sourceFile instanceof ISourceFileImplExtension)
{
((ISourceFileImplExtension)sourceFile).becomeWorkingCopy_(
EMPTY_CONTEXT, monitor);
return true;
}
return false;
}
/**
* Releases the given working copy that was acquired via a call to
* {@link #acquireWorkingCopy acquireWorkingCopy}.
* <p>
* This implementation invokes <code>((ISourceFileImplExtension)workingCopy).{@link
* ISourceFileImplExtension#releaseWorkingCopy_() releaseWorkingCopy_()}</code>.
* </p>
*
* @param workingCopy the working copy to release
* @param element the element
* @param info the element info
*/
protected void releaseWorkingCopy(ISourceFile workingCopy, Object element,
FileInfo info)
{
((ISourceFileImplExtension)workingCopy).releaseWorkingCopy_();
}
private void setUpSourceFileInfo(Object element, SourceFileInfo info)
{
ISourceFile sourceFile = getSourceFile(element);
if (sourceFile == null)
return;
info.sourceFile = sourceFile;
(info.setUpWorkingCopyJob = Job.createSystem(
"DeferredSourceFileDocumentProvider::setUpSourceFileInfo", //$NON-NLS-1$
monitor ->
{
if (!acquireWorkingCopy(sourceFile, element, info, monitor))
return;
if (!Elements.isWorkingCopy(sourceFile))
throw new AssertionError();
boolean releaseWorkingCopy = true;
try (IBuffer buffer = Elements.getBuffer(sourceFile))
{
IDocument document = null;
if (info.fTextFileBuffer != null)
document = info.fTextFileBuffer.getDocument();
if (!buffer.getDocument().equals(document))
throw new AssertionError();
synchronized (info)
{
if (!info.disposed)
{
info.workingCopyAcquired = true;
releaseWorkingCopy = false;
}
}
}
finally
{
if (releaseWorkingCopy)
releaseWorkingCopy(sourceFile, element, info);
}
})).schedule();
}
private void disposeSourceFileInfo(Object element, SourceFileInfo info)
{
info.setUpWorkingCopyJob.cancel();
ISourceFile workingCopy = null;
synchronized (info)
{
if (info.workingCopyAcquired)
workingCopy = info.sourceFile;
info.disposed = true;
}
if (workingCopy != null)
releaseWorkingCopy(workingCopy, element, info);
}
/**
* Subclass of {@link org.eclipse.ui.editors.text.TextFileDocumentProvider.FileInfo
* FileInfo} that can retain a reference to a source file.
*/
protected static class SourceFileInfo
extends FileInfo
{
ISourceFile sourceFile;
Job setUpWorkingCopyJob;
boolean workingCopyAcquired;
boolean disposed;
}
}