| /******************************************************************************* |
| * Copyright (c) 2000, 2017 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); |
| } else { |
| return type.newTypeHierarchy(pm); |
| } |
| } else { |
| 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; |
| } else { |
| 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 |
| } |
| } |
| |
| } |