blob: 0cc3c8f0a22377dfeead75910cb97fd4459a79a5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.editors.text;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import org.osgi.framework.Bundle;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceRuleFactory;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.IFileBuffer;
import org.eclipse.core.filebuffers.IFileBufferListener;
import org.eclipse.core.filebuffers.IFileBufferManager;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ISynchronizable;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IURIEditorInput;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.editors.text.NLSUtility;
import org.eclipse.ui.internal.editors.text.UISynchronizationContext;
import org.eclipse.ui.internal.editors.text.WorkspaceOperationRunner;
import org.eclipse.ui.keys.IBindingService;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.texteditor.AbstractMarkerAnnotationModel;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.IDocumentProviderExtension;
import org.eclipse.ui.texteditor.IDocumentProviderExtension2;
import org.eclipse.ui.texteditor.IDocumentProviderExtension3;
import org.eclipse.ui.texteditor.IDocumentProviderExtension4;
import org.eclipse.ui.texteditor.IDocumentProviderExtension5;
import org.eclipse.ui.texteditor.IElementStateListener;
import org.eclipse.ui.texteditor.IElementStateListenerExtension;
import org.eclipse.ui.texteditor.ISchedulingRuleProvider;
/**
* Shared document provider specialized for {@link org.eclipse.core.resources.IFile} based domain elements.
* A text file document provider can have a parent document provider to which
* it may delegate calls i.e. instead of delegating work to a super class it
* delegates to a parent document provider. The parent chain acts as chain
* of command.
* <p>
* Text file document providers use {@linkplain org.eclipse.core.filebuffers.ITextFileBuffer text file buffers}
* to access the file content. This allows to share it between various clients including
* headless ones. Text file document providers should be preferred over file document
* providers due to this advantage.
* </p>
* <p>
* Use a {@linkplain org.eclipse.ui.editors.text.ForwardingDocumentProvider forwarding document provider}
* if you need to ensure that all documents provided to clients are appropriately set up.
* </p>
* <p>
* Clients can directly instantiate and configure this class with a suitable parent
* document provider or provide their own subclass.
* </p>
*
* @since 3.0
*/
public class TextFileDocumentProvider implements IDocumentProvider, IDocumentProviderExtension, IDocumentProviderExtension2, IDocumentProviderExtension3, IDocumentProviderExtension5, IStorageDocumentProvider, IDocumentProviderExtension4 {
/**
* Operation created by the document provider and to be executed by the providers runnable context.
*/
protected static abstract class DocumentProviderOperation implements IRunnableWithProgress, ISchedulingRuleProvider {
/**
* The actual functionality of this operation.
*
* @param monitor the progress monitor
* @throws CoreException if the execution fails
*/
protected abstract void execute(IProgressMonitor monitor) throws CoreException;
@Override
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
try {
execute(monitor);
} catch (CoreException x) {
throw new InvocationTargetException(x);
}
}
@Override
public ISchedulingRule getSchedulingRule() {
return ResourcesPlugin.getWorkspace().getRoot();
}
}
/**
* @deprecated As of 3.3 - do not use
*/
@Deprecated
static protected class NullProvider implements IDocumentProvider, IDocumentProviderExtension, IDocumentProviderExtension2, IDocumentProviderExtension3, IDocumentProviderExtension4, IDocumentProviderExtension5, IStorageDocumentProvider {
static final private IStatus STATUS_ERROR= new Status(IStatus.ERROR, EditorsUI.PLUGIN_ID, IStatus.OK, TextEditorMessages.NullProvider_error, null);
@Override
public void connect(Object element) throws CoreException {}
@Override
public void disconnect(Object element) {}
@Override
public IDocument getDocument(Object element) { return null; }
@Override
public void resetDocument(Object element) throws CoreException {}
@Override
public void saveDocument(IProgressMonitor monitor, Object element, IDocument document, boolean overwrite) throws CoreException {}
@Override
public long getModificationStamp(Object element) { return 0; }
@Override
public long getSynchronizationStamp(Object element) { return 0; }
@Override
public boolean isDeleted(Object element) { return true; }
@Override
public boolean mustSaveDocument(Object element) { return false; }
@Override
public boolean canSaveDocument(Object element) { return false; }
@Override
public IAnnotationModel getAnnotationModel(Object element) { return null; }
@Override
public void aboutToChange(Object element) {}
@Override
public void changed(Object element) {}
@Override
public void addElementStateListener(IElementStateListener listener) {}
@Override
public void removeElementStateListener(IElementStateListener listener) {}
@Override
public boolean isReadOnly(Object element) { return true; }
@Override
public boolean isModifiable(Object element) { return false; }
@Override
public void validateState(Object element, Object computationContext) throws CoreException {}
@Override
public boolean isStateValidated(Object element) { return true; }
@Override
public void updateStateCache(Object element) throws CoreException {}
@Override
public void setCanSaveDocument(Object element) {}
@Override
public IStatus getStatus(Object element) { return STATUS_ERROR; }
@Override
public void synchronize(Object element) throws CoreException {}
@Override
public void setProgressMonitor(IProgressMonitor progressMonitor) {}
@Override
public IProgressMonitor getProgressMonitor() { return new NullProgressMonitor(); }
@Override
public boolean isSynchronized(Object element) { return true; }
@Override
public boolean isNotSynchronizedException(Object element, CoreException ex) { return false; }
@Override
public String getDefaultEncoding() { return null; }
@Override
public String getEncoding(Object element) { return null; }
@Override
public void setEncoding(Object element, String encoding) {}
@Override
public IContentType getContentType(Object element) throws CoreException { return null; }
}
static protected class FileInfo {
public Object fElement;
public int fCount;
public ITextFileBuffer fTextFileBuffer;
/**
* The file buffer location kind.
* @since 3.4
*/
public LocationKind fTextFileBufferLocationKind;
public IAnnotationModel fModel;
public boolean fCachedReadOnlyState;
}
static private class SingleElementIterator<E> implements Iterator<E> {
private E fElement;
public SingleElementIterator(E element) {
fElement= element;
}
@Override
public boolean hasNext() {
return fElement != null;
}
@Override
public E next() {
if (fElement != null) {
E result= fElement;
fElement= null;
return result;
}
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
protected class FileBufferListener implements IFileBufferListener {
public FileBufferListener() {
}
@Override
public void bufferContentAboutToBeReplaced(IFileBuffer file) {
List<IElementStateListener> list= new ArrayList<>(fElementStateListeners);
Iterator<IElementStateListener> e= list.iterator();
while (e.hasNext()) {
IElementStateListener l= e.next();
Iterator<Object> i= getElements(file);
while (i.hasNext())
l.elementContentAboutToBeReplaced(i.next());
}
}
@Override
public void bufferContentReplaced(IFileBuffer file) {
List<IElementStateListener> list= new ArrayList<>(fElementStateListeners);
Iterator<IElementStateListener> e= list.iterator();
while (e.hasNext()) {
IElementStateListener l= e.next();
Iterator<Object> i= getElements(file);
while (i.hasNext())
l.elementContentReplaced(i.next());
}
}
@Override
public void stateChanging(IFileBuffer file) {
Iterator<Object> i= getElements(file);
while (i.hasNext())
fireElementStateChanging(i.next());
}
@Override
public void dirtyStateChanged(IFileBuffer file, boolean isDirty) {
List<IElementStateListener> list= new ArrayList<>(fElementStateListeners);
Iterator<IElementStateListener> e= list.iterator();
while (e.hasNext()) {
IElementStateListener l= e.next();
Iterator<Object> i= getElements(file);
while (i.hasNext())
l.elementDirtyStateChanged(i.next(), isDirty);
}
}
@Override
public void stateValidationChanged(IFileBuffer file, boolean isStateValidated) {
List<IElementStateListener> list= new ArrayList<>(fElementStateListeners);
Iterator<IElementStateListener> e= list.iterator();
while (e.hasNext()) {
Object l= e.next();
if (l instanceof IElementStateListenerExtension) {
IElementStateListenerExtension x= (IElementStateListenerExtension) l;
Iterator<Object> i= getElements(file);
while (i.hasNext())
x.elementStateValidationChanged(i.next(), isStateValidated);
}
}
}
@Override
public void underlyingFileMoved(IFileBuffer file, IPath newLocation) {
IWorkspace workspace=ResourcesPlugin.getWorkspace();
IFile newFile= workspace.getRoot().getFile(newLocation);
IEditorInput input= new FileEditorInput(newFile);
List<IElementStateListener> list= new ArrayList<>(fElementStateListeners);
Iterator<IElementStateListener> e= list.iterator();
while (e.hasNext()) {
IElementStateListener l= e.next();
Iterator<Object> i= getElements(file);
while (i.hasNext())
l.elementMoved(i.next(), input);
}
}
@Override
public void underlyingFileDeleted(IFileBuffer file) {
List<IElementStateListener> list= new ArrayList<>(fElementStateListeners);
Iterator<IElementStateListener> e= list.iterator();
while (e.hasNext()) {
IElementStateListener l= e.next();
Iterator<Object> i= getElements(file);
while (i.hasNext())
l.elementDeleted(i.next());
}
}
@Override
public void stateChangeFailed(IFileBuffer file) {
Iterator<Object> i= getElements(file);
while (i.hasNext())
fireElementStateChangeFailed(i.next());
}
@Override
public void bufferCreated(IFileBuffer buffer) {
// ignore
}
@Override
public void bufferDisposed(IFileBuffer buffer) {
// ignore
}
}
/** The parent document provider. */
private IDocumentProvider fParentProvider;
/** Element information of all connected elements. */
private final Map<Object, FileInfo> fFileInfoMap= new HashMap<>();
/** Map from file buffers to their connected elements. Value is an Object or a {@code List<Object>}. */
private final Map<ITextFileBuffer, Object> fFileBufferMap= new HashMap<>();
/** The list of element state listeners. */
private List<IElementStateListener> fElementStateListeners= new ArrayList<>();
/** The file buffer listener. */
private final IFileBufferListener fFileBufferListener= new FileBufferListener();
/** The progress monitor. */
private IProgressMonitor fProgressMonitor;
/** The operation runner. */
private WorkspaceOperationRunner fOperationRunner;
/** The rule factory. */
private IResourceRuleFactory fResourceRuleFactory;
/**
* Creates a new text file document provider
* with no parent.
*/
public TextFileDocumentProvider() {
this(null);
}
/**
* Creates a new text file document provider
* which has the given parent provider.
*
* @param parentProvider the parent document provider
*/
public TextFileDocumentProvider(IDocumentProvider parentProvider) {
IFileBufferManager manager= FileBuffers.getTextFileBufferManager();
manager.setSynchronizationContext(new UISynchronizationContext());
if (parentProvider != null)
setParentDocumentProvider(parentProvider);
fResourceRuleFactory= ResourcesPlugin.getWorkspace().getRuleFactory();
}
/**
* Sets the given parent provider as this document
* provider's parent document provider.
*
* @param parentProvider the parent document provider
*/
public final void setParentDocumentProvider(IDocumentProvider parentProvider) {
Assert.isTrue(parentProvider instanceof IDocumentProviderExtension);
Assert.isTrue(parentProvider instanceof IDocumentProviderExtension2);
Assert.isTrue(parentProvider instanceof IDocumentProviderExtension3);
Assert.isTrue(parentProvider instanceof IStorageDocumentProvider);
fParentProvider= parentProvider;
}
/**
* Returns the parent document provider.
*
* @return the parent document provider
*/
final protected IDocumentProvider getParentProvider() {
if (fParentProvider == null)
fParentProvider= new StorageDocumentProvider();
return fParentProvider;
}
/**
* Returns the runnable context for this document provider.
*
* @param monitor the progress monitor
* @return the runnable context for this document provider
*/
protected IRunnableContext getOperationRunner(IProgressMonitor monitor) {
if (fOperationRunner == null)
fOperationRunner = new WorkspaceOperationRunner();
fOperationRunner.setProgressMonitor(monitor);
return fOperationRunner;
}
/**
* Executes the given operation in the providers runnable context.
*
* @param operation the operation to be executes
* @param monitor the progress monitor
* @throws CoreException the operation's core exception
*/
protected void executeOperation(DocumentProviderOperation operation, IProgressMonitor monitor) throws CoreException {
try {
IRunnableContext runner= getOperationRunner(monitor);
if (runner != null)
runner.run(false, false, operation);
else
operation.run(monitor);
} catch (InvocationTargetException x) {
Throwable e= x.getTargetException();
if (e instanceof CoreException)
throw (CoreException) e;
String message= (e.getMessage() != null ? e.getMessage() : ""); //$NON-NLS-1$
throw new CoreException(new Status(IStatus.ERROR, EditorsUI.PLUGIN_ID, IStatus.OK, message, e));
} catch (InterruptedException x) {
String message= (x.getMessage() != null ? x.getMessage() : ""); //$NON-NLS-1$
throw new CoreException(new Status(IStatus.CANCEL, EditorsUI.PLUGIN_ID, IStatus.OK, message, x));
}
}
@Override
public void connect(Object element) throws CoreException {
FileInfo info= fFileInfoMap.get(element);
if (info == null) {
info= createFileInfo(element);
if (info == null) {
getParentProvider().connect(element);
return;
}
info.fElement= element;
fFileInfoMap.put(element, info);
storeFileBufferMapping(element, info);
}
++ info.fCount;
}
/**
* Updates the file buffer map with a new relation between the file buffer
* of the given info and the given element.
*
* @param element the element
* @param info the element's file info object
*/
private void storeFileBufferMapping(Object element, FileInfo info) {
Object value= fFileBufferMap.get(info.fTextFileBuffer);
if (value instanceof List) {
@SuppressWarnings("unchecked")
List<Object> list= (List<Object>) value;
list.add(element);
return;
}
if (value == null) {
value= element;
} else {
List<Object> list= new ArrayList<>(2);
list.add(value);
list.add(element);
value= list;
}
fFileBufferMap.put(info.fTextFileBuffer, value);
}
/**
* Creates and returns a new and empty file info object.
* <p>
* Subclasses which extend {@link org.eclipse.ui.editors.text.TextFileDocumentProvider.FileInfo}
* should override this method.
* </p>
*
* @return a new and empty object of type <code>FileInfo</code>
*/
protected FileInfo createEmptyFileInfo() {
return new FileInfo();
}
/**
* Creates and returns the file info object
* for the given element.
* <p>
* Subclasses which extend {@link org.eclipse.ui.editors.text.TextFileDocumentProvider.FileInfo}
* will probably have to extend this method as well.
* </p>
*
* @param element the element
* @return a file info object of type <code>FileInfo</code>
* or <code>null</code> if none can be created
* @throws CoreException if the file info object could not successfully be created
*/
protected FileInfo createFileInfo(Object element) throws CoreException {
if (!(element instanceof IAdaptable))
return null;
IAdaptable adaptable= (IAdaptable) element;
IFile file= null;
ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
ITextFileBuffer fileBuffer= null;
LocationKind locationKind= null;
file= adaptable.getAdapter(IFile.class);
if (file != null) {
IPath location= file.getFullPath();
locationKind= LocationKind.IFILE;
manager.connect(location, locationKind,getProgressMonitor());
fileBuffer= manager.getTextFileBuffer(location, locationKind);
} else {
ILocationProvider provider= adaptable.getAdapter(ILocationProvider.class);
if (provider instanceof ILocationProviderExtension) {
URI uri= ((ILocationProviderExtension)provider).getURI(element);
if (ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(uri).length == 0) {
IFileStore fileStore= EFS.getStore(uri);
manager.connectFileStore(fileStore, getProgressMonitor());
fileBuffer= manager.getFileStoreTextFileBuffer(fileStore);
}
}
if (fileBuffer == null && provider != null) {
IPath location= provider.getPath(element);
if (location == null)
return null;
locationKind= LocationKind.NORMALIZE;
manager.connect(location, locationKind, getProgressMonitor());
fileBuffer= manager.getTextFileBuffer(location, locationKind);
file= FileBuffers.getWorkspaceFileAtLocation(location);
}
}
if (fileBuffer != null) {
fileBuffer.requestSynchronizationContext();
FileInfo info= createEmptyFileInfo();
info.fTextFileBuffer= fileBuffer;
info.fTextFileBufferLocationKind= locationKind;
info.fCachedReadOnlyState= isSystemFileReadOnly(info);
if (file != null)
info.fModel= createAnnotationModel(file);
if (info.fModel == null)
info.fModel= info.fTextFileBuffer.getAnnotationModel();
setUpSynchronization(info);
return info;
}
return null;
}
/**
* Sets up the synchronization for the document
* and the annotation mode.
*
* @param info the file info
* @since 3.2
*/
protected void setUpSynchronization(FileInfo info) {
if (info == null || info.fTextFileBuffer == null)
return;
IDocument document= info.fTextFileBuffer.getDocument();
IAnnotationModel model= info.fModel;
if (document instanceof ISynchronizable) {
Object lock= ((ISynchronizable)document).getLockObject();
if (lock == null) {
lock= new Object();
((ISynchronizable)document).setLockObject(lock);
}
if (model instanceof ISynchronizable)
((ISynchronizable) model).setLockObject(lock);
}
}
/**
* Creates and returns the annotation model for the given file.
*
* @param file the file
* @return the file's annotation model or <code>null</code> if none
*/
protected IAnnotationModel createAnnotationModel(IFile file) {
return null;
}
@Override
public void disconnect(Object element) {
FileInfo info= fFileInfoMap.get(element);
if (info == null) {
getParentProvider().disconnect(element);
return;
}
if (info.fCount == 1) {
fFileInfoMap.remove(element);
removeFileBufferMapping(element, info);
disposeFileInfo(element, info);
} else
-- info.fCount;
}
/**
* Removes the relation between the file buffer of the given info and the
* given element from the file buffer mapping.
*
* @param element the element
* @param info the element's file info object
*/
private void removeFileBufferMapping(Object element, FileInfo info) {
Object value= fFileBufferMap.get(info.fTextFileBuffer);
if (value == null)
return;
if (value instanceof List) {
List<?> list= (List<?>) value;
list.remove(element);
if (list.size() == 1)
fFileBufferMap.put(info.fTextFileBuffer, list.get(0));
} else if (value == element) {
fFileBufferMap.remove(info.fTextFileBuffer);
}
}
/**
* Releases all resources described by given element's info object.
* <p>
* Subclasses which extend {@link org.eclipse.ui.editors.text.TextFileDocumentProvider.FileInfo}
* will probably have to extend this method as well.
* </p>
*
* @param element the element
* @param info the element's file info object
*/
protected void disposeFileInfo(Object element, FileInfo info) {
IFileBufferManager manager= FileBuffers.getTextFileBufferManager();
try {
info.fTextFileBuffer.releaseSynchronizationContext();
if (info.fTextFileBufferLocationKind != null)
manager.disconnect(info.fTextFileBuffer.getLocation(), info.fTextFileBufferLocationKind, getProgressMonitor());
else
manager.disconnectFileStore(info.fTextFileBuffer.getFileStore(), getProgressMonitor());
} catch (CoreException x) {
handleCoreException(x, "FileDocumentProvider.disposeElementInfo"); //$NON-NLS-1$
}
}
/**
* Returns an iterator for all the elements that are connected to this file buffer.
*
* @param file the file buffer
* @return an iterator for all elements connected with the given file buffer
*/
protected Iterator<Object> getElements(IFileBuffer file) {
Object value= fFileBufferMap.get(file);
if (value instanceof List)
return new ArrayList<Object>((List<?>) value).iterator();
return new SingleElementIterator<>(value);
}
@Override
public IDocument getDocument(Object element) {
FileInfo info= fFileInfoMap.get(element);
if (info != null)
return info.fTextFileBuffer.getDocument();
return getParentProvider().getDocument(element);
}
@Override
public void resetDocument(Object element) throws CoreException {
final FileInfo info= fFileInfoMap.get(element);
if (info != null) {
DocumentProviderOperation operation= new DocumentProviderOperation() {
@Override
protected void execute(IProgressMonitor monitor) throws CoreException {
info.fTextFileBuffer.revert(monitor);
if (info.fModel instanceof AbstractMarkerAnnotationModel) {
AbstractMarkerAnnotationModel markerModel= (AbstractMarkerAnnotationModel) info.fModel;
markerModel.resetMarkers();
}
}
@Override
public ISchedulingRule getSchedulingRule() {
if (info.fElement instanceof IFileEditorInput) {
IFileEditorInput input= (IFileEditorInput) info.fElement;
return fResourceRuleFactory.refreshRule((input).getFile());
}
return null;
}
};
executeOperation(operation, getProgressMonitor());
} else {
getParentProvider().resetDocument(element);
}
}
@Override
public final void saveDocument(IProgressMonitor monitor, Object element, IDocument document, boolean overwrite) throws CoreException {
if (element == null)
return;
DocumentProviderOperation operation= createSaveOperation(element, document, overwrite);
if (operation != null)
executeOperation(operation, monitor);
else
getParentProvider().saveDocument(monitor, element, document, overwrite);
}
protected DocumentProviderOperation createSaveOperation(final Object element, final IDocument document, final boolean overwrite) throws CoreException {
final FileInfo info= fFileInfoMap.get(element);
if (info != null) {
if (info.fTextFileBuffer.getDocument() != document) {
// the info exists, but not for the given document
// -> saveAs was executed with a target that is already open
// in another editor
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=85519
Status status= new Status(IStatus.WARNING, EditorsUI.PLUGIN_ID, IStatus.OK, TextEditorMessages.TextFileDocumentProvider_saveAsTargetOpenInEditor, null);
throw new CoreException(status);
}
return new DocumentProviderOperation() {
@Override
public void execute(IProgressMonitor monitor) throws CoreException {
commitFileBuffer(monitor, info, overwrite);
}
@Override
public ISchedulingRule getSchedulingRule() {
if (info.fElement instanceof IFileEditorInput) {
IFileEditorInput input= (IFileEditorInput) info.fElement;
return computeSchedulingRule(input.getFile());
}
return null;
}
};
} else if (element instanceof IFileEditorInput) {
final IFile file= ((IFileEditorInput) element).getFile();
return new DocumentProviderOperation() {
@Override
public void execute(IProgressMonitor monitor) throws CoreException {
createFileFromDocument(monitor, file, document);
}
@Override
public ISchedulingRule getSchedulingRule() {
return computeSchedulingRule(file);
}
};
} else if (element instanceof IURIEditorInput) {
final URI uri= ((IURIEditorInput)element).getURI();
return new DocumentProviderOperation() {
@Override
public void execute(IProgressMonitor monitor) throws CoreException {
createFileStoreFromDocument(monitor, uri, document);
}
@Override
public ISchedulingRule getSchedulingRule() {
return null;
}
};
}
return null;
}
/**
* Commits the given file info's file buffer by changing the contents
* of the underlying file to the contents of this file buffer. After that
* call, <code>isDirty</code> returns <code>false</code> and <code>isSynchronized</code>
* returns <code>true</code>.
*
* @param monitor the progress monitor
* @param info the element's file info object
* @param overwrite indicates whether the underlying file should be overwritten if it is not synchronized with the file system
* @throws CoreException if writing or accessing the underlying file fails
*/
protected void commitFileBuffer(IProgressMonitor monitor, FileInfo info, boolean overwrite) throws CoreException {
Assert.isNotNull(info);
/* https://bugs.eclipse.org/bugs/show_bug.cgi?id=98327
* Make sure file gets saved in commit() if the underlying file has been deleted */
if (info.fElement instanceof IFileEditorInput) {
IFileEditorInput input= (IFileEditorInput) info.fElement;
IResource resource= input.getFile();
if (!resource.isSynchronized(IResource.DEPTH_ZERO) && isDeleted(input))
info.fTextFileBuffer.setDirty(true);
}
info.fTextFileBuffer.commit(monitor, overwrite);
if (info.fModel instanceof AbstractMarkerAnnotationModel) {
AbstractMarkerAnnotationModel model= (AbstractMarkerAnnotationModel) info.fModel;
model.updateMarkers(info.fTextFileBuffer.getDocument());
}
}
/**
* Creates the given file with the given document content.
*
* @param monitor the progress monitor
* @param file the file to be created
* @param document the document to be written to the file
* @throws CoreException if the creation of the file fails
*/
protected void createFileFromDocument(IProgressMonitor monitor, IFile file, IDocument document) throws CoreException {
try {
monitor.beginTask(TextEditorMessages.TextFileDocumentProvider_beginTask_saving, 2000);
ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
manager.connect(file.getFullPath(), LocationKind.IFILE, monitor);
ITextFileBuffer buffer= ITextFileBufferManager.DEFAULT.getTextFileBuffer(file.getFullPath(), LocationKind.IFILE);
buffer.getDocument().set(document.get());
buffer.commit(monitor, true);
manager.disconnect(file.getFullPath(), LocationKind.IFILE, monitor);
} finally {
monitor.done();
}
}
/**
* Creates the given file store with the given document content.
*
* @param monitor the progress monitor
* @param uri the location where the file store should be created
* @param document the document to be written to the file store
* @throws CoreException if the creation of the file store fails
* @since 3.3
*/
private void createFileStoreFromDocument(IProgressMonitor monitor, URI uri, IDocument document) throws CoreException {
try {
monitor.beginTask(TextEditorMessages.TextFileDocumentProvider_beginTask_saving, 2000);
IFileStore fileStore= EFS.getStore(uri);
FileBuffers.getTextFileBufferManager().connectFileStore(fileStore, monitor);
ITextFileBuffer buffer= FileBuffers.getTextFileBufferManager().getFileStoreTextFileBuffer(fileStore);
buffer.getDocument().set(document.get());
buffer.commit(monitor, true);
FileBuffers.getTextFileBufferManager().disconnectFileStore(fileStore, monitor);
} finally {
monitor.done();
}
}
@Override
public long getModificationStamp(Object element) {
FileInfo info= fFileInfoMap.get(element);
if (info != null)
return info.fTextFileBuffer.getModificationStamp();
return getParentProvider().getModificationStamp(element);
}
@Override
public long getSynchronizationStamp(Object element) {
FileInfo info= fFileInfoMap.get(element);
if (info != null)
return 0;
return getParentProvider().getSynchronizationStamp(element);
}
@Override
public boolean isDeleted(Object element) {
FileInfo info= fFileInfoMap.get(element);
if (info != null) {
IFileStore fileStore= getFileStore(info);
return fileStore == null ? true : !fileStore.fetchInfo().exists();
}
return getParentProvider().isDeleted(element);
}
@Override
public boolean mustSaveDocument(Object element) {
FileInfo info= fFileInfoMap.get(element);
if (info != null)
return (info.fCount == 1) && info.fTextFileBuffer.isDirty();
return getParentProvider().mustSaveDocument(element);
}
@Override
public boolean canSaveDocument(Object element) {
FileInfo info= fFileInfoMap.get(element);
if (info != null)
return info.fTextFileBuffer.isDirty();
return getParentProvider().canSaveDocument(element);
}
@Override
public IAnnotationModel getAnnotationModel(Object element) {
FileInfo info= fFileInfoMap.get(element);
if (info != null)
return info.fModel;
return getParentProvider().getAnnotationModel(element);
}
@Override
public void aboutToChange(Object element) {
FileInfo info= fFileInfoMap.get(element);
if (info == null)
getParentProvider().aboutToChange(element);
}
@Override
public void changed(Object element) {
FileInfo info= fFileInfoMap.get(element);
if (info == null)
getParentProvider().changed(element);
}
@Override
public void addElementStateListener(IElementStateListener listener) {
Assert.isNotNull(listener);
if (!fElementStateListeners.contains(listener)) {
fElementStateListeners.add(listener);
if (fElementStateListeners.size() == 1) {
IFileBufferManager manager= FileBuffers.getTextFileBufferManager();
manager.addFileBufferListener(fFileBufferListener);
}
}
getParentProvider().addElementStateListener(listener);
}
@Override
public void removeElementStateListener(IElementStateListener listener) {
Assert.isNotNull(listener);
fElementStateListeners.remove(listener);
if (fElementStateListeners.size() == 0) {
IFileBufferManager manager= FileBuffers.getTextFileBufferManager();
manager.removeFileBufferListener(fFileBufferListener);
}
getParentProvider().removeElementStateListener(listener);
}
@Override
public boolean isReadOnly(Object element) {
FileInfo info= fFileInfoMap.get(element);
if (info != null)
return info.fCachedReadOnlyState;
return ((IDocumentProviderExtension) getParentProvider()).isReadOnly(element);
}
@Override
public boolean isModifiable(Object element) {
FileInfo info= fFileInfoMap.get(element);
if (info != null)
return info.fTextFileBuffer.isStateValidated() ? !isReadOnly(element) : true;
return ((IDocumentProviderExtension) getParentProvider()).isModifiable(element);
}
@Override
public void validateState(Object element, final Object computationContext) throws CoreException {
final FileInfo info= fFileInfoMap.get(element);
if (info != null) {
DocumentProviderOperation operation= new DocumentProviderOperation() {
@Override
protected void execute(IProgressMonitor monitor) throws CoreException {
info.fTextFileBuffer.validateState(monitor, computationContext);
}
@Override
public ISchedulingRule getSchedulingRule() {
if (info.fElement instanceof IFileEditorInput) {
IFileEditorInput input= (IFileEditorInput) info.fElement;
return fResourceRuleFactory.validateEditRule(new IResource[] { input.getFile() });
}
return null;
}
};
executeOperation(operation, getProgressMonitor());
} else
((IDocumentProviderExtension) getParentProvider()).validateState(element, computationContext);
}
@Override
public boolean isStateValidated(Object element) {
FileInfo info= fFileInfoMap.get(element);
if (info != null)
return info.fTextFileBuffer.isStateValidated();
return ((IDocumentProviderExtension) getParentProvider()).isStateValidated(element);
}
@Override
public void updateStateCache(Object element) throws CoreException {
FileInfo info= fFileInfoMap.get(element);
if (info != null) {
boolean isReadOnly= isSystemFileReadOnly(info);
// See http://bugs.eclipse.org/bugs/show_bug.cgi?id=14469 for the dirty bit check
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=50699 for commenting that out
if (!info.fCachedReadOnlyState && isReadOnly /*&& !info.fTextFileBuffer.isDirty()*/)
info.fTextFileBuffer.resetStateValidation();
info.fCachedReadOnlyState= isReadOnly;
} else {
((IDocumentProviderExtension) getParentProvider()).updateStateCache(element);
}
}
@Override
public void setCanSaveDocument(Object element) {
FileInfo info= fFileInfoMap.get(element);
if (info == null)
((IDocumentProviderExtension) getParentProvider()).setCanSaveDocument(element);
}
@Override
public IStatus getStatus(Object element) {
FileInfo info= fFileInfoMap.get(element);
if (info == null)
return ((IDocumentProviderExtension) getParentProvider()).getStatus(element);
IStatus status= info.fTextFileBuffer.getStatus();
if (status.getCode() == IResourceStatus.OUT_OF_SYNC_LOCAL) {
String message= status.getMessage();
IBindingService bindingService= PlatformUI.getWorkbench().getService(IBindingService.class);
String keySequence= bindingService.getBestActiveBindingFormattedFor(IWorkbenchCommandConstants.FILE_REFRESH);
if (keySequence != null)
message= message + NLSUtility.format(TextEditorMessages.TextFileDocumentProvider_error_outOfSyncHintWithKeyBinding, keySequence);
else
message= message + TextEditorMessages.TextFileDocumentProvider_error_outOfSyncHint;
return new Status(status.getSeverity(), status.getPlugin(), status.getCode(), message, status.getException());
}
// Ensure that we don't open an empty document for an non-existent IFile
if (status.getSeverity() != IStatus.ERROR && element instanceof IFileEditorInput) {
IFile file= FileBuffers.getWorkspaceFileAtLocation(info.fTextFileBuffer.getLocation());
if (file == null || !file.exists()) {
String message= NLSUtility.format(TextEditorMessages.TextFileDocumentProvider_error_doesNotExist, ((IFileEditorInput)element).getFile().getFullPath());
return new Status(IStatus.ERROR, EditorsUI.PLUGIN_ID, IResourceStatus.RESOURCE_NOT_FOUND, message, null);
}
}
return status;
}
@Override
public void synchronize(Object element) throws CoreException {
final FileInfo info= fFileInfoMap.get(element);
if (info != null) {
DocumentProviderOperation operation= new DocumentProviderOperation() {
@Override
protected void execute(IProgressMonitor monitor) throws CoreException {
info.fTextFileBuffer.revert(monitor);
}
@Override
public ISchedulingRule getSchedulingRule() {
if (info.fElement instanceof IFileEditorInput) {
IFileEditorInput input= (IFileEditorInput) info.fElement;
return fResourceRuleFactory.refreshRule(input.getFile());
}
return null;
}
};
executeOperation(operation, getProgressMonitor());
} else {
((IDocumentProviderExtension) getParentProvider()).synchronize(element);
}
}
@Override
public void setProgressMonitor(IProgressMonitor progressMonitor) {
fProgressMonitor= progressMonitor;
((IDocumentProviderExtension2) getParentProvider()).setProgressMonitor(progressMonitor);
}
@Override
public IProgressMonitor getProgressMonitor() {
return fProgressMonitor;
}
@Override
public boolean isSynchronized(Object element) {
FileInfo info= fFileInfoMap.get(element);
if (info != null)
return info.fTextFileBuffer.isSynchronized();
return ((IDocumentProviderExtension3) getParentProvider()).isSynchronized(element);
}
@Override
public boolean isNotSynchronizedException(Object element, CoreException ex) {
IStatus status= ex.getStatus();
if (status == null || status instanceof MultiStatus)
return false;
if (status.getException() != null)
return false;
return status.getCode() == IResourceStatus.OUT_OF_SYNC_LOCAL;
}
@Override
public String getDefaultEncoding() {
return FileBuffers.getTextFileBufferManager().getDefaultEncoding();
}
@Override
public String getEncoding(Object element) {
FileInfo info= fFileInfoMap.get(element);
if (info != null)
return info.fTextFileBuffer.getEncoding();
return ((IStorageDocumentProvider) getParentProvider()).getEncoding(element);
}
@Override
public void setEncoding(Object element, String encoding) {
FileInfo info= fFileInfoMap.get(element);
if (info != null)
info.fTextFileBuffer.setEncoding(encoding);
else
((IStorageDocumentProvider) getParentProvider()).setEncoding(element, encoding);
}
@Override
public IContentType getContentType(Object element) throws CoreException {
FileInfo info= fFileInfoMap.get(element);
if (info != null)
return info.fTextFileBuffer.getContentType();
IDocumentProvider parent= getParentProvider();
if (parent instanceof IDocumentProviderExtension4)
return ((IDocumentProviderExtension4) parent).getContentType(element);
return null;
}
/**
* Defines the standard procedure to handle <code>CoreExceptions</code>. Exceptions
* are written to the plug-in log.
*
* @param exception the exception to be logged
* @param message the message to be logged
*/
protected void handleCoreException(CoreException exception, String message) {
Bundle bundle = Platform.getBundle(PlatformUI.PLUGIN_ID);
ILog log= Platform.getLog(bundle);
IStatus status= message != null ? new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.OK, message, exception) : exception.getStatus();
log.log(status);
}
/**
* Returns the file store denoted by the given info.
*
* @param info the element's file info object
* @return the {@link IFileStore} for the given file info
* @since 3.2
*/
protected IFileStore getFileStore(FileInfo info) {
return info.fTextFileBuffer.getFileStore();
}
/**
* Returns the system file denoted by the given info.
*
* @param info the element's file info object
* @return the system file for the given file info
* @deprecated As of 3.2, replaced by {@link #getFileStore(org.eclipse.ui.editors.text.TextFileDocumentProvider.FileInfo)}
*/
@Deprecated
protected File getSystemFile(FileInfo info) {
IPath path= info.fTextFileBuffer.getLocation();
return FileBuffers.getSystemFileAtLocation(path);
}
/**
* Returns whether the system file denoted by
* the given info is read-only.
*
* @param info the element's file info object
* @return <code>true</code> iff read-only
*/
protected boolean isSystemFileReadOnly(FileInfo info) {
IFileStore fileStore= getFileStore(info);
if (fileStore == null)
return false;
IFileInfo fileInfo= fileStore.fetchInfo();
return fileInfo.exists() && fileInfo.getAttribute(EFS.ATTRIBUTE_READ_ONLY);
}
/**
* Returns the file info object for the given element.
*
* @param element the element
* @return the file info object, or <code>null</code> if none
*/
protected FileInfo getFileInfo(Object element) {
return fFileInfoMap.get(element);
}
/**
* Returns an iterator over the elements connected via this document provider.
*
* @return an iterator over the list of elements
*/
protected Iterator<Object> getConnectedElementsIterator() {
return new HashSet<>(fFileInfoMap.keySet()).iterator();
}
/**
* Returns an iterator over this document provider's file info objects.
*
* @return the iterator over list of file info objects
*/
protected Iterator<FileInfo> getFileInfosIterator() {
return new ArrayList<>(fFileInfoMap.values()).iterator();
}
/**
* Informs all registered element state listeners
* about the current state change of the element.
*
* @param element the element
* @see IElementStateListenerExtension#elementStateChanging(Object)
*/
protected void fireElementStateChanging(Object element) {
List<IElementStateListener> list= new ArrayList<>(fElementStateListeners);
Iterator<IElementStateListener> e= list.iterator();
while (e.hasNext()) {
Object l= e.next();
if (l instanceof IElementStateListenerExtension) {
IElementStateListenerExtension x= (IElementStateListenerExtension) l;
x.elementStateChanging(element);
}
}
}
/**
* Informs all registered element state listeners
* about the failed state change of the element.
*
* @param element the element
* @see IElementStateListenerExtension#elementStateChangeFailed(Object)
*/
protected void fireElementStateChangeFailed(Object element) {
List<IElementStateListener> list= new ArrayList<>(fElementStateListeners);
Iterator<IElementStateListener> e= list.iterator();
while (e.hasNext()) {
Object l= e.next();
if (l instanceof IElementStateListenerExtension) {
IElementStateListenerExtension x= (IElementStateListenerExtension) l;
x.elementStateChangeFailed(element);
}
}
}
/**
* Computes the scheduling rule needed to create or modify a resource. If
* the resource exists, its modify rule is returned. If it does not, the
* resource hierarchy is iterated towards the workspace root to find the
* first parent of <code>toCreateOrModify</code> that exists. Then the
* 'create' rule for the last non-existing resource is returned.
*
* @param toCreateOrModify the resource to create or modify
* @return the minimal scheduling rule needed to modify or create a resource
* @since 3.1
*/
protected ISchedulingRule computeSchedulingRule(IResource toCreateOrModify) {
if (toCreateOrModify.exists())
return fResourceRuleFactory.modifyRule(toCreateOrModify);
IResource parent= toCreateOrModify;
do {
toCreateOrModify= parent;
parent= toCreateOrModify.getParent();
} while (parent != null && !parent.exists());
return fResourceRuleFactory.createRule(toCreateOrModify);
}
}