blob: 1516c511a1b2a005865a7497aa2d75f6ada15d01 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2004 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())) {
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 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();
}
}