/******************************************************************************* * 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(); } } }