new version with WorkingCopy Management
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / core / JavaElementDeltaBuilder.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.ArrayList;
14 import java.util.HashMap;
15 import java.util.Iterator;
16 import java.util.Map;
17
18 import net.sourceforge.phpdt.core.IJavaElement;
19 import net.sourceforge.phpdt.core.IJavaElementDelta;
20 import net.sourceforge.phpdt.core.IParent;
21 import net.sourceforge.phpdt.core.JavaModelException;
22 import net.sourceforge.phpdt.core.compiler.CharOperation;
23
24
25 /**
26  * A java element delta biulder creates a java element delta on
27  * a java element between the version of the java element
28  * at the time the comparator was created and the current version 
29  * of the java element.
30  *
31  * It performs this operation by locally caching the contents of 
32  * the java element when it is created. When the method
33  * createDeltas() is called, it creates a delta over the cached 
34  * contents and the new contents.
35  */
36 public class JavaElementDeltaBuilder {
37         /**
38          * The java element handle
39          */
40         IJavaElement javaElement;
41
42         /**
43          * The maximum depth in the java element children we should look into
44          */
45         int maxDepth = Integer.MAX_VALUE;
46
47         /**
48          * The old handle to info relationships
49          */
50         Map infos;
51
52         /**
53          * The old position info
54          */
55         Map oldPositions;
56
57         /**
58          * The new position info
59          */
60         Map newPositions;
61
62         /**
63          * Change delta
64          */
65         JavaElementDelta delta;
66
67         /**
68          * List of added elements
69          */
70         ArrayList added;
71
72         /**
73          * List of removed elements
74          */
75         ArrayList removed;
76         
77         /**
78          * Doubly linked list item
79          */
80         class ListItem {
81                 public IJavaElement previous;
82                 public IJavaElement next;
83
84                 public ListItem(IJavaElement previous, IJavaElement next) {
85                         this.previous = previous;
86                         this.next = next;
87                 }
88         }
89 /**
90  * Creates a java element comparator on a java element
91  * looking as deep as necessary.
92  */
93 public JavaElementDeltaBuilder(IJavaElement javaElement) {
94         this.javaElement = javaElement;
95         this.initialize();
96         this.recordElementInfo(
97                 javaElement, 
98                 (JavaModel)this.javaElement.getJavaModel(),
99                 0);
100 }
101 /**
102  * Creates a java element comparator on a java element
103  * looking only 'maxDepth' levels deep.
104  */
105 public JavaElementDeltaBuilder(IJavaElement javaElement, int maxDepth) {
106         this.javaElement = javaElement;
107         this.maxDepth = maxDepth;
108         this.initialize();
109         this.recordElementInfo(
110                 javaElement, 
111                 (JavaModel)this.javaElement.getJavaModel(),
112                 0);
113 }
114 /**
115  * Repairs the positioning information
116  * after an element has been added
117  */
118 private void added(IJavaElement element) {
119         this.added.add(element);
120         ListItem current = this.getNewPosition(element);
121         ListItem previous = null, next = null;
122         if (current.previous != null)
123                 previous = this.getNewPosition(current.previous);
124         if (current.next != null)
125                 next = this.getNewPosition(current.next);
126         if (previous != null)
127                 previous.next = current.next;
128         if (next != null)
129                 next.previous = current.previous;
130 }
131 /**
132  * Builds the java element deltas between the old content of the compilation
133  * unit and its new content.
134  */
135 public void buildDeltas() {
136         this.recordNewPositions(this.javaElement, 0);
137         this.findAdditions(this.javaElement, 0);
138         this.findDeletions();
139         this.findChangesInPositioning(this.javaElement, 0);
140         this.trimDelta(this.delta);
141         if (this.delta.getAffectedChildren().length == 0) {
142                 // this is a fine grained but not children affected -> mark as content changed
143                 this.delta.contentChanged();
144         }
145 }
146 /**
147  * Finds elements which have been added or changed.
148  */
149 private void findAdditions(IJavaElement newElement, int depth) {
150         JavaElementInfo oldInfo = this.getElementInfo(newElement);
151         if (oldInfo == null && depth < this.maxDepth) {
152                 this.delta.added(newElement);
153                 added(newElement);
154         } else {
155                 this.removeElementInfo(newElement);
156         }
157         
158         if (depth >= this.maxDepth) {
159                 // mark element as changed
160                 this.delta.changed(newElement, IJavaElementDelta.F_CONTENT);
161                 return;
162         }
163
164         JavaElementInfo newInfo = null;
165         try { 
166                 newInfo = (JavaElementInfo)((JavaElement)newElement).getElementInfo();
167         } catch (JavaModelException npe) {
168                 return;
169         }
170         
171         this.findContentChange(oldInfo, newInfo, newElement);
172                 
173         if (oldInfo != null && newElement instanceof IParent) {
174
175                 IJavaElement[] children = newInfo.getChildren();
176                 if (children != null) {
177                         int length = children.length;
178                         for(int i = 0; i < length; i++) {
179                                 this.findAdditions(children[i], depth + 1);
180                         }
181                 }               
182         }
183 }
184 /**
185  * Looks for changed positioning of elements.
186  */
187 private void findChangesInPositioning(IJavaElement element, int depth) {
188         if (depth >= this.maxDepth || this.added.contains(element) || this.removed.contains(element))
189                 return;
190                 
191         if (!isPositionedCorrectly(element)) {
192                 this.delta.changed(element, IJavaElementDelta.F_REORDER);
193         } 
194         
195         if (element instanceof IParent) {
196                 JavaElementInfo info = null;
197                 try { 
198                         info = (JavaElementInfo)((JavaElement)element).getElementInfo();
199                 } catch (JavaModelException npe) {
200                         return;
201                 }
202
203                 IJavaElement[] children = info.getChildren();
204                 if (children != null) {
205                         int length = children.length;
206                         for(int i = 0; i < length; i++) {
207                                 this.findChangesInPositioning(children[i], depth + 1);
208                         }
209                 }               
210         }
211 }
212 /**
213  * The elements are equivalent, but might have content changes.
214  */
215 private void findContentChange(JavaElementInfo oldInfo, JavaElementInfo newInfo, IJavaElement newElement) {
216         if (oldInfo instanceof MemberElementInfo && newInfo instanceof MemberElementInfo) {
217                 if (((MemberElementInfo)oldInfo).getModifiers() != ((MemberElementInfo)newInfo).getModifiers()) {
218                         this.delta.changed(newElement, IJavaElementDelta.F_MODIFIERS);
219                 } else if (oldInfo instanceof SourceMethodElementInfo && newInfo instanceof SourceMethodElementInfo) {
220                         if (!CharOperation.equals(
221                                         ((SourceMethodElementInfo)oldInfo).getReturnTypeName(), 
222                                         ((SourceMethodElementInfo)newInfo).getReturnTypeName())) {
223                                 this.delta.changed(newElement, IJavaElementDelta.F_CONTENT);
224                         }
225                 } else if (oldInfo instanceof SourceFieldElementInfo && newInfo instanceof SourceFieldElementInfo) {
226                         if (!CharOperation.equals(
227                                         ((SourceFieldElementInfo)oldInfo).getTypeName(), 
228                                         ((SourceFieldElementInfo)newInfo).getTypeName())) {
229                                 this.delta.changed(newElement, IJavaElementDelta.F_CONTENT);
230                         }
231                 }
232         }
233         if (oldInfo instanceof SourceTypeElementInfo && newInfo instanceof SourceTypeElementInfo) {
234                 SourceTypeElementInfo oldSourceTypeInfo = (SourceTypeElementInfo)oldInfo;
235                 SourceTypeElementInfo newSourceTypeInfo = (SourceTypeElementInfo)newInfo;
236                 if (!CharOperation.equals(oldSourceTypeInfo.getSuperclassName(), newSourceTypeInfo.getSuperclassName()) 
237                         || !CharOperation.equals(oldSourceTypeInfo.getInterfaceNames(), newSourceTypeInfo.getInterfaceNames())) {
238                         this.delta.changed(newElement, IJavaElementDelta.F_SUPER_TYPES);
239                 }
240         }
241 }
242 /**
243  * Adds removed deltas for any handles left in the table
244  */
245 private void findDeletions() {
246         Iterator iter = this.infos.keySet().iterator();
247         while(iter.hasNext()) {
248                 IJavaElement element = (IJavaElement)iter.next();
249                 this.delta.removed(element);
250                 this.removed(element);
251         }
252 }
253 private JavaElementInfo getElementInfo(IJavaElement element) {
254         return (JavaElementInfo)this.infos.get(element);
255 }
256 private ListItem getNewPosition(IJavaElement element) {
257         return (ListItem)this.newPositions.get(element);
258 }
259 private ListItem getOldPosition(IJavaElement element) {
260         return (ListItem)this.oldPositions.get(element);
261 }
262 private void initialize() {
263         this.infos = new HashMap(20);
264         this.oldPositions = new HashMap(20);
265         this.newPositions = new HashMap(20);
266         this.putOldPosition(this.javaElement, new ListItem(null, null));
267         this.putNewPosition(this.javaElement, new ListItem(null, null));
268         this.delta = new JavaElementDelta(javaElement);
269         
270         // if building a delta on a compilation unit or below, 
271         // it's a fine grained delta
272         if (javaElement.getElementType() >= IJavaElement.COMPILATION_UNIT) {
273                 this.delta.fineGrained();
274         }
275         
276         this.added = new ArrayList(5);
277         this.removed = new ArrayList(5);
278 }
279 /**
280  * Inserts position information for the elements into the new or old positions table
281  */
282 private void insertPositions(IJavaElement[] elements, boolean isNew) {
283         int length = elements.length;
284         IJavaElement previous = null, current = null, next = (length > 0) ? elements[0] : null;
285         for(int i = 0; i < length; i++) {
286                 previous = current;
287                 current = next;
288                 next = (i + 1 < length) ? elements[i + 1] : null;
289                 if (isNew) {
290                         this.putNewPosition(current, new ListItem(previous, next));
291                 } else {
292                         this.putOldPosition(current, new ListItem(previous, next));
293                 }
294         }
295 }
296 /**
297  * Returns whether the elements position has not changed.
298  */
299 private boolean isPositionedCorrectly(IJavaElement element) {
300         ListItem oldListItem = this.getOldPosition(element);
301         if (oldListItem == null) return false;
302         
303         ListItem newListItem = this.getNewPosition(element);
304         if (newListItem == null) return false;
305         
306         IJavaElement oldPrevious = oldListItem.previous;
307         IJavaElement newPrevious = newListItem.previous;
308         if (oldPrevious == null) {
309                 return newPrevious == null;
310         } else {
311                 return oldPrevious.equals(newPrevious);
312         }
313 }
314 private void putElementInfo(IJavaElement element, JavaElementInfo info) {
315         this.infos.put(element, info);
316 }
317 private void putNewPosition(IJavaElement element, ListItem position) {
318         this.newPositions.put(element, position);
319 }
320 private void putOldPosition(IJavaElement element, ListItem position) {
321         this.oldPositions.put(element, position);
322 }
323 /**
324  * Records this elements info, and attempts
325  * to record the info for the children.
326  */
327 private void recordElementInfo(IJavaElement element, JavaModel model, int depth) {
328         if (depth >= this.maxDepth) {
329                 return;
330         }
331         JavaElementInfo info = null;//(JavaElementInfo)JavaModelManager.getJavaModelManager().getInfo(element);
332         if (info == null) // no longer in the java model.
333                 return;
334         this.putElementInfo(element, info);
335                 
336         if (element instanceof IParent) {
337                 IJavaElement[] children = info.getChildren();
338                 if (children != null) {
339                         insertPositions(children, false);
340                         for(int i = 0, length = children.length; i < length; i++)
341                                 recordElementInfo(children[i], model, depth + 1);
342                 }
343         }
344 }
345 /**
346  * Fills the newPositions hashtable with the new position information
347  */
348 private void recordNewPositions(IJavaElement newElement, int depth) {
349         if (depth < this.maxDepth && newElement instanceof IParent) {
350                 JavaElementInfo info = null;
351                 try { 
352                         info = (JavaElementInfo)((JavaElement)newElement).getElementInfo();
353                 } catch (JavaModelException npe) {
354                         return;
355                 }
356
357                 IJavaElement[] children = info.getChildren();
358                 if (children != null) {
359                         insertPositions(children, true);
360                         for(int i = 0, length = children.length; i < length; i++) {
361                                 recordNewPositions(children[i], depth + 1);
362                         }
363                 }
364         }
365 }
366 /**
367  * Repairs the positioning information
368  * after an element has been removed
369  */
370 private void removed(IJavaElement element) {
371         this.removed.add(element);
372         ListItem current = this.getOldPosition(element);
373         ListItem previous = null, next = null;
374         if (current.previous != null)
375                 previous = this.getOldPosition(current.previous);
376         if (current.next != null)
377                 next = this.getOldPosition(current.next);
378         if (previous != null)
379                 previous.next = current.next;
380         if (next != null)
381                 next.previous = current.previous;
382         
383 }
384 private void removeElementInfo(IJavaElement element) {
385         this.infos.remove(element);
386 }
387 public String toString() {
388         StringBuffer buffer = new StringBuffer();
389         buffer.append("Built delta:\n"); //$NON-NLS-1$
390         buffer.append(this.delta.toString());
391         return buffer.toString();
392 }
393 /**
394  * Trims deletion deltas to only report the highest level of deletion
395  */
396 private void trimDelta(JavaElementDelta delta) {
397         if (delta.getKind() == IJavaElementDelta.REMOVED) {
398                 IJavaElementDelta[] children = delta.getAffectedChildren();
399                 for(int i = 0, length = children.length; i < length; i++) {
400                         delta.removeAffectedChild((JavaElementDelta)children[i]);
401                 }
402         } else {
403                 IJavaElementDelta[] children = delta.getAffectedChildren();
404                 for(int i = 0, length = children.length; i < length; i++) {
405                         trimDelta((JavaElementDelta)children[i]);
406                 }
407         }
408 }
409 }