Copyright (c) 2000, 2017 IBM Corporation and others. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ SPDX-License-Identifier: EPL-2.0 Contributors: IBM Corporation - initial API and implementation
/******************************************************************************* * Copyright (c) 2000, 2017 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * 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. */ @SuppressWarnings({"rawtypes", "unchecked"}) 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: IOpenable o = (IOpenable) element; ArrayList types = this.hierarchy.files.get(o); if (types != null) { allTypes.addAll(types); } break; case IJavaElement.TYPE: case IJavaElement.INITIALIZER: case IJavaElement.FIELD: case IJavaElement.METHOD: types = 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; } @Override 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(); } }