/******************************************************************************* * Copyright (c) 2000, 2003 IBM Corporation 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 API and implementation *******************************************************************************/ package net.sourceforge.phpdt.internal.core; //import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import net.sourceforge.phpdt.core.ICompilationUnit; import net.sourceforge.phpdt.core.IJavaElement; import net.sourceforge.phpdt.core.IJavaElementDelta; import net.sourceforge.phpdt.core.IJavaModel; import net.sourceforge.phpdt.core.IJavaModelStatus; import net.sourceforge.phpdt.core.IJavaModelStatusConstants; import net.sourceforge.phpdt.core.IPackageFragment; import net.sourceforge.phpdt.core.IWorkingCopy; import net.sourceforge.phpdt.core.JavaModelException; import net.sourceforge.phpdt.internal.core.util.PerThreadObject; import net.sourceforge.phpdt.internal.core.util.Util; 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; /** * Defines behavior common to all Java Model operations */ public abstract class JavaModelOperation implements IWorkspaceRunnable, IProgressMonitor { protected interface IPostAction { /* * Returns the id of this action. * * @see JavaModelOperation#postAction */ String getID(); /* * Run this action. */ void run() throws JavaModelException; } /* * Constants controlling the insertion mode of an action. * * @see JavaModelOperation#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; /* * 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; public static final String HAS_MODIFIED_RESOURCE_ATTR = "hasModifiedResource"; //$NON-NLS-1$ public static final String TRUE = "true"; //$NON-NLS-1$ // public static final String FALSE = "false"; //$NON-NLS-1$ /** * The elements this operation operates on, or null if this * operation does not operate on specific elements. */ protected IJavaElement[] fElementsToProcess; /** * The parent elements this operation operates with or null * if this operation does not operate with specific parent elements. */ protected IJavaElement[] fParentElements; /** * An empty collection of IJavaElement s - the common empty * result if no elements are created, or if this operation is not actually * executed. */ protected static IJavaElement[] NO_ELEMENTS = new IJavaElement[] {}; /** * The elements created by this operation - empty until the operation * actually creates elements. */ protected IJavaElement[] resultElements = NO_ELEMENTS; /** * The progress monitor passed into this operation */ protected IProgressMonitor progressMonitor = null; /** * A flag indicating whether this operation is nested. */ protected boolean isNested = false; /** * Conflict resolution policy - by default do not force (fail on a * conflict). */ protected boolean force = false; /* * A per thread stack of java model operations (PerThreadObject of * ArrayList). */ protected static PerThreadObject operationStacks = new PerThreadObject(); protected JavaModelOperation() { } /** * A common constructor for all Java Model operations. */ protected JavaModelOperation(IJavaElement[] elements) { fElementsToProcess = elements; } /** * Common constructor for all Java Model operations. */ protected JavaModelOperation(IJavaElement[] elementsToProcess, IJavaElement[] parentElements) { fElementsToProcess = elementsToProcess; fParentElements = parentElements; } /** * A common constructor for all Java Model operations. */ protected JavaModelOperation(IJavaElement[] elementsToProcess, IJavaElement[] parentElements, boolean force) { fElementsToProcess = elementsToProcess; fParentElements = parentElements; this.force = force; } /** * A common constructor for all Java Model operations. */ protected JavaModelOperation(IJavaElement[] elements, boolean force) { fElementsToProcess = elements; this.force = force; } /** * Common constructor for all Java Model operations. */ // protected JavaModelOperation(IJavaElement element) { // fElementsToProcess = new IJavaElement[] { element }; // } /** * A common constructor for all Java Model operations. */ // protected JavaModelOperation(IJavaElement element, boolean force) { // fElementsToProcess = new IJavaElement[] { element }; // 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 Java Model Manager. */ protected void addDelta(IJavaElementDelta delta) { JavaModelManager.getJavaModelManager().registerJavaModelDelta(delta); } /* * Registers the given reconcile delta with the Java Model Manager. */ protected void addReconcileDelta(IWorkingCopy workingCopy, IJavaElementDelta delta) { HashMap reconcileDeltas = JavaModelManager.getJavaModelManager().reconcileDeltas; JavaElementDelta previousDelta = (JavaElementDelta) reconcileDeltas .get(workingCopy); if (previousDelta != null) { IJavaElementDelta[] children = delta.getAffectedChildren(); for (int i = 0, length = children.length; i < length; i++) { JavaElementDelta child = (JavaElementDelta) children[i]; previousDelta.insertDeltaTree(child.getElement(), child); } } else { reconcileDeltas.put(workingCopy, delta); } } /* * Deregister the reconcile delta for the given working copy */ protected void removeReconcileDelta(IWorkingCopy workingCopy) { JavaModelManager.getJavaModelManager().reconcileDeltas .remove(workingCopy); } /** * @see IProgressMonitor */ public void beginTask(String name, int totalWork) { if (progressMonitor != null) { progressMonitor.beginTask(name, totalWork); } } /** * 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(Util .bind("operation.cancelled")); //$NON-NLS-1$ } } /** * Common code used to verify the elements this operation is processing. * * @see JavaModelOperation#verify() */ protected IJavaModelStatus commonVerify() { if (fElementsToProcess == null || fElementsToProcess.length == 0) { return new JavaModelStatus( IJavaModelStatusConstants.NO_ELEMENTS_TO_PROCESS); } for (int i = 0; i < fElementsToProcess.length; i++) { if (fElementsToProcess[i] == null) { return new JavaModelStatus( IJavaModelStatusConstants.NO_ELEMENTS_TO_PROCESS); } } return JavaModelStatus.VERIFIED_OK; } /** * Convenience method to copy resources */ protected void copyResources(IResource[] resources, IPath destinationPath) throws JavaModelException { 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 JavaModelException(e); } } /** * Convenience method to create a file */ // protected void createFile(IContainer folder, String name, // InputStream contents, boolean force) throws JavaModelException { // IFile file = folder.getFile(new Path(name)); // try { // file.create(contents, force ? IResource.FORCE // | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY, // getSubProgressMonitor(1)); // this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); // } catch (CoreException e) { // throw new JavaModelException(e); // } // } /** * Convenience method to create a folder */ protected void createFolder(IContainer parentFolder, String name, boolean force) throws JavaModelException { 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(force ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY, true, // local getSubProgressMonitor(1)); this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); } catch (CoreException e) { throw new JavaModelException(e); } } /** * Convenience method to delete an empty package fragment */ protected void deleteEmptyPackageFragment(IPackageFragment fragment, boolean force, IResource rootResource) throws JavaModelException { IContainer resource = (IContainer) fragment.getResource(); try { resource.delete(force ? 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(force ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY, getSubProgressMonitor(1)); this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); } } } catch (CoreException e) { throw new JavaModelException(e); } } /** * Convenience method to delete a resource */ protected void deleteResource(IResource resource, int flags) throws JavaModelException { try { resource.delete(flags, getSubProgressMonitor(1)); this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); } catch (CoreException e) { throw new JavaModelException(e); } } /** * Convenience method to delete resources */ protected void deleteResources(IResource[] resources, boolean force) throws JavaModelException { if (resources == null || resources.length == 0) return; IProgressMonitor subProgressMonitor = getSubProgressMonitor(resources.length); IWorkspace workspace = resources[0].getWorkspace(); try { workspace.delete(resources, force ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY, subProgressMonitor); this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); } catch (CoreException e) { throw new JavaModelException(e); } } /** * @see IProgressMonitor */ public void done() { if (progressMonitor != null) { progressMonitor.done(); } } /* * Returns whether the given path is equals to one of the given other paths. */ // 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; // } /** * Verifies the operation can proceed and executes the operation. Subclasses * should override #verify and executeOperation * to implement the specific operation behavior. * * @exception JavaModelException * The operation has failed. */ // protected void execute() throws JavaModelException { // IJavaModelStatus status= verify(); // if (status.isOK()) { // // if first time here, computes the root infos before executing the // operation // DeltaProcessor deltaProcessor = // JavaModelManager.getJavaModelManager().deltaProcessor; // if (deltaProcessor.roots == null) { // deltaProcessor.initializeRoots(); // } // executeOperation(); // } else { // throw new JavaModelException(status); // } // } /** * Convenience method to run an operation within this operation */ public void executeNestedOperation(JavaModelOperation operation, int subWorkAmount) throws JavaModelException { IProgressMonitor subProgressMonitor = getSubProgressMonitor(subWorkAmount); // fix for 1FW7IKC, part (1) try { operation.setNested(true); operation.run(subProgressMonitor); } catch (CoreException ce) { if (ce instanceof JavaModelException) { throw (JavaModelException) ce; } else { // translate the core exception to a java model exception if (ce.getStatus().getCode() == IResourceStatus.OPERATION_FAILED) { Throwable e = ce.getStatus().getException(); if (e instanceof JavaModelException) { throw (JavaModelException) e; } } throw new JavaModelException(ce); } } } /** * Performs the operation specific behavior. Subclasses must override. */ protected abstract void executeOperation() throws JavaModelException; /* * 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; JavaModelOperation topLevelOp = (JavaModelOperation) stack.get(0); if (topLevelOp.attributes == null) { return null; } else { return topLevelOp.attributes.get(key); } } /** * Returns the compilation unit the given element is contained in, or the * element itself (if it is a compilation unit), otherwise null. */ protected ICompilationUnit getCompilationUnitFor(IJavaElement element) { return ((JavaElement) element).getCompilationUnit(); } /* * 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.getCurrent(); if (stack == null) { stack = new ArrayList(); operationStacks.setCurrent(stack); } return stack; } /** * Returns the elements to which this operation applies, or * null if not applicable. */ protected IJavaElement[] getElementsToProcess() { return fElementsToProcess; } /** * Returns the element to which this operation applies, or null * if not applicable. */ protected IJavaElement getElementToProcess() { if (fElementsToProcess == null || fElementsToProcess.length == 0) { return null; } return fElementsToProcess[0]; } /** * Returns the Java Model this operation is operating in. */ public IJavaModel getJavaModel() { if (fElementsToProcess == null || fElementsToProcess.length == 0) { return getParentElement().getJavaModel(); } else { return fElementsToProcess[0].getJavaModel(); } } // protected IPath[] getNestedFolders(IPackageFragmentRoot root) throws // JavaModelException { // IPath rootPath = root.getPath(); // IClasspathEntry[] classpath = root.getJavaProject().getRawClasspath(); // int length = classpath.length; // IPath[] result = new IPath[length]; // int index = 0; // for (int i = 0; i < length; i++) { // IPath path = classpath[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 * null if not applicable. */ protected IJavaElement getParentElement() { if (fParentElements == null || fParentElements.length == 0) { return null; } return fParentElements[0]; } /** * Returns the parent elements to which this operation applies, or * null if not applicable. */ // protected IJavaElement[] getParentElements() { // return fParentElements; // } /** * Returns the elements created by this operation. */ // public IJavaElement[] 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; } public void internalWorked(double work) { if (progressMonitor != null) { progressMonitor.internalWorked(work); } } /** * @see IProgressMonitor */ public boolean isCanceled() { if (progressMonitor != null) { return progressMonitor.isCanceled(); } return false; } /** * Returns true if this operation performs no resource * modifications, otherwise false. 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; } /* * 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; } /** * Convenience method to move resources */ protected void moveResources(IResource[] resources, IPath destinationPath) throws JavaModelException { 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 JavaModelException(e); } } /** * Creates and returns a new IJavaElementDelta on the Java * Model. */ public JavaElementDelta newJavaElementDelta() { return new JavaElementDelta(getJavaModel()); } /* * Removes the last pushed operation from the stack of running operations. * Returns the poped operation or null if the stack was empty. */ protected JavaModelOperation popOperation() { ArrayList stack = getCurrentOperationStack(); int size = stack.size(); if (size > 0) { if (size == 1) { // top level operation operationStacks.setCurrent(null); // release reference (see // http://bugs.eclipse.org/bugs/show_bug.cgi?id=33927) } return (JavaModelOperation) stack.remove(size - 1); } else { return null; } } /* * Registers the given action to be run when the outer most java 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() + ") [JavaModelOperation.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; } } JavaModelOperation topLevelOp = (JavaModelOperation) 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; } } } /* * Returns whether the given path is the prefix of one of the given other * paths. */ // 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(JavaModelOperation operation) { getCurrentOperationStack().add(operation); } /** * Main entry point for Java Model operations. Executes this operation and * registers any deltas created. * * @see IWorkspaceRunnable * @exception CoreException * if the operation fails */ public void run(IProgressMonitor monitor) throws CoreException { JavaModelManager manager = JavaModelManager.getJavaModelManager(); DeltaProcessor deltaProcessor = manager.getDeltaProcessor(); int previousDeltaCount = deltaProcessor.javaModelDeltas.size(); try { progressMonitor = monitor; pushOperation(this); try { // computes the root infos before executing the operation // noop if aready initialized JavaModelManager.getJavaModelManager().deltaState .initializeRoots(); executeOperation(); } finally { if (this.isTopLevelOperation()) { this.runPostActions(); } } } finally { try { // update JavaModel using deltas that were recorded during this // operation for (int i = previousDeltaCount, size = manager.javaModelDeltas .size(); i < size; i++) { manager .updateJavaModel((IJavaElementDelta) manager.javaModelDeltas .get(i)); } // 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 ((manager.javaModelDeltas.size() > previousDeltaCount || !manager.reconcileDeltas .isEmpty()) && !this.hasModifiedResource()) { manager.fire(null, JavaModelManager.DEFAULT_CHANGE_EVENT); } // else deltas are fired while processing the resource // delta } } finally { popOperation(); } } } /** * Main entry point for Java Model operations. Runs a Java Model Operation * as an IWorkspaceRunnable if not read-only. */ public void runOperation(IProgressMonitor monitor) throws JavaModelException { IJavaModelStatus status = verify(); if (!status.isOK()) { throw new JavaModelException(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. ResourcesPlugin.getWorkspace().run(this, getSchedulingRule(), IWorkspace.AVOID_UPDATE, monitor); } } catch (CoreException ce) { if (ce instanceof JavaModelException) { throw (JavaModelException) ce; } else { if (ce.getStatus().getCode() == IResourceStatus.OPERATION_FAILED) { Throwable e = ce.getStatus().getException(); if (e instanceof JavaModelException) { throw (JavaModelException) e; } } throw new JavaModelException(ce); } } } protected void runPostActions() throws JavaModelException { while (this.actionsStart <= this.actionsEnd) { IPostAction postAction = this.actions[this.actionsStart++]; if (POST_ACTION_VERBOSE) { System.out .println("(" + Thread.currentThread() + ") [JavaModelOperation.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) { JavaModelOperation topLevelOp = (JavaModelOperation) this .getCurrentOperationStack().get(0); if (topLevelOp.attributes == null) { topLevelOp.attributes = new HashMap(); } topLevelOp.attributes.put(key, attribute); } /** * @see IProgressMonitor */ public void setCanceled(boolean b) { if (progressMonitor != null) { progressMonitor.setCanceled(b); } } /** * Sets whether this operation is nested or not. * * @see CreateElementInCUOperation#checkCanceled */ protected void setNested(boolean nested) { isNested = nested; } /** * @see IProgressMonitor */ public void setTaskName(String name) { if (progressMonitor != null) { progressMonitor.setTaskName(name); } } /** * @see IProgressMonitor */ 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 IJavaModelStatus */ protected IJavaModelStatus verify() { return commonVerify(); } /** * @see IProgressMonitor */ public void worked(int work) { if (progressMonitor != null) { progressMonitor.worked(work); checkCanceled(); } } }