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