content: /*******************************************************************************
   * Copyright (c) 2000, 2013 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.packageview;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  
  import org.eclipse.core.resources.IFolder;
  import org.eclipse.core.resources.IProject;
  import org.eclipse.core.resources.IResource;
  import org.eclipse.core.resources.IResourceDelta;
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.IProgressMonitor;
  import org.eclipse.core.runtime.IStatus;
  import org.eclipse.core.runtime.Status;
  import org.eclipse.core.runtime.jobs.Job;
  import org.eclipse.jface.util.IPropertyChangeListener;
  import org.eclipse.jface.util.PropertyChangeEvent;
  import org.eclipse.jface.viewers.AbstractTreeViewer;
  import org.eclipse.jface.viewers.IBasicPropertyConstants;
  import org.eclipse.jface.viewers.ITreeContentProvider;
  import org.eclipse.jface.viewers.StructuredSelection;
  import org.eclipse.jface.viewers.TreeViewer;
  import org.eclipse.jface.viewers.Viewer;
  import org.eclipse.swt.widgets.Control;
  import org.eclipse.swt.widgets.Display;
  import org.eclipse.swt.widgets.TreeItem;
  import org.eclipse.swt.widgets.Widget;
  import org.eclipse.ui.IWorkingSet;
  import org.eclipse.ui.progress.UIJob;
  import org.eclipse.wst.jsdt.core.ElementChangedEvent;
  import org.eclipse.wst.jsdt.core.IClassFile;
  import org.eclipse.wst.jsdt.core.IElementChangedListener;
  import org.eclipse.wst.jsdt.core.IIncludePathAttribute;
  import org.eclipse.wst.jsdt.core.IIncludePathEntry;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.IJavaScriptElementDelta;
  import org.eclipse.wst.jsdt.core.IJavaScriptModel;
  import org.eclipse.wst.jsdt.core.IJavaScriptProject;
  import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
  import org.eclipse.wst.jsdt.core.IPackageFragment;
  import org.eclipse.wst.jsdt.core.IPackageFragmentRoot;
  import org.eclipse.wst.jsdt.core.IParent;
  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;
  import org.eclipse.wst.jsdt.internal.ui.navigator.ContainerFolder;
  import org.eclipse.wst.jsdt.internal.ui.navigator.deferred.ClearPlaceHolderJob;
  import org.eclipse.wst.jsdt.internal.ui.navigator.deferred.LoadingModelNode;
  import org.eclipse.wst.jsdt.internal.ui.navigator.deferred.LoadingModelUIAnimationJob;
  import org.eclipse.wst.jsdt.internal.ui.workingsets.WorkingSetModel;
  import org.eclipse.wst.jsdt.ui.PreferenceConstants;
  import org.eclipse.wst.jsdt.ui.ProjectLibraryRoot;
  import org.eclipse.wst.jsdt.ui.StandardJavaScriptElementContentProvider;
   
  /**
   * Content provider for the PackageExplorer.
   * 
   * <p>
   * Since 2.1 this content provider can provide the children for flat or hierarchical
   * layout.
   * </p>
   * 
   * @see org.eclipse.wst.jsdt.ui.StandardJavaScriptElementContentProvider
   */
  public class PackageExplorerContentProvider extends StandardJavaScriptElementContentProvider implements ITreeContentProvider, IElementChangedListener, IPropertyChangeListener {
  	
  	protected static final int ORIGINAL= 0;
  	protected static final int PARENT= 1 << 0;
  	protected static final int GRANT_PARENT= 1 << 1;
  	protected static final int PROJECT= 1 << 2;
  	
  	private TreeViewer fViewer;
  	private Object fInput;
  	private boolean fIsFlatLayout;
  	private boolean fShowLibrariesNode;
  	private boolean fFoldPackages;
  	
  	private Collection fPendingUpdates;
  		
  	private UIJob fUpdateJob;
  
  	/**
  	 * Creates a new content provider for Java elements.
  	 * @param provideMembers if set, members of compilation units and class files are shown
  	 */
  	public PackageExplorerContentProvider(boolean provideMembers) {
  		super(provideMembers);
  		fShowLibrariesNode= false;
  		fIsFlatLayout= false;
  		fFoldPackages= arePackagesFoldedInHierarchicalLayout();
  		fPendingUpdates= null;
  		JavaScriptPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this);
  
  		fUpdateJob= null;
  	}
  	
  	private boolean arePackagesFoldedInHierarchicalLayout(){
  		return PreferenceConstants.getPreferenceStore().getBoolean(PreferenceConstants.APPEARANCE_FOLD_PACKAGES_IN_PACKAGE_EXPLORER);
  	}
  			
  	protected Object getViewerInput() {
  		return fInput;
  	}
  	
  	/* (non-Javadoc)
  	 * Method declared on IElementChangedListener.
  	 */
  	public void elementChanged(final ElementChangedEvent event) {
  		final ArrayList runnables= new ArrayList();
  		try {
  			// 58952 delete project does not update Package Explorer [package explorer] 
  			// if the input to the viewer is deleted then refresh to avoid the display of stale elements
  			if (inputDeleted(runnables))
  				return;
  
  			processDelta(event.getDelta(), runnables);
  		} catch (JavaScriptModelException e) {
  			JavaScriptPlugin.log(e);
  		} finally {	
  			executeRunnables(runnables);
  		}
  	}
  
  	protected final void executeRunnables(final Collection runnables) {
  
  		// now post all collected runnables
  		final Control ctrl= fViewer.getControl();
  		if (ctrl != null && !ctrl.isDisposed()) {
  			//Are we in the UIThread? If so spin it until we are done
  			if ((ctrl.getDisplay().getThread() == Thread.currentThread()) && !fViewer.isBusy()) {
  				runUpdates(runnables);
  			} else {
  				synchronized (this) {
  					if (fPendingUpdates == null) {
  						fPendingUpdates= runnables;
  					} else {
  						fPendingUpdates.addAll(runnables);
  					}
  				}
  				postAsyncUpdate(ctrl.getDisplay());
  			}
  		}
  	}
  	
  	private void postAsyncUpdate(final Display display) {
  		if (fUpdateJob == null) {
  			fUpdateJob= new UIJob(display, PackagesMessages.PackageExplorerContentProvider_update_job_description) {
  				public IStatus runInUIThread(IProgressMonitor monitor) {
  					TreeViewer viewer= fViewer;
  					if (viewer != null && viewer.isBusy()) {
  						schedule(100); // reschedule when viewer is busy: bug 184991
  					} else {
  						runPendingUpdates();
  					}
  					return Status.OK_STATUS;
  				}
  			};
  			fUpdateJob.setSystem(true);
  		}
  		fUpdateJob.schedule();
  	}         
  	
  	/**
  	 * Run all of the runnables that are the widget updates. Must be called in the display thread.
  	 */
  	public void runPendingUpdates() {
  		Collection pendingUpdates;
  		synchronized (this) {
  			pendingUpdates= fPendingUpdates;
  			fPendingUpdates= null;
  		}
  		if (pendingUpdates != null && fViewer != null) {
  			Control control = fViewer.getControl();
  			if (control != null && !control.isDisposed()) {
  				runUpdates(pendingUpdates);
  			}
  		}
  	}
  	
  	private void runUpdates(Collection runnables) {
  		Iterator runnableIterator = runnables.iterator();
  		while (runnableIterator.hasNext()){
  			((Runnable) runnableIterator.next()).run();
  		}
  	}
  	
  
  	private boolean inputDeleted(Collection runnables) {
  		if (fInput == null)
  			return false;
  		if ((fInput instanceof IJavaScriptElement) && ((IJavaScriptElement) fInput).exists())
  			return false;
  		if ((fInput instanceof IResource) && ((IResource) fInput).exists())
  			return false;
  		if (fInput instanceof WorkingSetModel)
  			return false;
  		if (fInput instanceof IWorkingSet) // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=156239
  			return false;
  		postRefresh(fInput, ORIGINAL, fInput, runnables);
  		return true;
  	}
  
  	/* (non-Javadoc)
  	 * Method declared on IContentProvider.
  	 */
  	public void dispose() {
  		super.dispose();
  		JavaScriptCore.removeElementChangedListener(this);
  		JavaScriptPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this);
  	}
  
  	/* (non-Javadoc)
  	 * @see org.eclipse.wst.jsdt.ui.StandardJavaScriptElementContentProvider#getPackageFragmentRootContent(org.eclipse.wst.jsdt.core.IPackageFragmentRoot)
  	 */
  	protected Object[] getPackageFragmentRootContent(IPackageFragmentRoot root) throws JavaScriptModelException {
  		if (fIsFlatLayout) {
  			return super.getPackageFragmentRootContent(root);
  		}
  		
  		// hierarchical package mode
  		ArrayList result= new ArrayList();
  		getHierarchicalPackageChildren(root, null, result);
  		if (!isProjectPackageFragmentRoot(root)) {
  			Object[] nonJavaResources= root.getNonJavaScriptResources();
  			for (int i= 0; i < nonJavaResources.length; i++) {
  				result.add(nonJavaResources[i]);
  			}
  		}
  		return result.toArray();
  	}
  	
  	/* (non-Javadoc)
  	 * @see org.eclipse.wst.jsdt.ui.StandardJavaScriptElementContentProvider#getPackageContent(org.eclipse.wst.jsdt.core.IPackageFragment)
  	 */
  	protected Object[] getPackageContent(IPackageFragment fragment) throws JavaScriptModelException {
  		if (fIsFlatLayout) {
  			return super.getPackageContent(fragment);
  		}
  		
  		// hierarchical package mode
  		ArrayList result= new ArrayList();
  		
  		getHierarchicalPackageChildren((IPackageFragmentRoot) fragment.getParent(), fragment, result);
  		Object[] nonPackages= super.getPackageContent(fragment);
  		if (result.isEmpty())
  			return nonPackages;
  		for (int i= 0; i < nonPackages.length; i++) {
  			result.add(nonPackages[i]);
  		}
  		return result.toArray();
  	}
  	
  	/* (non-Javadoc)
  	 * @see org.eclipse.wst.jsdt.ui.StandardJavaScriptElementContentProvider#getFolderContent(org.eclipse.core.resources.IFolder)
  	 */
  	protected Object[] getFolderContent(IFolder folder) throws CoreException {
  		if (fIsFlatLayout) {
  			return super.getFolderContent(folder);
  		}
  		
  		// hierarchical package mode
  		ArrayList result= new ArrayList();
  		
  		getHierarchicalPackagesInFolder(folder, result);
  		Object[] others= super.getFolderContent(folder);
  		if (result.isEmpty())
  			return others;
  		for (int i= 0; i < others.length; i++) {
  			result.add(others[i]);
  		}
  		return result.toArray();
  	}
  	
  	
  	public Object[] getChildren(Object parentElement) {
  		try {
  			if (parentElement instanceof IJavaScriptModel) 
  				return concatenate(getJavaProjects((IJavaScriptModel)parentElement), getNonJavaProjects((IJavaScriptModel)parentElement));
  
  			if(parentElement instanceof ContainerFolder) {
  				return getContainerPackageFragmentRoots((PackageFragmentRootContainer)((ContainerFolder)parentElement).getParentObject());
  			}
  			if (parentElement instanceof PackageFragmentRootContainer) {
  				if(LoadingModelNode.isBeingLoaded((PackageFragmentRootContainer)parentElement)) {
  					return new Object[] { 
  							LoadingModelNode.createPlaceHolder((PackageFragmentRootContainer)parentElement) }; 
  				} else { 
  					LoadingModelNode placeHolder = 
  						LoadingModelNode.createPlaceHolder((PackageFragmentRootContainer)parentElement);
  					/* we need to load the model, 
  						possible long running operation */					
  					if(LoadingModelNode.canBeginLoading((PackageFragmentRootContainer)parentElement))
  						new LoadModelJob((AbstractTreeViewer)fViewer, 
  								 placeHolder, 
  								 (PackageFragmentRootContainer)parentElement)
  							.schedule();
  						return new Object[] { placeHolder }; 
  				}
  			}
  			else if (parentElement instanceof NamespaceGroup && ((NamespaceGroup) parentElement).getPackageFragmentRootContainer() != null) {
  				return getContainerPackageFragmentRoots(((NamespaceGroup) parentElement).getPackageFragmentRootContainer(), true, ((NamespaceGroup) parentElement));
  			}
  			
  			if(parentElement instanceof ProjectLibraryRoot) {
  //				return ((ProjectLibraryRoot)parentElement).getChildren();
  				// Include source folders (and also scour their model contents)
  				Object[] children1 = ((ProjectLibraryRoot) parentElement).getChildren();
  				List sourceRoots = new ArrayList();
  				try {
  					IPackageFragmentRoot[] packageFragmentRoots = ((ProjectLibraryRoot) parentElement).getProject().getPackageFragmentRoots();
  					for (int i = 0; i < packageFragmentRoots.length; i++) {
  						IIncludePathEntry entry = packageFragmentRoots[i].getRawIncludepathEntry();
  						if (IIncludePathEntry.CPE_SOURCE == entry.getEntryKind()) {
  							boolean hidden = false;
  							IIncludePathAttribute[] attribs = entry.getExtraAttributes();
  							for (int k = 0; !hidden && attribs != null && k < attribs.length; k++) {
  								hidden |= (attribs[k] == IIncludePathAttribute.HIDE);
  							}
  							if (!hidden) {
  								sourceRoots.add(packageFragmentRoots[i]);
  							}
  						}
  					}
  				}
  				catch (JavaScriptModelException e) {
  					e.printStackTrace();
  				}
  				Object[] combined = new Object[children1.length + sourceRoots.size()];
  				System.arraycopy(children1, 0, combined, 0, children1.length);
  				if (!sourceRoots.isEmpty()) {
  					System.arraycopy(sourceRoots.toArray(), 0, combined, children1.length, sourceRoots.size());
  				}
  				return combined;
  			}
  //			if (parentElement instanceof IPackageFragmentRoot) {
  //				Object[] children = super.getChildren(parentElement);
  //				for (int i = 0; i < children.length; i++) {
  //					// replace the "default package" with its contents
  //					if (children[i] instanceof IPackageFragment && ((IPackageFragment) children[i]).isDefaultPackage()) {
  //						List combined = new ArrayList(children.length);
  //						for (int j = 0; j < children.length; j++) {
  //							if (j != i) {
  //								combined.add(children[j]);
  //							}
  //							else {
  //								Object[] defaultChildren = super.getChildren(children[j]);
  //								for (int k = 0; k < defaultChildren.length; k++) {
  //									combined.add(defaultChildren[k]);
  //								}
  //							}
  //						}
  //						return combined.toArray();
  //					}
  //				}
  //				return children;
  //			}
  			if (parentElement instanceof IProject) {
  				IProject project= (IProject) parentElement;
  				if (project.isAccessible())
  					return project.members();
  				return NO_CHILDREN;
  			}			
  			if (parentElement instanceof IPackageFragmentRoot && ((IPackageFragmentRoot)parentElement).isVirtual()) {
  				return getLibraryChildren((IPackageFragmentRoot)parentElement, fIsFlatLayout, null);
  			}
  			else if (parentElement instanceof NamespaceGroup && ((NamespaceGroup) parentElement).getPackageFragmentRoot() != null && ((NamespaceGroup) parentElement).getPackageFragmentRoot().isVirtual()) {
  				return getLibraryChildren(((NamespaceGroup) parentElement).getPackageFragmentRoot(), true, ((NamespaceGroup) parentElement));
  			}
  			else if(parentElement instanceof IPackageFragmentRoot && IIncludePathEntry.CPE_SOURCE == ((IPackageFragmentRoot) parentElement).getRawIncludepathEntry().getEntryKind()) {
  				return getSourceChildren(parentElement, fIsFlatLayout, null);
  			}
  			else if (parentElement instanceof NamespaceGroup && ((NamespaceGroup) parentElement).getPackageFragmentRoot() != null && IIncludePathEntry.CPE_SOURCE == ((NamespaceGroup) parentElement).getPackageFragmentRoot().getRawIncludepathEntry().getEntryKind()) {
  				return getSourceChildren(((NamespaceGroup) parentElement).getPackageFragmentRoot(), true, ((NamespaceGroup) parentElement));
  			}
  			// if script unit
  			else if (parentElement instanceof IJavaScriptUnit) {
  				return getSourceChildren(parentElement, fIsFlatLayout, null);
  			}
  			// if group with script unit as parent
  			else if(parentElement instanceof NamespaceGroup && ((NamespaceGroup) parentElement).getJavaScriptUnit() != null) {
  				return getSourceChildren(((NamespaceGroup) parentElement).getJavaScriptUnit(), true, ((NamespaceGroup) parentElement));
  			}
  			return super.getChildren(parentElement);
  		} catch (CoreException e) {
  			e.printStackTrace();
  			return NO_CHILDREN;
  		}
  	}
  	
  	/* (non-Javadoc)
  	 * @see org.eclipse.wst.jsdt.ui.StandardJavaScriptElementContentProvider#hasChildren(java.lang.Object)
  	 */
  	public boolean hasChildren(Object element) {
  		if (element instanceof JsGlobalScopeContainer) {
  			return true;//((JsGlobalScopeContainer) element).hasChildren();
  		}
  		if (element instanceof ProjectLibraryRoot) {
  			return ((ProjectLibraryRoot) element).hasChildren();
  		}
  		return super.hasChildren(element);
  	}
  
  	private Object[] getSourceChildren(Object parentElement, boolean neverGroup, NamespaceGroup onlyGroup) throws JavaScriptModelException {
  		/* if the parent is a fragment root use its children
  		 * else if parent element is a script unit, use it as the child
  		 */
  		Object[] rawChildren = new Object[0];
  		if(parentElement instanceof IPackageFragmentRoot) {
  			rawChildren = ((IParent) parentElement).getChildren();
  		} else if(parentElement instanceof IJavaScriptUnit) {
  			rawChildren = new Object[] { parentElement };
  		}
  		
  		if (rawChildren == null)
  			return new Object[0];
  
  		ArrayList allChildren = new ArrayList();
  		ArrayList expanded = new ArrayList();
  		expanded.addAll(Arrays.asList(rawChildren));
  
  		if (expanded.isEmpty())
  			return new Object[0];
  
  		Object next = expanded.remove(0);
  		Map groups = new HashMap();
  
  		while (next != null) {
  			if (next instanceof IPackageFragment) {
  				expanded.addAll(Arrays.asList(((IPackageFragment) next).getChildren()));
  			}
  			else if (next instanceof IPackageFragmentRoot) {
  				expanded.addAll(Arrays.asList(((IPackageFragmentRoot) next).getChildren()));
  			}
  			else if (next instanceof IClassFile || next instanceof IJavaScriptUnit) {
  				IJavaScriptElement[] filtered = filter(((IParent) next).getChildren());
  				List newChildren = Arrays.asList(filtered);
  				allChildren.removeAll(newChildren);
  				if (fIsFlatLayout || neverGroup) {
  					if (onlyGroup == null) {
  						allChildren.addAll(newChildren);
  					}
  					else {
  						for (int j = 0; j < filtered.length; j++) {
  							switch(filtered[j].getElementType()) {
  								case IJavaScriptElement.TYPE :
  								case IJavaScriptElement.FIELD :
  								case IJavaScriptElement.METHOD :
  								case IJavaScriptElement.INITIALIZER :
  								case IJavaScriptElement.LOCAL_VARIABLE : {
  									String displayName = filtered[j].getDisplayName();
  									int groupNamesEnd = displayName.lastIndexOf('.');
  									if (groupNamesEnd == onlyGroup.fNamePrefixLength && displayName.startsWith(onlyGroup.fNamePrefix)) {
  										allChildren.add(filtered[j]);
  									}
  									break;
  								}
  								default : {
  									allChildren.add(filtered[j]);
  								}
  							}
  						}
  					}
  				}
  				else {
  					for (int j = 0; j < filtered.length; j++) {
  						switch(filtered[j].getElementType()) {
  							case IJavaScriptElement.TYPE :
  							case IJavaScriptElement.FIELD :
  							case IJavaScriptElement.METHOD :
  							case IJavaScriptElement.INITIALIZER :
  							case IJavaScriptElement.LOCAL_VARIABLE : {
  								String displayName = filtered[j].getDisplayName();
  								int groupEnd = displayName.lastIndexOf('.');
  								if (groupEnd > 0) {
  									String groupName = displayName.substring(0, groupEnd);
  									if (!groups.containsKey(groupName)) {
  										// create the group based on the parent type
  										NamespaceGroup group = null;
  										if(parentElement instanceof IPackageFragmentRoot) {
  											group = new NamespaceGroup((IPackageFragmentRoot) parentElement, groupName);
  										} else if(parentElement instanceof IJavaScriptUnit) {
  											group = new NamespaceGroup((IJavaScriptUnit) parentElement, groupName);
  										}
  										
  										if(group != null) {
  											groups.put(groupName, group);
  											allChildren.add(group);
  										}
  									}
  								}
  								else {
  									allChildren.add(filtered[j]);
  								}
  
  								break;
  							}
  							default : {
  								allChildren.add(filtered[j]);
  							}
  						}
  					}
  				}
  			}
  
  			if (expanded.size() > 0)
  				next = expanded.remove(0);
  			else
  				next = null;
  
  		}
  
  		return allChildren.toArray();
  	}
  	
  	
  	private Object[] getLibraryChildren(IPackageFragmentRoot container, boolean neverGroup, NamespaceGroup onlyGroup) {		
  		Object[] children=null;
  		try {
  			children = container.getChildren();
  		} catch (JavaScriptModelException ex1) {
  			ex1.printStackTrace();
  		}
  		if(children==null) return null;
  		ArrayList allChildren = new ArrayList();
  		
  		Map groups = null;
  		if (!fIsFlatLayout) {
  			groups = new HashMap();
  		}
  	
  		boolean unique = false;
  		try {
  			while(!unique && children!=null && children.length>0) {
  				for(int i = 0;i<children.length;i++) {
  					String display1 = ((IJavaScriptElement)children[0]).getDisplayName();
  					String display2 = ((IJavaScriptElement)children[i]).getDisplayName();
  					if(!(   (display1==display2) || (display1!=null && display1.compareTo(display2)==0))){
  						allChildren.addAll(Arrays.asList(children));
  						unique=true;
  						break;
  					}
  				}
  				List more = new ArrayList();
  				for(int i = 0;!unique && i<children.length;i++) {
  					if(children[i] instanceof IPackageFragment) {
  						more.addAll(Arrays.asList(((IPackageFragment)children[i]).getChildren()));
  					}else if(children[i] instanceof IPackageFragmentRoot) {
  						more.addAll(Arrays.asList(((IPackageFragmentRoot)children[i]).getChildren()));
  					}else if(children[i] instanceof IClassFile || children[i] instanceof IJavaScriptUnit) {
  						IJavaScriptElement[] filtered = filter(((IParent) children[i]).getChildren());
  						List newChildren = Arrays.asList(filtered);
  						allChildren.removeAll(newChildren);
  						if (fIsFlatLayout || neverGroup) {
  							if (onlyGroup == null) {
  								allChildren.addAll(newChildren);
  							}
  							else {
  								for (int j = 0; j < filtered.length; j++) {
  									switch(filtered[j].getElementType()) {
  										case IJavaScriptElement.TYPE :
  										case IJavaScriptElement.FIELD :
  										case IJavaScriptElement.METHOD :
  										case IJavaScriptElement.INITIALIZER :
  										case IJavaScriptElement.LOCAL_VARIABLE : {
  											String displayName = filtered[j].getDisplayName();
  											int groupNamesEnd = displayName.lastIndexOf('.');
  											if (groupNamesEnd == onlyGroup.fNamePrefixLength && displayName.startsWith(onlyGroup.fNamePrefix)) {
  												allChildren.add(filtered[j]);
  											}
  											break;
  										}
  										default : {
  											allChildren.add(filtered[j]);
  										}
  									}
  								}
  							}
  						}
  						else {
  							for (int j = 0; j < filtered.length; j++) {
  								switch(filtered[j].getElementType()) {
  									case IJavaScriptElement.TYPE :
  									case IJavaScriptElement.FIELD :
  									case IJavaScriptElement.METHOD :
  									case IJavaScriptElement.INITIALIZER :
  									case IJavaScriptElement.LOCAL_VARIABLE : {
  										String displayName = filtered[j].getDisplayName();
  										int groupEnd = displayName.lastIndexOf('.');
  										if (groupEnd > 0) {
  											String groupName = displayName.substring(0, groupEnd);
  											if (!groups.containsKey(groupName)) {
  												NamespaceGroup group = new NamespaceGroup(container, groupName);
  												groups.put(groupName, group);
  												allChildren.add(group);
  											}
  										}
  										else {
  											allChildren.add(filtered[j]);
  										}
  
  										break;
  									}
  									default : {
  										allChildren.add(filtered[j]);
  									}
  								}
  							}
  						}
  					}
  					else {
  						/* bottomed out, now at javaScriptElement level */
  						unique=true;
  						break;
  					}
  					
  				}
  				if(!unique) children = more.toArray();
  			}
  		} catch (JavaScriptModelException ex) {
  			ex.printStackTrace();
  		}
  		
  		return allChildren.toArray();
  	}
  	/* (non-Javadoc)
  	 * @see org.eclipse.wst.jsdt.ui.StandardJavaScriptElementContentProvider#getPackageFragmentRoots(org.eclipse.wst.jsdt.core.IJavaScriptProject)
  	 */
  	protected Object[] getPackageFragmentRoots(IJavaScriptProject project) throws JavaScriptModelException {
  		if (!project.getProject().isOpen())
  			return NO_CHILDREN;
  			
  		List result= new ArrayList();
  
  		boolean addJARContainer= false;
  		ArrayList projectPackageFragmentRoots  = new ArrayList();
  		IPackageFragmentRoot[] roots= project.getPackageFragmentRoots();
  		for (int i= 0; i < roots.length; i++) {
  			IPackageFragmentRoot root= roots[i];
  			IIncludePathEntry classpathEntry= root.getRawIncludepathEntry();
  			
  			IIncludePathAttribute[] attribs = classpathEntry.getExtraAttributes();
  			boolean shouldHide = false;
  			for(int p = 0;p<attribs.length;p++){
  				if(attribs[p]==IIncludePathAttribute.HIDE) shouldHide = true;
  			}
  			
  			int entryKind= classpathEntry.getEntryKind();
  			if (entryKind == IIncludePathEntry.CPE_CONTAINER) {
  				// all JsGlobalScopeContainers are added later 
  			} else if (fShowLibrariesNode && (entryKind != IIncludePathEntry.CPE_SOURCE) && entryKind!=IIncludePathEntry.CPE_CONTAINER) {
  				addJARContainer= true;
  				projectPackageFragmentRoots.add(root);
  			} else {
  				if (isProjectPackageFragmentRoot(root)) {
  					// filter out package fragments that correspond to projects and
  					// replace them with the package fragments directly
  //					Object[] fragments= getPackageFragmentRootContent(root);
  //					for (int j= 0; j < fragments.length; j++) {
  //						result.add(fragments[j]);
  //					}
  				} /*else if(!shouldHide){
  					result.add(root);
  				}*/
  			}
  		}
  		
  		if (addJARContainer) {
  			projectPackageFragmentRoots.add(new LibraryContainer(project));
  		}
  		
  		// separate loop to make sure all containers are on the classpath
  		IIncludePathEntry[] rawClasspath= project.getRawIncludepath();
  		for (int i= 0; i < rawClasspath.length; i++) {
  			IIncludePathEntry classpathEntry= rawClasspath[i];
  			if (classpathEntry.getEntryKind() == IIncludePathEntry.CPE_CONTAINER) {
  				projectPackageFragmentRoots.add(new JsGlobalScopeContainer(project, classpathEntry));
  			}	
  		}	
  //		Object[] resources= project.getNonJavaScriptResources();
  //		for (int i= 0; i < resources.length; i++) {
  //			result.add(resources[i]);
  //		}
  		ProjectLibraryRoot projectLibs = new ProjectLibraryRoot(project);
  		result.add(0,projectLibs);
  		return result.toArray();
  	}
  	
  	public Object getParent(Object element) {
  		if (element instanceof NamespaceGroup) {
  			return ((NamespaceGroup)element).getParent();
  		}
  		if (element instanceof IPackageFragmentRoot) {
  			IJavaScriptProject project = (IJavaScriptProject) ((IPackageFragmentRoot) element).getAncestor(IJavaScriptElement.JAVASCRIPT_PROJECT);
  			if (project != null) {
  				return new ProjectLibraryRoot(project);
  			}
  		}
  		return super.getParent(element);
  	}
  
  //	private Object[] getContainerPackageFragmentRoots3(PackageFragmentRootContainer container) {
  //		Object[] children = container.getChildren();
  //		if(children==null) return null;
  //		ArrayList allChildren = new ArrayList();
  //		for(int i=0;i<children.length;i++) {
  //			try {
  //				allChildren.addAll(Arrays.asList(((IPackageFragmentRoot)children[i]).getChildren()));
  //			} catch (JavaScriptModelException ex) {
  //				
  //			}
  //		}
  //		return allChildren.toArray();
  //	}
  //	
  	private Object[] getContainerPackageFragmentRoots(PackageFragmentRootContainer container) {
  		return getContainerPackageFragmentRoots(container, false, null);
  	}
  	private Object[] getContainerPackageFragmentRoots(PackageFragmentRootContainer container, boolean neverGroup, NamespaceGroup onlyGroup) {
  		
  			Object[] children = container.getChildren();
  			if (children == null)
  				return new Object[0];
  			for (int i = 0; i < children.length; i++) {
  				/* if one of the children is not a JS model element, or is an archive, return as-is for further navigation */
  				if (!(children[i] instanceof IJavaScriptElement) || (((IJavaScriptElement) children[i]).getElementType() == IJavaScriptElement.PACKAGE_FRAGMENT_ROOT && ((IPackageFragmentRoot) children[i]).isArchive()))
  					return children;
  			}
  			
  			ArrayList allChildren = new ArrayList();
  			ArrayList expanded = new ArrayList();
  			expanded.addAll(Arrays.asList(children));
  			
  			
  			if(expanded.isEmpty()) return new Object[0];
  			
  			Map groups = null;
  			if (!fIsFlatLayout) {
  				groups = new HashMap();
  			}
  		
  			Object next = expanded.remove(0);
  
  			while(next!=null) {
  				try {
  					if(next instanceof IPackageFragment) {
  						expanded.addAll(Arrays.asList(((IPackageFragment)next).getChildren()));
  					}else if(next instanceof IPackageFragmentRoot) {
  						expanded.addAll(Arrays.asList(((IPackageFragmentRoot)next).getChildren()));
  					}else if(next instanceof IClassFile || next instanceof IJavaScriptUnit) {
  						IJavaScriptElement[] filtered = filter(((IParent) next).getChildren());
  						List newChildren = Arrays.asList(filtered);
  						allChildren.removeAll(newChildren);
  						if (fIsFlatLayout || neverGroup) {
  							if (onlyGroup == null) {
  								allChildren.addAll(newChildren);
  							}
  							else {
  								for (int j = 0; j < filtered.length; j++) {
  									switch(filtered[j].getElementType()) {
  										case IJavaScriptElement.TYPE :
  										case IJavaScriptElement.FIELD :
  										case IJavaScriptElement.METHOD :
  										case IJavaScriptElement.INITIALIZER :
  										case IJavaScriptElement.LOCAL_VARIABLE : {
  											String displayName = filtered[j].getDisplayName();
  											int groupNamesEnd = displayName.lastIndexOf('.');
  											if (groupNamesEnd == onlyGroup.fNamePrefixLength && displayName.startsWith(onlyGroup.fNamePrefix)) {
  												allChildren.add(filtered[j]);
  											}
  											break;
  										}
  										default : {
  											allChildren.add(filtered[j]);
  										}
  									}
  								}
  							}
  						}
  						else {
  							for (int j = 0; j < filtered.length; j++) {
  								switch(filtered[j].getElementType()) {
  									case IJavaScriptElement.TYPE :
  									case IJavaScriptElement.FIELD :
  									case IJavaScriptElement.METHOD :
  									case IJavaScriptElement.INITIALIZER :
  									case IJavaScriptElement.LOCAL_VARIABLE : {
  										String displayName = filtered[j].getDisplayName();
  										int groupEnd = displayName.lastIndexOf('.');
  										if (groupEnd > 0) {
  											String groupName = displayName.substring(0, groupEnd);
  											if (!groups.containsKey(groupName)) {
  												NamespaceGroup group = new NamespaceGroup(container, groupName);
  												groups.put(groupName, group);
  												allChildren.add(group);
  											}
  										}
  										else {
  											allChildren.add(filtered[j]);
  										}
  
  										break;
  									}
  									default : {
  										allChildren.add(filtered[j]);
  									}
  								}
  							}
  						}
  					}
  					else {
  						allChildren.add(next);
  					}
  				} catch (JavaScriptModelException ex) {
  					ex.printStackTrace();
  				}
  				
  				if(expanded.size()>0) 
  					next = expanded.remove(0);
  				else
  					next = null;
  				
  			}
  			
  			return allChildren.toArray();
  		
  		
  		
  	}
  	private Object[] getContainerPackageFragmentRootsDeprc(PackageFragmentRootContainer container, boolean createFolder) {
  		
  		
  		if(container!=null) {	
  			
  			Object[] children = container.getChildren();
  			if(children==null) return null;
  			ArrayList allChildren = new ArrayList();
  			
  			boolean unique = false;
  			
  				while(!unique && children!=null && children.length>0) {
  					String display1=null;
  					for(int i = 0;i<children.length;i++) {
  						display1 = ((IJavaScriptElement)children[0]).getDisplayName();
  						String display2 = ((IJavaScriptElement)children[i]).getDisplayName();
  						if(!(   (display1==display2) || (display1!=null && display1.compareTo(display2)==0))){
  							allChildren.addAll(Arrays.asList(children));
  							unique=true;
  							break;
  						}
  					}
  					if(!unique && createFolder) {
  						ContainerFolder folder = new ContainerFolder(display1, container);
  						return new Object[] {folder};
  					}
  					ArrayList more = new ArrayList();
  					for(int i = 0;!unique && i<children.length;i++) {
  					
  						try {
  							if(children[i] instanceof IPackageFragment) {
  								more.addAll(Arrays.asList(((IPackageFragment)children[i]).getChildren()));
  							}else if(children[i] instanceof IPackageFragmentRoot) {
  								more.addAll(Arrays.asList(((IPackageFragmentRoot)children[i]).getChildren()));
  							}else if(children[i] instanceof IClassFile) {
  								more.addAll(Arrays.asList( filter(((IClassFile)children[i]).getChildren())) );
  							}else if(children[i] instanceof IJavaScriptUnit) {
  								more.addAll(Arrays.asList( filter(((IJavaScriptUnit)children[i]).getChildren())) );
  							}else {
  								/* bottomed out, now at javaElement level */
  								unique=true;
  								break;
  							}
  						} catch (JavaScriptModelException ex) {
  							// TODO Auto-generated catch block
  							ex.printStackTrace();
  						}
  						
  					}
  					if(!unique) children = more.toArray();
  				}
  		
  			
  			
  			return allChildren.toArray();
  		}else {
  			return new Object[0];
  		}
  	}
  
  	private Object[] getNonJavaProjects(IJavaScriptModel model) throws JavaScriptModelException {
  		return model.getNonJavaScriptResources();
  	}
  
  	protected Object internalGetParent(Object element) {
  		if (!fIsFlatLayout && element instanceof IPackageFragment) {
  			return getHierarchicalPackageParent((IPackageFragment) element);
  		} else if (element instanceof IPackageFragmentRoot) {
  			// since we insert logical package containers we have to fix
  			// up the parent for package fragment roots so that they refer
  			// to the container and containers refer to the project
  			IPackageFragmentRoot root= (IPackageFragmentRoot)element;
  			
  			try {
  				IIncludePathEntry entry= root.getRawIncludepathEntry();
  				int entryKind= entry.getEntryKind();
  				if (entryKind == IIncludePathEntry.CPE_CONTAINER) {
  					return new JsGlobalScopeContainer(root.getJavaScriptProject(), entry);
  				} else if (fShowLibrariesNode && (entryKind == IIncludePathEntry.CPE_LIBRARY || entryKind == IIncludePathEntry.CPE_VARIABLE)) {
  					return new LibraryContainer(root.getJavaScriptProject());
  				}
  			} catch (JavaScriptModelException e) {
  				// fall through
  			}
  		} else if (element instanceof PackageFragmentRootContainer) {
  			return ((PackageFragmentRootContainer)element).getJavaProject();
  		}
  		return super.internalGetParent(element);
  	}
  
  	/* (non-Javadoc)
  	 * Method declared on IContentProvider.
  	 */
  	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
  		super.inputChanged(viewer, oldInput, newInput);
  		fViewer= (TreeViewer)viewer;
  		if (oldInput == null && newInput != null) {
  			JavaScriptCore.addElementChangedListener(this); 
  		} else if (oldInput != null && newInput == null) {
  			JavaScriptCore.removeElementChangedListener(this); 
  		}
  		fInput= newInput;
  	}
  
  	// hierarchical packages
  	/**
  	 * Returns the hierarchical packages inside a given fragment or root.
  	 * @param parent The parent package fragment root
  	 * @param fragment The package to get the children for or 'null' to get the children of the root.
  	 * @param result Collection where the resulting elements are added
  	 * @throws JavaScriptModelException
  	 */
  	private void getHierarchicalPackageChildren(IPackageFragmentRoot parent, IPackageFragment fragment, Collection result) throws JavaScriptModelException {
  		IJavaScriptElement[] children= parent.getChildren();
  		String prefix= fragment != null ? fragment.getElementName() + '/' : ""; //$NON-NLS-1$
  		if (prefix.length()==1)
  			prefix=""; //$NON-NLS-1$
  		int prefixLen= prefix.length();
  		for (int i= 0; i < children.length; i++) {
  			IPackageFragment curr= (IPackageFragment) children[i];
  			if (fragment==null)
  			{
  				if (curr.isDefaultPackage()) 
  					result.add(curr);
  			}
  			else
  			{
  				String name= curr.getElementName();
  				if (name.startsWith(prefix) && name.length() > prefixLen && name.indexOf('/', prefixLen) == -1) {
  					if (fFoldPackages) {
  						curr= getFolded(children, curr);
  					}
  					result.add(curr);
  				}
  			}
  
  		}
  	}
  	
  	/**
  	 * Returns the hierarchical packages inside a given folder.
  	 * @param folder The parent folder
  	 * @param result Collection where the resulting elements are added
  	 * @throws CoreException thrown when elements could not be accessed
  	 */
  	private void getHierarchicalPackagesInFolder(IFolder folder, Collection result) throws CoreException {
  		IResource[] resources= folder.members();
  		for (int i= 0; i < resources.length; i++) {
  			IResource resource= resources[i];
  			if (resource instanceof IFolder) {
  				IFolder curr= (IFolder) resource;
  				IJavaScriptElement element= JavaScriptCore.create(curr);
  				if (element instanceof IPackageFragment) {
  					if (element.exists()) {
  					if (fFoldPackages) {
  						IPackageFragment fragment= (IPackageFragment) element;
  						IPackageFragmentRoot root= (IPackageFragmentRoot) fragment.getParent();
  						element= getFolded(root.getChildren(), fragment);
  					}
  					result.add(element);	
  				} 
  			}	
  		}
  	}
  	}
  
  	public Object getHierarchicalPackageParent(IPackageFragment child) {
  		String name= child.getElementName();
  		IPackageFragmentRoot parent= (IPackageFragmentRoot) child.getParent();
  		int index= name.lastIndexOf('/');
  		if (index != -1) {
  			String realParentName= name.substring(0, index);
  			IPackageFragment element= parent.getPackageFragment(realParentName);
  			if (element.exists()) {
  				try {
  					if (fFoldPackages && isEmpty(element) && findSinglePackageChild(element, parent.getChildren()) != null) {
  						return getHierarchicalPackageParent(element);
  					}
  				} catch (JavaScriptModelException e) {
  					// ignore
  				}
  				return element;
  			} else { // bug 65240
  				IResource resource= element.getResource();
  				if (resource != null) {
  					return resource;
  				}
  			}
  		}
  		if (parent.getResource() instanceof IProject) {
  			return parent.getJavaScriptProject();
  		}
  		return parent;
  	}
  	
  	private static IPackageFragment getFolded(IJavaScriptElement[] children, IPackageFragment pack) throws JavaScriptModelException {
  		while (isEmpty(pack)) {
  			IPackageFragment collapsed= findSinglePackageChild(pack, children);
  			if (collapsed == null) {
  				return pack;
  			}
  			pack= collapsed;
  		}
  		return pack;
  	}
  		
  	private static boolean isEmpty(IPackageFragment fragment) throws JavaScriptModelException {
  		return !fragment.containsJavaResources() && fragment.getNonJavaScriptResources().length == 0;
  	}
  	
  	private static IPackageFragment findSinglePackageChild(IPackageFragment fragment, IJavaScriptElement[] children) {
  		String prefix= fragment.getElementName() + '/';
  		int prefixLen= prefix.length();
  		IPackageFragment found= null;
  		for (int i= 0; i < children.length; i++) {
  			IJavaScriptElement element= children[i];
  			String name= element.getElementName();
  			if (name.startsWith(prefix) && name.length() > prefixLen && name.indexOf('/', prefixLen) == -1) {
  				if (found == null) {
  					found= (IPackageFragment) element;
  				} else {
  					return null;
  				}
  			}
  		}
  		return found;
  	}
  	
  	// ------ delta processing ------
  
  	/**
  	 * Processes a delta recursively. When more than two children are affected the
  	 * tree is fully refreshed starting at this node.
  	 * 
  	 * @param delta the delta to process
  	 * @param runnables the resulting view changes as runnables (type {@link Runnable})
  	 * @return true is returned if the conclusion is to refresh a parent of an element. In that case no siblings need
  	 * to be processed
  	 * @throws JavaScriptModelException thrown when the access to an element failed
  	 */
  	private boolean processDelta(IJavaScriptElementDelta delta, Collection runnables) throws JavaScriptModelException {
  	
  		int kind= delta.getKind();
  		int flags= delta.getFlags();
  		IJavaScriptElement element= delta.getElement();
  		int elementType= element.getElementType();
  		
  		
  		if (elementType != IJavaScriptElement.JAVASCRIPT_MODEL && elementType != IJavaScriptElement.JAVASCRIPT_PROJECT) {
  			IJavaScriptProject proj= element.getJavaScriptProject();
  			if (proj == null || !proj.getProject().isOpen()) // TODO: Not needed if parent already did the 'open' check!
  				return false;	
  		}
  		
  		if (!fIsFlatLayout && elementType == IJavaScriptElement.PACKAGE_FRAGMENT) {
  			if (kind == IJavaScriptElementDelta.REMOVED) {
  				final Object parent = getHierarchicalPackageParent((IPackageFragment) element);
  				if (parent instanceof IPackageFragmentRoot) {
  					postRemove(element,  runnables);
  					return false;
  				} else {
  					postRefresh(internalGetParent(parent), GRANT_PARENT, element, runnables);
  					return true;
  				}
  			} else if (kind == IJavaScriptElementDelta.ADDED) {
  				final Object parent = getHierarchicalPackageParent((IPackageFragment) element);
  				if (parent instanceof IPackageFragmentRoot) {
  					postAdd(parent, element,  runnables);
  					return false;
  				} else {
  					postRefresh(internalGetParent(parent), GRANT_PARENT, element, runnables);
  					return true;
  				}
  			}
  			handleAffectedChildren(delta, element, runnables);
  			return false;
  		}
  		
  		if (elementType == IJavaScriptElement.JAVASCRIPT_UNIT) {
  			IJavaScriptUnit cu= (IJavaScriptUnit) element;
  			if (!JavaModelUtil.isPrimary(cu)) {
  				return false;
  			}
  						
  			if (!getProvideMembers() && cu.isWorkingCopy() && kind == IJavaScriptElementDelta.CHANGED) {
  				return false;
  			}
  			
  			if ((kind == IJavaScriptElementDelta.CHANGED) && !isStructuralCUChange(flags)) {
  				return false; // test moved ahead
  			}
  			
  			if (!isOnClassPath(cu)) { // TODO: isOnClassPath expensive! Should be put after all cheap tests
  				return false;
  			}
  			
  		}
  		
  		if (elementType == IJavaScriptElement.JAVASCRIPT_PROJECT) {
  			// handle open and closing of a project
  			if ((flags & (IJavaScriptElementDelta.F_CLOSED | IJavaScriptElementDelta.F_OPENED)) != 0) {			
  				postRefresh(element, ORIGINAL, element, runnables);
  				return false;
  			}
  			// if the raw class path has changed we refresh the entire project
  			if ((flags & IJavaScriptElementDelta.F_INCLUDEPATH_CHANGED) != 0) {
  				postRefresh(element, ORIGINAL, element, runnables);
  				if (element.getElementType() == IJavaScriptElement.JAVASCRIPT_PROJECT) {
  					postRefresh(((IJavaScriptProject)element).getProject(), ORIGINAL, ((IJavaScriptProject)element).getProject(), runnables);
  				}
  				return false;				
  			}
  			// refresh just like on removal
  			if (kind == IJavaScriptElementDelta.ADDED) {
  				postRefresh(element, PARENT, element, runnables);
  				return true;
  			}
  		}
  	
  		if (kind == IJavaScriptElementDelta.REMOVED) {
  			Object parent= internalGetParent(element);			
  			if (element instanceof IPackageFragment) {
  				// refresh package fragment root to allow filtering empty (parent) packages: bug 72923
  				if (fViewer.testFindItem(parent) != null)
  					postRefresh(parent, PARENT, element, runnables);
  				return true;
  			}
  			
  			postRemove(element, runnables);
  			if (parent instanceof IPackageFragment) 
  				postUpdateIcon((IPackageFragment)parent, runnables);
  			// we are filtering out empty subpackages, so we
  			// a package becomes empty we remove it from the viewer. 
  			if (isPackageFragmentEmpty(element.getParent())) {
  				if (fViewer.testFindItem(parent) != null)
  					postRefresh(internalGetParent(parent), GRANT_PARENT, element, runnables);
  				return true;
  			}  
  			return false;
  		}
  	
  		if (kind == IJavaScriptElementDelta.ADDED) { 
  			Object parent= internalGetParent(element);
  			// we are filtering out empty subpackages, so we
  			// have to handle additions to them specially. 
  			if (parent instanceof IPackageFragment) {
  				Object grandparent= internalGetParent(parent);
  				// 1GE8SI6: ITPJUI:WIN98 - Rename is not shown in Packages View
  				// avoid posting a refresh to an invisible parent
  				if (parent.equals(fInput)) {
  					postRefresh(parent, PARENT, element, runnables);
  				} else {
  					// refresh from grandparent if parent isn't visible yet
  					if (fViewer.testFindItem(parent) == null)
  						postRefresh(grandparent, GRANT_PARENT, element, runnables);
  					else {
  						postRefresh(parent, PARENT, element, runnables);
  					}	
  				}
  				return true;		
  			} else {  
  				postAdd(parent, element, runnables);
  			}
  		}
  	
  		if (elementType == IJavaScriptElement.JAVASCRIPT_UNIT) {
  			if (kind == IJavaScriptElementDelta.CHANGED) {
  				// isStructuralCUChange already performed above
  				postRefresh(element, ORIGINAL, element, runnables);
  				IResource underlyingResource = ((IJavaScriptUnit)element).getUnderlyingResource();
  				if(underlyingResource != null) {
  					postRefresh(underlyingResource, ORIGINAL, element, runnables);
  				}
  				updateSelection(delta, runnables);
  			}
  			return false;
  		}
  		// no changes possible in class files
  		if (elementType == IJavaScriptElement.CLASS_FILE)
  			return false;
  		
  		
  		if (elementType == IJavaScriptElement.PACKAGE_FRAGMENT_ROOT) {
  			// the contents of an external JAR has changed
  			if ((flags & IJavaScriptElementDelta.F_ARCHIVE_CONTENT_CHANGED) != 0) {
  				postRefresh(element, ORIGINAL, element, runnables);
  				return false;
  			}
  			// the source attachment of a JAR has changed
  			if ((flags & (IJavaScriptElementDelta.F_SOURCEATTACHED | IJavaScriptElementDelta.F_SOURCEDETACHED)) != 0)
  				postUpdateIcon(element, runnables);
  			
  			if (isClassPathChange(delta)) {
  				 // throw the towel and do a full refresh of the affected java project. 
  				postRefresh(element.getJavaScriptProject(), PROJECT, element, runnables);
  				return true;
  			}
  		}	
  		handleAffectedChildren(delta, element, runnables);
  		return false;
  	}
  	
  	private static boolean isStructuralCUChange(int flags) {
  		// No refresh on working copy creation (F_PRIMARY_WORKING_COPY)
  		return ((flags & IJavaScriptElementDelta.F_CHILDREN) != 0) || ((flags & (IJavaScriptElementDelta.F_CONTENT | IJavaScriptElementDelta.F_FINE_GRAINED)) == IJavaScriptElementDelta.F_CONTENT);
  	}
  	
  	/* package */ void handleAffectedChildren(IJavaScriptElementDelta delta, IJavaScriptElement element, Collection runnables) throws JavaScriptModelException {
  		int count= 0;
  		
  		IResourceDelta[] resourceDeltas= delta.getResourceDeltas();
  		if (resourceDeltas != null) {
  			for (int i= 0; i < resourceDeltas.length; i++) {
  				int kind= resourceDeltas[i].getKind();
  				if (kind == IResourceDelta.ADDED || kind == IResourceDelta.REMOVED) {
  					count++;
  				}
  			}
  		}
  		IJavaScriptElementDelta[] affectedChildren= delta.getAffectedChildren();
  		for (int i= 0; i < affectedChildren.length; i++) {
  			int kind= affectedChildren[i].getKind();
  			if (kind == IJavaScriptElementDelta.ADDED || kind == IJavaScriptElementDelta.REMOVED) {
  				count++;
  			}
  		}
  
  		if (count > 1) {
  			// more than one child changed, refresh from here downwards
  			if (element instanceof IPackageFragment) {
  				// a package fragment might become non empty refresh from the parent
  				IJavaScriptElement parent= (IJavaScriptElement) internalGetParent(element);
  				// 1GE8SI6: ITPJUI:WIN98 - Rename is not shown in Packages View
  				// avoid posting a refresh to an invisible parent
  				if (element.equals(fInput)) {
  					postRefresh(element, ORIGINAL, element, runnables);
  				} else {
  					postRefresh(parent, PARENT, element, runnables);
  				}
  			} else if (element instanceof IPackageFragmentRoot) {
  				Object toRefresh= internalGetParent(element);
  				postRefresh(toRefresh, ORIGINAL, toRefresh, runnables);
  			} else {
  				postRefresh(element, ORIGINAL, element, runnables);
  			}
  			return;
  		}
  		if (resourceDeltas != null) {
  			for (int i= 0; i < resourceDeltas.length; i++) {
  				if (processResourceDelta(resourceDeltas[i], element, runnables)) {
  					return; // early return, element got refreshed
  				}
  			}
  		}
  		for (int i= 0; i < affectedChildren.length; i++) {
  			if (processDelta(affectedChildren[i], runnables)) {
  				return; // early return, element got refreshed
  			}
  		}
  	}
  	
  	protected void processAffectedChildren(IJavaScriptElementDelta[] affectedChildren, Collection runnables) throws JavaScriptModelException {
  		for (int i= 0; i < affectedChildren.length; i++) {
  			processDelta(affectedChildren[i], runnables);
  		}
  	}
  
  	private boolean isOnClassPath(IJavaScriptUnit element) {
  		IJavaScriptProject project= element.getJavaScriptProject();
  		if (project == null || !project.exists())
  			return false;
  		return project.isOnIncludepath(element);
  	}
  
  	/**
  	 * Updates the selection. It finds newly added elements
  	 * and selects them.
  	 * @param delta the delta to process
  	 * @param runnables the resulting view changes as runnables (type {@link Runnable})
  	 */
  	private void updateSelection(IJavaScriptElementDelta delta, Collection runnables) {
  		final IJavaScriptElement addedElement= findAddedElement(delta);
  		if (addedElement != null) {
  			final StructuredSelection selection= new StructuredSelection(addedElement);
  			runnables.add(new Runnable() {
  				public void run() {
  					// 19431
  					// if the item is already visible then select it
  					if (fViewer.testFindItem(addedElement) != null)
  						fViewer.setSelection(selection);
  				}
  			});	
  		}	
  	}
  
  	private IJavaScriptElement findAddedElement(IJavaScriptElementDelta delta) {
  		if (delta.getKind() == IJavaScriptElementDelta.ADDED)  
  			return delta.getElement();
  		
  		IJavaScriptElementDelta[] affectedChildren= delta.getAffectedChildren();
  		for (int i= 0; i < affectedChildren.length; i++) 
  			return findAddedElement(affectedChildren[i]);
  			
  		return null;
  	}
  
  	/**
  	 * Updates the package icon
  	 * @param element the element to update
  	 * @param runnables the resulting view changes as runnables (type {@link Runnable})
  	 */
  	 private void postUpdateIcon(final IJavaScriptElement element, Collection runnables) {
  		 runnables.add(new Runnable() {
  			public void run() {
  				// 1GF87WR: ITPUI:ALL - SWTEx + NPE closing a workbench window.
  				fViewer.update(element, new String[]{IBasicPropertyConstants.P_IMAGE});
  			}
  		});
  	 }
  
  	/**
  	 * Process a resource delta.
  	 * 
  	 * @param delta the delta to process
  	 * @param parent the parent
  	 * @param runnables the resulting view changes as runnables (type {@link Runnable})
  	 * @return true if the parent got refreshed
  	 */
  	private boolean processResourceDelta(IResourceDelta delta, Object parent, Collection runnables) {
  		int status= delta.getKind();
  		int flags= delta.getFlags();
  		
  		IResource resource= delta.getResource();
  		// filter out changes affecting the output folder
  		if (resource == null)
  			return false;	
  			
  		// this could be optimized by handling all the added children in the parent
  		if ((status & IResourceDelta.REMOVED) != 0) {
  			if (parent instanceof IPackageFragment) {
  				// refresh one level above to deal with empty package filtering properly
  				postRefresh(internalGetParent(parent), PARENT, parent, runnables);
  				return true;
  			} else {
  				postRemove(resource, runnables);
  				return false;
  			}
  		}
  		if ((status & IResourceDelta.ADDED) != 0) {
  			if (parent instanceof IPackageFragment) {
  				// refresh one level above to deal with empty package filtering properly
  				postRefresh(internalGetParent(parent), PARENT, parent, runnables);	
  				return true;
  			} else
  			{
  				postAdd(parent, resource, runnables);
  				return false;
  			}
  		}
  		if ((status & IResourceDelta.CHANGED) != 0) {
  			if ((flags & IResourceDelta.TYPE) != 0) {
  				postRefresh(parent, PARENT, resource, runnables);
  				return true;
  			}
  		}
  		// open/close state change of a project
  		if ((flags & IResourceDelta.OPEN) != 0) {
  			postProjectStateChanged(internalGetParent(parent), runnables);
  			return true;		
  		}
  		IResourceDelta[] resourceDeltas= delta.getAffectedChildren();
  		 
  		int count= 0;
  		for (int i= 0; i < resourceDeltas.length; i++) {
  			int kind= resourceDeltas[i].getKind();
  			if (kind == IResourceDelta.ADDED || kind == IResourceDelta.REMOVED) {
  				count++;
  				if (count > 1) {
  					postRefresh(parent, PARENT, resource, runnables);
  					return true;
  				}
  			}
  		}	
  		for (int i= 0; i < resourceDeltas.length; i++) {
  			if (processResourceDelta(resourceDeltas[i], resource, runnables)) {
  				return false; // early return, element got refreshed
  			}
  		}
  		return false;
  	}
  	
  	public void setIsFlatLayout(boolean state) {
  		fIsFlatLayout= state;
  	}
  	
  	public void setShowLibrariesNode(boolean state) {
  		fShowLibrariesNode= state;
  	}
  	
  	private void postRefresh(Object root, int relation, Object affectedElement, Collection runnables) {
  		// JFace doesn't refresh when object isn't part of the viewer
  		// Therefore move the refresh start down to the viewer's input
  		if (isParent(root, fInput)) 
  			root= fInput;
  		List toRefresh= new ArrayList(1);
  		toRefresh.add(root);
  		augmentElementToRefresh(toRefresh, relation, affectedElement);
  		postRefresh(toRefresh, true, runnables);
  	}
  	
  	/**
  	 * Can be implemented by subclasses to add additional elements to refresh
  	 * 
  	 * @param toRefresh the elements to refresh
  	 * @param relation the relation to the affected element ({@link #GRANT_PARENT}, {@link #PARENT}, {@link #ORIGINAL}, {@link #PROJECT})
  	 * @param affectedElement the affected element
  	 */
  	protected void augmentElementToRefresh(List toRefresh, int relation, Object affectedElement) {
  	}
  
  	private boolean isParent(Object root, Object child) {
  		Object parent= getParent(child);
  		if (parent == null)
  			return false;
  		if (parent.equals(root))
  			return true;
  		return isParent(root, parent);
  	}
  
  	protected void postRefresh(final List toRefresh, final boolean updateLabels, Collection runnables) {
  		runnables.add(new Runnable() {
  			public void run() {
  				for (Iterator iter= toRefresh.iterator(); iter.hasNext();) {
  					fViewer.refresh(iter.next(), updateLabels);
  				}
  			}
  		});
  	}
  
  	protected void postAdd(final Object parent, final Object element, Collection runnables) {
  		runnables.add(new Runnable() {
  			public void run() {
  				Widget[] items= fViewer.testFindItems(element);
  				for (int i= 0; i < items.length; i++) {
  					Widget item= items[i];
  					if (item instanceof TreeItem && !item.isDisposed()) {
  						TreeItem parentItem= ((TreeItem) item).getParentItem();
  						if (parentItem != null && !parentItem.isDisposed() && parent.equals(parentItem.getData())) {
  							return; // no add, element already added (most likely by a refresh)
  						}
  					}
  				}
  				fViewer.add(parent, element);
  			}
  		});
  	}
  
  	protected void postRemove(final Object element, Collection runnables) {
  		runnables.add(new Runnable() {
  			public void run() {
  				fViewer.remove(element);
  			}
  		});
  	}
  
  	protected void postProjectStateChanged(final Object root, Collection runnables) {
  		runnables.add(new Runnable() {
  			public void run() {
  				fViewer.refresh(root, true);
  				// trigger a synthetic selection change so that action refresh their
  				// enable state.
  				fViewer.setSelection(fViewer.getSelection());
  			}
  		});
  	}
  	
  	
  	/*
  	 * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
  	 */
  	public void propertyChange(PropertyChangeEvent event) {
  		if (arePackagesFoldedInHierarchicalLayout() != fFoldPackages){
  			fFoldPackages= arePackagesFoldedInHierarchicalLayout();
  			if (fViewer != null && !fViewer.getControl().isDisposed()) {
  				fViewer.getControl().setRedraw(false);
  				Object[] expandedObjects= fViewer.getExpandedElements();
  				fViewer.refresh();	
  				fViewer.setExpandedElements(expandedObjects);
  				fViewer.getControl().setRedraw(true);
  			}
  		}
  	}
  	
  	// inner class for deferred model loading
  	public class LoadModelJob extends Job {
  		 
  		private LoadingModelNode placeHolder;
  		private AbstractTreeViewer viewer;
  		private PackageFragmentRootContainer packageFragmentRootContainer;
  	
  		public LoadModelJob(AbstractTreeViewer viewer, LoadingModelNode placeHolder, PackageFragmentRootContainer packageFragmentRootContainer) {
  			super(placeHolder.getText());
  			this.viewer = viewer;
  			this.placeHolder = placeHolder;
  			this.packageFragmentRootContainer = packageFragmentRootContainer;
  		}
  	
  		protected IStatus run(IProgressMonitor monitor) { 
  	
  			LoadingModelUIAnimationJob updateUIJob = new LoadingModelUIAnimationJob(viewer, placeHolder);
  			updateUIJob.schedule();
  	
  			Object[] retVal = new Object[0];
  			try {
  				// Load the model in the background after starting the animation job
  				
  				retVal = getContainerPackageFragmentRoots(packageFragmentRootContainer, fIsFlatLayout, null);
  				
  			} finally { 
  				/* dispose of the place holder, causes the termination of the animation job */
  				placeHolder.dispose();
  				new ClearPlaceHolderJob(viewer, placeHolder, packageFragmentRootContainer, retVal).schedule();
  			}
  			
  			return Status.OK_STATUS;
  		}
  	
  	}
  }
  