| /********************************************************************** |
| Copyright (c) 2000, 2002 IBM Corp. and others. |
| All rights reserved. This program and the accompanying materials |
| are made available under the terms of the Common Public License v1.0 |
| which accompanies this distribution, and is available at |
| http://www.eclipse.org/legal/cpl-v10.html |
| |
| Contributors: |
| IBM Corporation - Initial implementation |
| **********************************************************************/ |
| |
| package org.eclipse.ui.editors.text;
|
|
|
|
|
| import java.io.ByteArrayInputStream;
|
| import java.io.IOException;
|
| import java.io.InputStream;
|
| import java.util.ArrayList;
|
| import java.util.Iterator;
|
|
|
| import org.eclipse.swt.widgets.Display;
|
|
|
| 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.NullProgressMonitor;
|
| 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.IElementStateListener;
|
| import org.eclipse.ui.texteditor.ResourceMarkerAnnotationModel;
|
|
|
|
|
|
|
| /**
|
| * Shareable document provider specialized for file resources (<code>IFile</code>).<p>
|
| * This class may be instantiated or be subclassed.
|
| */
|
| public class FileDocumentProvider extends StorageDocumentProvider {
|
|
|
|
|
| /**
|
| * Runnable encapsulating an element state change. This runnable ensures |
| * that a element change failed message is sent out to the element state listeners |
| * in case an exception occurred.
|
| * @since 2.0
|
| */
|
| protected class SafeChange implements Runnable {
|
| |
| /** The input that changes. */
|
| private IFileEditorInput fInput;
|
|
|
| /**
|
| * Creates a new safe runnable for the given input. |
| * @param input the input
|
| */
|
| public SafeChange(IFileEditorInput input) {
|
| fInput= input;
|
| }
|
|
|
| /** |
| * Subclass responsibility. |
| * @param input the input |
| * @exception Exception in case of error |
| */
|
| protected void execute(IFileEditorInput input) throws Exception {
|
| }
|
|
|
| /*
|
| * @see java.lang.Runnable#run()
|
| * @since 2.0
|
| */
|
| public void run() {
|
|
|
| if (getElementInfo(fInput) == null) {
|
| fireElementStateChangeFailed(fInput);
|
| return;
|
| }
|
|
|
| try {
|
| execute(fInput);
|
| } catch (Exception e) {
|
| fireElementStateChangeFailed(fInput);
|
| }
|
| }
|
| };
|
|
|
|
|
| /**
|
| * 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. |
| * @param fileEditorInput the editor input to be synchronized
|
| */
|
| public FileSynchronizer(IFileEditorInput fileEditorInput) {
|
| fFileEditorInput= fileEditorInput;
|
| };
|
|
|
| /**
|
| * Creates a new file synchronizer. Is not yet installed on a resource. |
| * @param fileEditorInput the editor input to be synchronized
|
| * @deprecated use FileSynchronizer(IFileEditorInput)
|
| */
|
| public FileSynchronizer(FileEditorInput fileEditorInput) {
|
| fFileEditorInput= fileEditorInput;
|
| };
|
|
|
| /**
|
| * Returns the file wrapped by the file editor input. |
| * @return the file wrapped by the editor input associated with that synchronizer
|
| */
|
| 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 SafeChange(fFileEditorInput) {
|
| protected void execute(IFileEditorInput input) throws Exception {
|
| handleElementContentChanged(input);
|
| }
|
| };
|
| }
|
| }
|
| break;
|
| case IResourceDelta.REMOVED:
|
| if ((IResourceDelta.MOVED_TO & delta.getFlags()) != 0) {
|
| final IPath path= delta.getMovedToPath();
|
| runnable= new SafeChange(fFileEditorInput) {
|
| protected void execute(IFileEditorInput input) throws Exception {
|
| handleElementMoved(input, path);
|
| }
|
| };
|
| } else {
|
| FileInfo info= (FileInfo) getElementInfo(fFileEditorInput);
|
| if (!info.fCanBeSaved) {
|
| runnable= new SafeChange(fFileEditorInput) {
|
| protected void execute(IFileEditorInput input) throws Exception {
|
| handleElementDeleted(input);
|
| }
|
| };
|
| }
|
| }
|
| 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) {
|
|
|
| if (runnable instanceof SafeChange)
|
| fireElementStateChanging(fFileEditorInput);
|
|
|
| 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 information 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;
|
| |
| /** |
| * Creates a new file info. |
| * @param document the document |
| * @param model the annotation model |
| * @param fileSynchronizer the file synchronizer |
| */
|
| public FileInfo(IDocument document, IAnnotationModel model, FileSynchronizer fileSynchronizer) {
|
| super(document, model);
|
| fFileSynchronizer= fileSynchronizer;
|
| }
|
| };
|
|
|
|
|
| /**
|
| * Creates a new document provider.
|
| */
|
| public FileDocumentProvider() {
|
| super();
|
| }
|
|
|
| /**
|
| * Overrides <code>StorageDocumentProvider#setDocumentContent(IDocument, IEditorInput)</code>.
|
| * @deprecated use file encoding based version
|
| * @since 2.0
|
| */
|
| 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 StorageDocumentProvider#setDocumentContent(IDocument, IEditorInput, String)
|
| * @since 2.0
|
| */
|
| protected boolean setDocumentContent(IDocument document, IEditorInput editorInput, String encoding) throws CoreException {
|
| if (editorInput instanceof IFileEditorInput) {
|
| IFile file= ((IFileEditorInput) editorInput).getFile();
|
| setDocumentContent(document, file.getContents(false), encoding);
|
| return true;
|
| }
|
| return super.setDocumentContent(document, editorInput, encoding);
|
| }
|
|
|
| /*
|
| * @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 org.eclipse.ui.texteditor.IDocumentProviderExtension#synchronize(Object)
|
| * @since 2.0
|
| */
|
| public void synchronize(Object element) throws CoreException {
|
| if (element instanceof IFileEditorInput) {
|
|
|
| IFileEditorInput input= (IFileEditorInput) element;
|
|
|
| FileInfo info= (FileInfo) getElementInfo(element);
|
| if (info != null) {
|
|
|
| info.fFileSynchronizer.uninstall();
|
| input.getFile().refreshLocal(IResource.DEPTH_INFINITE, null);
|
| info.fFileSynchronizer.install();
|
|
|
| handleElementContentChanged((IFileEditorInput) element);
|
| }
|
| return;
|
|
|
| }
|
| super.synchronize(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(ResourcesPlugin.getEncoding()));
|
| IFile file= input.getFile();
|
|
|
| if (file.exists()) {
|
|
|
| FileInfo info= (FileInfo) getElementInfo(element);
|
|
|
| if (info != null && !overwrite)
|
| checkSynchronizationState(info.fModificationStamp, file);
|
|
|
| // inform about the upcoming content change
|
| fireElementStateChanging(element);
|
| try {
|
| file.setContents(stream, overwrite, true, monitor);
|
| } catch (CoreException x) {
|
| // inform about failure
|
| fireElementStateChangeFailed(element);
|
| throw x;
|
| } catch (RuntimeException x) {
|
| // inform about failure
|
| fireElementStateChangeFailed(element);
|
| throw x;
|
| }
|
|
|
| // If here, the editor state will be flipped to "not dirty".
|
| // Thus, the state changing flag will be reset.
|
|
|
| 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, new NullProgressMonitor());
|
| } catch (CoreException x) {
|
| handleCoreException(x,TextEditorMessages.getString("FileDocumentProvider.createElementInfo")); //$NON-NLS-1$
|
| }
|
|
|
| IDocument d= null;
|
| IStatus s= null;
|
|
|
| try {
|
| d= createDocument(element);
|
| } catch (CoreException x) {
|
| s= x.getStatus();
|
| d= new Document();
|
| }
|
|
|
| IAnnotationModel m= createAnnotationModel(element);
|
| FileSynchronizer f= new FileSynchronizer(input);
|
| f.install();
|
|
|
| FileInfo info= new FileInfo(d, m, f);
|
| info.fModificationStamp= computeModificationStamp(input.getFile());
|
| info.fStatus= s;
|
|
|
| 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);
|
|
|
| IDocument document= new Document();
|
| IStatus status= null;
|
|
|
| try {
|
|
|
| try {
|
| fileEditorInput.getFile().refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
|
| } catch (CoreException x) {
|
| handleCoreException(x, "FileDocumentProvider.handleElementContentChanged"); //$NON-NLS-1$
|
| }
|
|
|
| setDocumentContent(document, fileEditorInput, info.fEncoding);
|
|
|
| } catch (CoreException x) {
|
| status= x.getStatus();
|
| }
|
|
|
| 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());
|
| info.fStatus= status;
|
|
|
| addUnchangedElementListeners(fileEditorInput, info);
|
|
|
| fireElementContentReplaced(fileEditorInput);
|
|
|
| } else {
|
|
|
| removeUnchangedElementListeners(fileEditorInput, info);
|
|
|
| // fires only the dirty state related event
|
| info.fCanBeSaved= false;
|
| info.fModificationStamp= computeModificationStamp(fileEditorInput.getFile());
|
| info.fStatus= status;
|
|
|
| addUnchangedElementListeners(fileEditorInput, info);
|
|
|
| fireElementDirtyStateChanged(fileEditorInput, false);
|
| }
|
| }
|
|
|
| /**
|
| * 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)
|
| * @since 2.0
|
| */
|
| 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)
|
| * @since 2.0
|
| */
|
| public boolean isModifiable(Object element) {
|
| if (!isStateValidated(element)) {
|
| if (element instanceof IFileEditorInput)
|
| return true;
|
| }
|
| return super.isModifiable(element);
|
| }
|
|
|
| /*
|
| * @see IDocumentProvider#resetDocument(Object)
|
| * @since 2.0
|
| */
|
| public void resetDocument(Object element) throws CoreException {
|
| // http://dev.eclipse.org/bugs/show_bug.cgi?id=19014
|
| if (element instanceof IFileEditorInput) {
|
| IFileEditorInput input= (IFileEditorInput) element;
|
| try {
|
| input.getFile().refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
|
| } catch (CoreException x) {
|
| handleCoreException(x,TextEditorMessages.getString("FileDocumentProvider.resetDocument")); //$NON-NLS-1$
|
| }
|
| }
|
| super.resetDocument(element);
|
| }
|
| } |