/*******************************************************************************
* Copyright (c) 2000, 2009 QNX Software Systems 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
*
* Contributors:
* QNX Software Systems - Initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.internal.core.model;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.ElementChangedEvent;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICElementDelta;
import org.eclipse.cdt.core.model.ICModel;
import org.eclipse.cdt.core.model.ICModelStatus;
import org.eclipse.cdt.core.model.ICModelStatusConstants;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.ISourceReference;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.core.model.IWorkingCopy;
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 C Model operations
*/
public abstract class CModelOperation implements IWorkspaceRunnable, IProgressMonitor {
/**
* The elements this operation operates on,
* or <code>null</code> if this operation
* does not operate on specific elements.
*/
protected ICElement[] fElementsToProcess;
/**
* The parent elements this operation operates with
* or <code>null</code> if this operation
* does not operate with specific parent elements.
*/
protected ICElement[] parentElements;
/**
* An empty collection of <code>ICElement</code>s - the common
* empty result if no elements are created, or if this
* operation is not actually executed.
*/
protected static final ICElement[] fgEmptyResult= new ICElement[] {};
/**
* Collection of <code>ICElementDelta</code>s created by this operation.
* This collection starts out <code>null</code> and becomes an
* array of <code>ICElementDelta</code>s if the operation creates any
* deltas. This collection is registered with the C Model notification
* manager if the operation completes successfully.
*/
protected List<ICElementDelta> fDeltas= null;
/**
* The elements created by this operation - empty
* until the operation actually creates elements.
*/
protected ICElement[] fResultElements= fgEmptyResult;
/**
* The progress monitor passed into this operation
*/
protected IProgressMonitor fMonitor= null;
/**
* A flag indicating whether this operation is nested.
*/
protected boolean fNested = false;
/**
* Conflict resolution policy - by default do not force (fail on a conflict).
*/
protected boolean fForce= false;
/*
* Whether the operation has modified resources, and thus whether resource
* delta notification will happen.
*/
protected boolean hasModifiedResource = false;
/*
* A per thread stack of java model operations (PerThreadObject of ArrayList).
*/
protected final static ThreadLocal<ArrayList<CModelOperation>> operationStacks =
new ThreadLocal<ArrayList<CModelOperation>>();
protected CModelOperation() {
}
/**
* A common constructor for all C Model operations.
*/
protected CModelOperation(ICElement[] elements) {
fElementsToProcess = elements;
}
/**
* Common constructor for all C Model operations.
*/
protected CModelOperation(ICElement[] elementsToProcess, ICElement[] parentElements) {
fElementsToProcess = elementsToProcess;
this.parentElements= parentElements;
}
/**
* A common constructor for all C Model operations.
*/
protected CModelOperation(ICElement[] elementsToProcess, ICElement[] parentElements, boolean force) {
fElementsToProcess = elementsToProcess;
this.parentElements= parentElements;
fForce= force;
}
/**
* A common constructor for all C Model operations.
*/
protected CModelOperation(ICElement[] elements, boolean force) {
fElementsToProcess = elements;
fForce= force;
}
/**
* Common constructor for all C Model operations.
*/
protected CModelOperation(ICElement element) {
fElementsToProcess = new ICElement[]{element};
}
/**
* A common constructor for all C Model operations.
*/
protected CModelOperation(ICElement element, boolean force) {
fElementsToProcess = new ICElement[]{element};
fForce= force;
}
/**
* Adds the given delta to the collection of deltas
* that this operation has created. These deltas are
* automatically registered with the C Model Manager
* when the operation completes.
*/
protected void addDelta(ICElementDelta delta) {
if (fDeltas == null)
fDeltas = new LinkedList<ICElementDelta>();
fDeltas.add(delta);
}
/*
* Registers the given reconcile delta with the C Model Manager.
*/
protected void addReconcileDelta(IWorkingCopy workingCopy, ICElementDelta delta) {
HashMap<IWorkingCopy, ICElementDelta> reconcileDeltas = CModelManager.getDefault().reconcileDeltas;
CElementDelta previousDelta = (CElementDelta)reconcileDeltas.get(workingCopy);
if (previousDelta != null) {
ICElementDelta[] children = delta.getAffectedChildren();
for (ICElementDelta element : children) {
CElementDelta child = (CElementDelta)element;
previousDelta.insertDeltaTree(child.getElement(), child);
}
} else {
reconcileDeltas.put(workingCopy, delta);
}
}
/*
* Deregister the reconcile delta for the given working copy
*/
protected void removeReconcileDelta(IWorkingCopy workingCopy) {
CModelManager.getDefault().reconcileDeltas.remove(workingCopy);
}
/**
* @see IProgressMonitor
*/
public void beginTask(String name, int totalWork) {
if (fMonitor != null) {
fMonitor.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 canceling the operation has been requested
* @see IProgressMonitor#isCanceled
*/
protected void checkCanceled() {
if (isCanceled()) {
throw new OperationCanceledException(CoreModelMessages.getString("operation.canceled")); //$NON-NLS-1$
}
}
/**
* Common code used to verify the elements this operation is processing.
* @see CModelOperation#verify()
*/
protected ICModelStatus commonVerify() {
if (fElementsToProcess == null || fElementsToProcess.length == 0) {
return new CModelStatus(ICModelStatusConstants.NO_ELEMENTS_TO_PROCESS);
}
for (ICElement elementsToProces : fElementsToProcess) {
if (elementsToProces == null) {
return new CModelStatus(ICModelStatusConstants.NO_ELEMENTS_TO_PROCESS);
}
}
return CModelStatus.VERIFIED_OK;
}
/**
* Returns the translation unit the given element is contained in,
* or the element itself (if it is a compilation unit),
* otherwise <code>null</code>.
*/
protected ITranslationUnit getTranslationUnitFor(ICElement element) {
if (element instanceof ITranslationUnit) {
return (ITranslationUnit)element;
} else if (element instanceof ISourceReference) {
ISourceReference ref = (ISourceReference)element;
return ref.getTranslationUnit();
}
return null;
}
/**
* Convenience method to copy resources
*/
protected void copyResources(IResource[] resources, IPath destinationPath) throws CModelException {
IProgressMonitor subProgressMonitor = getSubProgressMonitor(resources.length);
IWorkspace workspace = resources[0].getWorkspace();
try {
workspace.copy(resources, destinationPath, false, subProgressMonitor);
this.hasModifiedResource = true;
} catch (CoreException e) {
throw new CModelException(e);
}
}
/**
* Convenience method to create a file
*/
protected void createFile(IContainer folder, String name, InputStream contents, boolean force) throws CModelException {
IFile file= folder.getFile(new Path(name));
try {
file.create(contents, force, getSubProgressMonitor(1));
this.hasModifiedResource = true;
} catch (CoreException e) {
throw new CModelException(e);
}
}
/**
* Convenience method to create a folder
*/
protected void createFolder(IContainer parentFolder, String name, boolean force) throws CModelException {
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, true, getSubProgressMonitor(1));
this.hasModifiedResource = true;
} catch (CoreException e) {
throw new CModelException(e);
}
}
/**
* Convenience method to delete a resource
*/
protected void deleteResource(IResource resource, boolean force) throws CModelException {
try {
resource.delete(force, getSubProgressMonitor(1));
this.hasModifiedResource = true;
} catch (CoreException e) {
throw new CModelException(e);
}
}
/**
* Convenience method to delete resources
*/
protected void deleteResources(IResource[] resources, boolean force) throws CModelException {
if (resources == null || resources.length == 0) return;
IProgressMonitor subProgressMonitor = getSubProgressMonitor(resources.length);
IWorkspace workspace = resources[0].getWorkspace();
try {
workspace.delete(resources, force, subProgressMonitor);
this.hasModifiedResource = true;
} catch (CoreException e) {
throw new CModelException(e);
}
}
/**
* @see IProgressMonitor
*/
public void done() {
if (fMonitor != null) {
fMonitor.done();
}
}
/**
* Verifies the operation can proceed and executes the operation.
* Subclasses should override <code>#verify</code> and
* <code>executeOperation</code> to implement the specific operation behavior.
*
* @exception CModelException The operation has failed.
*/
protected void execute() throws CModelException {
ICModelStatus status= verify();
if (status.isOK()) {
executeOperation();
} else {
throw new CModelException(status);
}
}
/**
* Convenience method to run an operation within this operation
*/
public void executeNestedOperation(CModelOperation operation, int subWorkAmount) throws CModelException {
IProgressMonitor subProgressMonitor = getSubProgressMonitor(subWorkAmount);
// fix for 1FW7IKC, part (1)
try {
operation.setNested(true);
operation.run(subProgressMonitor);
if (operation.hasModifiedResource()) {
this.hasModifiedResource = true;
}
//accumulate the nested operation deltas
if (operation.fDeltas != null) {
for (ICElementDelta delta : operation.fDeltas) {
addDelta(delta);
}
}
} catch (CoreException ce) {
if (ce instanceof CModelException) {
throw (CModelException)ce;
}
// translate the core exception to a c model exception
if (ce.getStatus().getCode() == IResourceStatus.OPERATION_FAILED) {
Throwable e = ce.getStatus().getException();
if (e instanceof CModelException) {
throw (CModelException) e;
}
}
throw new CModelException(ce);
}
}
/**
* Performs the operation specific behavior. Subclasses must override.
*/
protected abstract void executeOperation() throws CModelException;
/**
* Returns the elements to which this operation applies,
* or <code>null</code> if not applicable.
*/
protected ICElement[] getElementsToProcess() {
return fElementsToProcess;
}
/**
* Returns the element to which this operation applies,
* or <code>null</code> if not applicable.
*/
protected ICElement getElementToProcess() {
if (fElementsToProcess == null || fElementsToProcess.length == 0) {
return null;
}
return fElementsToProcess[0];
}
/**
* Returns the C Model this operation is operating in.
*/
public ICModel getCModel() {
if (fElementsToProcess == null || fElementsToProcess.length == 0) {
return getParentElement().getCModel();
}
return fElementsToProcess[0].getCModel();
}
/**
* Returns the parent element to which this operation applies,
* or <code>null</code> if not applicable.
*/
protected ICElement 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 ICElement[] getParentElements() {
return parentElements;
}
/**
* Returns the elements created by this operation.
*/
public ICElement[] getResultElements() {
return fResultElements;
}
/**
* Returns the scheduling rule for this operation (i.e. the resource that needs to be locked
* while this operation is running.
* Subclasses can override.
*/
public ISchedulingRule getSchedulingRule() {
return ResourcesPlugin.getWorkspace().getRoot();
}
/**
* Creates and returns a subprogress monitor if appropriate.
*/
protected IProgressMonitor getSubProgressMonitor(int workAmount) {
IProgressMonitor sub = null;
if (fMonitor != null) {
sub = new SubProgressMonitor(fMonitor, workAmount, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK);
}
return sub;
}
/**
* Returns the <code>IWorkspace</code> this operation is working in, or
* <code>null</code> if this operation has no elements to process.
*/
protected IWorkspace getWorkspace() {
if (fElementsToProcess != null && fElementsToProcess.length > 0) {
ICProject project = fElementsToProcess[0].getCProject();
if (project != null) {
return project.getCModel().getWorkspace();
}
}
return null;
}
/**
* 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.hasModifiedResource;
}
public void internalWorked(double work) {
if (fMonitor != null) {
fMonitor.internalWorked(work);
}
}
/**
* @see IProgressMonitor
*/
public boolean isCanceled() {
if (fMonitor != null) {
return fMonitor.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;
}
/**
* Convenience method to move resources
*/
protected void moveResources(IResource[] resources, IPath destinationPath) throws CModelException {
IProgressMonitor subProgressMonitor = null;
if (fMonitor != null) {
subProgressMonitor = new SubProgressMonitor(fMonitor, resources.length, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK);
}
IWorkspace workspace = resources[0].getWorkspace();
try {
workspace.move(resources, destinationPath, false, subProgressMonitor);
this.hasModifiedResource = true;
} catch (CoreException e) {
throw new CModelException(e);
}
}
/**
* Creates and returns a new <code>ICElementDelta</code>
* on the C Model.
*/
public CElementDelta newCElementDelta() {
return new CElementDelta(getCModel());
}
/**
* Registers any deltas this operation created, with the
* C Model manager.
*/
protected void registerDeltas() {
if (fDeltas != null && !fNested) {
// hook to ensure working copies remain consistent
//makeWorkingCopiesConsistent(fDeltas);
CModelManager manager= CModelManager.getDefault();
for (ICElementDelta delta : fDeltas) {
manager.registerCModelDelta(delta);
}
}
}
/*
* 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<CModelOperation> getCurrentOperationStack() {
ArrayList<CModelOperation> stack = operationStacks.get();
if (stack == null) {
stack = new ArrayList<CModelOperation>();
operationStacks.set(stack);
}
return stack;
}
/*
* Removes the last pushed operation from the stack of running operations.
* Returns the poped operation or null if the stack was empty.
*/
protected CModelOperation popOperation() {
ArrayList<CModelOperation> 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 stack.remove(size-1);
} else {
return null;
}
}
/*
* Pushes the given operation on the stack of operations currently running in this thread.
*/
protected void pushOperation(CModelOperation operation) {
getCurrentOperationStack().add(operation);
}
/*
* Returns whether this operation is the first operation to run in the current thread.
*/
protected boolean isTopLevelOperation() {
ArrayList<CModelOperation> stack;
return (stack = this.getCurrentOperationStack()).size() > 0 && stack.get(0) == this;
}
/**
* Main entry point for C 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 {
CModelManager manager= CModelManager.getDefault();
int previousDeltaCount = manager.fCModelDeltas.size();
pushOperation(this);
try {
fMonitor = monitor;
execute();
} finally {
try {
registerDeltas();
// Fire if we change something
if (isTopLevelOperation()) {
if ((manager.fCModelDeltas.size() > previousDeltaCount || !manager.reconcileDeltas.isEmpty())
&& !this.hasModifiedResource()) {
manager.fire(ElementChangedEvent.POST_CHANGE);
}
}
} finally {
popOperation();
}
}
}
/**
* Main entry point for C Model operations. Runs a C Model Operation as an IWorkspaceRunnable
* if not read-only.
*/
public void runOperation(IProgressMonitor monitor) throws CModelException {
ICModelStatus status = verify();
if (!status.isOK()) {
throw new CModelException(status);
}
try {
if (isReadOnly()) {
run(monitor);
} else {
// use IWorkspace.run(...) to ensure that a build will be done in autobuild mode
getCModel().getUnderlyingResource().getWorkspace().run(
this, getSchedulingRule(), IWorkspace.AVOID_UPDATE, monitor);
}
} catch (CoreException ce) {
if (ce instanceof CModelException) {
throw (CModelException) ce;
} else if (ce.getStatus().getCode() == IResourceStatus.OPERATION_FAILED) {
Throwable e = ce.getStatus().getException();
if (e instanceof CModelException) {
throw (CModelException) e;
}
}
throw new CModelException(ce);
}
}
/**
* @see IProgressMonitor
*/
public void setCanceled(boolean b) {
if (fMonitor != null) {
fMonitor.setCanceled(b);
}
}
/**
* Sets whether this operation is nested or not.
* @see CreateElementInTUOperation#checkCanceled
*/
protected void setNested(boolean nested) {
fNested = nested;
}
/**
* @see IProgressMonitor
*/
public void setTaskName(String name) {
if (fMonitor != null) {
fMonitor.setTaskName(name);
}
}
/**
* @see IProgressMonitor
*/
public void subTask(String name) {
if (fMonitor != null) {
fMonitor.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 ICModelStatus
*/
protected ICModelStatus verify() {
return commonVerify();
}
/**
* @see IProgressMonitor
*/
public void worked(int work) {
if (fMonitor != null) {
fMonitor.worked(work);
checkCanceled();
}
}
}