/******************************************************************************* * Copyright (c) 2000, 2009 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 *******************************************************************************/ package org.eclipse.jdt.internal.core.hierarchy; import java.util.*; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import org.eclipse.jdt.core.*; import org.eclipse.jdt.core.IJavaElementDelta; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.core.JavaElement; import org.eclipse.jdt.internal.core.SimpleDelta; /* * Collects changes (reported through fine-grained deltas) that can affect a type hierarchy. */ public class ChangeCollector { /* * A table from ITypes to TypeDeltas */ HashMap changes = new HashMap(); TypeHierarchy hierarchy; public ChangeCollector(TypeHierarchy hierarchy) { this.hierarchy = hierarchy; } /* * Adds the children of the given delta to the list of changes. */ private void addAffectedChildren(IJavaElementDelta delta) throws JavaModelException { IJavaElementDelta[] children = delta.getAffectedChildren(); for (int i = 0, length = children.length; i < length; i++) { IJavaElementDelta child = children[i]; IJavaElement childElement = child.getElement(); switch (childElement.getElementType()) { case IJavaElement.IMPORT_CONTAINER: addChange((IImportContainer)childElement, child); break; case IJavaElement.IMPORT_DECLARATION: addChange((IImportDeclaration)childElement, child); break; case IJavaElement.TYPE: addChange((IType)childElement, child); break; case IJavaElement.INITIALIZER: case IJavaElement.FIELD: case IJavaElement.METHOD: addChange((IMember)childElement, child); break; } } } /* * Adds the given delta on a compilation unit to the list of changes. */ public void addChange(ICompilationUnit cu, IJavaElementDelta newDelta) throws JavaModelException { int newKind = newDelta.getKind(); switch (newKind) { case IJavaElementDelta.ADDED: ArrayList allTypes = new ArrayList(); getAllTypesFromElement(cu, allTypes); for (int i = 0, length = allTypes.size(); i < length; i++) { IType type = (IType)allTypes.get(i); addTypeAddition(type, (SimpleDelta)this.changes.get(type)); } break; case IJavaElementDelta.REMOVED: allTypes = new ArrayList(); getAllTypesFromHierarchy((JavaElement)cu, allTypes); for (int i = 0, length = allTypes.size(); i < length; i++) { IType type = (IType)allTypes.get(i); addTypeRemoval(type, (SimpleDelta)this.changes.get(type)); } break; case IJavaElementDelta.CHANGED: addAffectedChildren(newDelta); break; } } private void addChange(IImportContainer importContainer, IJavaElementDelta newDelta) throws JavaModelException { int newKind = newDelta.getKind(); if (newKind == IJavaElementDelta.CHANGED) { addAffectedChildren(newDelta); return; } SimpleDelta existingDelta = (SimpleDelta)this.changes.get(importContainer); if (existingDelta != null) { switch (newKind) { case IJavaElementDelta.ADDED: if (existingDelta.getKind() == IJavaElementDelta.REMOVED) { // REMOVED then ADDED this.changes.remove(importContainer); } break; case IJavaElementDelta.REMOVED: if (existingDelta.getKind() == IJavaElementDelta.ADDED) { // ADDED then REMOVED this.changes.remove(importContainer); } break; // CHANGED handled above } } else { SimpleDelta delta = new SimpleDelta(); switch (newKind) { case IJavaElementDelta.ADDED: delta.added(); break; case IJavaElementDelta.REMOVED: delta.removed(); break; } this.changes.put(importContainer, delta); } } private void addChange(IImportDeclaration importDecl, IJavaElementDelta newDelta) { SimpleDelta existingDelta = (SimpleDelta)this.changes.get(importDecl); int newKind = newDelta.getKind(); if (existingDelta != null) { switch (newKind) { case IJavaElementDelta.ADDED: if (existingDelta.getKind() == IJavaElementDelta.REMOVED) { // REMOVED then ADDED this.changes.remove(importDecl); } break; case IJavaElementDelta.REMOVED: if (existingDelta.getKind() == IJavaElementDelta.ADDED) { // ADDED then REMOVED this.changes.remove(importDecl); } break; // CHANGED cannot happen for import declaration } } else { SimpleDelta delta = new SimpleDelta(); switch (newKind) { case IJavaElementDelta.ADDED: delta.added(); break; case IJavaElementDelta.REMOVED: delta.removed(); break; } this.changes.put(importDecl, delta); } } /* * Adds a change for the given member (a method, a field or an initializer) and the types it defines. */ private void addChange(IMember member, IJavaElementDelta newDelta) throws JavaModelException { int newKind = newDelta.getKind(); switch (newKind) { case IJavaElementDelta.ADDED: ArrayList allTypes = new ArrayList(); getAllTypesFromElement(member, allTypes); for (int i = 0, length = allTypes.size(); i < length; i++) { IType innerType = (IType)allTypes.get(i); addTypeAddition(innerType, (SimpleDelta)this.changes.get(innerType)); } break; case IJavaElementDelta.REMOVED: allTypes = new ArrayList(); getAllTypesFromHierarchy((JavaElement)member, allTypes); for (int i = 0, length = allTypes.size(); i < length; i++) { IType type = (IType)allTypes.get(i); addTypeRemoval(type, (SimpleDelta)this.changes.get(type)); } break; case IJavaElementDelta.CHANGED: addAffectedChildren(newDelta); break; } } /* * Adds a change for the given type and the types it defines. */ private void addChange(IType type, IJavaElementDelta newDelta) throws JavaModelException { int newKind = newDelta.getKind(); SimpleDelta existingDelta = (SimpleDelta)this.changes.get(type); switch (newKind) { case IJavaElementDelta.ADDED: addTypeAddition(type, existingDelta); ArrayList allTypes = new ArrayList(); getAllTypesFromElement(type, allTypes); for (int i = 0, length = allTypes.size(); i < length; i++) { IType innerType = (IType)allTypes.get(i); addTypeAddition(innerType, (SimpleDelta)this.changes.get(innerType)); } break; case IJavaElementDelta.REMOVED: addTypeRemoval(type, existingDelta); allTypes = new ArrayList(); getAllTypesFromHierarchy((JavaElement)type, allTypes); for (int i = 0, length = allTypes.size(); i < length; i++) { IType innerType = (IType)allTypes.get(i); addTypeRemoval(innerType, (SimpleDelta)this.changes.get(innerType)); } break; case IJavaElementDelta.CHANGED: addTypeChange(type, newDelta.getFlags(), existingDelta); addAffectedChildren(newDelta); break; } } private void addTypeAddition(IType type, SimpleDelta existingDelta) throws JavaModelException { if (existingDelta != null) { switch (existingDelta.getKind()) { case IJavaElementDelta.REMOVED: // REMOVED then ADDED boolean hasChange = false; if (hasSuperTypeChange(type)) { existingDelta.superTypes(); hasChange = true; } if (hasVisibilityChange(type)) { existingDelta.modifiers(); hasChange = true; } if (!hasChange) { this.changes.remove(type); } break; // CHANGED then ADDED // or ADDED then ADDED: should not happen } } else { // check whether the type addition affects the hierarchy String typeName = type.getElementName(); if (this.hierarchy.hasSupertype(typeName) || this.hierarchy.subtypesIncludeSupertypeOf(type) || this.hierarchy.missingTypes.contains(typeName)) { SimpleDelta delta = new SimpleDelta(); delta.added(); this.changes.put(type, delta); } } } private void addTypeChange(IType type, int newFlags, SimpleDelta existingDelta) throws JavaModelException { if (existingDelta != null) { switch (existingDelta.getKind()) { case IJavaElementDelta.CHANGED: // CHANGED then CHANGED int existingFlags = existingDelta.getFlags(); boolean hasChange = false; if ((existingFlags & IJavaElementDelta.F_SUPER_TYPES) != 0 && hasSuperTypeChange(type)) { existingDelta.superTypes(); hasChange = true; } if ((existingFlags & IJavaElementDelta.F_MODIFIERS) != 0 && hasVisibilityChange(type)) { existingDelta.modifiers(); hasChange = true; } if (!hasChange) { // super types and visibility are back to the ones in the existing hierarchy this.changes.remove(type); } break; // ADDED then CHANGED: leave it as ADDED // REMOVED then CHANGED: should not happen } } else { // check whether the type change affects the hierarchy SimpleDelta typeDelta = null; if ((newFlags & IJavaElementDelta.F_SUPER_TYPES) != 0 && this.hierarchy.includesTypeOrSupertype(type)) { typeDelta = new SimpleDelta(); typeDelta.superTypes(); } if ((newFlags & IJavaElementDelta.F_MODIFIERS) != 0 && (this.hierarchy.hasSupertype(type.getElementName()) || type.equals(this.hierarchy.focusType))) { if (typeDelta == null) { typeDelta = new SimpleDelta(); } typeDelta.modifiers(); } if (typeDelta != null) { this.changes.put(type, typeDelta); } } } private void addTypeRemoval(IType type, SimpleDelta existingDelta) { if (existingDelta != null) { switch (existingDelta.getKind()) { case IJavaElementDelta.ADDED: // ADDED then REMOVED this.changes.remove(type); break; case IJavaElementDelta.CHANGED: // CHANGED then REMOVED existingDelta.removed(); break; // REMOVED then REMOVED: should not happen } } else { // check whether the type removal affects the hierarchy if (this.hierarchy.contains(type)) { SimpleDelta typeDelta = new SimpleDelta(); typeDelta.removed(); this.changes.put(type, typeDelta); } } } /* * Returns all types defined in the given element excluding the given element. */ private void getAllTypesFromElement(IJavaElement element, ArrayList allTypes) throws JavaModelException { switch (element.getElementType()) { case IJavaElement.COMPILATION_UNIT: IType[] types = ((ICompilationUnit)element).getTypes(); for (int i = 0, length = types.length; i < length; i++) { IType type = types[i]; allTypes.add(type); getAllTypesFromElement(type, allTypes); } break; case IJavaElement.TYPE: types = ((IType)element).getTypes(); for (int i = 0, length = types.length; i < length; i++) { IType type = types[i]; allTypes.add(type); getAllTypesFromElement(type, allTypes); } break; case IJavaElement.INITIALIZER: case IJavaElement.FIELD: case IJavaElement.METHOD: IJavaElement[] children = ((IMember)element).getChildren(); for (int i = 0, length = children.length; i < length; i++) { IType type = (IType)children[i]; allTypes.add(type); getAllTypesFromElement(type, allTypes); } break; } } /* * Returns all types in the existing hierarchy that have the given element as a parent. */ private void getAllTypesFromHierarchy(JavaElement element, ArrayList allTypes) { switch (element.getElementType()) { case IJavaElement.COMPILATION_UNIT: ArrayList types = (ArrayList)this.hierarchy.files.get(element); if (types != null) { allTypes.addAll(types); } break; case IJavaElement.TYPE: case IJavaElement.INITIALIZER: case IJavaElement.FIELD: case IJavaElement.METHOD: types = (ArrayList)this.hierarchy.files.get(((IMember)element).getCompilationUnit()); if (types != null) { for (int i = 0, length = types.size(); i < length; i++) { IType type = (IType)types.get(i); if (element.isAncestorOf(type)) { allTypes.add(type); } } } break; } } private boolean hasSuperTypeChange(IType type) throws JavaModelException { // check super class IType superclass = this.hierarchy.getSuperclass(type); String existingSuperclassName = superclass == null ? null : superclass.getElementName(); String newSuperclassName = type.getSuperclassName(); if (existingSuperclassName != null && !existingSuperclassName.equals(newSuperclassName)) { return true; } // check super interfaces IType[] existingSuperInterfaces = this.hierarchy.getSuperInterfaces(type); String[] newSuperInterfaces = type.getSuperInterfaceNames(); if (existingSuperInterfaces.length != newSuperInterfaces.length) { return true; } for (int i = 0, length = newSuperInterfaces.length; i < length; i++) { String superInterfaceName = newSuperInterfaces[i]; if (!superInterfaceName.equals(newSuperInterfaces[i])) { return true; } } return false; } private boolean hasVisibilityChange(IType type) throws JavaModelException { int existingFlags = this.hierarchy.getCachedFlags(type); int newFlags = type.getFlags(); return existingFlags != newFlags; } /* * Whether the hierarchy needs refresh according to the changes collected so far. */ public boolean needsRefresh() { return this.changes.size() != 0; } public String toString() { StringBuffer buffer = new StringBuffer(); Iterator iterator = this.changes.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = (Map.Entry)iterator.next(); buffer.append(((JavaElement)entry.getKey()).toDebugString()); buffer.append(entry.getValue()); if (iterator.hasNext()) { buffer.append('\n'); } } return buffer.toString(); } }