content: /*******************************************************************************
   * Copyright (c) 2000, 2008 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
   *
   * Contributors:
   *     IBM Corporation - initial API and implementation
   *******************************************************************************/
  package org.eclipse.wst.jsdt.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.jface.operation.IRunnableContext;
  import org.eclipse.jface.operation.IRunnableWithProgress;
  import org.eclipse.wst.jsdt.core.ElementChangedEvent;
  import org.eclipse.wst.jsdt.core.IClassFile;
  import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
  import org.eclipse.wst.jsdt.core.IElementChangedListener;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.IJavaScriptElementDelta;
  import org.eclipse.wst.jsdt.core.IJavaScriptProject;
  import org.eclipse.wst.jsdt.core.IPackageFragment;
  import org.eclipse.wst.jsdt.core.IPackageFragmentRoot;
  import org.eclipse.wst.jsdt.core.IRegion;
  import org.eclipse.wst.jsdt.core.IType;
  import org.eclipse.wst.jsdt.core.ITypeHierarchy;
  import org.eclipse.wst.jsdt.core.ITypeHierarchyChangedListener;
  import org.eclipse.wst.jsdt.core.JavaScriptCore;
  import org.eclipse.wst.jsdt.core.JavaScriptModelException;
  import org.eclipse.wst.jsdt.internal.corext.util.JavaModelUtil;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  
  /**
   * 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 IJavaScriptElement fInputElement;
  	private boolean fIsSuperTypesOnly;
  	
  	private List 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 IJavaScriptElement getInputElement() {
  		return fInputElement;
  	}
  	
  	
  	public void freeHierarchy() {
  		if (fHierarchy != null) {
  			fHierarchy.removeTypeHierarchyChangedListener(this);
  			JavaScriptCore.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= (ITypeHierarchyLifeCycleListener) fChangeListeners.get(i);
  			curr.typeHierarchyChanged(this, changedTypes);
  		}
  	}
  			
  	public void ensureRefreshedTypeHierarchy(final IJavaScriptElement 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= new IRunnableWithProgress() {
  				public void run(IProgressMonitor pm) throws InvocationTargetException, InterruptedException {
  					try {
  						doHierarchyRefresh(element, pm);
  					} catch (JavaScriptModelException e) {
  						throw new InvocationTargetException(e);
  					} catch (OperationCanceledException e) {
  						throw new InterruptedException();
  					}
  				}
  			};
  			fHierarchyRefreshNeeded= true;
  			context.run(true, true, op);
  			fHierarchyRefreshNeeded= false;
  		}
  	}
  	
  	private ITypeHierarchy createTypeHierarchy(IJavaScriptElement element, IProgressMonitor pm) throws JavaScriptModelException {
  		if (element.getElementType() == IJavaScriptElement.TYPE) {
  			IType type= (IType) element;
  			if (fIsSuperTypesOnly) {
  				return type.newSupertypeHierarchy(pm);
  			} else {
  				return type.newTypeHierarchy(pm);
  			}
  		} else {
  			IRegion region= JavaScriptCore.newRegion();
  			if (element.getElementType() == IJavaScriptElement.JAVASCRIPT_PROJECT) {
  				// for projects only add the contained source folders
  				IPackageFragmentRoot[] roots= ((IJavaScriptProject) element).getPackageFragmentRoots();
  				for (int i= 0; i < roots.length; i++) {
  					if (!roots[i].isExternal()) {
  						region.add(roots[i]);
  					}
  				}
  			} else if (element.getElementType() == IJavaScriptElement.PACKAGE_FRAGMENT) {
  				IPackageFragmentRoot[] roots= element.getJavaScriptProject().getPackageFragmentRoots();
  				String name= element.getElementName();
  				for (int i= 0; i < roots.length; i++) {
  					IPackageFragment pack= roots[i].getPackageFragment(name);
  					if (pack.exists()) {
  						region.add(pack);
  					}
  				}
  			} else {
  				region.add(element);
  			}
  			IJavaScriptProject jproject= element.getJavaScriptProject();
  			return jproject.newTypeHierarchy(region, pm);
  		}
  	}
  	
  	
  	public synchronized void doHierarchyRefresh(IJavaScriptElement element, IProgressMonitor pm) throws JavaScriptModelException {
  		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);
  			JavaScriptCore.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);
  		JavaScriptCore.addElementChangedListener(this);
  		fHierarchyRefreshNeeded= false;
  	}		
  	
  	/*
  	 * @see ITypeHierarchyChangedListener#typeHierarchyChanged
  	 */
  	public void typeHierarchyChanged(ITypeHierarchy typeHierarchy) {
  	 	fHierarchyRefreshNeeded= true;
   		fireChange(null);
  	}		
  
  	/*
  	 * @see IElementChangedListener#elementChanged(ElementChangedEvent)
  	 */
  	public void elementChanged(ElementChangedEvent event) {
  		if (fChangeListeners.isEmpty()) {
  			return;
  		}
  		
  		if (fHierarchyRefreshNeeded) {
  			return;
  		} else {
  			ArrayList changedTypes= new ArrayList();
  			processDelta(event.getDelta(), changedTypes);
  			if (changedTypes.size() > 0) {
  				fireChange((IType[]) changedTypes.toArray(new IType[changedTypes.size()]));
  			}
  		}
  	}
  	
  	/*
  	 * Assume that the hierarchy is intact (no refresh needed)
  	 */					
  	private void processDelta(IJavaScriptElementDelta delta, ArrayList changedTypes) {
  		IJavaScriptElement element= delta.getElement();
  		switch (element.getElementType()) {
  			case IJavaScriptElement.TYPE:
  				processTypeDelta((IType) element, changedTypes);
  				processChildrenDelta(delta, changedTypes); // (inner types)
  				break;
  			case IJavaScriptElement.JAVASCRIPT_MODEL:
  			case IJavaScriptElement.JAVASCRIPT_PROJECT:
  			case IJavaScriptElement.PACKAGE_FRAGMENT_ROOT:
  			case IJavaScriptElement.PACKAGE_FRAGMENT:
  				processChildrenDelta(delta, changedTypes);
  				break;
  			case IJavaScriptElement.JAVASCRIPT_UNIT:
  				IJavaScriptUnit cu= (IJavaScriptUnit)element;
  				if (!JavaModelUtil.isPrimary(cu)) {
  					return;
  				}
  				
  				if (delta.getKind() == IJavaScriptElementDelta.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 (JavaScriptModelException e) {
  						JavaScriptPlugin.log(e);
  					}
  				} else {
  					processChildrenDelta(delta, changedTypes);
  				}
  				break;
  			case IJavaScriptElement.CLASS_FILE:	
  				if (delta.getKind() == IJavaScriptElementDelta.CHANGED) {
  					IType type= ((IClassFile) element).getType();
  					processTypeDelta(type, changedTypes);
  				} else {
  					processChildrenDelta(delta, changedTypes);
  				}
  				break;				
  		}
  	}
  	
  	private boolean isPossibleStructuralChange(int flags) {
  		return (flags & (IJavaScriptElementDelta.F_CONTENT | IJavaScriptElementDelta.F_FINE_GRAINED)) == IJavaScriptElementDelta.F_CONTENT;
  	}
  	
  	private void processTypeDelta(IType type, ArrayList changedTypes) {
  		if (getHierarchy().contains(type)) {
  			changedTypes.add(type);
  		}
  	}
  	
  	private void processChildrenDelta(IJavaScriptElementDelta delta, ArrayList changedTypes) {
  		IJavaScriptElementDelta[] children= delta.getAffectedChildren();
  		for (int i= 0; i < children.length; i++) {
  			processDelta(children[i], changedTypes); // recursive
  		}
  	}
  	
  
  }
  