blob: 3dae8688df6daabdd2e59dafd3c8b0bf8fe834ad [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2018 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.ui.typehierarchy;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.ElementChangedEvent;
import org.eclipse.dltk.core.IElementChangedListener;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.IModelElementDelta;
import org.eclipse.dltk.core.IProjectFragment;
import org.eclipse.dltk.core.IRegion;
import org.eclipse.dltk.core.IScriptFolder;
import org.eclipse.dltk.core.IScriptProject;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.core.ITypeHierarchy;
import org.eclipse.dltk.core.ITypeHierarchyChangedListener;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.core.ScriptModelUtil;
import org.eclipse.dltk.ui.DLTKUIPlugin;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
/**
* Manages a type hierarchy, to keep it refreshed, and to allow it to be shared.
*/
public class TypeHierarchyLifeCycle
implements ITypeHierarchyChangedListener, IElementChangedListener {
private boolean fHierarchyRefreshNeeded;
private ITypeHierarchy fHierarchy;
private IModelElement fInputElement;
private boolean fIsSuperTypesOnly;
private List<ITypeHierarchyLifeCycleListener> fChangeListeners;
public TypeHierarchyLifeCycle() {
this(false);
}
public TypeHierarchyLifeCycle(boolean isSuperTypesOnly) {
fHierarchy = null;
fInputElement = null;
fIsSuperTypesOnly = isSuperTypesOnly;
fChangeListeners = new ArrayList<>(2);
}
public ITypeHierarchy getHierarchy() {
return fHierarchy;
}
public IModelElement getInputElement() {
return fInputElement;
}
public void freeHierarchy() {
if (fHierarchy != null) {
fHierarchy.removeTypeHierarchyChangedListener(this);
DLTKCore.removeElementChangedListener(this);
fHierarchy = null;
fInputElement = null;
}
}
public void removeChangedListener(
ITypeHierarchyLifeCycleListener listener) {
fChangeListeners.remove(listener);
}
public void addChangedListener(ITypeHierarchyLifeCycleListener listener) {
if (!fChangeListeners.contains(listener)) {
fChangeListeners.add(listener);
}
}
private void fireChange(IType[] changedTypes) {
for (int i = fChangeListeners.size() - 1; i >= 0; i--) {
ITypeHierarchyLifeCycleListener curr = fChangeListeners.get(i);
curr.typeHierarchyChanged(this, changedTypes);
}
}
public void ensureRefreshedTypeHierarchy(final IModelElement element,
IRunnableContext context)
throws InvocationTargetException, InterruptedException {
if (element == null || !element.exists()) {
freeHierarchy();
return;
}
boolean hierachyCreationNeeded = (fHierarchy == null
|| !element.equals(fInputElement));
if (hierachyCreationNeeded || fHierarchyRefreshNeeded) {
IRunnableWithProgress op = pm -> {
try {
doHierarchyRefresh(element, pm);
} catch (ModelException e1) {
throw new InvocationTargetException(e1);
} catch (OperationCanceledException e2) {
throw new InterruptedException();
}
};
fHierarchyRefreshNeeded = true;
context.run(true, true, op);
fHierarchyRefreshNeeded = false;
}
}
private ITypeHierarchy createTypeHierarchy(IModelElement element,
IProgressMonitor pm) throws ModelException {
if (element.getElementType() == IModelElement.TYPE) {
IType type = (IType) element;
if (fIsSuperTypesOnly) {
return type.newSupertypeHierarchy(pm);
}
return type.newTypeHierarchy(pm);
}
IRegion region = DLTKCore.newRegion();
if (element.getElementType() == IModelElement.SCRIPT_PROJECT) {
// for projects only add the contained source folders
IProjectFragment[] roots = ((IScriptProject) element)
.getProjectFragments();
for (int i = 0; i < roots.length; i++) {
if (!roots[i].isExternal()) {
region.add(roots[i]);
}
}
} else if (element.getElementType() == IModelElement.PROJECT_FRAGMENT) {
IProjectFragment[] roots = element.getScriptProject()
.getProjectFragments();
String name = element.getElementName();
for (int i = 0; i < roots.length; i++) {
IScriptFolder pack = roots[i].getScriptFolder(name);
if (pack.exists()) {
region.add(pack);
}
}
} else {
region.add(element);
}
IScriptProject jproject = element.getScriptProject();
return jproject.newTypeHierarchy(region, pm);
}
public synchronized void doHierarchyRefresh(IModelElement element,
IProgressMonitor pm) throws ModelException {
boolean hierachyCreationNeeded = (fHierarchy == null
|| !element.equals(fInputElement));
// to ensure the order of the two listeners always remove / add
// listeners on operations
// on type hierarchies
if (fHierarchy != null) {
fHierarchy.removeTypeHierarchyChangedListener(this);
DLTKCore.removeElementChangedListener(this);
}
if (hierachyCreationNeeded) {
fHierarchy = createTypeHierarchy(element, pm);
if (pm != null && pm.isCanceled()) {
throw new OperationCanceledException();
}
fInputElement = element;
} else {
fHierarchy.refresh(pm);
}
fHierarchy.addTypeHierarchyChangedListener(this);
DLTKCore.addElementChangedListener(this);
fHierarchyRefreshNeeded = false;
}
@Override
public void typeHierarchyChanged(ITypeHierarchy typeHierarchy) {
fHierarchyRefreshNeeded = true;
fireChange(null);
}
@Override
public void elementChanged(ElementChangedEvent event) {
if (fChangeListeners.isEmpty()) {
return;
}
if (fHierarchyRefreshNeeded) {
return;
}
ArrayList<IType> changedTypes = new ArrayList<>();
processDelta(event.getDelta(), changedTypes);
if (changedTypes.size() > 0) {
fireChange(changedTypes.toArray(new IType[changedTypes.size()]));
}
}
/*
* Assume that the hierarchy is intact (no refresh needed)
*/
private void processDelta(IModelElementDelta delta,
ArrayList<IType> changedTypes) {
IModelElement element = delta.getElement();
switch (element.getElementType()) {
case IModelElement.TYPE:
processTypeDelta((IType) element, changedTypes);
processChildrenDelta(delta, changedTypes); // (inner types)
break;
case IModelElement.SCRIPT_MODEL:
case IModelElement.SCRIPT_PROJECT:
case IModelElement.PROJECT_FRAGMENT:
processChildrenDelta(delta, changedTypes);
break;
case IModelElement.SOURCE_MODULE:
ISourceModule cu = (ISourceModule) element;
if (!ScriptModelUtil.isPrimary(cu)) {
return;
}
if (delta.getKind() == IModelElementDelta.CHANGED
&& isPossibleStructuralChange(delta.getFlags())) {
try {
if (cu.exists()) {
IType[] types = cu.getAllTypes();
for (int i = 0; i < types.length; i++) {
processTypeDelta(types[i], changedTypes);
}
}
} catch (ModelException e) {
DLTKUIPlugin.log(e);
}
} else {
processChildrenDelta(delta, changedTypes);
}
break;
}
}
private boolean isPossibleStructuralChange(int flags) {
return (flags & (IModelElementDelta.F_CONTENT
| IModelElementDelta.F_FINE_GRAINED)) == IModelElementDelta.F_CONTENT;
}
private void processTypeDelta(IType type, ArrayList<IType> changedTypes) {
if (getHierarchy().contains(type)) {
changedTypes.add(type);
}
}
private void processChildrenDelta(IModelElementDelta delta,
ArrayList<IType> changedTypes) {
IModelElementDelta[] children = delta.getAffectedChildren();
for (int i = 0; i < children.length; i++) {
processDelta(children[i], changedTypes); // recursive
}
}
}