1 /*******************************************************************************
2 * Copyright (c) 2000, 2003 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package net.sourceforge.phpdt.internal.core;
13 import java.io.InputStream;
14 import java.util.ArrayList;
15 import java.util.HashMap;
17 import net.sourceforge.phpdt.core.ICompilationUnit;
18 import net.sourceforge.phpdt.core.IJavaElement;
19 import net.sourceforge.phpdt.core.IJavaElementDelta;
20 import net.sourceforge.phpdt.core.IJavaModel;
21 import net.sourceforge.phpdt.core.IJavaModelStatus;
22 import net.sourceforge.phpdt.core.IJavaModelStatusConstants;
23 import net.sourceforge.phpdt.core.IPackageFragment;
24 import net.sourceforge.phpdt.core.IWorkingCopy;
25 import net.sourceforge.phpdt.core.JavaModelException;
26 import net.sourceforge.phpdt.internal.core.util.PerThreadObject;
28 import org.eclipse.core.resources.IContainer;
29 import org.eclipse.core.resources.IFile;
30 import org.eclipse.core.resources.IFolder;
31 import org.eclipse.core.resources.IResource;
32 import org.eclipse.core.resources.IResourceStatus;
33 import org.eclipse.core.resources.IWorkspace;
34 import org.eclipse.core.resources.IWorkspaceRunnable;
35 import org.eclipse.core.runtime.CoreException;
36 import org.eclipse.core.runtime.IPath;
37 import org.eclipse.core.runtime.IProgressMonitor;
38 import org.eclipse.core.runtime.OperationCanceledException;
39 import org.eclipse.core.runtime.Path;
40 import org.eclipse.core.runtime.SubProgressMonitor;
44 * Defines behavior common to all Java Model operations
46 public abstract class JavaModelOperation implements IWorkspaceRunnable, IProgressMonitor {
47 protected interface IPostAction {
49 * Returns the id of this action.
50 * @see JavaModelOperation#postAction
56 void run() throws JavaModelException;
59 * Constants controlling the insertion mode of an action.
60 * @see JavaModelOperation#postAction
62 protected static final int APPEND = 1; // insert at the end
63 protected static final int REMOVEALL_APPEND = 2; // remove all existing ones with same ID, and add new one at the end
64 protected static final int KEEP_EXISTING = 3; // do not insert if already existing with same ID
67 * Whether tracing post actions is enabled.
69 protected static boolean POST_ACTION_VERBOSE;
72 * A list of IPostActions.
74 protected IPostAction[] actions;
75 protected int actionsStart = 0;
76 protected int actionsEnd = -1;
78 * A HashMap of attributes that can be used by operations
80 protected HashMap attributes;
82 public static final String HAS_MODIFIED_RESOURCE_ATTR = "hasModifiedResource"; //$NON-NLS-1$
83 public static final String TRUE = "true"; //$NON-NLS-1$
84 //public static final String FALSE = "false"; //$NON-NLS-1$
87 * The elements this operation operates on,
88 * or <code>null</code> if this operation
89 * does not operate on specific elements.
91 protected IJavaElement[] fElementsToProcess;
93 * The parent elements this operation operates with
94 * or <code>null</code> if this operation
95 * does not operate with specific parent elements.
97 protected IJavaElement[] fParentElements;
99 * An empty collection of <code>IJavaElement</code>s - the common
100 * empty result if no elements are created, or if this
101 * operation is not actually executed.
103 protected static IJavaElement[] fgEmptyResult= new IJavaElement[] {};
107 * The elements created by this operation - empty
108 * until the operation actually creates elements.
110 protected IJavaElement[] fResultElements= fgEmptyResult;
113 * The progress monitor passed into this operation
115 protected IProgressMonitor fMonitor= null;
117 * A flag indicating whether this operation is nested.
119 protected boolean fNested = false;
121 * Conflict resolution policy - by default do not force (fail on a conflict).
123 protected boolean fForce= false;
126 * A per thread stack of java model operations (PerThreadObject of ArrayList).
128 protected static PerThreadObject operationStacks = new PerThreadObject();
129 protected JavaModelOperation() {
132 * A common constructor for all Java Model operations.
134 protected JavaModelOperation(IJavaElement[] elements) {
135 fElementsToProcess = elements;
138 * Common constructor for all Java Model operations.
140 protected JavaModelOperation(IJavaElement[] elementsToProcess, IJavaElement[] parentElements) {
141 fElementsToProcess = elementsToProcess;
142 fParentElements= parentElements;
145 * A common constructor for all Java Model operations.
147 protected JavaModelOperation(IJavaElement[] elementsToProcess, IJavaElement[] parentElements, boolean force) {
148 fElementsToProcess = elementsToProcess;
149 fParentElements= parentElements;
153 * A common constructor for all Java Model operations.
155 protected JavaModelOperation(IJavaElement[] elements, boolean force) {
156 fElementsToProcess = elements;
161 * Common constructor for all Java Model operations.
163 protected JavaModelOperation(IJavaElement element) {
164 fElementsToProcess = new IJavaElement[]{element};
167 * A common constructor for all Java Model operations.
169 protected JavaModelOperation(IJavaElement element, boolean force) {
170 fElementsToProcess = new IJavaElement[]{element};
175 * Registers the given action at the end of the list of actions to run.
177 protected void addAction(IPostAction action) {
178 int length = this.actions.length;
179 if (length == ++this.actionsEnd) {
180 System.arraycopy(this.actions, 0, this.actions = new IPostAction[length*2], 0, length);
182 this.actions[this.actionsEnd] = action;
185 * Registers the given delta with the Java Model Manager.
187 protected void addDelta(IJavaElementDelta delta) {
188 JavaModelManager.getJavaModelManager().registerJavaModelDelta(delta);
191 * Registers the given reconcile delta with the Java Model Manager.
193 protected void addReconcileDelta(IWorkingCopy workingCopy, IJavaElementDelta delta) {
194 HashMap reconcileDeltas = JavaModelManager.getJavaModelManager().reconcileDeltas;
195 JavaElementDelta previousDelta = (JavaElementDelta)reconcileDeltas.get(workingCopy);
196 if (previousDelta != null) {
197 IJavaElementDelta[] children = delta.getAffectedChildren();
198 for (int i = 0, length = children.length; i < length; i++) {
199 JavaElementDelta child = (JavaElementDelta)children[i];
200 previousDelta.insertDeltaTree(child.getElement(), child);
203 reconcileDeltas.put(workingCopy, delta);
207 * Deregister the reconcile delta for the given working copy
209 protected void removeReconcileDelta(IWorkingCopy workingCopy) {
210 JavaModelManager.getJavaModelManager().reconcileDeltas.remove(workingCopy);
213 * @see IProgressMonitor
215 public void beginTask(String name, int totalWork) {
216 if (fMonitor != null) {
217 fMonitor.beginTask(name, totalWork);
221 * Checks with the progress monitor to see whether this operation
222 * should be canceled. An operation should regularly call this method
223 * during its operation so that the user can cancel it.
225 * @exception OperationCanceledException if cancelling the operation has been requested
226 * @see IProgressMonitor#isCanceled
228 protected void checkCanceled() {
230 throw new OperationCanceledException(Util.bind("operation.cancelled")); //$NON-NLS-1$
234 * Common code used to verify the elements this operation is processing.
235 * @see JavaModelOperation#verify()
237 protected IJavaModelStatus commonVerify() {
238 if (fElementsToProcess == null || fElementsToProcess.length == 0) {
239 return new JavaModelStatus(IJavaModelStatusConstants.NO_ELEMENTS_TO_PROCESS);
241 for (int i = 0; i < fElementsToProcess.length; i++) {
242 if (fElementsToProcess[i] == null) {
243 return new JavaModelStatus(IJavaModelStatusConstants.NO_ELEMENTS_TO_PROCESS);
246 return JavaModelStatus.VERIFIED_OK;
249 * Convenience method to copy resources
251 protected void copyResources(IResource[] resources, IPath destinationPath) throws JavaModelException {
252 IProgressMonitor subProgressMonitor = getSubProgressMonitor(resources.length);
253 IWorkspace workspace = resources[0].getWorkspace();
255 workspace.copy(resources, destinationPath, false, subProgressMonitor);
256 this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE);
257 } catch (CoreException e) {
258 throw new JavaModelException(e);
262 * Convenience method to create a file
264 protected void createFile(IContainer folder, String name, InputStream contents, boolean force) throws JavaModelException {
265 IFile file= folder.getFile(new Path(name));
269 force ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY,
270 getSubProgressMonitor(1));
271 this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE);
272 } catch (CoreException e) {
273 throw new JavaModelException(e);
277 * Convenience method to create a folder
279 protected void createFolder(IContainer parentFolder, String name, boolean force) throws JavaModelException {
280 IFolder folder= parentFolder.getFolder(new Path(name));
282 // we should use true to create the file locally. Only VCM should use tru/false
284 force ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY,
286 getSubProgressMonitor(1));
287 this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE);
288 } catch (CoreException e) {
289 throw new JavaModelException(e);
293 * Convenience method to delete an empty package fragment
295 protected void deleteEmptyPackageFragment(
296 IPackageFragment fragment,
298 IResource rootResource)
299 throws JavaModelException {
301 IContainer resource = (IContainer) fragment.getResource();
305 force ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY,
306 getSubProgressMonitor(1));
307 this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE);
308 while (resource instanceof IFolder) {
309 // deleting a package: delete the parent if it is empty (eg. deleting x.y where folder x doesn't have resources but y)
310 // without deleting the package fragment root
311 resource = resource.getParent();
312 if (!resource.equals(rootResource) && resource.members().length == 0) {
314 force ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY,
315 getSubProgressMonitor(1));
316 this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE);
319 } catch (CoreException e) {
320 throw new JavaModelException(e);
324 * Convenience method to delete a resource
326 protected void deleteResource(IResource resource,int flags) throws JavaModelException {
328 resource.delete(flags, getSubProgressMonitor(1));
329 this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE);
330 } catch (CoreException e) {
331 throw new JavaModelException(e);
335 * Convenience method to delete resources
337 protected void deleteResources(IResource[] resources, boolean force) throws JavaModelException {
338 if (resources == null || resources.length == 0) return;
339 IProgressMonitor subProgressMonitor = getSubProgressMonitor(resources.length);
340 IWorkspace workspace = resources[0].getWorkspace();
344 force ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY,
346 this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE);
347 } catch (CoreException e) {
348 throw new JavaModelException(e);
352 * @see IProgressMonitor
355 if (fMonitor != null) {
360 * Returns whether the given path is equals to one of the given other paths.
362 protected boolean equalsOneOf(IPath path, IPath[] otherPaths) {
363 for (int i = 0, length = otherPaths.length; i < length; i++) {
364 if (path.equals(otherPaths[i])) {
371 * Verifies the operation can proceed and executes the operation.
372 * Subclasses should override <code>#verify</code> and
373 * <code>executeOperation</code> to implement the specific operation behavior.
375 * @exception JavaModelException The operation has failed.
377 protected void execute() throws JavaModelException {
378 IJavaModelStatus status= verify();
380 // if first time here, computes the root infos before executing the operation
381 DeltaProcessor deltaProcessor = JavaModelManager.getJavaModelManager().deltaProcessor;
382 if (deltaProcessor.roots == null) {
383 // TODO khartlage temp-del
384 // deltaProcessor.initializeRoots();
389 throw new JavaModelException(status);
393 * Convenience method to run an operation within this operation
395 public void executeNestedOperation(JavaModelOperation operation, int subWorkAmount) throws JavaModelException {
396 IProgressMonitor subProgressMonitor = getSubProgressMonitor(subWorkAmount);
397 // fix for 1FW7IKC, part (1)
399 operation.setNested(true);
400 operation.run(subProgressMonitor);
401 } catch (CoreException ce) {
402 if (ce instanceof JavaModelException) {
403 throw (JavaModelException)ce;
405 // translate the core exception to a java model exception
406 if (ce.getStatus().getCode() == IResourceStatus.OPERATION_FAILED) {
407 Throwable e = ce.getStatus().getException();
408 if (e instanceof JavaModelException) {
409 throw (JavaModelException) e;
412 throw new JavaModelException(ce);
417 * Performs the operation specific behavior. Subclasses must override.
419 protected abstract void executeOperation() throws JavaModelException;
421 * Returns the attribute registered at the given key with the top level operation.
422 * Returns null if no such attribute is found.
424 protected Object getAttribute(Object key) {
425 ArrayList stack = this.getCurrentOperationStack();
426 if (stack.size() == 0) return null;
427 JavaModelOperation topLevelOp = (JavaModelOperation)stack.get(0);
428 if (topLevelOp.attributes == null) {
431 return topLevelOp.attributes.get(key);
435 * Returns the compilation unit the given element is contained in,
436 * or the element itself (if it is a compilation unit),
437 * otherwise <code>null</code>.
439 protected ICompilationUnit getCompilationUnitFor(IJavaElement element) {
441 return ((JavaElement)element).getCompilationUnit();
444 * Returns the stack of operations running in the current thread.
445 * Returns an empty stack if no operations are currently running in this thread.
447 protected ArrayList getCurrentOperationStack() {
448 ArrayList stack = (ArrayList)operationStacks.getCurrent();
450 stack = new ArrayList();
451 operationStacks.setCurrent(stack);
456 * Returns the elements to which this operation applies,
457 * or <code>null</code> if not applicable.
459 protected IJavaElement[] getElementsToProcess() {
460 return fElementsToProcess;
463 * Returns the element to which this operation applies,
464 * or <code>null</code> if not applicable.
466 protected IJavaElement getElementToProcess() {
467 if (fElementsToProcess == null || fElementsToProcess.length == 0) {
470 return fElementsToProcess[0];
473 * Returns the Java Model this operation is operating in.
475 public IJavaModel getJavaModel() {
476 if (fElementsToProcess == null || fElementsToProcess.length == 0) {
477 return getParentElement().getJavaModel();
479 return fElementsToProcess[0].getJavaModel();
482 // protected IPath[] getNestedFolders(IPackageFragmentRoot root) throws JavaModelException {
483 // IPath rootPath = root.getPath();
484 // IClasspathEntry[] classpath = root.getJavaProject().getRawClasspath();
485 // int length = classpath.length;
486 // IPath[] result = new IPath[length];
488 // for (int i = 0; i < length; i++) {
489 // IPath path = classpath[i].getPath();
490 // if (rootPath.isPrefixOf(path) && !rootPath.equals(path)) {
491 // result[index++] = path;
494 // if (index < length) {
495 // System.arraycopy(result, 0, result = new IPath[index], 0, index);
500 * Returns the parent element to which this operation applies,
501 * or <code>null</code> if not applicable.
503 protected IJavaElement getParentElement() {
504 if (fParentElements == null || fParentElements.length == 0) {
507 return fParentElements[0];
510 * Returns the parent elements to which this operation applies,
511 * or <code>null</code> if not applicable.
513 protected IJavaElement[] getParentElements() {
514 return fParentElements;
517 * Returns the elements created by this operation.
519 public IJavaElement[] getResultElements() {
520 return fResultElements;
523 * Creates and returns a subprogress monitor if appropriate.
525 protected IProgressMonitor getSubProgressMonitor(int workAmount) {
526 IProgressMonitor sub = null;
527 if (fMonitor != null) {
528 sub = new SubProgressMonitor(fMonitor, workAmount, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK);
534 * Returns whether this operation has performed any resource modifications.
535 * Returns false if this operation has not been executed yet.
537 public boolean hasModifiedResource() {
538 return !this.isReadOnly() && this.getAttribute(HAS_MODIFIED_RESOURCE_ATTR) == TRUE;
540 public void internalWorked(double work) {
541 if (fMonitor != null) {
542 fMonitor.internalWorked(work);
546 * @see IProgressMonitor
548 public boolean isCanceled() {
549 if (fMonitor != null) {
550 return fMonitor.isCanceled();
555 * Returns <code>true</code> if this operation performs no resource modifications,
556 * otherwise <code>false</code>. Subclasses must override.
558 public boolean isReadOnly() {
562 * Returns whether this operation is the first operation to run in the current thread.
564 protected boolean isTopLevelOperation() {
567 (stack = this.getCurrentOperationStack()).size() > 0
568 && stack.get(0) == this;
571 * Returns the index of the first registered action with the given id, starting from a given position.
572 * Returns -1 if not found.
574 protected int firstActionWithID(String id, int start) {
575 for (int i = start; i <= this.actionsEnd; i++) {
576 if (this.actions[i].getID().equals(id)) {
584 * Convenience method to move resources
586 protected void moveResources(IResource[] resources, IPath destinationPath) throws JavaModelException {
587 IProgressMonitor subProgressMonitor = null;
588 if (fMonitor != null) {
589 subProgressMonitor = new SubProgressMonitor(fMonitor, resources.length, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK);
591 IWorkspace workspace = resources[0].getWorkspace();
593 workspace.move(resources, destinationPath, false, subProgressMonitor);
594 this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE);
595 } catch (CoreException e) {
596 throw new JavaModelException(e);
600 * Creates and returns a new <code>IJavaElementDelta</code>
603 public JavaElementDelta newJavaElementDelta() {
604 return new JavaElementDelta(getJavaModel());
607 * Removes the last pushed operation from the stack of running operations.
608 * Returns the poped operation or null if the stack was empty.
610 protected JavaModelOperation popOperation() {
611 ArrayList stack = getCurrentOperationStack();
612 int size = stack.size();
614 if (size == 1) { // top level operation
615 operationStacks.setCurrent(null); // release reference (see http://bugs.eclipse.org/bugs/show_bug.cgi?id=33927)
617 return (JavaModelOperation)stack.remove(size-1);
623 * Registers the given action to be run when the outer most java model operation has finished.
624 * The insertion mode controls whether:
625 * - the action should discard all existing actions with the same id, and be queued at the end (REMOVEALL_APPEND),
626 * - the action should be ignored if there is already an action with the same id (KEEP_EXISTING),
627 * - the action should be queued at the end without looking at existing actions (APPEND)
629 protected void postAction(IPostAction action, int insertionMode) {
630 if (POST_ACTION_VERBOSE) {
631 System.out.print("(" + Thread.currentThread() + ") [JavaModelOperation.postAction(IPostAction, int)] Posting action " + action.getID()); //$NON-NLS-1$ //$NON-NLS-2$
632 switch(insertionMode) {
633 case REMOVEALL_APPEND:
634 System.out.println(" (REMOVEALL_APPEND)"); //$NON-NLS-1$
637 System.out.println(" (KEEP_EXISTING)"); //$NON-NLS-1$
640 System.out.println(" (APPEND)"); //$NON-NLS-1$
645 JavaModelOperation topLevelOp = (JavaModelOperation)getCurrentOperationStack().get(0);
646 IPostAction[] postActions = topLevelOp.actions;
647 if (postActions == null) {
648 topLevelOp.actions = postActions = new IPostAction[1];
649 postActions[0] = action;
650 topLevelOp.actionsEnd = 0;
652 String id = action.getID();
653 switch (insertionMode) {
654 case REMOVEALL_APPEND :
655 int index = this.actionsStart-1;
656 while ((index = topLevelOp.firstActionWithID(id, index+1)) >= 0) {
657 // remove action[index]
658 System.arraycopy(postActions, index+1, postActions, index, topLevelOp.actionsEnd - index);
659 postActions[topLevelOp.actionsEnd--] = null;
661 topLevelOp.addAction(action);
664 if (topLevelOp.firstActionWithID(id, 0) < 0) {
665 topLevelOp.addAction(action);
669 topLevelOp.addAction(action);
675 * Returns whether the given path is the prefix of one of the given other paths.
677 protected boolean prefixesOneOf(IPath path, IPath[] otherPaths) {
678 for (int i = 0, length = otherPaths.length; i < length; i++) {
679 if (path.isPrefixOf(otherPaths[i])) {
686 * Pushes the given operation on the stack of operations currently running in this thread.
688 protected void pushOperation(JavaModelOperation operation) {
689 getCurrentOperationStack().add(operation);
693 * Main entry point for Java Model operations. Executes this operation
694 * and registers any deltas created.
696 * @see IWorkspaceRunnable
697 * @exception CoreException if the operation fails
699 public void run(IProgressMonitor monitor) throws CoreException {
700 JavaModelManager manager = JavaModelManager.getJavaModelManager();
701 int previousDeltaCount = manager.javaModelDeltas.size();
708 if (this.isTopLevelOperation()) {
709 this.runPostActions();
713 // TODO khartlage temp-del
715 // // update JavaModel using deltas that were recorded during this operation
716 // for (int i = previousDeltaCount, size = manager.javaModelDeltas.size(); i < size; i++) {
717 // manager.updateJavaModel((IJavaElementDelta)manager.javaModelDeltas.get(i));
721 // // - the operation is a top level operation
722 // // - the operation did produce some delta(s)
723 // // - but the operation has not modified any resource
724 // if (this.isTopLevelOperation()) {
725 // if ((manager.javaModelDeltas.size() > previousDeltaCount || !manager.reconcileDeltas.isEmpty())
726 // && !this.hasModifiedResource()) {
727 // manager.fire(null, JavaModelManager.DEFAULT_CHANGE_EVENT);
728 // } // else deltas are fired while processing the resource delta
735 protected void runPostActions() throws JavaModelException {
736 while (this.actionsStart <= this.actionsEnd) {
737 IPostAction postAction = this.actions[this.actionsStart++];
738 if (POST_ACTION_VERBOSE) {
739 System.out.println("(" + Thread.currentThread() + ") [JavaModelOperation.runPostActions()] Running action " + postAction.getID()); //$NON-NLS-1$ //$NON-NLS-2$
745 * Registers the given attribute at the given key with the top level operation.
747 protected void setAttribute(Object key, Object attribute) {
748 JavaModelOperation topLevelOp = (JavaModelOperation)this.getCurrentOperationStack().get(0);
749 if (topLevelOp.attributes == null) {
750 topLevelOp.attributes = new HashMap();
752 topLevelOp.attributes.put(key, attribute);
755 * @see IProgressMonitor
757 public void setCanceled(boolean b) {
758 if (fMonitor != null) {
759 fMonitor.setCanceled(b);
763 * Sets whether this operation is nested or not.
764 * @see CreateElementInCUOperation#checkCanceled
766 protected void setNested(boolean nested) {
770 * @see IProgressMonitor
772 public void setTaskName(String name) {
773 if (fMonitor != null) {
774 fMonitor.setTaskName(name);
778 * @see IProgressMonitor
780 public void subTask(String name) {
781 if (fMonitor != null) {
782 fMonitor.subTask(name);
786 * Returns a status indicating if there is any known reason
787 * this operation will fail. Operations are verified before they
790 * Subclasses must override if they have any conditions to verify
791 * before this operation executes.
793 * @see IJavaModelStatus
795 protected IJavaModelStatus verify() {
796 return commonVerify();
800 * @see IProgressMonitor
802 public void worked(int work) {
803 if (fMonitor != null) {
804 fMonitor.worked(work);