/******************************************************************************* * 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 } } }