| /******************************************************************************* |
| * Copyright (c) 2000, 2013 IBM Corporation 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: |
| * IBM Corporation - initial API and implementation |
| * Technical University Berlin - extended API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.core; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.jdt.core.IAnnotation; |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.IJavaElementDelta; |
| import org.eclipse.jdt.core.IParent; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.internal.core.util.Util; |
| import org.eclipse.objectteams.otdt.internal.core.SourceMethodMappingInfo; |
| |
| /** |
| * OTDT changes: |
| * What: during lookup of infos for a given IJavaElement handle IOTJavaElements via their corresponding java element |
| * Why: couldn't detect, e.g., removal of callin mappings. |
| * Note: two locations still abort handling of IOTJavaElements (CCE). |
| * |
| * A java element delta builder creates a java element delta on |
| * a java element between the version of the java element |
| * at the time the comparator was created and the current version |
| * of the java element. |
| * |
| * It performs this operation by locally caching the contents of |
| * the java element when it is created. When the method |
| * createDeltas() is called, it creates a delta over the cached |
| * contents and the new contents. |
| */ |
| @SuppressWarnings({ "rawtypes", "unchecked" }) |
| public class JavaElementDeltaBuilder { |
| /** |
| * The java element handle |
| */ |
| IJavaElement javaElement; |
| |
| /** |
| * The maximum depth in the java element children we should look into |
| */ |
| int maxDepth = Integer.MAX_VALUE; |
| |
| /** |
| * The old handle to info relationships |
| */ |
| Map infos; |
| Map annotationInfos; |
| |
| /** |
| * The old position info |
| */ |
| Map oldPositions; |
| |
| /** |
| * The new position info |
| */ |
| Map newPositions; |
| |
| /** |
| * Change delta |
| */ |
| public JavaElementDelta delta = null; |
| |
| /** |
| * List of added elements |
| */ |
| HashSet added; |
| |
| /** |
| * List of removed elements |
| */ |
| HashSet removed; |
| |
| /** |
| * Doubly linked list item |
| */ |
| static class ListItem { |
| public IJavaElement previous; |
| public IJavaElement next; |
| |
| public ListItem(IJavaElement previous, IJavaElement next) { |
| this.previous = previous; |
| this.next = next; |
| } |
| } |
| /** |
| * Creates a java element comparator on a java element |
| * looking as deep as necessary. |
| */ |
| public JavaElementDeltaBuilder(IJavaElement javaElement) { |
| this.javaElement = javaElement; |
| initialize(); |
| recordElementInfo( |
| javaElement, |
| (JavaModel)this.javaElement.getJavaModel(), |
| 0); |
| } |
| /** |
| * Creates a java element comparator on a java element |
| * looking only 'maxDepth' levels deep. |
| */ |
| public JavaElementDeltaBuilder(IJavaElement javaElement, int maxDepth) { |
| this.javaElement = javaElement; |
| this.maxDepth = maxDepth; |
| initialize(); |
| recordElementInfo( |
| javaElement, |
| (JavaModel)this.javaElement.getJavaModel(), |
| 0); |
| } |
| /** |
| * Repairs the positioning information |
| * after an element has been added |
| */ |
| private void added(IJavaElement element) { |
| this.added.add(element); |
| ListItem current = getNewPosition(element); |
| ListItem previous = null, next = null; |
| if (current.previous != null) |
| previous = getNewPosition(current.previous); |
| if (current.next != null) |
| next = getNewPosition(current.next); |
| if (previous != null) |
| previous.next = current.next; |
| if (next != null) |
| next.previous = current.previous; |
| } |
| /** |
| * Builds the java element deltas between the old content of the compilation |
| * unit and its new content. |
| */ |
| public void buildDeltas() { |
| this.delta = new JavaElementDelta(this.javaElement); |
| // if building a delta on a compilation unit or below, |
| // it's a fine grained delta |
| if (this.javaElement.getElementType() >= IJavaElement.COMPILATION_UNIT) { |
| this.delta.fineGrained(); |
| } |
| recordNewPositions(this.javaElement, 0); |
| findAdditions(this.javaElement, 0); |
| findDeletions(); |
| findChangesInPositioning(this.javaElement, 0); |
| trimDelta(this.delta); |
| if (this.delta.getAffectedChildren().length == 0) { |
| // this is a fine grained but not children affected -> mark as content changed |
| this.delta.contentChanged(); |
| } |
| } |
| private boolean equals(char[][][] first, char[][][] second) { |
| if (first == second) |
| return true; |
| if (first == null || second == null) |
| return false; |
| if (first.length != second.length) |
| return false; |
| |
| for (int i = first.length; --i >= 0;) |
| if (!CharOperation.equals(first[i], second[i])) |
| return false; |
| return true; |
| } |
| /** |
| * Finds elements which have been added or changed. |
| */ |
| private void findAdditions(IJavaElement newElement, int depth) { |
| JavaElementInfo oldInfo = getElementInfo(newElement); |
| if (oldInfo == null && depth < this.maxDepth) { |
| this.delta.added(newElement); |
| added(newElement); |
| } else { |
| removeElementInfo(newElement); |
| } |
| |
| if (depth >= this.maxDepth) { |
| // mark element as changed |
| this.delta.changed(newElement, IJavaElementDelta.F_CONTENT); |
| return; |
| } |
| |
| JavaElementInfo newInfo = null; |
| try { |
| newInfo = (JavaElementInfo)((JavaElement)newElement).getElementInfo(); |
| } catch (JavaModelException npe) { |
| return; |
| } |
| |
| findContentChange(oldInfo, newInfo, newElement); |
| |
| if (oldInfo != null && newElement instanceof IParent) { |
| IJavaElement[] children = newInfo.getChildren(); |
| if (children != null) { |
| int length = children.length; |
| for(int i = 0; i < length; i++) { |
| findAdditions(children[i], depth + 1); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Looks for changed positioning of elements. |
| */ |
| private void findChangesInPositioning(IJavaElement element, int depth) { |
| if (depth >= this.maxDepth || this.added.contains(element) || this.removed.contains(element)) |
| return; |
| |
| if (!isPositionedCorrectly(element)) { |
| this.delta.changed(element, IJavaElementDelta.F_REORDER); |
| } |
| |
| if (element instanceof IParent) { |
| JavaElementInfo info = null; |
| try { |
| info = (JavaElementInfo)((JavaElement)element).getElementInfo(); |
| } catch (JavaModelException npe) { |
| return; |
| } |
| IJavaElement[] children = info.getChildren(); |
| if (children != null) { |
| int length = children.length; |
| for(int i = 0; i < length; i++) { |
| findChangesInPositioning(children[i], depth + 1); |
| } |
| } |
| } |
| } |
| private void findAnnotationChanges(IAnnotation[] oldAnnotations, IAnnotation[] newAnnotations, IJavaElement parent) { |
| ArrayList annotationDeltas = null; |
| for (int i = 0, length = newAnnotations.length; i < length; i++) { |
| IAnnotation newAnnotation = newAnnotations[i]; |
| Object oldInfo = this.annotationInfos.remove(newAnnotation); |
| if (oldInfo == null) { |
| JavaElementDelta annotationDelta = new JavaElementDelta(newAnnotation); |
| annotationDelta.added(); |
| if (annotationDeltas == null) annotationDeltas = new ArrayList(); |
| annotationDeltas.add(annotationDelta); |
| continue; |
| } else { |
| AnnotationInfo newInfo = null; |
| try { |
| newInfo = (AnnotationInfo) ((JavaElement) newAnnotation).getElementInfo(); |
| } catch (JavaModelException npe) { |
| return; |
| } |
| if (!Util.equalArraysOrNull(((AnnotationInfo) oldInfo).members, newInfo.members)) { |
| JavaElementDelta annotationDelta = new JavaElementDelta(newAnnotation); |
| annotationDelta.changed(IJavaElementDelta.F_CONTENT); |
| if (annotationDeltas == null) annotationDeltas = new ArrayList(); |
| annotationDeltas.add(annotationDelta); |
| } |
| } |
| } |
| for (int i = 0, length = oldAnnotations.length; i < length; i++) { |
| IAnnotation oldAnnotation = oldAnnotations[i]; |
| if (this.annotationInfos.remove(oldAnnotation) != null) { |
| JavaElementDelta annotationDelta = new JavaElementDelta(oldAnnotation); |
| annotationDelta.removed(); |
| if (annotationDeltas == null) annotationDeltas = new ArrayList(); |
| annotationDeltas.add(annotationDelta); } |
| } |
| if (annotationDeltas == null) |
| return; |
| int size = annotationDeltas.size(); |
| if (size > 0) { |
| JavaElementDelta parentDelta = this.delta.changed(parent, IJavaElementDelta.F_ANNOTATIONS); |
| parentDelta.annotationDeltas = (IJavaElementDelta[]) annotationDeltas.toArray(new IJavaElementDelta[size]); |
| } |
| } |
| /** |
| * The elements are equivalent, but might have content changes. |
| */ |
| private void findContentChange(JavaElementInfo oldInfo, JavaElementInfo newInfo, IJavaElement newElement) { |
| if (oldInfo instanceof MemberElementInfo && newInfo instanceof MemberElementInfo) { |
| if (((MemberElementInfo)oldInfo).getModifiers() != ((MemberElementInfo)newInfo).getModifiers()) { |
| this.delta.changed(newElement, IJavaElementDelta.F_MODIFIERS); |
| } |
| if (oldInfo instanceof AnnotatableInfo && newInfo instanceof AnnotatableInfo) { |
| findAnnotationChanges(((AnnotatableInfo) oldInfo).annotations, ((AnnotatableInfo) newInfo).annotations, newElement); |
| } |
| if (oldInfo instanceof SourceMethodElementInfo && newInfo instanceof SourceMethodElementInfo) { |
| SourceMethodElementInfo oldSourceMethodInfo = (SourceMethodElementInfo)oldInfo; |
| SourceMethodElementInfo newSourceMethodInfo = (SourceMethodElementInfo)newInfo; |
| if (!CharOperation.equals(oldSourceMethodInfo.getReturnTypeName(), newSourceMethodInfo.getReturnTypeName()) |
| || !CharOperation.equals(oldSourceMethodInfo.getTypeParameterNames(), newSourceMethodInfo.getTypeParameterNames()) |
| || !equals(oldSourceMethodInfo.getTypeParameterBounds(), newSourceMethodInfo.getTypeParameterBounds())) { |
| this.delta.changed(newElement, IJavaElementDelta.F_CONTENT); |
| } |
| } else if (oldInfo instanceof SourceFieldElementInfo && newInfo instanceof SourceFieldElementInfo) { |
| if (!CharOperation.equals( |
| ((SourceFieldElementInfo)oldInfo).getTypeName(), |
| ((SourceFieldElementInfo)newInfo).getTypeName())) { |
| this.delta.changed(newElement, IJavaElementDelta.F_CONTENT); |
| } |
| //{ObjectTeams: compare SourceMethodMappingInfo: |
| } else if (oldInfo instanceof SourceMethodMappingInfo && newInfo instanceof SourceMethodMappingInfo) { |
| SourceMethodMappingInfo oldMappingInfo = (SourceMethodMappingInfo) oldInfo; |
| SourceMethodMappingInfo newMappingInfo = (SourceMethodMappingInfo) newInfo; |
| if (!oldMappingInfo.modifiersEqual(newMappingInfo)) |
| this.delta.changed(newElement, IJavaElementDelta.F_MODIFIERS); |
| char[] oldName = oldMappingInfo.getCallinName(); |
| char[] newName = newMappingInfo.getCallinName(); |
| if ( oldMappingInfo.isCallin() |
| && !(oldName[0] == '<' && newName[0] == '<') // if both are generated don't bother to compare |
| && !CharOperation.equals(oldName, newName)) |
| this.delta.changed(newElement, IJavaElementDelta.F_CONTENT); |
| if (!oldMappingInfo.signaturesEqual(newMappingInfo)) |
| this.delta.changed(newElement, IJavaElementDelta.F_CONTENT); |
| // SH} |
| } else if (oldInfo instanceof SourceTypeElementInfo && newInfo instanceof SourceTypeElementInfo) { |
| SourceTypeElementInfo oldSourceTypeInfo = (SourceTypeElementInfo)oldInfo; |
| SourceTypeElementInfo newSourceTypeInfo = (SourceTypeElementInfo)newInfo; |
| if (!CharOperation.equals(oldSourceTypeInfo.getSuperclassName(), newSourceTypeInfo.getSuperclassName()) |
| || !CharOperation.equals(oldSourceTypeInfo.getInterfaceNames(), newSourceTypeInfo.getInterfaceNames())) { |
| this.delta.changed(newElement, IJavaElementDelta.F_SUPER_TYPES); |
| } |
| //{ObjectTeams: handle baseclass in analogy to superclass: |
| if (!CharOperation.equals(oldSourceTypeInfo.getBaseclassName(), newSourceTypeInfo.getBaseclassName())) { |
| this.delta.changed(newElement, IJavaElementDelta.F_SUPER_TYPES); |
| } |
| // SH} |
| if (!CharOperation.equals(oldSourceTypeInfo.getTypeParameterNames(), newSourceTypeInfo.getTypeParameterNames()) |
| || !equals(oldSourceTypeInfo.getTypeParameterBounds(), newSourceTypeInfo.getTypeParameterBounds())) { |
| this.delta.changed(newElement, IJavaElementDelta.F_CONTENT); |
| } |
| HashMap oldTypeCategories = oldSourceTypeInfo.categories; |
| HashMap newTypeCategories = newSourceTypeInfo.categories; |
| if (oldTypeCategories != null) { |
| // take the union of old and new categories elements (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=125675) |
| Set elements; |
| if (newTypeCategories != null) { |
| elements = new HashSet(oldTypeCategories.keySet()); |
| elements.addAll(newTypeCategories.keySet()); |
| } else |
| elements = oldTypeCategories.keySet(); |
| Iterator iterator = elements.iterator(); |
| while (iterator.hasNext()) { |
| IJavaElement element = (IJavaElement) iterator.next(); |
| String[] oldCategories = (String[]) oldTypeCategories.get(element); |
| String[] newCategories = newTypeCategories == null ? null : (String[]) newTypeCategories.get(element); |
| if (!Util.equalArraysOrNull(oldCategories, newCategories)) { |
| this.delta.changed(element, IJavaElementDelta.F_CATEGORIES); |
| } |
| } |
| } else if (newTypeCategories != null) { |
| Iterator elements = newTypeCategories.keySet().iterator(); |
| while (elements.hasNext()) { |
| IJavaElement element = (IJavaElement) elements.next(); |
| this.delta.changed(element, IJavaElementDelta.F_CATEGORIES); // all categories for this element were removed |
| } |
| } |
| } |
| } |
| } |
| /** |
| * Adds removed deltas for any handles left in the table |
| */ |
| private void findDeletions() { |
| Iterator iter = this.infos.keySet().iterator(); |
| while(iter.hasNext()) { |
| IJavaElement element = (IJavaElement)iter.next(); |
| this.delta.removed(element); |
| removed(element); |
| } |
| } |
| private JavaElementInfo getElementInfo(IJavaElement element) { |
| return (JavaElementInfo)this.infos.get(element); |
| } |
| private ListItem getNewPosition(IJavaElement element) { |
| return (ListItem)this.newPositions.get(element); |
| } |
| private ListItem getOldPosition(IJavaElement element) { |
| return (ListItem)this.oldPositions.get(element); |
| } |
| private void initialize() { |
| this.infos = new HashMap(20); |
| this.oldPositions = new HashMap(20); |
| this.newPositions = new HashMap(20); |
| this.oldPositions.put(this.javaElement, new ListItem(null, null)); |
| this.newPositions.put(this.javaElement, new ListItem(null, null)); |
| this.added = new HashSet(5); |
| this.removed = new HashSet(5); |
| } |
| /** |
| * Inserts position information for the elements into the new or old positions table |
| */ |
| private void insertPositions(IJavaElement[] elements, boolean isNew) { |
| int length = elements.length; |
| IJavaElement previous = null, current = null, next = (length > 0) ? elements[0] : null; |
| for(int i = 0; i < length; i++) { |
| previous = current; |
| current = next; |
| next = (i + 1 < length) ? elements[i + 1] : null; |
| if (isNew) { |
| this.newPositions.put(current, new ListItem(previous, next)); |
| } else { |
| this.oldPositions.put(current, new ListItem(previous, next)); |
| } |
| } |
| } |
| /** |
| * Returns whether the elements position has not changed. |
| */ |
| private boolean isPositionedCorrectly(IJavaElement element) { |
| ListItem oldListItem = getOldPosition(element); |
| if (oldListItem == null) return false; |
| |
| ListItem newListItem = getNewPosition(element); |
| if (newListItem == null) return false; |
| |
| IJavaElement oldPrevious = oldListItem.previous; |
| IJavaElement newPrevious = newListItem.previous; |
| if (oldPrevious == null) { |
| return newPrevious == null; |
| } else { |
| return oldPrevious.equals(newPrevious); |
| } |
| } |
| /** |
| * Records this elements info, and attempts |
| * to record the info for the children. |
| */ |
| private void recordElementInfo(IJavaElement element, JavaModel model, int depth) { |
| if (depth >= this.maxDepth) { |
| return; |
| } |
| JavaElementInfo info = (JavaElementInfo)JavaModelManager.getJavaModelManager().getInfo(element); |
| if (info == null) // no longer in the java model. |
| return; |
| this.infos.put(element, info); |
| |
| if (element instanceof IParent) { |
| IJavaElement[] children = info.getChildren(); |
| if (children != null) { |
| insertPositions(children, false); |
| for(int i = 0, length = children.length; i < length; i++) |
| recordElementInfo(children[i], model, depth + 1); |
| } |
| } |
| IAnnotation[] annotations = null; |
| if (info instanceof AnnotatableInfo) |
| annotations = ((AnnotatableInfo) info).annotations; |
| if (annotations != null) { |
| if (this.annotationInfos == null) |
| this.annotationInfos = new HashMap(); |
| JavaModelManager manager = JavaModelManager.getJavaModelManager(); |
| for (int i = 0, length = annotations.length; i < length; i++) { |
| this.annotationInfos.put(annotations[i], manager.getInfo(annotations[i])); |
| } |
| } |
| } |
| /** |
| * Fills the newPositions hashtable with the new position information |
| */ |
| private void recordNewPositions(IJavaElement newElement, int depth) { |
| if (depth < this.maxDepth && newElement instanceof IParent) { |
| JavaElementInfo info = null; |
| try { |
| info = (JavaElementInfo)((JavaElement)newElement).getElementInfo(); |
| } catch (JavaModelException npe) { |
| return; |
| } |
| |
| IJavaElement[] children = info.getChildren(); |
| if (children != null) { |
| insertPositions(children, true); |
| for(int i = 0, length = children.length; i < length; i++) { |
| recordNewPositions(children[i], depth + 1); |
| } |
| } |
| } |
| } |
| /** |
| * Repairs the positioning information |
| * after an element has been removed |
| */ |
| private void removed(IJavaElement element) { |
| this.removed.add(element); |
| ListItem current = getOldPosition(element); |
| ListItem previous = null, next = null; |
| if (current.previous != null) |
| previous = getOldPosition(current.previous); |
| if (current.next != null) |
| next = getOldPosition(current.next); |
| if (previous != null) |
| previous.next = current.next; |
| if (next != null) |
| next.previous = current.previous; |
| |
| } |
| private void removeElementInfo(IJavaElement element) { |
| this.infos.remove(element); |
| } |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.append("Built delta:\n"); //$NON-NLS-1$ |
| buffer.append(this.delta == null ? "<null>" : this.delta.toString()); //$NON-NLS-1$ |
| return buffer.toString(); |
| } |
| /** |
| * Trims deletion deltas to only report the highest level of deletion |
| */ |
| private void trimDelta(JavaElementDelta elementDelta) { |
| if (elementDelta.getKind() == IJavaElementDelta.REMOVED) { |
| IJavaElementDelta[] children = elementDelta.getAffectedChildren(); |
| for(int i = 0, length = children.length; i < length; i++) { |
| elementDelta.removeAffectedChild((JavaElementDelta)children[i]); |
| } |
| } else { |
| IJavaElementDelta[] children = elementDelta.getAffectedChildren(); |
| for(int i = 0, length = children.length; i < length; i++) { |
| trimDelta((JavaElementDelta)children[i]); |
| } |
| } |
| } |
| } |