/******************************************************************************* * Copyright (c) 2000, 2016 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 * *******************************************************************************/ 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(); } }