new version with WorkingCopy Management
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / core / MultiOperation.java
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
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package net.sourceforge.phpdt.internal.core;
12
13 import java.util.HashMap;
14 import java.util.Map;
15
16 import net.sourceforge.phpdt.core.ICompilationUnit;
17 import net.sourceforge.phpdt.core.IJavaElement;
18 import net.sourceforge.phpdt.core.IJavaModelStatus;
19 import net.sourceforge.phpdt.core.IJavaModelStatusConstants;
20 import net.sourceforge.phpdt.core.IPackageFragment;
21 import net.sourceforge.phpdt.core.JavaModelException;
22
23 import org.eclipse.core.runtime.IStatus;
24
25
26 /**
27  * This class is used to perform operations on multiple <code>IJavaElement</code>.
28  * It is responible for running each operation in turn, collecting
29  * the errors and merging the corresponding <code>JavaElementDelta</code>s.
30  * <p>
31  * If several errors occured, they are collected in a multi-status
32  * <code>JavaModelStatus</code>. Otherwise, a simple <code>JavaModelStatus</code>
33  * is thrown.
34  */
35 public abstract class MultiOperation extends JavaModelOperation {
36         /**
37          * The list of renamings supplied to the operation
38          */
39         protected String[] fRenamingsList= null;
40         /**
41          * Table specifying the new parent for elements being 
42          * copied/moved/renamed.
43          * Keyed by elements being processed, and
44          * values are the corresponding destination parent.
45          */
46         protected Map fParentElements;
47         /**
48          * Table specifying insertion positions for elements being 
49          * copied/moved/renamed. Keyed by elements being processed, and
50          * values are the corresponding insertion point.
51          * @see processElements(IProgressMonitor)
52          */
53         protected Map fInsertBeforeElements= new HashMap(1);
54         /**
55          * This table presents the data in <code>fRenamingList</code> in a more
56          * convenient way.
57          */
58         protected Map fRenamings;
59         /**
60          * Creates a new <code>MultiOperation</code>.
61          */
62         protected MultiOperation(IJavaElement[] elementsToProcess, IJavaElement[] parentElements, boolean force) {
63                 super(elementsToProcess, parentElements, force);
64                 fParentElements = new HashMap(elementsToProcess.length);
65                 if (elementsToProcess.length == parentElements.length) {
66                         for (int i = 0; i < elementsToProcess.length; i++) {
67                                 fParentElements.put(elementsToProcess[i], parentElements[i]);
68                         }
69                 } else { //same destination for all elements to be moved/copied/renamed
70                         for (int i = 0; i < elementsToProcess.length; i++) {
71                                 fParentElements.put(elementsToProcess[i], parentElements[0]);
72                         }
73                 }
74         
75         }
76         /**
77          * Creates a new <code>MultiOperation</code> on <code>elementsToProcess</code>.
78          */
79         protected MultiOperation(IJavaElement[] elementsToProcess, boolean force) {
80                 super(elementsToProcess, force);
81         }
82         /**
83          * Convenience method to create a <code>JavaModelException</code>
84          * embending a <code>JavaModelStatus</code>.
85          */
86         protected void error(int code, IJavaElement element) throws JavaModelException {
87                 throw new JavaModelException(new JavaModelStatus(code, element));
88         }
89         /**
90          * Executes the operation.
91          *
92          * @exception JavaModelException if one or several errors occured during the operation.
93          * If multiple errors occured, the corresponding <code>JavaModelStatus</code> is a
94          * multi-status. Otherwise, it is a simple one.
95          */
96         protected void executeOperation() throws JavaModelException {
97                 processElements();
98         }
99         /**
100          * Returns the parent of the element being copied/moved/renamed.
101          */
102         protected IJavaElement getDestinationParent(IJavaElement child) {
103                 return (IJavaElement)fParentElements.get(child);
104         }
105         /**
106          * Returns the name to be used by the progress monitor.
107          */
108         protected abstract String getMainTaskName();
109         /**
110          * Returns the new name for <code>element</code>, or <code>null</code>
111          * if there are no renamings specified.
112          */
113         protected String getNewNameFor(IJavaElement element) {
114                 if (fRenamings != null)
115                         return (String) fRenamings.get(element);
116                 else
117                         return null;
118         }
119         /**
120          * Sets up the renamings hashtable - keys are the elements and
121          * values are the new name.
122          */
123         private void initializeRenamings() {
124                 if (fRenamingsList != null && fRenamingsList.length == fElementsToProcess.length) {
125                         fRenamings = new HashMap(fRenamingsList.length);
126                         for (int i = 0; i < fRenamingsList.length; i++) {
127                                 if (fRenamingsList[i] != null) {
128                                         fRenamings.put(fElementsToProcess[i], fRenamingsList[i]);
129                                 }
130                         }
131                 }
132         }
133         /**
134          * Returns <code>true</code> if this operation represents a move or rename, <code>false</code>
135          * if this operation represents a copy.<br>
136          * Note: a rename is just a move within the same parent with a name change.
137          */
138         protected boolean isMove() {
139                 return false;
140         }
141         /**
142          * Returns <code>true</code> if this operation represents a rename, <code>false</code>
143          * if this operation represents a copy or move.
144          */
145         protected boolean isRename() {
146                 return false;
147         }
148         
149         /**
150          * Subclasses must implement this method to process a given <code>IJavaElement</code>.
151          */
152         protected abstract void processElement(IJavaElement element) throws JavaModelException;
153         /**
154          * Processes all the <code>IJavaElement</code>s in turn, collecting errors
155          * and updating the progress monitor.
156          *
157          * @exception JavaModelException if one or several operation(s) was unable to
158          * be completed.
159          */
160         protected void processElements() throws JavaModelException {
161                 beginTask(getMainTaskName(), fElementsToProcess.length);
162                 IJavaModelStatus[] errors = new IJavaModelStatus[3];
163                 int errorsCounter = 0;
164                 for (int i = 0; i < fElementsToProcess.length; i++) {
165                         try {
166                                 verify(fElementsToProcess[i]);
167                                 processElement(fElementsToProcess[i]);
168                         } catch (JavaModelException jme) {
169                                 if (errorsCounter == errors.length) {
170                                         // resize
171                                         System.arraycopy(errors, 0, (errors = new IJavaModelStatus[errorsCounter*2]), 0, errorsCounter);
172                                 }
173                                 errors[errorsCounter++] = jme.getJavaModelStatus();
174                         } finally {
175                                 worked(1);
176                         }
177                 }
178                 done();
179                 if (errorsCounter == 1) {
180                         throw new JavaModelException(errors[0]);
181                 } else if (errorsCounter > 1) {
182                         if (errorsCounter != errors.length) {
183                                 // resize
184                                 System.arraycopy(errors, 0, (errors = new IJavaModelStatus[errorsCounter]), 0, errorsCounter);
185                         }
186                         throw new JavaModelException(JavaModelStatus.newMultiStatus(errors));
187                 }
188         }
189         /**
190          * Sets the insertion position in the new container for the modified element. The element
191          * being modified will be inserted before the specified new sibling. The given sibling
192          * must be a child of the destination container specified for the modified element.
193          * The default is <code>null</code>, which indicates that the element is to be
194          * inserted at the end of the container.
195          */
196         public void setInsertBefore(IJavaElement modifiedElement, IJavaElement newSibling) {
197                 fInsertBeforeElements.put(modifiedElement, newSibling);
198         }
199         /**
200          * Sets the new names to use for each element being copied. The renamings
201          * correspond to the elements being processed, and the number of
202          * renamings must match the number of elements being processed.
203          * A <code>null</code> entry in the list indicates that an element
204          * is not to be renamed.
205          *
206          * <p>Note that some renamings may not be used.  If both a parent
207          * and a child have been selected for copy/move, only the parent
208          * is changed.  Therefore, if a new name is specified for the child,
209          * the child's name will not be changed.
210          */
211         public void setRenamings(String[] renamings) {
212                 fRenamingsList = renamings;
213                 initializeRenamings();
214         }
215         /**
216          * This method is called for each <code>IJavaElement</code> before
217          * <code>processElement</code>. It should check that this <code>element</code>
218          * can be processed.
219          */
220         protected abstract void verify(IJavaElement element) throws JavaModelException;
221         /**
222          * Verifies that the <code>destination</code> specified for the <code>element</code> is valid for the types of the
223          * <code>element</code> and <code>destination</code>.
224          */
225         protected void verifyDestination(IJavaElement element, IJavaElement destination) throws JavaModelException {
226                 if (destination == null || !destination.exists())
227                         error(IJavaModelStatusConstants.ELEMENT_DOES_NOT_EXIST, destination);
228                 
229                 int destType = destination.getElementType();
230                 switch (element.getElementType()) {
231                         case IJavaElement.PACKAGE_DECLARATION :
232                         case IJavaElement.IMPORT_DECLARATION :
233                                 if (destType != IJavaElement.COMPILATION_UNIT)
234                                         error(IJavaModelStatusConstants.INVALID_DESTINATION, element);
235                                 break;
236                         case IJavaElement.TYPE :
237                                 if (destType != IJavaElement.COMPILATION_UNIT && destType != IJavaElement.TYPE)
238                                         error(IJavaModelStatusConstants.INVALID_DESTINATION, element);
239                                 break;
240                         case IJavaElement.METHOD :
241                         case IJavaElement.FIELD :
242 //                      case IJavaElement.INITIALIZER :
243 //                              if (destType != IJavaElement.TYPE || destination instanceof BinaryType)
244 //                                      error(IJavaModelStatusConstants.INVALID_DESTINATION, element);
245 //                              break;
246                         case IJavaElement.COMPILATION_UNIT :
247                                 if (destType != IJavaElement.PACKAGE_FRAGMENT)
248                                         error(IJavaModelStatusConstants.INVALID_DESTINATION, element);
249                                 else if (isMove() && ((ICompilationUnit) element).isWorkingCopy())
250                                         error(IJavaModelStatusConstants.INVALID_ELEMENT_TYPES, element);
251                                 break;
252                         case IJavaElement.PACKAGE_FRAGMENT :
253                                 IPackageFragment fragment = (IPackageFragment) element;
254                                 IJavaElement parent = fragment.getParent();
255                                 if (parent.isReadOnly())
256                                         error(IJavaModelStatusConstants.READ_ONLY, element);
257                                 else if (destType != IJavaElement.PACKAGE_FRAGMENT_ROOT)
258                                         error(IJavaModelStatusConstants.INVALID_DESTINATION, element);
259                                 break;
260                         default :
261                                 error(IJavaModelStatusConstants.INVALID_ELEMENT_TYPES, element);
262                 }
263         }
264         /**
265          * Verify that the new name specified for <code>element</code> is
266          * valid for that type of Java element.
267          */
268         protected void verifyRenaming(IJavaElement element) throws JavaModelException {
269                 String newName = getNewNameFor(element);
270                 boolean isValid = true;
271         
272                 switch (element.getElementType()) {
273                         case IJavaElement.PACKAGE_FRAGMENT :
274                                 if (element.getElementName().equals(IPackageFragment.DEFAULT_PACKAGE_NAME)) {
275                                         // don't allow renaming of default package (see PR #1G47GUM)
276                                         throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.NAME_COLLISION, element));
277                                 }
278 //                              isValid = JavaConventions.validatePackageName(newName).getSeverity() != IStatus.ERROR;
279                                 isValid = true;
280                                 break;
281                         case IJavaElement.COMPILATION_UNIT :
282 //                              isValid = JavaConventions.validateCompilationUnitName(newName).getSeverity() != IStatus.ERROR;
283                         isValid = true;
284                                 break;
285                         case IJavaElement.INITIALIZER :
286                                 isValid = false; //cannot rename initializers
287                                 break;
288                         default :
289 //                              isValid = JavaConventions.validateIdentifier(newName).getSeverity() != IStatus.ERROR;
290                         isValid = true;
291                                 break;
292                 }
293         
294                 if (!isValid) {
295                         throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.INVALID_NAME, element, newName));
296                 }
297         }
298         /**
299          * Verifies that the positioning sibling specified for the <code>element</code> is exists and
300          * its parent is the destination container of this <code>element</code>.
301          */
302         protected void verifySibling(IJavaElement element, IJavaElement destination) throws JavaModelException {
303                 IJavaElement insertBeforeElement = (IJavaElement) fInsertBeforeElements.get(element);
304                 if (insertBeforeElement != null) {
305                         if (!insertBeforeElement.exists() || !insertBeforeElement.getParent().equals(destination)) {
306                                 error(IJavaModelStatusConstants.INVALID_SIBLING, insertBeforeElement);
307                         }
308                 }
309         }
310 }