| /******************************************************************************* |
| * Copyright (c) 2005, 2007 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 |
| * |
| |
| *******************************************************************************/ |
| package org.eclipse.dltk.internal.core; |
| |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| |
| import org.eclipse.core.resources.IContainer; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IFolder; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceStatus; |
| import org.eclipse.core.resources.IWorkspace; |
| import org.eclipse.core.resources.IWorkspaceRunnable; |
| 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.OperationCanceledException; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.SubProgressMonitor; |
| import org.eclipse.core.runtime.jobs.ISchedulingRule; |
| import org.eclipse.dltk.core.IBuildpathEntry; |
| import org.eclipse.dltk.core.IModelElement; |
| import org.eclipse.dltk.core.IModelElementDelta; |
| import org.eclipse.dltk.core.IModelStatus; |
| import org.eclipse.dltk.core.IModelStatusConstants; |
| import org.eclipse.dltk.core.IProjectFragment; |
| import org.eclipse.dltk.core.IScriptFolder; |
| import org.eclipse.dltk.core.IScriptModel; |
| import org.eclipse.dltk.core.ISourceModule; |
| import org.eclipse.dltk.core.ModelException; |
| |
| |
| /** |
| * Defines behavior common to all script Model operations |
| */ |
| public abstract class ModelOperation implements IWorkspaceRunnable, IProgressMonitor { |
| protected interface IPostAction { |
| /* |
| * Returns the id of this action. |
| * |
| * @see ModelOperation#postAction |
| */ |
| String getID(); |
| |
| /* |
| * Run this action. |
| */ |
| void run() throws ModelException; |
| } |
| /* |
| * Constants controlling the insertion mode of an action. |
| * |
| * @see ModelOperation#postAction |
| */ |
| protected static final int APPEND = 1; // insert at the end |
| protected static final int REMOVEALL_APPEND = 2; // remove all existing |
| // ones with same ID, |
| // and add new one at |
| // the end |
| protected static final int KEEP_EXISTING = 3; // do not insert if already |
| // existing with same ID |
| /* |
| * Whether tracing post actions is enabled. |
| */ |
| protected static boolean POST_ACTION_VERBOSE; |
| public static final String HAS_MODIFIED_RESOURCE_ATTR = "hasModifiedResource"; //$NON-NLS-1$ |
| public static final String TRUE = "true"; //$NON-NLS-1$ |
| /** |
| * The elements created by this operation - empty until the operation |
| * actually creates elements. |
| */ |
| /* |
| * A per thread stack of model operations (PerThreadObject of ArrayList). |
| */ |
| protected static ThreadLocal operationStacks = new ThreadLocal(); |
| /* |
| * A list of IPostActions. |
| */ |
| protected IPostAction[] actions; |
| protected int actionsStart = 0; |
| protected int actionsEnd = -1; |
| /* |
| * A HashMap of attributes that can be used by operations |
| */ |
| protected HashMap attributes; |
| /** |
| * The elements this operation operates on, or <code>null</code> if this |
| * operation does not operate on specific elements. |
| */ |
| protected IModelElement[] elementsToProcess; |
| |
| /** |
| * The parent elements this operation operates with or <code>null</code> |
| * if this operation does not operate with specific parent elements. |
| */ |
| protected IModelElement[] parentElements; |
| protected IModelElement[] resultElements = ModelElement.NO_ELEMENTS; |
| /** |
| * The progress monitor passed into this operation |
| */ |
| protected IProgressMonitor progressMonitor = null; |
| /** |
| * Conflict resolution policy - by default do not force (fail on a |
| * conflict). |
| */ |
| protected boolean force = false; |
| /** |
| * A flag indicating whether this operation is nested. |
| */ |
| protected boolean isNested = false; |
| |
| protected ModelOperation() { |
| // default constructor used in subclasses |
| } |
| |
| protected ModelOperation(IModelElement element) { |
| this.elementsToProcess = new IModelElement[] { |
| element |
| }; |
| } |
| |
| /** |
| * A common constructor for all Model operations. |
| */ |
| protected ModelOperation(IModelElement[] elements) { |
| this.elementsToProcess = elements; |
| } |
| |
| /** |
| * A common constructor for all Model operations. |
| */ |
| protected ModelOperation(IModelElement[] elements, boolean force) { |
| this.elementsToProcess = elements; |
| this.force = force; |
| } |
| |
| /** |
| * A common constructor for all script Model operations. |
| */ |
| protected ModelOperation(IModelElement[] elementsToProcess, IModelElement[] parentElements, boolean force) { |
| this.elementsToProcess = elementsToProcess; |
| this.parentElements = parentElements; |
| this.force = force; |
| } |
| |
| /* |
| * Registers the given action at the end of the list of actions to run. |
| */ |
| protected void addAction(IPostAction action) { |
| int length = this.actions.length; |
| if (length == ++this.actionsEnd) { |
| System.arraycopy(this.actions, 0, this.actions = new IPostAction[length * 2], 0, length); |
| } |
| this.actions[this.actionsEnd] = action; |
| } |
| |
| /* |
| * Registers the given delta with the script Model Manager. |
| */ |
| protected void addDelta(IModelElementDelta delta) { |
| ModelManager.getModelManager().getDeltaProcessor().registerModelDelta(delta); |
| } |
| |
| /* |
| * Registers the given reconcile delta with the script Model Manager. |
| */ |
| protected void addReconcileDelta(ISourceModule workingCopy, IModelElementDelta delta) { |
| HashMap reconcileDeltas = ModelManager.getModelManager().getDeltaProcessor().reconcileDeltas; |
| ModelElementDelta previousDelta = (ModelElementDelta) reconcileDeltas.get(workingCopy); |
| if (previousDelta != null) { |
| IModelElementDelta[] children = delta.getAffectedChildren(); |
| for (int i = 0, length = children.length; i < length; i++) { |
| ModelElementDelta child = (ModelElementDelta) children[i]; |
| previousDelta.insertDeltaTree(child.getElement(), child); |
| } |
| } else { |
| reconcileDeltas.put(workingCopy, delta); |
| } |
| } |
| |
| @Override |
| public void beginTask(String name, int totalWork) { |
| if (progressMonitor != null) { |
| progressMonitor.beginTask(name, totalWork); |
| } |
| } |
| |
| /* |
| * Returns whether this operation can modify the package fragment roots. |
| */ |
| protected boolean canModifyRoots() { |
| return false; |
| } |
| |
| /** |
| * Checks with the progress monitor to see whether this operation should be |
| * canceled. An operation should regularly call this method during its |
| * operation so that the user can cancel it. |
| * |
| * @exception OperationCanceledException |
| * if cancelling the operation has been requested |
| * @see IProgressMonitor#isCanceled |
| */ |
| protected void checkCanceled() { |
| if (isCanceled()) { |
| throw new OperationCanceledException(Messages.ModelOperation_operationCancelled); |
| } |
| } |
| |
| /** |
| * Common code used to verify the elements this operation is processing. |
| * |
| * @see ModelOperation#verify() |
| */ |
| protected IModelStatus commonVerify() { |
| if (elementsToProcess == null || elementsToProcess.length == 0) { |
| return new ModelStatus(IModelStatusConstants.NO_ELEMENTS_TO_PROCESS); |
| } |
| for (int i = 0; i < elementsToProcess.length; i++) { |
| if (elementsToProcess[i] == null) { |
| return new ModelStatus(IModelStatusConstants.NO_ELEMENTS_TO_PROCESS); |
| } |
| } |
| return ModelStatus.VERIFIED_OK; |
| } |
| |
| /** |
| * Convenience method to copy resources |
| */ |
| protected void copyResources(IResource[] resources, IPath destinationPath) throws ModelException { |
| IProgressMonitor subProgressMonitor = getSubProgressMonitor(resources.length); |
| IWorkspace workspace = resources[0].getWorkspace(); |
| try { |
| workspace.copy(resources, destinationPath, false, subProgressMonitor); |
| this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); |
| } catch (CoreException e) { |
| throw new ModelException(e); |
| } |
| } |
| |
| /** |
| * Convenience method to create a file |
| */ |
| protected void createFile(IContainer folder, String name, InputStream contents, boolean forceFlag) throws ModelException { |
| IFile file = folder.getFile(new Path(name)); |
| try { |
| file.create(contents, forceFlag ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY, getSubProgressMonitor(1)); |
| this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); |
| } catch (CoreException e) { |
| throw new ModelException(e); |
| } |
| } |
| |
| /** |
| * Convenience method to create a folder |
| */ |
| protected void createFolder(IContainer parentFolder, String name, boolean forceFlag) throws ModelException { |
| IFolder folder = parentFolder.getFolder(new Path(name)); |
| try { |
| // we should use true to create the file locally. Only VCM should |
| // use tru/false |
| folder.create(forceFlag ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY, true, // local |
| getSubProgressMonitor(1)); |
| this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); |
| } catch (CoreException e) { |
| throw new ModelException(e); |
| } |
| } |
| |
| protected void deleteEmptyScriptFolder(IScriptFolder fragment, boolean forceFlag, IResource rootResource) throws ModelException { |
| IContainer resource = (IContainer) fragment.getResource(); |
| try { |
| resource.delete(forceFlag ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY, getSubProgressMonitor(1)); |
| this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); |
| while (resource instanceof IFolder) { |
| // deleting a package: delete the parent if it is empty (eg. |
| // deleting x.y where folder x doesn't have resources but y) |
| // without deleting the package fragment root |
| resource = resource.getParent(); |
| if (!resource.equals(rootResource) && resource.members().length == 0) { |
| resource.delete(forceFlag ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY, getSubProgressMonitor(1)); |
| this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); |
| } |
| } |
| } catch (CoreException e) { |
| throw new ModelException(e); |
| } |
| } |
| |
| protected void deleteResource(IResource resource, int flags) throws ModelException { |
| try { |
| resource.delete(flags, getSubProgressMonitor(1)); |
| this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); |
| } catch (CoreException e) { |
| throw new ModelException(e); |
| } |
| } |
| |
| /** |
| * Convenience method to delete resources |
| */ |
| protected void deleteResources(IResource[] resources, boolean forceFlag) throws ModelException { |
| if (resources == null || resources.length == 0) |
| return; |
| IProgressMonitor subProgressMonitor = getSubProgressMonitor(resources.length); |
| IWorkspace workspace = resources[0].getWorkspace(); |
| try { |
| workspace.delete(resources, forceFlag ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY, subProgressMonitor); |
| this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); |
| } catch (CoreException e) { |
| throw new ModelException(e); |
| } |
| } |
| |
| @Override |
| public void done() { |
| if (progressMonitor != null) { |
| progressMonitor.done(); |
| } |
| } |
| |
| protected boolean equalsOneOf(IPath path, IPath[] otherPaths) { |
| for (int i = 0, length = otherPaths.length; i < length; i++) { |
| if (path.equals(otherPaths[i])) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Convenience method to run an operation within this operation |
| */ |
| public void executeNestedOperation(ModelOperation operation, int subWorkAmount) throws ModelException { |
| IModelStatus status = operation.verify(); |
| if (!status.isOK()) { |
| throw new ModelException(status); |
| } |
| IProgressMonitor subProgressMonitor = getSubProgressMonitor(subWorkAmount); |
| // fix for 1FW7IKC, part (1) |
| try { |
| operation.setNested(true); |
| operation.run(subProgressMonitor); |
| } catch (CoreException ce) { |
| if (ce instanceof ModelException) { |
| throw (ModelException) ce; |
| } else { |
| // translate the core exception to ascriptmodel exception |
| if (ce.getStatus().getCode() == IResourceStatus.OPERATION_FAILED) { |
| Throwable e = ce.getStatus().getException(); |
| if (e instanceof ModelException) { |
| throw (ModelException) e; |
| } |
| } |
| throw new ModelException(ce); |
| } |
| } |
| } |
| |
| /** |
| * Performs the operation specific behavior. Subclasses must override. |
| */ |
| protected abstract void executeOperation() throws ModelException; |
| |
| /* |
| * Returns the index of the first registered action with the given id, |
| * starting from a given position. Returns -1 if not found. |
| */ |
| protected int firstActionWithID(String id, int start) { |
| for (int i = start; i <= this.actionsEnd; i++) { |
| if (this.actions[i].getID().equals(id)) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| /* |
| * Returns the attribute registered at the given key with the top level |
| * operation. Returns null if no such attribute is found. |
| */ |
| protected Object getAttribute(Object key) { |
| ArrayList stack = this.getCurrentOperationStack(); |
| if (stack.size() == 0) |
| return null; |
| ModelOperation topLevelOp = (ModelOperation) stack.get(0); |
| if (topLevelOp.attributes == null) { |
| return null; |
| } else { |
| return topLevelOp.attributes.get(key); |
| } |
| } |
| |
| /* |
| * Returns the stack of operations running in the current thread. Returns an |
| * empty stack if no operations are currently running in this thread. |
| */ |
| protected ArrayList getCurrentOperationStack() { |
| ArrayList stack = (ArrayList) operationStacks.get(); |
| if (stack == null) { |
| stack = new ArrayList(); |
| operationStacks.set(stack); |
| } |
| return stack; |
| } |
| |
| /** |
| * Returns the element to which this operation applies, or <code>null</code> |
| * if not applicable. |
| */ |
| protected IModelElement getElementToProcess() { |
| if (elementsToProcess == null || elementsToProcess.length == 0) { |
| return null; |
| } |
| return elementsToProcess[0]; |
| } |
| |
| /** |
| * Returns the Model this operation is operating in. |
| */ |
| public IScriptModel getModel() { |
| if (elementsToProcess == null || elementsToProcess.length == 0) { |
| return getParentElement().getModel(); |
| } else { |
| return elementsToProcess[0].getModel(); |
| } |
| } |
| |
| protected IPath[] getNestedFolders(IProjectFragment root) throws ModelException { |
| IPath rootPath = root.getPath(); |
| IBuildpathEntry[] buildpath = root.getScriptProject().getRawBuildpath(); |
| int length = buildpath.length; |
| IPath[] result = new IPath[length]; |
| int index = 0; |
| for (int i = 0; i < length; i++) { |
| IPath path = buildpath[i].getPath(); |
| if (rootPath.isPrefixOf(path) && !rootPath.equals(path)) { |
| result[index++] = path; |
| } |
| } |
| if (index < length) { |
| System.arraycopy(result, 0, result = new IPath[index], 0, index); |
| } |
| return result; |
| } |
| |
| /** |
| * Returns the parent element to which this operation applies, or |
| * <code>null</code> if not applicable. |
| */ |
| protected IModelElement getParentElement() { |
| if (parentElements == null || parentElements.length == 0) { |
| return null; |
| } |
| return parentElements[0]; |
| } |
| |
| /** |
| * Returns the parent elements to which this operation applies, or |
| * <code>null</code> if not applicable. |
| */ |
| protected IModelElement[] getParentElements() { |
| return parentElements; |
| } |
| |
| /** |
| * Returns the elements created by this operation. |
| */ |
| public IModelElement[] getResultElements() { |
| return resultElements; |
| } |
| |
| /* |
| * Returns the scheduling rule for this operation (i.e. the resource that |
| * needs to be locked while this operation is running. Subclasses can |
| * override. |
| */ |
| protected ISchedulingRule getSchedulingRule() { |
| return ResourcesPlugin.getWorkspace().getRoot(); |
| } |
| |
| /** |
| * Creates and returns a subprogress monitor if appropriate. |
| */ |
| protected IProgressMonitor getSubProgressMonitor(int workAmount) { |
| IProgressMonitor sub = null; |
| if (progressMonitor != null) { |
| sub = new SubProgressMonitor(progressMonitor, workAmount, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK); |
| } |
| return sub; |
| } |
| |
| /** |
| * Returns whether this operation has performed any resource modifications. |
| * Returns false if this operation has not been executed yet. |
| */ |
| public boolean hasModifiedResource() { |
| return !this.isReadOnly() && this.getAttribute(HAS_MODIFIED_RESOURCE_ATTR) == TRUE; |
| } |
| |
| @Override |
| public void internalWorked(double work) { |
| if (progressMonitor != null) { |
| progressMonitor.internalWorked(work); |
| } |
| } |
| |
| @Override |
| public boolean isCanceled() { |
| if (progressMonitor != null) { |
| return progressMonitor.isCanceled(); |
| } |
| return false; |
| } |
| |
| /** |
| * Returns <code>true</code> if this operation performs no resource |
| * modifications, otherwise <code>false</code>. Subclasses must override. |
| */ |
| public boolean isReadOnly() { |
| return false; |
| } |
| |
| /* |
| * Returns whether this operation is the first operation to run in the |
| * current thread. |
| */ |
| protected boolean isTopLevelOperation() { |
| ArrayList stack; |
| return (stack = this.getCurrentOperationStack()).size() > 0 && stack.get(0) == this; |
| } |
| |
| /** |
| * Convenience method to move resources |
| */ |
| protected void moveResources(IResource[] resources, IPath destinationPath) throws ModelException { |
| IProgressMonitor subProgressMonitor = null; |
| if (progressMonitor != null) { |
| subProgressMonitor = new SubProgressMonitor(progressMonitor, resources.length, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK); |
| } |
| IWorkspace workspace = resources[0].getWorkspace(); |
| try { |
| workspace.move(resources, destinationPath, false, subProgressMonitor); |
| this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); |
| } catch (CoreException e) { |
| throw new ModelException(e); |
| } |
| } |
| |
| /** |
| * Creates and returns a new <code>IModelElementDelta</code> on the model. |
| */ |
| public ModelElementDelta newModelElementDelta() { |
| return new ModelElementDelta(getModel()); |
| } |
| |
| /* |
| * Removes the last pushed operation from the stack of running operations. |
| * Returns the poped operation or null if the stack was empty. |
| */ |
| protected ModelOperation popOperation() { |
| ArrayList stack = getCurrentOperationStack(); |
| int size = stack.size(); |
| if (size > 0) { |
| if (size == 1) { // top level operation |
| operationStacks.set(null); // release reference (see |
| // http://bugs.eclipse.org/bugs/show_bug.cgi?id=33927) |
| } |
| return (ModelOperation) stack.remove(size - 1); |
| } else { |
| return null; |
| } |
| } |
| |
| /* |
| * Registers the given action to be run when the outer most model operation |
| * has finished. The insertion mode controls whether: - the action should |
| * discard all existing actions with the same id, and be queued at the end |
| * (REMOVEALL_APPEND), - the action should be ignored if there is already an |
| * action with the same id (KEEP_EXISTING), - the action should be queued at |
| * the end without looking at existing actions (APPEND) |
| */ |
| protected void postAction(IPostAction action, int insertionMode) { |
| if (POST_ACTION_VERBOSE) { |
| System.out.print("(" + Thread.currentThread() + ") [ModelOperation.postAction(IPostAction, int)] Posting action " + action.getID()); //$NON-NLS-1$ //$NON-NLS-2$ |
| switch (insertionMode) { |
| case REMOVEALL_APPEND: |
| System.out.println(" (REMOVEALL_APPEND)"); //$NON-NLS-1$ |
| break; |
| case KEEP_EXISTING: |
| System.out.println(" (KEEP_EXISTING)"); //$NON-NLS-1$ |
| break; |
| case APPEND: |
| System.out.println(" (APPEND)"); //$NON-NLS-1$ |
| break; |
| } |
| } |
| ModelOperation topLevelOp = (ModelOperation) getCurrentOperationStack().get(0); |
| IPostAction[] postActions = topLevelOp.actions; |
| if (postActions == null) { |
| topLevelOp.actions = postActions = new IPostAction[1]; |
| postActions[0] = action; |
| topLevelOp.actionsEnd = 0; |
| } else { |
| String id = action.getID(); |
| switch (insertionMode) { |
| case REMOVEALL_APPEND: |
| int index = this.actionsStart - 1; |
| while ((index = topLevelOp.firstActionWithID(id, index + 1)) >= 0) { |
| // remove action[index] |
| System.arraycopy(postActions, index + 1, postActions, index, topLevelOp.actionsEnd - index); |
| postActions[topLevelOp.actionsEnd--] = null; |
| } |
| topLevelOp.addAction(action); |
| break; |
| case KEEP_EXISTING: |
| if (topLevelOp.firstActionWithID(id, 0) < 0) { |
| topLevelOp.addAction(action); |
| } |
| break; |
| case APPEND: |
| topLevelOp.addAction(action); |
| break; |
| } |
| } |
| } |
| |
| protected boolean prefixesOneOf(IPath path, IPath[] otherPaths) { |
| for (int i = 0, length = otherPaths.length; i < length; i++) { |
| if (path.isPrefixOf(otherPaths[i])) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * Pushes the given operation on the stack of operations currently running |
| * in this thread. |
| */ |
| protected void pushOperation(ModelOperation operation) { |
| getCurrentOperationStack().add(operation); |
| } |
| |
| /* |
| * Removes all actions with the given id from the queue of post actions. |
| * Does nothing if no such action is in the queue. |
| */ |
| protected void removeAllPostAction(String actionID) { |
| if (POST_ACTION_VERBOSE) { |
| System.out.println("(" + Thread.currentThread() + ") [ModelOperation.removeAllPostAction(String)] Removing actions " + actionID); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| ModelOperation topLevelOp = (ModelOperation) getCurrentOperationStack().get(0); |
| IPostAction[] postActions = topLevelOp.actions; |
| if (postActions == null) |
| return; |
| int index = this.actionsStart - 1; |
| while ((index = topLevelOp.firstActionWithID(actionID, index + 1)) >= 0) { |
| // remove action[index] |
| System.arraycopy(postActions, index + 1, postActions, index, topLevelOp.actionsEnd - index); |
| postActions[topLevelOp.actionsEnd--] = null; |
| } |
| } |
| |
| /* |
| * Deregister the reconcile delta for the given working copy |
| */ |
| protected void removeReconcileDelta(ISourceModule workingCopy) { |
| ModelManager.getModelManager().getDeltaProcessor().reconcileDeltas.remove(workingCopy); |
| } |
| |
| /** |
| * Runs this operation and registers any deltas created. |
| * |
| * @see IWorkspaceRunnable |
| * @exception CoreException |
| * if the operation fails |
| */ |
| @Override |
| public void run(IProgressMonitor monitor) throws CoreException { |
| ModelManager manager = ModelManager.getModelManager(); |
| DeltaProcessor deltaProcessor = manager.getDeltaProcessor(); |
| int previousDeltaCount = deltaProcessor.modelDeltas.size(); |
| try { |
| progressMonitor = monitor; |
| pushOperation(this); |
| try { |
| if (canModifyRoots()) { |
| // computes the root infos before executing the operation |
| // noop if aready initialized |
| ModelManager.getModelManager().deltaState.initializeRoots(); |
| } |
| executeOperation(); |
| } finally { |
| if (this.isTopLevelOperation()) { |
| this.runPostActions(); |
| } |
| } |
| } finally { |
| try { |
| // reacquire delta processor as it can have been reset during |
| // executeOperation() |
| deltaProcessor = manager.getDeltaProcessor(); |
| // update Model using deltas that were recorded during this |
| // operation |
| for (int i = previousDeltaCount, size = deltaProcessor.modelDeltas.size(); i < size; i++) { |
| deltaProcessor.updateModel(deltaProcessor.modelDeltas |
| .get(i)); |
| } |
| // close the parents of the created elements and reset their |
| // project's cache (in case we are in an |
| // IWorkspaceRunnable and the clients wants to use the created |
| // element's parent) |
| for (int i = 0, length = this.resultElements.length; i < length; i++) { |
| IModelElement element = this.resultElements[i]; |
| Openable openable = (Openable) element.getOpenable(); |
| if (!(openable instanceof SourceModule) || !((SourceModule) openable).isWorkingCopy()) { |
| /* |
| * a working copy must remain a child of its parent even |
| * after a move |
| */ |
| ((ModelElement) openable.getParent()).close(); |
| } |
| switch (element.getElementType()) { |
| case IModelElement.PROJECT_FRAGMENT: |
| case IModelElement.SCRIPT_FOLDER: |
| ((ScriptProject) element.getScriptProject()).resetCaches(); |
| break; |
| } |
| } |
| // fire only iff: |
| // - the operation is a top level operation |
| // - the operation did produce some delta(s) |
| // - but the operation has not modified any resource |
| if (this.isTopLevelOperation()) { |
| if ((deltaProcessor.modelDeltas.size() > previousDeltaCount || !deltaProcessor.reconcileDeltas.isEmpty()) |
| && !this.hasModifiedResource()) { |
| deltaProcessor.fire(null, DeltaProcessor.DEFAULT_CHANGE_EVENT); |
| } // else deltas are fired while processing the resource |
| // delta |
| } |
| } finally { |
| popOperation(); |
| } |
| } |
| } |
| |
| /** |
| * Main entry point for Model operations. Runs a Model Operation as an |
| * IWorkspaceRunnable if not read-only. |
| */ |
| public void runOperation(IProgressMonitor monitor) throws ModelException { |
| IModelStatus status = verify(); |
| if (!status.isOK()) { |
| throw new ModelException(status); |
| } |
| try { |
| if (isReadOnly()) { |
| run(monitor); |
| } else { |
| // Use IWorkspace.run(...) to ensure that a build will be done |
| // in autobuild mode. |
| // Note that if the tree is locked, this will throw a |
| // CoreException, but this is ok |
| // as this operation is modifying the tree (not read-only) and a |
| // CoreException will be thrown anyway. |
| IWorkspace wc = ResourcesPlugin.getWorkspace(); |
| wc.run(this, getSchedulingRule(), IWorkspace.AVOID_UPDATE, monitor); |
| } |
| } catch (CoreException ce) { |
| if (ce instanceof ModelException) { |
| throw (ModelException) ce; |
| } else { |
| if (ce.getStatus().getCode() == IResourceStatus.OPERATION_FAILED) { |
| Throwable e = ce.getStatus().getException(); |
| if (e instanceof ModelException) { |
| throw (ModelException) e; |
| } |
| } |
| throw new ModelException(ce); |
| } |
| } |
| } |
| |
| protected void runPostActions() throws ModelException { |
| while (this.actionsStart <= this.actionsEnd) { |
| IPostAction postAction = this.actions[this.actionsStart++]; |
| if (POST_ACTION_VERBOSE) { |
| System.out.println("(" + Thread.currentThread() + ") [ModelOperation.runPostActions()] Running action " + postAction.getID()); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| postAction.run(); |
| } |
| } |
| |
| /* |
| * Registers the given attribute at the given key with the top level |
| * operation. |
| */ |
| protected void setAttribute(Object key, Object attribute) { |
| ModelOperation topLevelOp = (ModelOperation) this.getCurrentOperationStack().get(0); |
| if (topLevelOp.attributes == null) { |
| topLevelOp.attributes = new HashMap(); |
| } |
| topLevelOp.attributes.put(key, attribute); |
| } |
| |
| @Override |
| public void setCanceled(boolean value) { |
| if (progressMonitor != null) { |
| progressMonitor.setCanceled(value); |
| } |
| } |
| |
| /** |
| * Sets whether this operation is nested or not. |
| * |
| * @see CreateElementInCUOperation#checkCanceled |
| */ |
| protected void setNested(boolean nested) { |
| isNested = nested; |
| } |
| |
| @Override |
| public void setTaskName(String name) { |
| if (progressMonitor != null) { |
| progressMonitor.setTaskName(name); |
| } |
| } |
| |
| @Override |
| public void subTask(String name) { |
| if (progressMonitor != null) { |
| progressMonitor.subTask(name); |
| } |
| } |
| |
| /** |
| * Returns a status indicating if there is any known reason this operation |
| * will fail. Operations are verified before they are run. |
| * |
| * Subclasses must override if they have any conditions to verify before |
| * this operation executes. |
| * |
| * @see IModelStatus |
| */ |
| protected IModelStatus verify() { |
| return commonVerify(); |
| } |
| |
| @Override |
| public void worked(int work) { |
| if (progressMonitor != null) { |
| progressMonitor.worked(work); |
| checkCanceled(); |
| } |
| } |
| } |