blob: 7b86662ab99b990a2c40e39434f8c3febd7061f6 [file] [log] [blame]
package org.eclipse.ui.editors.text;
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved.
*/
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ContainerGenerator;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.texteditor.ResourceMarkerAnnotationModel;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.jface.dialogs.MessageDialog;
/**
* Shareable document provider specialized for file resources (<code>IFile</code>).<p>
* This class may be instantiated or be subclassed.
*/
public class FileDocumentProvider extends StorageDocumentProvider {
/**
* Synchronizes the document with external resource changes.
*/
protected class FileSynchronizer implements IResourceChangeListener, IResourceDeltaVisitor {
/** The file editor input */
protected IFileEditorInput fFileEditorInput;
/**
* Creates a new file synchronizer. Is not yet installed on a resource.
*/
public FileSynchronizer(IFileEditorInput fileEditorInput) {
fFileEditorInput= fileEditorInput;
};
/**
* Creates a new file synchronizer. Is not yet installed on a resource.
* @deprecated since 0.042 use FileSynchronizer(IFileEditorInput)
*/
public FileSynchronizer(FileEditorInput fileEditorInput) {
fFileEditorInput= fileEditorInput;
};
/**
* Returns the file wrapped by the file editor input.
*/
protected IFile getFile() {
return fFileEditorInput.getFile();
}
/**
* Installs the synchronizer on the input's file.
*/
public void install() {
getFile().getWorkspace().addResourceChangeListener(this);
}
/**
* Uninstalls the synchronizer from the input's file.
*/
public void uninstall() {
getFile().getWorkspace().removeResourceChangeListener(this);
}
/*
* @see IResourceChangeListener#resourceChanged(IResourceChangeEvent)
*/
public void resourceChanged(IResourceChangeEvent e) {
IResourceDelta delta= e.getDelta();
try {
if (delta != null)
delta.accept(this);
} catch (CoreException x) {
handleCoreException(x, TextEditorMessages.getString("FileDocumentProvider.resourceChanged")); //$NON-NLS-1$
}
}
/*
* @see IResourceDeltaVisitor#visit(IResourceDelta)
*/
public boolean visit(IResourceDelta delta) throws CoreException {
if (delta != null && getFile().equals(delta.getResource())) {
Runnable runnable= null;
switch (delta.getKind()) {
case IResourceDelta.CHANGED:
if ((IResourceDelta.CONTENT & delta.getFlags()) != 0) {
FileInfo info= (FileInfo) getElementInfo(fFileEditorInput);
if (!info.fCanBeSaved && computeModificationStamp(getFile()) != info.fModificationStamp) {
runnable= new Runnable() {
public void run() {
if (getElementInfo(fFileEditorInput) != null)
handleElementContentChanged(fFileEditorInput);
}
};
}
}
break;
case IResourceDelta.REMOVED:
if ((IResourceDelta.MOVED_TO & delta.getFlags()) != 0) {
final IPath path= delta.getMovedToPath();
runnable= new Runnable() {
public void run() {
if (getElementInfo(fFileEditorInput) != null)
handleElementMoved(fFileEditorInput, path);
}
};
} else {
FileInfo info= (FileInfo) getElementInfo(fFileEditorInput);
if (!info.fCanBeSaved) {
runnable= new Runnable() {
public void run() {
if (getElementInfo(fFileEditorInput) != null)
handleElementDeleted(fFileEditorInput);
}
};
}
}
break;
}
if (runnable != null)
update(runnable);
}
return true; // because we are sitting on files anyway
}
/*
* Posts the update code "behind" the running operation.
*
* @param runnable the update code
*/
protected void update(Runnable runnable) {
IWorkbench workbench= PlatformUI.getWorkbench();
IWorkbenchWindow[] windows= workbench.getWorkbenchWindows();
if (windows != null && windows.length > 0) {
Display display= windows[0].getShell().getDisplay();
display.asyncExec(runnable);
} else
runnable.run();
}
};
/**
* Bundle of all required informations to allow files as
* underlying document resources.
*/
protected class FileInfo extends StorageInfo {
/** The file synchronizer */
public FileSynchronizer fFileSynchronizer;
/** The time stamp at which this provider changed the file */
public long fModificationStamp= IResource.NULL_STAMP;
public FileInfo(IDocument document, IAnnotationModel model, FileSynchronizer fileSynchronizer) {
super(document, model);
fFileSynchronizer= fileSynchronizer;
}
};
/**
* Creates a new document provider.
*/
public FileDocumentProvider() {
super();
}
/*
* @see StorageDocumentProvider#setDocumentContent(IDocument, IEditorInput)
*/
protected boolean setDocumentContent(IDocument document, IEditorInput editorInput) throws CoreException {
if (editorInput instanceof IFileEditorInput) {
IFile file= ((IFileEditorInput) editorInput).getFile();
setDocumentContent(document, file.getContents(false));
return true;
}
return super.setDocumentContent(document, editorInput);
}
/*
* @see AbstractDocumentProvider#createAnnotationModel(Object)
*/
protected IAnnotationModel createAnnotationModel(Object element) throws CoreException {
if (element instanceof IFileEditorInput) {
IFileEditorInput input= (IFileEditorInput) element;
return new ResourceMarkerAnnotationModel(input.getFile());
}
return super.createAnnotationModel(element);
}
/**
* Checks whether the given resource has been changed on the
* local file system by comparing the actual time stamp with the
* cached one. If the resource has been changed, a <code>CoreException</code>
* is thrown.
*
* @param cachedModificationStamp the chached modification stamp
* @param resource the resource to check
* @exception CoreException if resource has been changed on the file system
*/
protected void checkSynchronizationState(long cachedModificationStamp, IResource resource) throws CoreException {
if (cachedModificationStamp != computeModificationStamp(resource)) {
Status status= new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IResourceStatus.OUT_OF_SYNC_LOCAL, TextEditorMessages.getString("FileDocumentProvider.error.out_of_sync"), null); //$NON-NLS-1$
throw new CoreException(status);
}
}
/**
* Computes the initial modification stamp for the given resource.
*
* @param resource the resource
* @return the modification stamp
*/
protected long computeModificationStamp(IResource resource) {
long modificationStamp= resource.getModificationStamp();
IPath path= resource.getLocation();
if (path == null)
return modificationStamp;
modificationStamp= path.toFile().lastModified();
return modificationStamp;
}
/*
* @see IDocumentProvider#getModificationStamp(Object)
*/
public long getModificationStamp(Object element) {
if (element instanceof IFileEditorInput) {
IFileEditorInput input= (IFileEditorInput) element;
return computeModificationStamp(input.getFile());
}
return super.getModificationStamp(element);
}
/*
* @see IDocumentProvider#getSynchronizationStamp(Object)
*/
public long getSynchronizationStamp(Object element) {
if (element instanceof IFileEditorInput) {
FileInfo info= (FileInfo) getElementInfo(element);
return info.fModificationStamp;
}
return super.getSynchronizationStamp(element);
}
/*
* @see IDocumentProvider#isDeleted(Object)
*/
public boolean isDeleted(Object element) {
if (element instanceof IFileEditorInput) {
IFileEditorInput input= (IFileEditorInput) element;
IPath path= input.getFile().getLocation();
if (path == null)
return true;
return !path.toFile().exists();
}
return super.isDeleted(element);
}
/*
* @see AbstractDocumentProvider#doSaveDocument(IProgressMonitor, Object, IDocument, boolean)
*/
protected void doSaveDocument(IProgressMonitor monitor, Object element, IDocument document, boolean overwrite) throws CoreException {
if (element instanceof IFileEditorInput) {
IFileEditorInput input= (IFileEditorInput) element;
// try {
InputStream stream= new ByteArrayInputStream(document.get().getBytes());
// InputStream stream= new ByteArrayInputStream(document.get().getBytes(ResourcesPlugin.getEncoding()));
IFile file= input.getFile();
if (file.exists()) {
FileInfo info= (FileInfo) getElementInfo(element);
if (info != null && !overwrite)
checkSynchronizationState(info.fModificationStamp, file);
file.setContents(stream, overwrite, true, monitor);
if (info != null) {
ResourceMarkerAnnotationModel model= (ResourceMarkerAnnotationModel) info.fModel;
model.updateMarkers(info.fDocument);
info.fModificationStamp= computeModificationStamp(file);
}
} else {
try {
monitor.beginTask(TextEditorMessages.getString("FileDocumentProvider.task.saving"), 2000); //$NON-NLS-1$
ContainerGenerator generator = new ContainerGenerator(file.getParent().getFullPath());
generator.generateContainer(new SubProgressMonitor(monitor, 1000));
file.create(stream, false, new SubProgressMonitor(monitor, 1000));
}
finally {
monitor.done();
}
}
// } catch (IOException x) {
// IStatus s= new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.OK, x.getMessage(), x);
// throw new CoreException(s);
// }
} else {
super.doSaveDocument(monitor, element, document, overwrite);
}
}
/*
* @see AbstractDocumentProvider#createElementInfo(Object)
*/
protected ElementInfo createElementInfo(Object element) throws CoreException {
if (element instanceof IFileEditorInput) {
IFileEditorInput input= (IFileEditorInput) element;
try {
input.getFile().refreshLocal(IResource.DEPTH_INFINITE, null);
} catch (CoreException x) {
handleCoreException(x,TextEditorMessages.getString("FileDocumentProvider.createElementInfo")); //$NON-NLS-1$
}
IDocument d= createDocument(element);
IAnnotationModel m= createAnnotationModel(element);
FileSynchronizer f= new FileSynchronizer(input);
f.install();
FileInfo info= new FileInfo(d, m, f);
info.fModificationStamp= computeModificationStamp(input.getFile());
return info;
}
return super.createElementInfo(element);
}
/*
* @see AbstractDocumentProvider#disposeElementInfo(Object, ElementInfo)
*/
protected void disposeElementInfo(Object element, ElementInfo info) {
if (info instanceof FileInfo) {
FileInfo fileInfo= (FileInfo) info;
if (fileInfo.fFileSynchronizer != null)
fileInfo.fFileSynchronizer.uninstall();
}
super.disposeElementInfo(element, info);
}
/**
* Updates the element info to a change of the file content and sends out
* appropriate notifications.
*
* @param fileEditorInput the input of an text editor
*/
protected void handleElementContentChanged(IFileEditorInput fileEditorInput) {
FileInfo info= (FileInfo) getElementInfo(fileEditorInput);
try {
IDocument document= new Document();
setDocumentContent(document, fileEditorInput);
String newContent= document.get();
if ( !newContent.equals(info.fDocument.get())) {
// set the new content and fire content related events
fireElementContentAboutToBeReplaced(fileEditorInput);
removeUnchangedElementListeners(fileEditorInput, info);
info.fDocument.removeDocumentListener(info);
info.fDocument.set(newContent);
info.fCanBeSaved= false;
info.fModificationStamp= computeModificationStamp(fileEditorInput.getFile());
addUnchangedElementListeners(fileEditorInput, info);
fireElementContentReplaced(fileEditorInput);
} else {
removeUnchangedElementListeners(fileEditorInput, info);
// fires only the dirty state related event
info.fCanBeSaved= false;
info.fModificationStamp= computeModificationStamp(fileEditorInput.getFile());
addUnchangedElementListeners(fileEditorInput, info);
fireElementDirtyStateChanged(fileEditorInput, false);
}
} catch (CoreException x) {
handleCoreException(x, TextEditorMessages.getString("FileDocumentProvider.updateContent")); //$NON-NLS-1$
}
}
/**
* Sends out the notification that the file serving as document input has been moved.
*
* @param fileEditorInput the input of an text editor
* @param path the path of the new location of the file
*/
protected void handleElementMoved(IFileEditorInput fileEditorInput, IPath path) {
IWorkspace workspace= ResourcesPlugin.getWorkspace();
IFile newFile= workspace.getRoot().getFile(path);
fireElementMoved(fileEditorInput, newFile == null ? null : new FileEditorInput(newFile));
}
/**
* Sends out the notification that the file serving as document input has been deleted.
*
* @param fileEditorInput the input of an text editor
*/
protected void handleElementDeleted(IFileEditorInput fileEditorInput) {
fireElementDeleted(fileEditorInput);
}
/*
* @see AbstractDocumentProvider#getElementInfo(Object)
* It's only here to circumvent visibility issues with certain compilers.
*/
protected ElementInfo getElementInfo(Object element) {
return super.getElementInfo(element);
}
/*
* @see AbstractDocumentProvider#doValidateState(Object, Object)
*/
protected void doValidateState(Object element, Object computationContext) throws CoreException {
if (element instanceof IFileEditorInput) {
IFileEditorInput input= (IFileEditorInput) element;
FileInfo info= (FileInfo) getElementInfo(input);
if (info != null) {
IFile file= input.getFile();
if (file.isReadOnly()) { // do not use cached state here
IWorkspace workspace= file.getWorkspace();
workspace.validateEdit(new IFile[] { file }, computationContext);
}
}
}
super.doValidateState(element, computationContext);
}
/*
* @see IDocumentProviderExtension#isModifiable(Object)
*/
public boolean isModifiable(Object element) {
if (!isStateValidated(element)) {
if (element instanceof IFileEditorInput)
return true;
}
return super.isModifiable(element);
}
}