blob: 6ec30baeb2db4047c5fc9a69a6f72f26a661a7c0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2016 IBM Corporation and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.dltk.internal.core.hierarchy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.dltk.core.IMember;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.IModelElementDelta;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.internal.core.ModelElement;
import org.eclipse.dltk.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(IModelElementDelta delta) throws ModelException {
IModelElementDelta[] children = delta.getAffectedChildren();
for (int i = 0, length = children.length; i < length; i++) {
IModelElementDelta child = children[i];
IModelElement childElement = child.getElement();
switch (childElement.getElementType()) {
// case IModelElement.IMPORT_CONTAINER:
// addChange((IImportContainer)childElement, child);
// break;
// case IModelElement.IMPORT_DECLARATION:
// addChange((IImportDeclaration)childElement, child);
// break;
case IModelElement.TYPE:
addChange((IType)childElement, child);
break;
// case IModelElement.INITIALIZER:
case IModelElement.FIELD:
case IModelElement.METHOD:
addChange((IMember)childElement, child);
break;
}
}
}
/*
* Adds the given delta on a compilation unit to the list of changes.
*/
public void addChange(ISourceModule cu, IModelElementDelta newDelta) throws ModelException {
int newKind = newDelta.getKind();
switch (newKind) {
case IModelElementDelta.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 IModelElementDelta.REMOVED:
allTypes = new ArrayList();
getAllTypesFromHierarchy((ModelElement)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 IModelElementDelta.CHANGED:
addAffectedChildren(newDelta);
break;
}
}
// private void addChange(IImportContainer importContainer, IModelElementDelta newDelta) throws ModelException {
// int newKind = newDelta.getKind();
// if (newKind == IModelElementDelta.CHANGED) {
// addAffectedChildren(newDelta);
// return;
// }
// SimpleDelta existingDelta = (SimpleDelta)this.changes.get(importContainer);
// if (existingDelta != null) {
// switch (newKind) {
// case IModelElementDelta.ADDED:
// if (existingDelta.getKind() == IModelElementDelta.REMOVED) {
// // REMOVED then ADDED
// this.changes.remove(importContainer);
// }
// break;
// case IModelElementDelta.REMOVED:
// if (existingDelta.getKind() == IModelElementDelta.ADDED) {
// // ADDED then REMOVED
// this.changes.remove(importContainer);
// }
// break;
// // CHANGED handled above
// }
// } else {
// SimpleDelta delta = new SimpleDelta();
// switch (newKind) {
// case IModelElementDelta.ADDED:
// delta.added();
// break;
// case IModelElementDelta.REMOVED:
// delta.removed();
// break;
// }
// this.changes.put(importContainer, delta);
// }
// }
//
// private void addChange(IImportDeclaration importDecl, IModelElementDelta newDelta) {
// SimpleDelta existingDelta = (SimpleDelta)this.changes.get(importDecl);
// int newKind = newDelta.getKind();
// if (existingDelta != null) {
// switch (newKind) {
// case IModelElementDelta.ADDED:
// if (existingDelta.getKind() == IModelElementDelta.REMOVED) {
// // REMOVED then ADDED
// this.changes.remove(importDecl);
// }
// break;
// case IModelElementDelta.REMOVED:
// if (existingDelta.getKind() == IModelElementDelta.ADDED) {
// // ADDED then REMOVED
// this.changes.remove(importDecl);
// }
// break;
// // CHANGED cannot happen for import declaration
// }
// } else {
// SimpleDelta delta = new SimpleDelta();
// switch (newKind) {
// case IModelElementDelta.ADDED:
// delta.added();
// break;
// case IModelElementDelta.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, IModelElementDelta newDelta) throws ModelException {
int newKind = newDelta.getKind();
switch (newKind) {
case IModelElementDelta.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 IModelElementDelta.REMOVED:
allTypes = new ArrayList();
getAllTypesFromHierarchy((ModelElement)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 IModelElementDelta.CHANGED:
addAffectedChildren(newDelta);
break;
}
}
/*
* Adds a change for the given type and the types it defines.
*/
private void addChange(IType type, IModelElementDelta newDelta) throws ModelException {
int newKind = newDelta.getKind();
SimpleDelta existingDelta = (SimpleDelta)this.changes.get(type);
switch (newKind) {
case IModelElementDelta.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 IModelElementDelta.REMOVED:
addTypeRemoval(type, existingDelta);
allTypes = new ArrayList();
getAllTypesFromHierarchy((ModelElement)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 IModelElementDelta.CHANGED:
addTypeChange(type, newDelta.getFlags(), existingDelta);
addAffectedChildren(newDelta);
break;
}
}
private void addTypeAddition(IType type, SimpleDelta existingDelta) throws ModelException {
if (existingDelta != null) {
switch (existingDelta.getKind()) {
case IModelElementDelta.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 ModelException {
if (existingDelta != null) {
switch (existingDelta.getKind()) {
case IModelElementDelta.CHANGED:
// CHANGED then CHANGED
int existingFlags = existingDelta.getFlags();
boolean hasChange = false;
if ((existingFlags & IModelElementDelta.F_SUPER_TYPES) != 0
&& hasSuperTypeChange(type)) {
existingDelta.superTypes();
hasChange = true;
}
if ((existingFlags & IModelElementDelta.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 & IModelElementDelta.F_SUPER_TYPES) != 0
&& this.hierarchy.includesTypeOrSupertype(type)) {
typeDelta = new SimpleDelta();
typeDelta.superTypes();
}
if ((newFlags & IModelElementDelta.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 IModelElementDelta.ADDED:
// ADDED then REMOVED
this.changes.remove(type);
break;
case IModelElementDelta.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(IModelElement element, ArrayList allTypes) throws ModelException {
switch (element.getElementType()) {
case IModelElement.SOURCE_MODULE:
IType[] types = ((ISourceModule)element).getTypes();
for (int i = 0, length = types.length; i < length; i++) {
IType type = types[i];
allTypes.add(type);
getAllTypesFromElement(type, allTypes);
}
break;
case IModelElement.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 IModelElement.INITIALIZER:
case IModelElement.FIELD:
case IModelElement.METHOD:
IModelElement[] children = ((IMember)element).getChildren();
for (int i = 0, length = children.length; i < length; i++) {
IModelElement child = children[i];
if (child.getElementType() == IModelElement.TYPE) {
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(ModelElement element, ArrayList allTypes) {
switch (element.getElementType()) {
case IModelElement.SOURCE_MODULE:
ArrayList types = this.hierarchy.files.get(element);
if (types != null) {
allTypes.addAll(types);
}
break;
case IModelElement.TYPE:
// case IModelElement.INITIALIZER:
case IModelElement.FIELD:
case IModelElement.METHOD:
types = this.hierarchy.files
.get(((IMember) element).getSourceModule());
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 ModelException {
// check super classes
IType[] existingSuperClasses = this.hierarchy.getSuperclass(type);
String[] newSuperClases = type.getSuperClasses();
if (newSuperClases == null && existingSuperClasses.length != 0) {
return true;
}
if (existingSuperClasses.length != newSuperClases.length) {
return true;
}
for (int i = 0, length = newSuperClases.length; i < length; i++) {
String superClassName = newSuperClases[i];
if (!superClassName.equals(newSuperClases[i])) {
return true;
}
}
return false;
}
private boolean hasVisibilityChange(IType type) throws ModelException {
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;
}
@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(((ModelElement)entry.getKey()).toDebugString());
buffer.append(entry.getValue());
if (iterator.hasNext()) {
buffer.append('\n');
}
}
return buffer.toString();
}
}