| /******************************************************************************* |
| * Copyright (c) 2000, 2015 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * 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.isEmpty()) { |
| 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; |
| IFile file= input.getFile(); |
| ISchedulingRule validateEditRule= fResourceRuleFactory.validateEditRule(new IResource[] { file }); |
| if (validateEditRule == null) { |
| // Note that factory decides to provide a null rule for modifiable files (not read-only). |
| // Null rule means, that org.eclipse.core.internal.resources.WorkManager.checkIn(ISchedulingRule, IProgressMonitor) |
| // will run jobManager.beginRule(null, monitor); which will NOT show any progress dialog |
| // and will *immediately* lock UI thread via lock.acquire(); while the workspace is locked |
| // Providing here a file we enforce the progress dialog, where this operation can be cancelled by user, |
| // so that an occasional "Modify" or "Save" of the editor will NOT block UI forever. |
| return file; |
| } else { |
| return validateEditRule; |
| } |
| } |
| 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); |
| } |
| } |