/*******************************************************************************
 * Copyright (c) 2000, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Terry Parker <tparker@google.com> - DeltaProcessor exhibits O(N^2) behavior, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=354332
 *     Terry Parker <tparker@google.com> - DeltaProcessor misses state changes in archive files, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=357425
 *     Terry Parker <tparker@google.com> - [performance] Low hit rates in JavaModel caches - https://bugs.eclipse.org/421165
 *******************************************************************************/
package org.eclipse.jdt.internal.core;

import java.io.File;
import java.net.URL;
import java.util.*;

import org.eclipse.core.resources.IBuildConfiguration;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.SourceElementParser;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.core.JavaModelManager.PerProjectInfo;
import org.eclipse.jdt.internal.core.builder.JavaBuilder;
import org.eclipse.jdt.internal.core.hierarchy.TypeHierarchy;
import org.eclipse.jdt.internal.core.search.AbstractSearchScope;
import org.eclipse.jdt.internal.core.search.JavaWorkspaceScope;
import org.eclipse.jdt.internal.core.search.indexing.IndexManager;
import org.eclipse.jdt.internal.core.util.Util;

/**
 * This class is used by <code>JavaModelManager</code> to convert
 * <code>IResourceDelta</code>s into <code>IJavaElementDelta</code>s.
 * It also does some processing on the <code>JavaElement</code>s involved
 * (e.g. closing them or updating classpaths).
 * <p>
 * High level summary of what the delta processor does:
 * <ul>
 * <li>reacts to resource deltas</li>
 * <li>fires corresponding Java element deltas</li>
 * <li>deltas also contain non-Java resources changes</li>
 * <li>updates the model to reflect the Java element changes</li>
 * <li>notifies type hierarchies of the changes</li>
 * <li>triggers indexing of the changed elements</li>
 * <li>refresh external archives (delta, model update, indexing)</li>
 * <li>is thread safe (one delta processor instance per thread, see DeltaProcessingState#resourceChanged(...))</li>
 * <li>handles .classpath changes (updates package fragment roots, update project references, validate classpath (.classpath format,
 * 		resolved classpath, cycles))</li>
 * </ul>
 */
public class DeltaProcessor {

	/*
	 * An object to hold information about a project's output folders (where .class files are generated).
	 */
	static class OutputsInfo {
		int outputCount;
		IPath[] paths;
		int[] traverseModes;
		OutputsInfo(IPath[] paths, int[] traverseModes, int outputCount) {
			this.paths = paths;
			this.traverseModes = traverseModes;
			this.outputCount = outputCount;
		}
		@Override
		public String toString() {
			if (this.paths == null) return "<none>"; //$NON-NLS-1$
			StringBuilder buffer = new StringBuilder();
			for (int i = 0; i < this.outputCount; i++) {
				buffer.append("path="); //$NON-NLS-1$
				buffer.append(this.paths[i].toString());
				buffer.append("\n->traverse="); //$NON-NLS-1$
				switch (this.traverseModes[i]) {
					case BINARY:
						buffer.append("BINARY"); //$NON-NLS-1$
						break;
					case IGNORE:
						buffer.append("IGNORE"); //$NON-NLS-1$
						break;
					case SOURCE:
						buffer.append("SOURCE"); //$NON-NLS-1$
						break;
					default:
						buffer.append("<unknown>"); //$NON-NLS-1$
				}
				if (i+1 < this.outputCount) {
					buffer.append('\n');
				}
			}
			return buffer.toString();
		}
	}

	/*
	 * An object to hold information about IPackageFragmentRoots (which correspond to
	 * individual classpath entry items, e.g., a java/javatests source root or library
	 * archive jar.)
	 */
	public static class RootInfo {
		final char[][] inclusionPatterns;
		final char[][] exclusionPatterns;
		final public JavaProject project;
		final IPath rootPath;
		final int entryKind;
		final IClasspathAttribute[] extraAttributes;
		IPackageFragmentRoot root;
		IPackageFragmentRoot cache;

		RootInfo(JavaProject project, IPath rootPath, char[][] inclusionPatterns, char[][] exclusionPatterns, IClasspathEntry entry) {
			this.project = project;
			this.rootPath = rootPath;
			this.inclusionPatterns = inclusionPatterns;
			this.exclusionPatterns = exclusionPatterns;
			this.entryKind = entry.getEntryKind();
			this.extraAttributes = entry.getExtraAttributes();
			this.cache = getPackageFragmentRoot();
		}
		public IPackageFragmentRoot getPackageFragmentRoot() {
			IPackageFragmentRoot tRoot = null;
			Object target = JavaModel.getTarget(this.rootPath, false/*don't check existence*/);
			if (target instanceof IResource) {
				tRoot = this.project.getPackageFragmentRoot((IResource)target, this.rootPath, this.extraAttributes);
			} else {
				IPath canonicalizedPath = JavaProject.createPackageFragementKey(new Path(this.rootPath.toOSString()));
				tRoot = this.project.getPackageFragmentRoot0(canonicalizedPath, this.extraAttributes);
			}
			return tRoot;
		}
		public IPackageFragmentRoot getPackageFragmentRoot(IResource resource) {
			if (this.root == null) {
				if (resource != null) {
					this.root = this.project.getPackageFragmentRoot(resource, null/*no entry path*/, this.extraAttributes);
				} else {
					this.root = getPackageFragmentRoot();
				}
			}
			if (this.root != null)
				this.cache = this.root;
			return this.root;
		}
		boolean isRootOfProject(IPath path) {
			return this.rootPath.equals(path) && this.project.getProject().getFullPath().isPrefixOf(path);
		}
		@Override
		public String toString() {
			StringBuilder buffer = new StringBuilder("project="); //$NON-NLS-1$
			if (this.project == null) {
				buffer.append("null"); //$NON-NLS-1$
			} else {
				buffer.append(this.project.getElementName());
			}
			buffer.append("\npath="); //$NON-NLS-1$
			if (this.rootPath == null) {
				buffer.append("null"); //$NON-NLS-1$
			} else {
				buffer.append(this.rootPath.toString());
			}
			buffer.append("\nincluding="); //$NON-NLS-1$
			if (this.inclusionPatterns == null) {
				buffer.append("null"); //$NON-NLS-1$
			} else {
				for (int i = 0, length = this.inclusionPatterns.length; i < length; i++) {
					buffer.append(new String(this.inclusionPatterns[i]));
					if (i < length-1) {
						buffer.append("|"); //$NON-NLS-1$
					}
				}
			}
			buffer.append("\nexcluding="); //$NON-NLS-1$
			if (this.exclusionPatterns == null) {
				buffer.append("null"); //$NON-NLS-1$
			} else {
				for (int i = 0, length = this.exclusionPatterns.length; i < length; i++) {
					buffer.append(new String(this.exclusionPatterns[i]));
					if (i < length-1) {
						buffer.append("|"); //$NON-NLS-1$
					}
				}
			}
			return buffer.toString();
		}
	}

	private final static int IGNORE = 0;
	private final static int SOURCE = 1;
	private final static int BINARY = 2;

	private final static String EXTERNAL_JAR_ADDED = "external jar added"; //$NON-NLS-1$
	private final static String EXTERNAL_JAR_CHANGED = "external jar changed"; //$NON-NLS-1$
	private final static String EXTERNAL_JAR_REMOVED = "external jar removed"; //$NON-NLS-1$
	private final static String EXTERNAL_JAR_UNCHANGED = "external jar unchanged"; //$NON-NLS-1$
	private final static String INTERNAL_JAR_IGNORE = "internal jar ignore"; //$NON-NLS-1$

	private final static int NON_JAVA_RESOURCE = -1;
	public static boolean DEBUG = false;
	public static boolean VERBOSE = false;
	public static boolean PERF = false;

	public static final int DEFAULT_CHANGE_EVENT = 0; // must not collide with ElementChangedEvent event masks

	/*
	 * Answer a combination of the lastModified stamp and the size.
	 * Used for detecting external JAR changes
	 */
	public static long getTimeStamp(File file) {
		return file.lastModified() + file.length();
	}

	/*
	 * The global state of delta processing.
	 */
	private DeltaProcessingState state;

	/*
	 * The Java model manager
	 */
	JavaModelManager manager;

	/*
	 * The <code>JavaElementDelta</code> corresponding to the <code>IResourceDelta</code> being translated.
	 */
	private JavaElementDelta currentDelta;

	/* The java element that was last created (see createElement(IResource)).
	 * This is used as a stack of java elements (using getParent() to pop it, and
	 * using the various get*(...) to push it. */
	private Openable currentElement;

	/*
	 * Queue of deltas created explicily by the Java Model that
	 * have yet to be fired.
	 */
	public List<IJavaElementDelta> javaModelDeltas= new ArrayList<>();

	/*
	 * Queue of reconcile deltas on working copies that have yet to be fired.
	 * This is a table form IWorkingCopy to IJavaElementDelta
	 */
	public Map<ICompilationUnit, IJavaElementDelta> reconcileDeltas = new HashMap<>();

	/*
	 * Turns delta firing on/off. By default it is on.
	 */
	private boolean isFiring= true;

	/*
	 * Used to update the JavaModel for <code>IJavaElementDelta</code>s.
	 */
	private final ModelUpdater modelUpdater = new ModelUpdater();

	/* A set of IJavaProject whose caches need to be reset */
	public Set<IJavaElement> projectCachesToReset = new HashSet<>();

	/* A table from IJavaProject to an array of IPackageFragmentRoot.
	 * This table contains the pkg fragment roots of the project that are being deleted.
	 */
	public Map<IJavaProject, IPackageFragmentRoot[]> oldRoots;

	/*
	 * Type of event that should be processed no matter what the real event type is.
	 */
	public int overridenEventType = -1;

	/*
	 * Cache SourceElementParser for the project being visited
	 */
	private SourceElementParser sourceElementParserCache;

	public DeltaProcessor(DeltaProcessingState state, JavaModelManager manager) {
		this.state = state;
		this.manager = manager;
	}

	/*
	 * Adds the dependents of the given project to the list of the projects
	 * to update.
	 */
	private void addDependentProjects(IJavaProject project, Map<IJavaProject, IJavaProject[]> projectDependencies, Set<IJavaElement> result) {
		IJavaProject[] dependents = projectDependencies.get(project);
		if (dependents == null) return;
		for (int i = 0, length = dependents.length; i < length; i++) {
			IJavaProject dependent = dependents[i];
			if (result.contains(dependent))
				continue; // no need to go further as the project is already known
			result.add(dependent);
			addDependentProjects(dependent, projectDependencies, result);
		}
	}
	/*
	 * Adds the given child handle to its parent's cache of children.
	 */
	private void addToParentInfo(Openable child) {
		Openable parent = (Openable) child.getParent();
		if (parent != null && parent.isOpen()) {
			try {
				OpenableElementInfo info = (OpenableElementInfo) parent.getElementInfo();
				// https://bugs.eclipse.org/bugs/show_bug.cgi?id=338006
				// Insert the package fragment roots in the same order as the classpath order.
				if (child instanceof IPackageFragmentRoot)
					addPackageFragmentRoot(info, (IPackageFragmentRoot) child);
				else
					info.addChild(child);
 			} catch (JavaModelException e) {
				// do nothing - we already checked if open
			}
		}
	}

	private void addPackageFragmentRoot(OpenableElementInfo parent, IPackageFragmentRoot child)
			throws JavaModelException {

		IJavaElement[] roots = parent.getChildren();
		if (roots.length > 0) {
			IClasspathEntry[] resolvedClasspath = ((JavaProject) child.getJavaProject()).getResolvedClasspath();
			IPath currentEntryPath = child.getResolvedClasspathEntry().getPath();
			int indexToInsert = -1;
			int lastComparedIndex = -1;
			int i = 0, j = 0;
			for (; i < roots.length && j < resolvedClasspath.length;) {

				IClasspathEntry classpathEntry = resolvedClasspath[j];
				if (lastComparedIndex != j && currentEntryPath.equals(classpathEntry.getPath())) {
					indexToInsert = i;
					break;
				}
				lastComparedIndex = j;

				IClasspathEntry rootEntry = ((IPackageFragmentRoot) roots[i]).getResolvedClasspathEntry();
				if (rootEntry.getPath().equals(classpathEntry.getPath()))
					i++;
				else
					j++;
			}

			for (; i < roots.length; i++) {
				// If the new root is already among the children, no need to proceed further. Just return.
				if (roots[i].equals(child)) {
					return;
				}
				// If we start seeing root's classpath entry different from the child's entry, then the child can't
				// be present further down the roots array.
				if (!((IPackageFragmentRoot) roots[i]).getResolvedClasspathEntry().getPath()
						.equals(currentEntryPath))
					break;
			}

			if (indexToInsert >= 0) {
				int newSize = roots.length + 1;
				IPackageFragmentRoot[] newChildren = new IPackageFragmentRoot[newSize];

				if (indexToInsert > 0)
					System.arraycopy(roots, 0, newChildren, 0, indexToInsert);

				newChildren[indexToInsert] = child;
				System.arraycopy(roots, indexToInsert, newChildren, indexToInsert + 1, (newSize - indexToInsert - 1));
				parent.setChildren(newChildren);
				return;
			}
		}
		parent.addChild(child);
	}
	/*
	 * Process the given delta and look for projects being added, opened, closed or
	 * with a java nature being added or removed.
	 * Note that projects being deleted are checked in deleting(IProject).
	 * In all cases, add the project's dependents to the list of projects to update
	 * so that the classpath related markers can be updated.
	 */
	private void checkProjectsAndClasspathChanges(IResourceDelta delta) {
		IResource resource = delta.getResource();
		IResourceDelta[] children = null;

		switch (resource.getType()) {
			case IResource.ROOT :
				// workaround for bug 15168 circular errors not reported
				this.state.getOldJavaProjecNames(); // force list to be computed
				children = delta.getAffectedChildren();
				break;
			case IResource.PROJECT :
				// NB: No need to check project's nature as if the project is not a java project:
				//     - if the project is added or changed this is a noop for projectsBeingDeleted
				//     - if the project is closed, it has already lost its java nature
				IProject project = (IProject)resource;
				JavaProject javaProject = (JavaProject)JavaCore.create(project);
				switch (delta.getKind()) {
					case IResourceDelta.ADDED :
						this.manager.forceBatchInitializations(false/*not initAfterLoad*/);

						// remember that the project's cache must be reset
						this.projectCachesToReset.add(javaProject);

						// workaround for bug 15168 circular errors not reported
						if (JavaProject.hasJavaNature(project)) {
							addToParentInfo(javaProject);
							readRawClasspath(javaProject);
							// ensure project references are updated (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=121569)
							checkProjectReferenceChange(project, javaProject);
							// and external folders as well
							checkExternalFolderChange(project, javaProject);
						}

						this.state.rootsAreStale = true;
						break;

					case IResourceDelta.CHANGED :
							if ((delta.getFlags() & IResourceDelta.OPEN) != 0) {
								this.manager.forceBatchInitializations(false/*not initAfterLoad*/);

								// remember that the project's cache must be reset
								this.projectCachesToReset.add(javaProject);

								// workaround for bug 15168 circular errors not reported
								if (project.isOpen()) {
									if (JavaProject.hasJavaNature(project)) {
										addToParentInfo(javaProject);
										readRawClasspath(javaProject);
										// ensure project references are updated
										checkProjectReferenceChange(project, javaProject);
										// and external folders as well
										checkExternalFolderChange(project, javaProject);
									}
								} else {
									try {
										javaProject.close();
									} catch (JavaModelException e) {
										// java project doesn't exist: ignore
									}
									removeFromParentInfo(javaProject);
									this.manager.removePerProjectInfo(javaProject, false /* don't remove index files and timestamp info of external jar */);
									this.manager.containerRemove(javaProject);
								}
								this.state.rootsAreStale = true;
							} else if ((delta.getFlags() & IResourceDelta.DESCRIPTION) != 0) {
								boolean wasJavaProject = this.state.findJavaProject(project.getName()) != null;
								boolean isJavaProject = JavaProject.hasJavaNature(project);
								if (wasJavaProject != isJavaProject) {
									this.manager.forceBatchInitializations(false/*not initAfterLoad*/);

									// java nature added or removed: remember that the project's cache must be reset
									this.projectCachesToReset.add(javaProject);

									// workaround for bug 15168 circular errors not reported
									if (isJavaProject) {
										addToParentInfo(javaProject);
										readRawClasspath(javaProject);
										// ensure project references are updated (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=172666)
										checkProjectReferenceChange(project, javaProject);
										// and external folders as well
										checkExternalFolderChange(project, javaProject);
									} else {
										// remove classpath cache so that initializeRoots() will not consider the project has a classpath
										this.manager.removePerProjectInfo(javaProject, true /* remove external jar files indexes and timestamps */);
										// remove container cache for this project
										this.manager.containerRemove(javaProject);
										// close project
										try {
											javaProject.close();
										} catch (JavaModelException e) {
											// java project doesn't exist: ignore
										}
										removeFromParentInfo(javaProject);
									}
									this.state.rootsAreStale = true;
								} else {
									// in case the project was removed then added then changed (see bug 19799)
									if (isJavaProject) { // need nature check - 18698
										addToParentInfo(javaProject);
										children = delta.getAffectedChildren();
									}
								}
							} else {
								// workaround for bug 15168 circular errors not reported
								// in case the project was removed then added then changed
								if (JavaProject.hasJavaNature(project)) { // need nature check - 18698
									addToParentInfo(javaProject);
									children = delta.getAffectedChildren();
								}
							}
							break;

					case IResourceDelta.REMOVED :
						this.manager.forceBatchInitializations(false/*not initAfterLoad*/);

						// remove classpath cache so that initializeRoots() will not consider the project has a classpath
						this.manager.removePerProjectInfo(javaProject, true /* remove external jar files indexes and timestamps*/);
						// remove container cache for this project
						this.manager.containerRemove(javaProject);
						JavaModelManager.getModulePathManager().removeEntry(javaProject);
						this.state.rootsAreStale = true;
						break;
				}

				break;
			case IResource.FOLDER:
				switch (delta.getKind()) {
					case IResourceDelta.ADDED:
					case IResourceDelta.REMOVED:
						// Close the containing package fragment root to reset its cached children.
						// See http://bugs.eclipse.org/500714
						try {
							IPackageFragmentRoot root = findContainingPackageFragmentRoot(resource);
							if (root != null && root.isOpen())
								root.close();
						} catch (JavaModelException e) {
							Util.log(e);
						}
						break;

					case IResourceDelta.CHANGED: // look for .jar file change to update classpath
						children = delta.getAffectedChildren();
						break;
				}
				break;
			case IResource.FILE :
				IFile file = (IFile) resource;
				int kind = delta.getKind();
				RootInfo rootInfo;
				if (file.getName().equals(JavaProject.CLASSPATH_FILENAME)) {
					/* classpath file change */
					this.manager.forceBatchInitializations(false/*not initAfterLoad*/);
					switch (kind) {
						case IResourceDelta.CHANGED :
							int flags = delta.getFlags();
							if ((flags & IResourceDelta.CONTENT) == 0  // only consider content change
								&& (flags & IResourceDelta.ENCODING) == 0 // and encoding change
								&& (flags & IResourceDelta.MOVED_FROM) == 0) {// and also move and override scenario (see http://dev.eclipse.org/bugs/show_bug.cgi?id=21420)
								break;
							}
						//$FALL-THROUGH$
						case IResourceDelta.ADDED :
						case IResourceDelta.REMOVED :
							javaProject = (JavaProject)JavaCore.create(file.getProject());

							// force to (re)read the .classpath file
							// in case of removal (IResourceDelta.REMOVED) this will reset the classpath to its default and create the right delta
							// (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=211290)
							readRawClasspath(javaProject);
							break;
					}
					this.state.rootsAreStale = true;
				} else if ((rootInfo = rootInfo(file.getFullPath(), kind)) != null && rootInfo.entryKind == IClasspathEntry.CPE_LIBRARY) {
					javaProject = (JavaProject)JavaCore.create(file.getProject());
					javaProject.resetResolvedClasspath();
					this.state.rootsAreStale = true;
				} else if (file.getName().toLowerCase().contains(new String(TypeConstants.MODULE_INFO_FILE_NAME))) {
					switch(kind) {
						case IResourceDelta.CHANGED :
							int flags = delta.getFlags();
							if ((flags & IResourceDelta.CONTENT) == 0)
								break;
							javaProject = (JavaProject)JavaCore.create(file.getProject());
							this.manager.removePerProjectInfo(javaProject, false);
							this.state.rootsAreStale = true;
							break;
							//$FALL-THROUGH$
						case IResourceDelta.ADDED :
						case IResourceDelta.REMOVED :
							javaProject = (JavaProject)JavaCore.create(file.getProject());
							try {
								// Make sure module description is read
								javaProject.close();
							} catch (JavaModelException e) {
								// do nothing
							}
							break;
					}
				}
				break;

		}
		if (children != null) {
			for (int i = 0; i < children.length; i++) {
				checkProjectsAndClasspathChanges(children[i]);
			}
		}
	}

	private IPackageFragmentRoot findContainingPackageFragmentRoot(IResource resource) throws JavaModelException {
		IProject project = resource.getProject();
		if (JavaProject.hasJavaNature(project)) {
			IJavaProject javaProject = JavaCore.create(project);
			IPath path = resource.getProjectRelativePath();
			IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots();
			for (IPackageFragmentRoot root : roots) {
				IResource rootResource = null;
				try {
					rootResource = root.getUnderlyingResource();
				} catch (JavaModelException e) {
					if (!e.isDoesNotExist())
						throw e;
				}
				if (rootResource != null && !resource.equals(rootResource) &&
						rootResource.getProjectRelativePath().isPrefixOf(path)) {
					return root;
				}
			}
		}
		return null;
	}

	private void checkExternalFolderChange(IProject project, JavaProject javaProject) {
		ClasspathChange change = this.state.getClasspathChange(project);
		this.state.addExternalFolderChange(javaProject, change == null ? null : change.oldResolvedClasspath);
	}

	private void checkProjectReferenceChange(IProject project, JavaProject javaProject) {
		project.clearCachedDynamicReferences();
		this.state.addProjectReferenceChange(javaProject);
	}

	private void readRawClasspath(JavaProject javaProject) {
		// force to (re)read the .classpath file
		try {
			PerProjectInfo perProjectInfo = javaProject.getPerProjectInfo();
			if (!perProjectInfo.writtingRawClasspath) // to avoid deadlock, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=221680
				perProjectInfo.readAndCacheClasspath(javaProject);
		} catch (JavaModelException e) {
			if (VERBOSE) {
				e.printStackTrace();
			}
		}
	}
	private void checkSourceAttachmentChange(IResourceDelta delta, IResource res) {
		IPath rootPath = this.state.sourceAttachments.get(externalPath(res));
		if (rootPath != null) {
			RootInfo rootInfo = rootInfo(rootPath, delta.getKind());
			if (rootInfo != null) {
				IJavaProject projectOfRoot = rootInfo.project;
				IPackageFragmentRoot root = null;
				try {
					// close the root so that source attachment cache is flushed
					root = projectOfRoot.findPackageFragmentRoot(rootPath);
					if (root != null) {
						root.close();
					}
				} catch (JavaModelException e) {
					// root doesn't exist: ignore
				}
				if (root == null) return;
				switch (delta.getKind()) {
					case IResourceDelta.ADDED:
						currentDelta().sourceAttached(root);
						break;
					case IResourceDelta.CHANGED:
						currentDelta().sourceDetached(root);
						currentDelta().sourceAttached(root);
						break;
					case IResourceDelta.REMOVED:
						currentDelta().sourceDetached(root);
						break;
				}
			}
		}
	}
	/*
	 * Closes the given element, which removes it from the cache of open elements.
	 */
	private void close(Openable element) {
		try {
			element.close();
		} catch (JavaModelException e) {
			// do nothing
		}
	}
	/*
	 * Generic processing for elements with changed contents:<ul>
	 * <li>The element is closed such that any subsequent accesses will re-open
	 * the element reflecting its new structure.
	 * <li>An entry is made in the delta reporting a content change (K_CHANGE with F_CONTENT flag set).
	 * </ul>
	 * Delta argument could be null if processing an external JAR change
	 */
	private void contentChanged(Openable element) {

		boolean isPrimary = false;
		boolean isPrimaryWorkingCopy = false;
		if (element.getElementType() == IJavaElement.COMPILATION_UNIT) {
			CompilationUnit cu = (CompilationUnit)element;
			isPrimary = cu.isPrimary();
			isPrimaryWorkingCopy = isPrimary && cu.isWorkingCopy();
		}
		if (isPrimaryWorkingCopy) {
			// filter out changes to primary compilation unit in working copy mode
			// just report a change to the resource (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=59500)
			currentDelta().changed(element, IJavaElementDelta.F_PRIMARY_RESOURCE);
		} else {
			close(element);
			int flags = IJavaElementDelta.F_CONTENT;
			if (element instanceof JarPackageFragmentRoot){
				flags |= IJavaElementDelta.F_ARCHIVE_CONTENT_CHANGED;
				// need also to reset project cache otherwise it will be out-of-date
				// see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=162621
				this.projectCachesToReset.add(element.getJavaProject());
			}
			if (isPrimary) {
				flags |= IJavaElementDelta.F_PRIMARY_RESOURCE;
			}
			currentDelta().changed(element, flags);
		}
	}
	/*
	 * Creates the openables corresponding to this resource.
	 * Returns null if none was found.
	 */
	private Openable createElement(IResource resource, int elementType, RootInfo rootInfo) {
		if (resource == null) return null;

		IPath path = resource.getFullPath();
		IJavaElement element = null;
		switch (elementType) {

			case IJavaElement.JAVA_PROJECT:

				// note that non-java resources rooted at the project level will also enter this code with
				// an elementType JAVA_PROJECT (see #elementType(...)).
				if (resource instanceof IProject){

					popUntilPrefixOf(path);

					if (this.currentElement != null
						&& this.currentElement.getElementType() == IJavaElement.JAVA_PROJECT
						&& ((IJavaProject)this.currentElement).getProject().equals(resource)) {
						return this.currentElement;
					}
					if  (rootInfo != null && rootInfo.project.getProject().equals(resource)){
						element = rootInfo.project;
						break;
					}
					IProject proj = (IProject)resource;
					if (JavaProject.hasJavaNature(proj)) {
						element = JavaCore.create(proj);
					} else {
						// java project may have been been closed or removed (look for
						// element amongst old java project s list).
						element =  this.state.findJavaProject(proj.getName());
					}
				}
				break;
			case IJavaElement.PACKAGE_FRAGMENT_ROOT:
				element = rootInfo == null ? JavaCore.create(resource) : rootInfo.getPackageFragmentRoot(resource);
				break;
			case IJavaElement.PACKAGE_FRAGMENT:
				if (rootInfo != null) {
					if (rootInfo.project.contains(resource)) {
						PackageFragmentRoot root = (PackageFragmentRoot) rootInfo.getPackageFragmentRoot(null);
						// create package handle
						IPath pkgPath = path.removeFirstSegments(root.resource().getFullPath().segmentCount());
						String[] pkgName = pkgPath.segments();
						element = root.getPackageFragment(pkgName);
					}
				} else {
					// find the element that encloses the resource
					popUntilPrefixOf(path);

					if (this.currentElement == null) {
						element = JavaCore.create(resource);
					} else {
						// find the root
						PackageFragmentRoot root = this.currentElement.getPackageFragmentRoot();
						if (root == null) {
							element =  JavaCore.create(resource);
						} else if (root.getJavaProject().contains(resource)) {
							// create package handle
							IPath pkgPath = path.removeFirstSegments(root.getPath().segmentCount());
							String[] pkgName = pkgPath.segments();
							element = root.getPackageFragment(pkgName);
						}
					}
				}
				break;
			case IJavaElement.COMPILATION_UNIT:
			case IJavaElement.CLASS_FILE:
				// find the element that encloses the resource
				popUntilPrefixOf(path);

				if (this.currentElement == null) {
					element =  rootInfo == null ? JavaCore.create(resource) : JavaModelManager.create(resource, rootInfo.project);
				} else {
					// find the package
					IPackageFragment pkgFragment = null;
					switch (this.currentElement.getElementType()) {
						case IJavaElement.PACKAGE_FRAGMENT_ROOT:
							PackageFragmentRoot root = (PackageFragmentRoot)this.currentElement;
							IPath rootPath = root.getPath();
							IPath pkgPath = path.removeLastSegments(1);
							String[] pkgName = pkgPath.removeFirstSegments(rootPath.segmentCount()).segments();
							pkgFragment = root.getPackageFragment(pkgName);
							break;
						case IJavaElement.PACKAGE_FRAGMENT:
							Openable pkg = this.currentElement;
							if (pkg.getPath().equals(path.removeLastSegments(1))) {
								pkgFragment = (IPackageFragment)pkg;
							} // else case of package x which is a prefix of x.y
							break;
						case IJavaElement.COMPILATION_UNIT:
						case IJavaElement.CLASS_FILE:
							pkgFragment = (IPackageFragment)this.currentElement.getParent();
							break;
					}
					if (pkgFragment == null) {
						element =  rootInfo == null ? JavaCore.create(resource) : JavaModelManager.create(resource, rootInfo.project);
					} else {
						if (elementType == IJavaElement.COMPILATION_UNIT) {
							// create compilation unit handle
							// fileName validation has been done in elementType(IResourceDelta, int, boolean)
							String fileName = path.lastSegment();
							element = pkgFragment.getCompilationUnit(fileName);
						} else {
							// create class file handle
							// fileName validation has been done in elementType(IResourceDelta, int, boolean)
							String fileName = path.lastSegment();
							if (TypeConstants.MODULE_INFO_CLASS_NAME_STRING.equals(fileName))
								element = pkgFragment.getModularClassFile();
							else
								element = pkgFragment.getClassFile(fileName);
						}
					}
				}
				break;
		}
		if (element == null) return null;
		this.currentElement = (Openable)element;
		return this.currentElement;
	}

	public void checkExternalArchiveChanges(IJavaElement[] elementsScope,  IProgressMonitor monitor) throws JavaModelException {
		checkExternalArchiveChanges(elementsScope, false, monitor);
	}
	/*
	 * Check all external archive (referenced by given roots, projects or model) status and issue a corresponding root delta.
	 * Also triggers index updates
	 */
	private void checkExternalArchiveChanges(IJavaElement[] elementsScope, boolean asynchronous, IProgressMonitor monitor) throws JavaModelException {
		if (monitor != null && monitor.isCanceled())
			throw new OperationCanceledException();
		try {
			if (monitor != null) monitor.beginTask("", 1); //$NON-NLS-1$

			boolean hasExternalWorkingCopyProject = false;
			for (int i = 0, length = elementsScope.length; i < length; i++) {
				IJavaElement element = elementsScope[i];
				this.state.addForRefresh(elementsScope[i]);
				if (element.getElementType() == IJavaElement.JAVA_MODEL) {
					// ensure external working copies' projects' caches are reset
					Set<IJavaProject> projects = JavaModelManager.getJavaModelManager().getExternalWorkingCopyProjects();
					if (projects != null) {
						hasExternalWorkingCopyProject = true;
						Iterator<IJavaProject> iterator = projects.iterator();
						while (iterator.hasNext()) {
							JavaProject project = (JavaProject) iterator.next();
							project.resetCaches();
						}
					}
				}
			}
			Set<IJavaElement> elementsToRefresh = this.state.removeExternalElementsToRefresh();
			boolean hasDelta = elementsToRefresh != null && createExternalArchiveDelta(elementsToRefresh, monitor);
			if (hasDelta){
				IJavaElementDelta[] projectDeltas = this.currentDelta.getAffectedChildren();
				final int length = projectDeltas.length;
				final IProject[] projectsToTouch = new IProject[length];
				for (int i = 0; i < length; i++) {
					IJavaElementDelta delta = projectDeltas[i];
					JavaProject javaProject = (JavaProject)delta.getElement();
					projectsToTouch[i] = javaProject.getProject();
				}
				if (projectsToTouch.length > 0) {
					if (asynchronous){
						this.manager.touchProjects(projectsToTouch, monitor);
					}
					else {
						// touch the projects to force them to be recompiled while taking the workspace lock
						//	 so that there is no concurrency with the Java builder
						// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=96575
						IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
							@Override
							public void run(IProgressMonitor progressMonitor) throws CoreException {
								for (int i = 0; i < projectsToTouch.length; i++) {
									IProject project = projectsToTouch[i];

									// touch to force a build of this project
									if (JavaBuilder.DEBUG)
										System.out.println("Touching project " + project.getName() + " due to external jar file change"); //$NON-NLS-1$ //$NON-NLS-2$
									project.touch(progressMonitor);
								}
							}
						};
						try {
							ResourcesPlugin.getWorkspace().run(runnable, monitor);
						} catch (CoreException e) {
							throw new JavaModelException(e);
						}
					}
				}

				if (this.currentDelta != null) { // if delta has not been fired while creating markers
					fire(this.currentDelta, DEFAULT_CHANGE_EVENT);
				}
			} else if (hasExternalWorkingCopyProject) {
				// flush jar type cache
				JavaModelManager.getJavaModelManager().resetJarTypeCache();
			}
		} finally {
			this.currentDelta = null;
			if (monitor != null) monitor.done();
		}
	}

	/*
	 * Check if external archives have changed for the given elements and create the corresponding deltas.
	 * Returns whether at least one delta was created.
	 */
	private boolean createExternalArchiveDelta(Set<IJavaElement> refreshedElements, IProgressMonitor monitor) {

		Map<IPath, String> externalArchivesStatus = new HashMap<>();
		boolean hasDelta = false;

		// find JARs to refresh
		Set<IPath> archivePathsToRefresh = new HashSet<>();
		Iterator<IJavaElement> iterator = refreshedElements.iterator();
		while (iterator.hasNext()) {
			IJavaElement element = iterator.next();
			switch(element.getElementType()){
				case IJavaElement.PACKAGE_FRAGMENT_ROOT :
					archivePathsToRefresh.add(element.getPath());
					break;
				case IJavaElement.JAVA_PROJECT :
					JavaProject javaProject = (JavaProject) element;
					if (!JavaProject.hasJavaNature(javaProject.getProject())) {
						// project is not accessible or has lost its Java nature
						break;
					}
					IClasspathEntry[] classpath;
					try {
						classpath = javaProject.getResolvedClasspath();
						for (int j = 0, cpLength = classpath.length; j < cpLength; j++){
							if (classpath[j].getEntryKind() == IClasspathEntry.CPE_LIBRARY){
								archivePathsToRefresh.add(classpath[j].getPath());
							}
						}
					} catch (JavaModelException e) {
						// project doesn't exist -> ignore
					}
					break;
				case IJavaElement.JAVA_MODEL :
					Iterator<String> projectNames = this.state.getOldJavaProjecNames().iterator();
					while (projectNames.hasNext()) {
						String projectName = projectNames.next();
						IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
						if (!JavaProject.hasJavaNature(project)) {
							// project is not accessible or has lost its Java nature
							continue;
						}
						javaProject = (JavaProject) JavaCore.create(project);
						try {
							classpath = javaProject.getResolvedClasspath();
							for (int k = 0, cpLength = classpath.length; k < cpLength; k++){
								if (classpath[k].getEntryKind() == IClasspathEntry.CPE_LIBRARY){
									archivePathsToRefresh.add(classpath[k].getPath());
								}
							}
						} catch (JavaModelException e2) {
							// project doesn't exist -> ignore
							continue;
						}
					}
					break;
			}
		}

		// perform refresh
		Iterator<String> projectNames = this.state.getOldJavaProjecNames().iterator();
		IWorkspaceRoot wksRoot = ResourcesPlugin.getWorkspace().getRoot();
		while (projectNames.hasNext()) {

			if (monitor != null && monitor.isCanceled()) break;

			String projectName = projectNames.next();
			IProject project = wksRoot.getProject(projectName);
			if (!JavaProject.hasJavaNature(project)) {
				// project is not accessible or has lost its Java nature
				continue;
			}
			JavaProject javaProject = (JavaProject) JavaCore.create(project);
			IClasspathEntry[] entries;
			try {
				entries = javaProject.getResolvedClasspath();
			} catch (JavaModelException e1) {
				// project does not exist -> ignore
				continue;
			}
			boolean deltaContainsModifiedJar = false;
			for (int j = 0; j < entries.length; j++){
				if (entries[j].getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
					IPath entryPath = entries[j].getPath();

					if (!archivePathsToRefresh.contains(entryPath)) continue; // not supposed to be refreshed

					String status = externalArchivesStatus.get(entryPath);
					if (status == null){

						// Clear the external file state for this path, since this method is responsible for updating it.
						this.manager.clearExternalFileState(entryPath);

						// compute shared status
						Object targetLibrary = JavaModel.getTarget(entryPath, true);

						if (targetLibrary == null){ // missing JAR
							if (this.state.getExternalLibTimeStamps().remove(entryPath) != null /* file was known*/
									&& this.state.roots.get(entryPath) != null /* and it was on the classpath*/) {
								externalArchivesStatus.put(entryPath, EXTERNAL_JAR_REMOVED);
								// the jar was physically removed: remove the index
								this.manager.indexManager.removeIndex(entryPath);
							}

						} else if (targetLibrary instanceof File){ // external JAR

							File externalFile = (File)targetLibrary;

							// check timestamp to figure if JAR has changed in some way
							Long oldTimestamp =this.state.getExternalLibTimeStamps().get(entryPath);
							long newTimeStamp = getTimeStamp(externalFile);
							if (oldTimestamp != null){

								if (newTimeStamp == 0){ // file doesn't exist
									externalArchivesStatus.put(entryPath, EXTERNAL_JAR_REMOVED);
									this.state.getExternalLibTimeStamps().remove(entryPath);
									// remove the index
									this.manager.indexManager.removeIndex(entryPath);

								} else if (oldTimestamp.longValue() != newTimeStamp){
									externalArchivesStatus.put(entryPath, EXTERNAL_JAR_CHANGED);
									this.state.getExternalLibTimeStamps().put(entryPath, Long.valueOf(newTimeStamp));
									// first remove the index so that it is forced to be re-indexed
									this.manager.indexManager.removeIndex(entryPath);
									// then index the jar
									this.manager.indexManager.indexLibrary(entryPath, project.getProject(), ((ClasspathEntry)entries[j]).getLibraryIndexLocation(), true);
								} else {
									URL indexLocation = ((ClasspathEntry)entries[j]).getLibraryIndexLocation();
									if (indexLocation != null) { // force reindexing, this could be faster rather than maintaining the list
										this.manager.indexManager.indexLibrary(entryPath, project.getProject(), indexLocation);
									}
									externalArchivesStatus.put(entryPath, EXTERNAL_JAR_UNCHANGED);
								}
							} else {
								if (newTimeStamp == 0){ // jar still doesn't exist
									externalArchivesStatus.put(entryPath, EXTERNAL_JAR_UNCHANGED);
								} else {
									externalArchivesStatus.put(entryPath, EXTERNAL_JAR_ADDED);
									this.state.getExternalLibTimeStamps().put(entryPath, Long.valueOf(newTimeStamp));
									// index the new jar
									this.manager.indexManager.removeIndex(entryPath);
									this.manager.indexManager.indexLibrary(entryPath, project.getProject(), ((ClasspathEntry)entries[j]).getLibraryIndexLocation());
								}
							}
						} else { // internal JAR
							externalArchivesStatus.put(entryPath, INTERNAL_JAR_IGNORE);
						}
					}
					// according to computed status, generate a delta
					status = externalArchivesStatus.get(entryPath);
					if (status != null){
						if (status == EXTERNAL_JAR_ADDED){
							PackageFragmentRoot root = (PackageFragmentRoot) javaProject.getPackageFragmentRoot(entryPath.toString());
							if (VERBOSE){
								System.out.println("- External JAR ADDED, affecting root: "+root.getElementName()); //$NON-NLS-1$
							}
							elementAdded(root, null, null);
							deltaContainsModifiedJar = true;
							this.state.addClasspathValidation(javaProject); // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=185733
							hasDelta = true;
						} else if (status == EXTERNAL_JAR_CHANGED) {
							PackageFragmentRoot root = (PackageFragmentRoot) javaProject.getPackageFragmentRoot(entryPath.toString());
							if (VERBOSE){
								System.out.println("- External JAR CHANGED, affecting root: "+root.getElementName()); //$NON-NLS-1$
							}
							// TODO(sxenos): this is causing each change event for an external jar file to be fired twice.
							// We need to preserve the clearing of cached information in the jar but defer the actual firing of
							// the event until after the indexer has processed the jar.
							contentChanged(root);
							deltaContainsModifiedJar = true;
							hasDelta = true;
						} else if (status == EXTERNAL_JAR_REMOVED) {
							PackageFragmentRoot root = (PackageFragmentRoot) javaProject.getPackageFragmentRoot(entryPath.toString());
							if (VERBOSE){
								System.out.println("- External JAR REMOVED, affecting root: "+root.getElementName()); //$NON-NLS-1$
							}
							elementRemoved(root, null, null);
							deltaContainsModifiedJar = true;
							this.state.addClasspathValidation(javaProject); // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=185733
							hasDelta = true;
						}
					}
				}
			}

			if (deltaContainsModifiedJar) {
				javaProject.resetResolvedClasspath();
			}
		}

		if (hasDelta){
			// flush jar type cache
			JavaModelManager.getJavaModelManager().resetJarTypeCache();
		}
		return hasDelta;
	}
	private JavaElementDelta currentDelta() {
		if (this.currentDelta == null) {
			this.currentDelta = new JavaElementDelta(this.manager.getJavaModel());
		}
		return this.currentDelta;
	}
	/*
	 * Note that the project is about to be deleted.
	 */
	private void deleting(IProject project) {

		try {
			// discard indexing jobs that belong to this project so that the project can be
			// deleted without interferences from the index manager
			this.manager.indexManager.discardJobs(project.getName());

			JavaProject javaProject = (JavaProject)JavaCore.create(project);

			// remember roots of this project
			if (this.oldRoots == null) {
				this.oldRoots = new HashMap<>();
			}
			if (javaProject.isOpen()) {
				this.oldRoots.put(javaProject, javaProject.getPackageFragmentRoots());
			} else {
				// compute roots without opening project
				this.oldRoots.put(
					javaProject,
					javaProject.computePackageFragmentRoots(
						javaProject.getResolvedClasspath(),
						false,
						true, // respect limit modules
						null /*no reverse map*/));
			}

			javaProject.close();

			// workaround for bug 15168 circular errors not reported
			this.state.getOldJavaProjecNames(); // foce list to be computed

			removeFromParentInfo(javaProject);

			// remove preferences from per project info
			this.manager.resetProjectPreferences(javaProject);
		} catch (JavaModelException e) {
			// java project doesn't exist: ignore
		}
	}
	/*
	 * Processing for an element that has been added:<ul>
	 * <li>If the element is a project, do nothing, and do not process
	 * children, as when a project is created it does not yet have any
	 * natures - specifically a java nature.
	 * <li>If the elemet is not a project, process it as added (see
	 * <code>basicElementAdded</code>.
	 * </ul>
	 * Delta argument could be null if processing an external JAR change
	 */
	private void elementAdded(Openable element, IResourceDelta delta, RootInfo rootInfo) {
		int elementType = element.getElementType();

		if (elementType == IJavaElement.JAVA_PROJECT) {
			// project add is handled by JavaProject.configure() because
			// when a project is created, it does not yet have a Java nature
			IProject project;
			if (delta != null && JavaProject.hasJavaNature(project = (IProject)delta.getResource())) {
				addToParentInfo(element);
				this.manager.getPerProjectInfo(project, true /*create info if needed*/).rememberExternalLibTimestamps();
				if ((delta.getFlags() & IResourceDelta.MOVED_FROM) != 0) {
					Openable movedFromElement = element.getJavaModel().getJavaProject(delta.getMovedFromPath().lastSegment());
					currentDelta().movedTo(element, movedFromElement);
				} else {
					// Force the project to be closed as it might have been opened
					// before the resource modification came in and it might have a new child
					// For example, in an IWorkspaceRunnable:
					// 1. create a Java project P (where P=src)
					// 2. open project P
					// 3. add folder f in P's pkg fragment root
					// When the resource delta comes in, only the addition of P is notified,
					// but the pkg fragment root of project P is already opened, thus its children are not recomputed
					// and it appears to contain only the default package.
					close(element);

					currentDelta().added(element);
				}
				this.state.updateRoots(element.getPath(), delta, this);

				// remember that the project's cache must be reset
				this.projectCachesToReset.add(element);
			}
		} else {
			if (delta == null || (delta.getFlags() & IResourceDelta.MOVED_FROM) == 0) {
				// regular element addition
				if (isPrimaryWorkingCopy(element, elementType) ) {
					// filter out changes to primary compilation unit in working copy mode
					// just report a change to the resource (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=59500)
					currentDelta().changed(element, IJavaElementDelta.F_PRIMARY_RESOURCE);
				} else {
					addToParentInfo(element);

					// Force the element to be closed as it might have been opened
					// before the resource modification came in and it might have a new child
					// For example, in an IWorkspaceRunnable:
					// 1. create a package fragment p using a java model operation
					// 2. open package p
					// 3. add file X.java in folder p
					// When the resource delta comes in, only the addition of p is notified,
					// but the package p is already opened, thus its children are not recomputed
					// and it appears empty.
					close(element);

					currentDelta().added(element);
				}
			} else {
				// element is moved
				addToParentInfo(element);
				close(element);

				IPath movedFromPath = delta.getMovedFromPath();
				IResource res = delta.getResource();
				IResource movedFromRes;
				if (res instanceof IFile) {
					movedFromRes = res.getWorkspace().getRoot().getFile(movedFromPath);
				} else {
					movedFromRes = res.getWorkspace().getRoot().getFolder(movedFromPath);
				}

				// find the element type of the moved from element
				IPath rootPath = externalPath(movedFromRes);
				RootInfo movedFromInfo = enclosingRootInfo(rootPath, IResourceDelta.REMOVED);
				int movedFromType =
					elementType(
						movedFromRes,
						IResourceDelta.REMOVED,
						element.getParent().getElementType(),
						movedFromInfo);

				// reset current element as it might be inside a nested root (popUntilPrefixOf() may use the outer root)
				this.currentElement = null;

				// create the moved from element
				Openable movedFromElement =
					elementType != IJavaElement.JAVA_PROJECT && movedFromType == IJavaElement.JAVA_PROJECT ?
						null : // outside classpath
						createElement(movedFromRes, movedFromType, movedFromInfo);
				if (movedFromElement == null) {
					// moved from outside classpath
					currentDelta().added(element);
				} else {
					currentDelta().movedTo(element, movedFromElement);
				}
			}

			switch (elementType) {
				case IJavaElement.PACKAGE_FRAGMENT_ROOT :
					// when a root is added, and is on the classpath, the project must be updated
					JavaProject project = element.getJavaProject();

					// remember that the project's cache must be reset
					this.projectCachesToReset.add(project);

					break;
				case IJavaElement.PACKAGE_FRAGMENT :
					// reset project's package fragment cache
					project = element.getJavaProject();
					this.projectCachesToReset.add(project);

					break;

				case IJavaElement.COMPILATION_UNIT :
					if (element.getElementName().equals(new String(TypeConstants.MODULE_INFO_FILE_NAME))) {
						this.projectCachesToReset.add(element.getJavaProject()); // change unnamed -> named
					}

					break;
			}
		}
	}
	/*
	 * Generic processing for a removed element:<ul>
	 * <li>Close the element, removing its structure from the cache
	 * <li>Remove the element from its parent's cache of children
	 * <li>Add a REMOVED entry in the delta
	 * </ul>
	 * Delta argument could be null if processing an external JAR change
	 */
	private void elementRemoved(Openable element, IResourceDelta delta, RootInfo rootInfo) {

		int elementType = element.getElementType();
		if (delta == null || (delta.getFlags() & IResourceDelta.MOVED_TO) == 0) {
			// regular element removal
			if (isPrimaryWorkingCopy(element, elementType) ) {
				// filter out changes to primary compilation unit in working copy mode
				// just report a change to the resource (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=59500)
				currentDelta().changed(element, IJavaElementDelta.F_PRIMARY_RESOURCE);
			} else {
				close(element);
				removeFromParentInfo(element);
				currentDelta().removed(element);
			}
		} else {
			// element is moved
			close(element);
			removeFromParentInfo(element);
			IPath movedToPath = delta.getMovedToPath();
			IResource res = delta.getResource();
			IResource movedToRes;
			switch (res.getType()) {
				case IResource.PROJECT:
					movedToRes = res.getWorkspace().getRoot().getProject(movedToPath.lastSegment());
					break;
				case IResource.FOLDER:
					movedToRes = res.getWorkspace().getRoot().getFolder(movedToPath);
					break;
				case IResource.FILE:
					movedToRes = res.getWorkspace().getRoot().getFile(movedToPath);
					break;
				default:
					return;
			}

			// find the element type of the moved from element
			IPath rootPath = externalPath(movedToRes);
			RootInfo movedToInfo = enclosingRootInfo(rootPath, IResourceDelta.ADDED);
			int movedToType =
				elementType(
					movedToRes,
					IResourceDelta.ADDED,
					element.getParent().getElementType(),
					movedToInfo);

			// reset current element as it might be inside a nested root (popUntilPrefixOf() may use the outer root)
			this.currentElement = null;

			// create the moved To element
			Openable movedToElement =
				elementType != IJavaElement.JAVA_PROJECT && movedToType == IJavaElement.JAVA_PROJECT ?
					null : // outside classpath
					createElement(movedToRes, movedToType, movedToInfo);
			if (movedToElement == null) {
				// moved outside classpath
				currentDelta().removed(element);
			} else {
				currentDelta().movedFrom(element, movedToElement);
			}
		}

		switch (elementType) {
			case IJavaElement.JAVA_MODEL :
				this.manager.indexManager.reset();
				break;
			case IJavaElement.JAVA_PROJECT :
				this.state.updateRoots(element.getPath(), delta, this);

				// remember that the project's cache must be reset
				this.projectCachesToReset.add(element);

				break;
			case IJavaElement.PACKAGE_FRAGMENT_ROOT :
				JavaProject project = element.getJavaProject();

				// remember that the project's cache must be reset
				this.projectCachesToReset.add(project);

				break;
			case IJavaElement.PACKAGE_FRAGMENT :
				// reset package fragment cache
				project = element.getJavaProject();
				this.projectCachesToReset.add(project);

				break;

			case IJavaElement.COMPILATION_UNIT :
				if (element.getElementName().equals(new String(TypeConstants.MODULE_INFO_FILE_NAME))) {
					this.projectCachesToReset.add(element.getJavaProject()); // change named -> unnamed
				}

				break;
		}
	}
	/*
	 * Returns the type of the java element the given delta matches to.
	 * Returns NON_JAVA_RESOURCE if unknown (e.g. a non-java resource or excluded .java file)
	 */
	private int elementType(IResource res, int kind, int parentType, RootInfo rootInfo) {
		switch (parentType) {
			case IJavaElement.JAVA_MODEL:
				// case of a movedTo or movedFrom project (other cases are handled in processResourceDelta(...)
				return IJavaElement.JAVA_PROJECT;

			case NON_JAVA_RESOURCE:
			case IJavaElement.JAVA_PROJECT:
				if (rootInfo == null) {
					rootInfo = enclosingRootInfo(res.getFullPath(), kind);
				}
				if (rootInfo != null && rootInfo.isRootOfProject(res.getFullPath())) {
					return IJavaElement.PACKAGE_FRAGMENT_ROOT;
				}
				// not yet in a package fragment root or root of another project
				// or package fragment to be included (see below)
				// $FALL-THROUGH$

			case IJavaElement.PACKAGE_FRAGMENT_ROOT:
			case IJavaElement.PACKAGE_FRAGMENT:
				if (rootInfo == null) {
					IPath rootPath = externalPath(res);
					rootInfo = enclosingRootInfo(rootPath, kind);
				}
				if (rootInfo == null) {
					return NON_JAVA_RESOURCE;
				}
				if (Util.isExcluded(res, rootInfo.inclusionPatterns, rootInfo.exclusionPatterns)) {
					return NON_JAVA_RESOURCE;
				}
				if (res.getType() == IResource.FOLDER) {
					if (parentType == NON_JAVA_RESOURCE && !Util.isExcluded(res.getParent(), rootInfo.inclusionPatterns, rootInfo.exclusionPatterns)) {
						// parent is a non-Java resource because it doesn't have a valid package name (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=130982)
						return NON_JAVA_RESOURCE;
					}
					String sourceLevel = rootInfo.project == null ? null : rootInfo.project.getOption(JavaCore.COMPILER_SOURCE, true);
					String complianceLevel = rootInfo.project == null ? null : rootInfo.project.getOption(JavaCore.COMPILER_COMPLIANCE, true);
					if (Util.isValidFolderNameForPackage(res.getName(), sourceLevel, complianceLevel)) {
						return IJavaElement.PACKAGE_FRAGMENT;
					}
					return NON_JAVA_RESOURCE;
				}
				String fileName = res.getName();
				String sourceLevel = rootInfo.project == null ? null : rootInfo.project.getOption(JavaCore.COMPILER_SOURCE, true);
				String complianceLevel = rootInfo.project == null ? null : rootInfo.project.getOption(JavaCore.COMPILER_COMPLIANCE, true);
				if (Util.isValidCompilationUnitName(fileName, sourceLevel, complianceLevel)) {
					return IJavaElement.COMPILATION_UNIT;
				} else if (Util.isValidClassFileName(fileName, sourceLevel, complianceLevel)) {
					return IJavaElement.CLASS_FILE;
				} else {
					IPath rootPath = externalPath(res);
					if ((rootInfo = rootInfo(rootPath, kind)) != null
							&& rootInfo.project.getProject().getFullPath().isPrefixOf(rootPath) /*ensure root is a root of its project (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=185310) */) {
						// case of proj=src=bin and resource is a jar file on the classpath
						return IJavaElement.PACKAGE_FRAGMENT_ROOT;
					} else {
						return NON_JAVA_RESOURCE;
					}
				}

			default:
				return NON_JAVA_RESOURCE;
		}
	}
	/*
	 * Flushes all deltas without firing them.
	 */
	public void flush() {
		this.javaModelDeltas = new ArrayList<>();
	}

	private SourceElementParser getSourceElementParser(Openable element) {
		if (this.sourceElementParserCache == null)
			this.sourceElementParserCache = this.manager.indexManager.getSourceElementParser(element.getJavaProject(), null/*requestor will be set by indexer*/);
		return this.sourceElementParserCache;
	}
	/*
	 * Finds the root info this path is included in.
	 * Returns null if not found.
	 */
	private RootInfo enclosingRootInfo(IPath path, int kind) {
		while (path != null && path.segmentCount() > 0) {
			RootInfo rootInfo =  rootInfo(path, kind);
			if (rootInfo != null) return rootInfo;
			path = path.removeLastSegments(1);
		}
		return null;
	}

	private IPath externalPath(IResource res) {
		IPath resourcePath = res.getFullPath();
		if (ExternalFoldersManager.isInternalPathForExternalFolder(resourcePath))
			return res.getLocation();
		return resourcePath;
	}

	/*
	 * Fire Java Model delta, flushing them after the fact after post_change notification.
	 * If the firing mode has been turned off, this has no effect.
	 */
	public void fire(IJavaElementDelta customDelta, int eventType) {
		if (!this.isFiring) return;

		if (DEBUG) {
			System.out.println("-----------------------------------------------------------------------------------------------------------------------");//$NON-NLS-1$
		}

		IJavaElementDelta deltaToNotify;
		if (customDelta == null){
			deltaToNotify = mergeDeltas(this.javaModelDeltas);
		} else {
			deltaToNotify = customDelta;
		}

		// Refresh internal scopes
		if (deltaToNotify != null) {
			Iterator<AbstractSearchScope> scopes = this.manager.searchScopes.keySet().iterator();
			while (scopes.hasNext()) {
				AbstractSearchScope scope = scopes.next();
				scope.processDelta(deltaToNotify, eventType);
			}
			JavaWorkspaceScope workspaceScope = this.manager.workspaceScope;
			if (workspaceScope != null)
				workspaceScope.processDelta(deltaToNotify, eventType);
		}

		// Notification

		// Important: if any listener reacts to notification by updating the listeners list or mask, these lists will
		// be duplicated, so it is necessary to remember original lists in a variable (since field values may change under us)
		IElementChangedListener[] listeners;
		int[] listenerMask;
		int listenerCount;
		synchronized (this.state) {
			listeners = this.state.elementChangedListeners;
			listenerMask = this.state.elementChangedListenerMasks;
			listenerCount = this.state.elementChangedListenerCount;
		}

		switch (eventType) {
			case DEFAULT_CHANGE_EVENT:
			case ElementChangedEvent.POST_CHANGE:
				firePostChangeDelta(deltaToNotify, listeners, listenerMask, listenerCount);
				fireReconcileDelta(listeners, listenerMask, listenerCount);
				break;
		}
	}

	private void firePostChangeDelta(
		IJavaElementDelta deltaToNotify,
		IElementChangedListener[] listeners,
		int[] listenerMask,
		int listenerCount) {

		// post change deltas
		if (DEBUG){
			System.out.println("FIRING POST_CHANGE Delta ["+Thread.currentThread()+"]:"); //$NON-NLS-1$//$NON-NLS-2$
			System.out.println(deltaToNotify == null ? "<NONE>" : deltaToNotify.toString()); //$NON-NLS-1$
		}
		if (deltaToNotify != null) {
			// flush now so as to keep listener reactions to post their own deltas for subsequent iteration
			flush();

			// mark the operation stack has not modifying resources since resource deltas are being fired
			JavaModelOperation.setAttribute(JavaModelOperation.HAS_MODIFIED_RESOURCE_ATTR, null);

			notifyListeners(deltaToNotify, ElementChangedEvent.POST_CHANGE, listeners, listenerMask, listenerCount);
		}
	}
	private void fireReconcileDelta(
		IElementChangedListener[] listeners,
		int[] listenerMask,
		int listenerCount) {


		IJavaElementDelta deltaToNotify = mergeDeltas(this.reconcileDeltas.values());
		if (DEBUG){
			System.out.println("FIRING POST_RECONCILE Delta ["+Thread.currentThread()+"]:"); //$NON-NLS-1$//$NON-NLS-2$
			System.out.println(deltaToNotify == null ? "<NONE>" : deltaToNotify.toString()); //$NON-NLS-1$
		}
		if (deltaToNotify != null) {
			// flush now so as to keep listener reactions to post their own deltas for subsequent iteration
			this.reconcileDeltas = new HashMap<>();

			notifyListeners(deltaToNotify, ElementChangedEvent.POST_RECONCILE, listeners, listenerMask, listenerCount);
		}
	}
	/*
	 * Returns whether a given delta contains some information relevant to the JavaModel,
	 * in particular it will not consider SYNC or MARKER only deltas.
	 */
	private boolean isAffectedBy(IResourceDelta rootDelta){
		//if (rootDelta == null) System.out.println("NULL DELTA");
		//long start = System.currentTimeMillis();
		if (rootDelta != null) {
			// use local exception to quickly escape from delta traversal
			class FoundRelevantDeltaException extends RuntimeException {
				private static final long serialVersionUID = 7137113252936111022L; // backward compatible
				// only the class name is used (to differenciate from other RuntimeExceptions)
			}
			try {
				rootDelta.accept(new IResourceDeltaVisitor() {
					@Override
					public boolean visit(IResourceDelta delta) /* throws CoreException */ {
						switch (delta.getKind()){
							case IResourceDelta.ADDED :
							case IResourceDelta.REMOVED :
								throw new FoundRelevantDeltaException();
							case IResourceDelta.CHANGED :
								// if any flag is set but SYNC or MARKER, this delta should be considered
								if (delta.getAffectedChildren().length == 0 // only check leaf delta nodes
										&& (delta.getFlags() & ~(IResourceDelta.SYNC | IResourceDelta.MARKERS)) != 0) {
									throw new FoundRelevantDeltaException();
								}
						}
						return true;
					}
				},
				IContainer.INCLUDE_HIDDEN);
			} catch(FoundRelevantDeltaException e) {
				//System.out.println("RELEVANT DELTA detected in: "+ (System.currentTimeMillis() - start));
				return true;
			} catch(CoreException e) { // ignore delta if not able to traverse
			}
		}
		//System.out.println("IGNORE SYNC DELTA took: "+ (System.currentTimeMillis() - start));
		return false;
	}
	/*
	 * Returns whether the given element is a primary compilation unit in working copy mode.
	 */
	private boolean isPrimaryWorkingCopy(IJavaElement element, int elementType) {
		if (elementType == IJavaElement.COMPILATION_UNIT) {
			CompilationUnit cu = (CompilationUnit)element;
			return cu.isPrimary() && cu.isWorkingCopy();
		}
		return false;
	}
	/*
	 * Returns whether the given resource is in one of the given output folders and if
	 * it is filtered out from this output folder.
	 */
	private boolean isResFilteredFromOutput(RootInfo rootInfo, OutputsInfo info, IResource res, int elementType) {
		if (info != null) {
			JavaProject javaProject = null;
			String sourceLevel = null;
			String complianceLevel = null;
			IPath resPath = res.getFullPath();
			for (int i = 0;  i < info.outputCount; i++) {
				if (info.paths[i].isPrefixOf(resPath)) {
					if (info.traverseModes[i] != IGNORE) {
						// case of bin=src
						if (info.traverseModes[i] == SOURCE && elementType == IJavaElement.CLASS_FILE) {
							return true;
						}
						// case of .class file under project and no source folder
						// proj=bin
						if (elementType == IJavaElement.JAVA_PROJECT && res instanceof IFile) {
							if (sourceLevel == null) {
								// Get java project to use its source and compliance levels
								javaProject = rootInfo == null ?
									(JavaProject)createElement(res.getProject(), IJavaElement.JAVA_PROJECT, null) :
									rootInfo.project;
								if (javaProject != null) {
									sourceLevel = javaProject.getOption(JavaCore.COMPILER_SOURCE, true);
									complianceLevel = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true);
								}
							}
							if (Util.isValidClassFileName(res.getName(), sourceLevel, complianceLevel)) {
								return true;
							}
						}
					} else {
						return true;
					}
				}
			}
		}
		return false;
	}
	/*
	 * Merges all awaiting deltas.
	 */
	private IJavaElementDelta mergeDeltas(Collection<IJavaElementDelta> deltas) {
		if (deltas.size() == 0) return null;
		if (deltas.size() == 1) return deltas.iterator().next();

		if (VERBOSE) {
			System.out.println("MERGING " + deltas.size() + " DELTAS ["+Thread.currentThread()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}

		Iterator<IJavaElementDelta> iterator = deltas.iterator();
		JavaElementDelta rootDelta = new JavaElementDelta(this.manager.javaModel);
		boolean insertedTree = false;
		while (iterator.hasNext()) {
			JavaElementDelta delta = (JavaElementDelta)iterator.next();
			if (VERBOSE) {
				System.out.println(delta.toString());
			}
			IJavaElement element = delta.getElement();
			if (this.manager.javaModel.equals(element)) {
				IJavaElementDelta[] children = delta.getAffectedChildren();
				for (int j = 0; j < children.length; j++) {
					JavaElementDelta projectDelta = (JavaElementDelta) children[j];
					rootDelta.insertDeltaTree(projectDelta.getElement(), projectDelta);
					insertedTree = true;
				}
				IResourceDelta[] resourceDeltas = delta.getResourceDeltas();
				if (resourceDeltas != null) {
					for (int i = 0, length = resourceDeltas.length; i < length; i++) {
						rootDelta.addResourceDelta(resourceDeltas[i]);
						insertedTree = true;
					}
				}
			} else {
				rootDelta.insertDeltaTree(element, delta);
				insertedTree = true;
			}
		}
		if (insertedTree) return rootDelta;
		return null;
	}
	private void notifyListeners(IJavaElementDelta deltaToNotify, int eventType, IElementChangedListener[] listeners, int[] listenerMask, int listenerCount) {
		final ElementChangedEvent extraEvent = new ElementChangedEvent(deltaToNotify, eventType);
		for (int i= 0; i < listenerCount; i++) {
			if ((listenerMask[i] & eventType) != 0){
				final IElementChangedListener listener = listeners[i];
				long start = -1;
				if (VERBOSE) {
					System.out.print("Listener #" + (i+1) + "=" + listener.toString());//$NON-NLS-1$//$NON-NLS-2$
					start = System.currentTimeMillis();
				}
				// wrap callbacks with Safe runnable for subsequent listeners to be called when some are causing grief
				SafeRunner.run(new ISafeRunnable() {
					@Override
					public void handleException(Throwable exception) {
						Util.log(exception, "Exception occurred in listener of Java element change notification"); //$NON-NLS-1$
					}
					@Override
					public void run() throws Exception {
						PerformanceStats stats = null;
						if(PERF) {
							stats = PerformanceStats.getStats(JavaModelManager.DELTA_LISTENER_PERF, listener);
							stats.startRun();
						}
						listener.elementChanged(extraEvent);
						if(PERF) {
							stats.endRun();
						}
					}
				});
				if (VERBOSE) {
					System.out.println(" -> " + (System.currentTimeMillis()-start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
				}
			}
		}
	}
	private void notifyTypeHierarchies(IElementChangedListener[] listeners, int listenerCount) {
		for (int i= 0; i < listenerCount; i++) {
			final IElementChangedListener listener = listeners[i];
			if (!(listener instanceof TypeHierarchy)) continue;

			// wrap callbacks with Safe runnable for subsequent listeners to be called when some are causing grief
			SafeRunner.run(new ISafeRunnable() {
				@Override
				public void handleException(Throwable exception) {
					Util.log(exception, "Exception occurred in listener of Java element change notification"); //$NON-NLS-1$
				}
				@Override
				public void run() throws Exception {
					TypeHierarchy typeHierarchy = (TypeHierarchy)listener;
					if (typeHierarchy.hasFineGrainChanges()) {
						// case of changes in primary working copies
						typeHierarchy.needsRefresh = true;
						typeHierarchy.fireChange();
					}
				}
			});
		}
	}
	/*
	 * Generic processing for elements with changed contents:<ul>
	 * <li>The element is closed such that any subsequent accesses will re-open
	 * the element reflecting its new structure.
	 * <li>An entry is made in the delta reporting a content change (K_CHANGE with F_CONTENT flag set).
	 * </ul>
	 */
	private void nonJavaResourcesChanged(Openable element, IResourceDelta delta) 	throws JavaModelException {
		// reset non-java resources if element was open
		if (element.isOpen()) {
			JavaElementInfo info = (JavaElementInfo)element.getElementInfo();
			switch (element.getElementType()) {
				case IJavaElement.JAVA_MODEL :
					((JavaModelInfo) info).setNonJavaResources(null);
					if (!ExternalFoldersManager.isInternalPathForExternalFolder(delta.getFullPath()))
						currentDelta().addResourceDelta(delta);
					return;
				case IJavaElement.JAVA_PROJECT :
					((JavaProjectElementInfo) info).setNonJavaResources(null);

					// if a package fragment root is the project, clear it too
					JavaProject project = (JavaProject) element;
					PackageFragmentRoot projectRoot =
						(PackageFragmentRoot) project.getPackageFragmentRoot(project.getProject());
					if (projectRoot.isOpen()) {
						((PackageFragmentRootInfo) projectRoot.getElementInfo()).setNonJavaResources(null);
					}
					break;
				case IJavaElement.PACKAGE_FRAGMENT :
					 ((PackageFragmentInfo) info).setNonJavaResources(null);
					break;
				case IJavaElement.PACKAGE_FRAGMENT_ROOT :
					 ((PackageFragmentRootInfo) info).setNonJavaResources(null);
			}
		}

		JavaElementDelta current = currentDelta();
		JavaElementDelta elementDelta = current.find(element);
		if (elementDelta == null) {
			// don't use find after creating the delta as it can be null (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63434)
			elementDelta = current.changed(element, IJavaElementDelta.F_CONTENT);
		}
		if (!ExternalFoldersManager.isInternalPathForExternalFolder(delta.getFullPath()))
			elementDelta.addResourceDelta(delta);
	}
	/*
	 * Returns the old root info for the given path and project.
	 */
	private RootInfo oldRootInfo(IPath path, JavaProject project) {
		RootInfo oldInfo = this.state.oldRoots.get(path);
		if (oldInfo == null)
			return null;
		if (oldInfo.project.equals(project))
			return oldInfo;
		List<RootInfo> oldInfos = this.state.oldOtherRoots.get(path);
		if (oldInfos == null)
			return null;
		for (int i = 0, length = oldInfos.size(); i < length; i++) {
			oldInfo = oldInfos.get(i);
			if (oldInfo.project.equals(project))
				return oldInfo;
		}
		return null;
	}
	/*
	 * Returns the other root infos for the given path. Look in the old other roots table if kind is REMOVED.
	 */
	private List<RootInfo> otherRootsInfo(IPath path, int kind) {
		if (kind == IResourceDelta.REMOVED) {
			return this.state.oldOtherRoots.get(path);
		}
		return this.state.otherRoots.get(path);
	}

	private OutputsInfo outputsInfo(RootInfo rootInfo, IResource res) {
		try {
			JavaProject proj =
				rootInfo == null ?
					(JavaProject)createElement(res.getProject(), IJavaElement.JAVA_PROJECT, null) :
					rootInfo.project;
			if (proj != null) {
				IPath projectOutput = proj.getOutputLocation();
				int traverseMode = IGNORE;
				if (proj.getProject().getFullPath().equals(projectOutput)){ // case of proj==bin==src
					return new OutputsInfo(new IPath[] {projectOutput}, new int[] {SOURCE}, 1);
				}
				IClasspathEntry[] classpath = proj.getResolvedClasspath();
				IPath[] outputs = new IPath[classpath.length+1];
				int[] traverseModes = new int[classpath.length+1];
				int outputCount = 1;
				outputs[0] = projectOutput;
				traverseModes[0] = traverseMode;
				for (int i = 0, length = classpath.length; i < length; i++) {
					IClasspathEntry entry = classpath[i];
					IPath entryPath = entry.getPath();
					IPath output = entry.getOutputLocation();
					if (output != null) {
						outputs[outputCount] = output;
						// check case of src==bin
						if (entryPath.equals(output)) {
							traverseModes[outputCount++] = (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) ? SOURCE : BINARY;
						} else {
							traverseModes[outputCount++] = IGNORE;
						}
					}

					// check case of src==bin
					if (entryPath.equals(projectOutput)) {
						traverseModes[0] = (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) ? SOURCE : BINARY;
					}
				}
				return new OutputsInfo(outputs, traverseModes, outputCount);
			}
		} catch (JavaModelException e) {
			// java project doesn't exist: ignore
		}
		return null;
	}
	private void popUntilPrefixOf(IPath path) {
		while (this.currentElement != null) {
			IPath currentElementPath = null;
			if (this.currentElement instanceof IPackageFragmentRoot) {
				currentElementPath = ((IPackageFragmentRoot)this.currentElement).getPath();
			} else {
				IResource currentElementResource = this.currentElement.resource();
				if (currentElementResource != null) {
					currentElementPath = currentElementResource.getFullPath();
				}
			}
			if (currentElementPath != null) {
				if (this.currentElement instanceof IPackageFragment
					&& ((IPackageFragment) this.currentElement).isDefaultPackage()
					&& currentElementPath.segmentCount() != path.segmentCount()-1) {
						// default package and path is not a direct child
						this.currentElement = (Openable)this.currentElement.getParent();
				}
				if (currentElementPath.isPrefixOf(path)) {
					return;
				}
			}
			this.currentElement = (Openable)this.currentElement.getParent();
		}
	}
	/*
	 * Converts a <code>IResourceDelta</code> rooted in a <code>Workspace</code> into
	 * the corresponding set of <code>IJavaElementDelta</code>, rooted in the
	 * relevant <code>JavaModel</code>s.
	 */
	private IJavaElementDelta processResourceDelta(IResourceDelta changes) {

		try {
			IJavaModel model = this.manager.getJavaModel();
			if (!model.isOpen()) {
				// force opening of java model so that java element delta are reported
				try {
					model.open(null);
				} catch (JavaModelException e) {
					if (VERBOSE) {
						e.printStackTrace();
					}
					return null;
				}
			}
			this.state.initializeRoots(false/*not initiAfterLoad*/);
			this.currentElement = null;

			// get the workspace delta, and start processing there.
			IResourceDelta[] deltas = changes.getAffectedChildren(IResourceDelta.ADDED | IResourceDelta.REMOVED | IResourceDelta.CHANGED, IContainer.INCLUDE_HIDDEN);
			for (int i = 0; i < deltas.length; i++) {
				IResourceDelta delta = deltas[i];
				IResource res = delta.getResource();

				// find out the element type
				RootInfo rootInfo = null;
				int elementType;
				IProject proj = (IProject)res;
				boolean wasJavaProject = this.state.findJavaProject(proj.getName()) != null;
				boolean isJavaProject = JavaProject.hasJavaNature(proj);
				if (!wasJavaProject && !isJavaProject) {
					elementType = NON_JAVA_RESOURCE;
				} else {
					IPath rootPath = externalPath(res);
					rootInfo = enclosingRootInfo(rootPath, delta.getKind());
					if (rootInfo != null && rootInfo.isRootOfProject(rootPath)) {
						elementType = IJavaElement.PACKAGE_FRAGMENT_ROOT;
					} else {
						elementType = IJavaElement.JAVA_PROJECT;
					}
				}

				// traverse delta
				traverseDelta(delta, elementType, rootInfo, null);

				if (elementType == NON_JAVA_RESOURCE
						|| (wasJavaProject != isJavaProject && (delta.getKind()) == IResourceDelta.CHANGED)) { // project has changed nature (description or open/closed)
					try {
						// add child as non java resource
						nonJavaResourcesChanged((JavaModel)model, delta);
					} catch (JavaModelException e) {
						// java model could not be opened
					}
				}

			}
			resetProjectCaches();

			return this.currentDelta;
		} finally {
			this.currentDelta = null;
		}
	}
	/*
	 * Traverse the set of projects which have changed namespace, and reset their
	 * caches and their dependents
	 */
	public void resetProjectCaches() {
		if (this.projectCachesToReset.isEmpty())
			return;

		JavaModelManager.getJavaModelManager().resetJarTypeCache();

		Iterator<IJavaElement> iterator = this.projectCachesToReset.iterator();
		Map<IJavaProject, IJavaProject[]> projectDepencies = this.state.projectDependencies;
		Set<IJavaElement> affectedDependents = new HashSet<>();
		while (iterator.hasNext()) {
			JavaProject project = (JavaProject)iterator.next();
			project.resetCaches();
			addDependentProjects(project, projectDepencies, affectedDependents);
		}
		// reset caches of dependent projects
		iterator = affectedDependents.iterator();
		while (iterator.hasNext()) {
			JavaProject project = (JavaProject) iterator.next();
			project.resetCaches();
		}

		this.projectCachesToReset.clear();
	}
	/*
	 * Registers the given delta with this delta processor.
	 */
	public void registerJavaModelDelta(IJavaElementDelta delta) {
		this.javaModelDeltas.add(delta);
	}
	/*
	 * Removes the given element from its parents cache of children. If the
	 * element does not have a parent, or the parent is not currently open,
	 * this has no effect.
	 */
	private void removeFromParentInfo(Openable child) {

		Openable parent = (Openable) child.getParent();
		if (parent != null && parent.isOpen()) {
			try {
				OpenableElementInfo info = (OpenableElementInfo) parent.getElementInfo();
				info.removeChild(child);
			} catch (JavaModelException e) {
				// do nothing - we already checked if open
			}
		}
	}
	/*
	 * Notification that some resource changes have happened
	 * on the platform, and that the Java Model should update any required
	 * internal structures such that its elements remain consistent.
	 * Translates <code>IResourceDeltas</code> into <code>IJavaElementDeltas</code>.
	 *
	 * @see IResourceDelta
	 * @see IResource
	 */
	public void resourceChanged(IResourceChangeEvent event) {

		int eventType = this.overridenEventType == -1 ? event.getType() : this.overridenEventType;
		IResource resource = event.getResource();
		IResourceDelta delta = event.getDelta();

		switch(eventType){
			case IResourceChangeEvent.PRE_DELETE :
				try {
					if(resource.getType() == IResource.PROJECT
						&& ((IProject) resource).hasNature(JavaCore.NATURE_ID)) {

						deleting((IProject)resource);
					}
				} catch(CoreException e){
					// project doesn't exist or is not open: ignore
				}
				return;

			case IResourceChangeEvent.PRE_REFRESH:
				IProject [] projects = null;
				Object o = event.getSource();
				if (o instanceof IProject) {
					projects = new IProject[] { (IProject) o };
				} else if (o instanceof IWorkspace) {
					// https://bugs.eclipse.org/bugs/show_bug.cgi?id=261594. The single workspace refresh
					// notification we see, implies that all projects are about to be refreshed.
					 projects = ((IWorkspace) o).getRoot().getProjects(IContainer.INCLUDE_HIDDEN);
				}
				//https://bugs.eclipse.org/bugs/show_bug.cgi?id=302295
				// Refresh all project references together in a single job
				JavaModelManager.getExternalManager().refreshReferences(projects, null);

				IJavaProject[] javaElements = new IJavaProject[projects.length];
				for (int index = 0; index < projects.length; index++) {
					javaElements[index] = JavaCore.create(projects[index]);
				}
				try {
					checkExternalArchiveChanges(javaElements, true, null);
				} catch (JavaModelException e) {
		        	if (!e.isDoesNotExist())
		        		Util.log(e, "Exception while updating external archives"); //$NON-NLS-1$
				}
				return;

			case IResourceChangeEvent.POST_CHANGE :
				Set<IJavaElement> elementsToRefresh = this.state.removeExternalElementsToRefresh();
				if (isAffectedBy(delta) // avoid populating for SYNC or MARKER deltas
						|| elementsToRefresh != null) {
					try {
						try {
							stopDeltas();
							checkProjectsAndClasspathChanges(delta);

							// generate external archive change deltas
							if (elementsToRefresh != null) {
								createExternalArchiveDelta(elementsToRefresh, null);
							}

							// generate classpath change deltas
							Map<IProject, ClasspathChange> classpathChanges = this.state.removeAllClasspathChanges();
							if (classpathChanges.size() > 0) {
								boolean hasDelta = this.currentDelta != null;
								JavaElementDelta javaDelta = currentDelta();
								Iterator<ClasspathChange> changes = classpathChanges.values().iterator();
								while (changes.hasNext()) {
									ClasspathChange change = changes.next();
									int result = change.generateDelta(javaDelta, false/*don't add classpath change*/);
									if ((result & ClasspathChange.HAS_DELTA) != 0) {
										hasDelta = true;

										// need to recompute root infos
										this.state.rootsAreStale = true;

										change.requestIndexing();
										this.state.addClasspathValidation(change.project);
									}
									if ((result & ClasspathChange.HAS_PROJECT_CHANGE) != 0) {
										change.project.getProject().clearCachedDynamicReferences();
										this.state.addProjectReferenceChange(change.project);
									}
									if ((result & ClasspathChange.HAS_LIBRARY_CHANGE) != 0) {
										this.state.addExternalFolderChange(change.project, change.oldResolvedClasspath);
									}
								}
								// process late coming external elements to refresh (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=212769 )
								elementsToRefresh = this.state.removeExternalElementsToRefresh();
								if (elementsToRefresh != null) {
									hasDelta |= createExternalArchiveDelta(elementsToRefresh, null);
								}
								if (!hasDelta)
									this.currentDelta = null;
							}

							// generate Java deltas from resource changes
							IJavaElementDelta translatedDelta = processResourceDelta(delta);
							if (translatedDelta != null) {
								registerJavaModelDelta(translatedDelta);
							}
						} finally {
							this.sourceElementParserCache = null; // don't hold onto parser longer than necessary
							startDeltas();
						}
						notifyAndFire(null);
					} finally {
						// workaround for bug 15168 circular errors not reported
						this.state.resetOldJavaProjectNames();
						this.oldRoots = null;
					}
				}
				return;

			case IResourceChangeEvent.PRE_BUILD :
				// force initialization of roots before builders run to avoid deadlock in another thread
				// (note this is no-op if already initialized)
				// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=241751
				this.state.initializeRoots(false/*not initiAfterLoad*/);

				boolean isAffected = isAffectedBy(delta);
				boolean needCycleValidation = isAffected && validateClasspaths(delta);

				// update external folders if necessary
			    ExternalFolderChange[] folderChanges = this.state.removeExternalFolderChanges();
				if (folderChanges != null) {
				    for (int i = 0, length = folderChanges.length; i < length; i++) {
				        try {
					        folderChanges[i].updateExternalFoldersIfNecessary(false/*do not refresh since we are not in the thread that added the external folder to the classpath*/, null);
				        } catch (JavaModelException e) {
				        	if (!e.isDoesNotExist())
				        		Util.log(e, "Exception while updating external folders"); //$NON-NLS-1$
				        }
				    }
				}

				// create classpath markers if necessary
				ClasspathValidation[] validations = this.state.removeClasspathValidations();
				if (validations != null) {
					for (int i = 0, length = validations.length; i < length; i++) {
						ClasspathValidation validation = validations[i];
						validation.validate();
					}
				}

				// update project references if necessary
				Set<IJavaProject> referencedProjects = this.state.removeProjectReferenceChanges();
				needCycleValidation = needCycleValidation || !referencedProjects.isEmpty();

				if (needCycleValidation) {
					for (IJavaProject next : referencedProjects) {
						next.getProject().clearCachedDynamicReferences();
					}
					// update all cycle markers since the project references changes may have affected cycles
					try {
						JavaProject.validateCycles(null);
					} catch (JavaModelException e) {
						// a project no longer exists
					}
				}

				if (isAffected) {
					Object source = event.getSource();
					projects = null;
					if (source instanceof IWorkspace) {
						 projects = ((IWorkspace) source).getRoot().getProjects();
					} else if (source instanceof IProject) {
						projects = new IProject[] {(IProject) source};
					} else {
						Util.log(new Exception(),
								"Expected to see a workspace or project on the PRE_BUILD resource change but was: " + source.toString()); //$NON-NLS-1$
					}
					if (projects != null) {
						// If we are about to do a build and a Java project's first builder is not the Java builder,
						// then it is possible that one of the earlier builders will build a jar file that is on that
						// project's classpath. If we see that, then to be safe we must flush the caching of the
						// JavaModelManager's external file state.
						// A possible further optimization for this situation where earlier builders can affect the
						// Java builder would be to add a new classpath element attribute that identifies whether
						// or not a library jar is "stable" and needs to be flushed.
						for (int i = 0; i < projects.length; i++) {
							try {
								IProject project = projects[i];
								if (project.isOpen() && project.hasNature(JavaCore.NATURE_ID)) {
									IBuildConfiguration[] configs = project.getBuildConfigs();
									if (configs.length > 1 && !JavaCore.BUILDER_ID.equals(configs[0].getName())) {
										this.manager.resetExternalFilesCache();
										break;
									}
								}
							} catch (CoreException exception) {
				        		Util.log(exception, "Exception while checking builder configuration ordering"); //$NON-NLS-1$
							}
						 }
					}
					JavaBuilder.buildStarting();
				}

				// does not fire any deltas
				return;

			case IResourceChangeEvent.POST_BUILD :
				JavaBuilder.buildFinished();
				return;
		}
	}

	public void notifyAndFire(IJavaElementDelta delta) {
		IElementChangedListener[] listeners;
		int listenerCount;
		synchronized (this.state) {
			listeners = this.state.elementChangedListeners;
			listenerCount = this.state.elementChangedListenerCount;
		}
		notifyTypeHierarchies(listeners, listenerCount);
		fire(delta, ElementChangedEvent.POST_CHANGE);
	}

	/*
	 * Returns the root info for the given path. Look in the old roots table if kind is REMOVED.
	 */
	private RootInfo rootInfo(IPath path, int kind) {
		if (kind == IResourceDelta.REMOVED) {
			return this.state.oldRoots.get(path);
		}
		return this.state.roots.get(path);
	}
	/*
	 * Turns the firing mode to on. That is, deltas that are/have been
	 * registered will be fired.
	 */
	private void startDeltas() {
		this.isFiring= true;
	}
	/*
	 * Turns the firing mode to off. That is, deltas that are/have been
	 * registered will not be fired until deltas are started again.
	 */
	private void stopDeltas() {
		this.isFiring= false;
	}
	/*
	 * Converts an <code>IResourceDelta</code> and its children into
	 * the corresponding <code>IJavaElementDelta</code>s.
	 */
	private void traverseDelta(
		IResourceDelta delta,
		int elementType,
		RootInfo rootInfo,
		OutputsInfo outputsInfo) {

		IResource res = delta.getResource();

		// set stack of elements
		if (this.currentElement == null && rootInfo != null) {
			this.currentElement = rootInfo.project;
		}

		// process current delta
		boolean processChildren = true;
		if (res instanceof IProject) {
			// reset source element parser cache
			this.sourceElementParserCache = null;

			processChildren =
				updateCurrentDeltaAndIndex(
					delta,
					elementType == IJavaElement.PACKAGE_FRAGMENT_ROOT ?
						IJavaElement.JAVA_PROJECT : // case of prj=src
						elementType,
					rootInfo);
		} else if (rootInfo != null) {
			processChildren = updateCurrentDeltaAndIndex(delta, elementType, rootInfo);
		} else {
			// not yet inside a package fragment root
			processChildren = true;
		}

		// get the project's output locations and traverse mode
		if (outputsInfo == null) outputsInfo = outputsInfo(rootInfo, res);

		// process children if needed
		if (processChildren) {
			IResourceDelta[] children = delta.getAffectedChildren();
			boolean oneChildOnClasspath = false;
			int length = children.length;
			IResourceDelta[] orphanChildren = null;
			Openable parent = null;
			boolean isValidParent = true;
			for (int i = 0; i < length; i++) {
				IResourceDelta child = children[i];
				IResource childRes = child.getResource();

				// check source attachment change
				checkSourceAttachmentChange(child, childRes);

				// find out whether the child is a package fragment root of the current project
				IPath childPath = externalPath(childRes);
				int childKind = child.getKind();
				RootInfo childRootInfo = rootInfo(childPath, childKind);
				RootInfo originalChildRootInfo = childRootInfo;
				if (childRootInfo != null && !childRootInfo.isRootOfProject(childPath)) {
					// package fragment root of another project (dealt with later)
					childRootInfo = null;
				}

				// compute child type
				int childType =
					elementType(
						childRes,
						childKind,
						elementType,
						rootInfo == null ? childRootInfo : rootInfo
					);

				// is childRes in the output folder and is it filtered out ?
				boolean isResFilteredFromOutput = isResFilteredFromOutput(rootInfo, outputsInfo, childRes, childType);

				boolean isNestedRoot = rootInfo != null && childRootInfo != null;
				if (!isResFilteredFromOutput
						&& !isNestedRoot) { // do not treat as non-java rsc if nested root

					traverseDelta(child, childType, rootInfo == null ? childRootInfo : rootInfo, outputsInfo); // traverse delta for child in the same project

					if (childType == NON_JAVA_RESOURCE) {
						if (rootInfo != null) { // if inside a package fragment root
							if (!isValidParent) continue;
							if (parent == null) {
								// find the parent of the non-java resource to attach to
								if (this.currentElement == null
										|| !rootInfo.project.equals(this.currentElement.getJavaProject())) { // note if currentElement is the IJavaModel, getJavaProject() is null
									// force the currentProject to be used
									this.currentElement = rootInfo.project;
								}
								if (elementType == IJavaElement.JAVA_PROJECT
									|| (elementType == IJavaElement.PACKAGE_FRAGMENT_ROOT
										&& res instanceof IProject)) {
									// NB: attach non-java resource to project (not to its package fragment root)
									parent = rootInfo.project;
								} else {
									parent = createElement(res, elementType, rootInfo);
								}
								if (parent == null) {
									isValidParent = false;
									continue;
								}
							}
							// add child as non java resource
							try {
								nonJavaResourcesChanged(parent, child);
							} catch (JavaModelException e) {
								// ignore
							}
						} else {
							// the non-java resource (or its parent folder) will be attached to the java project
							if (orphanChildren == null) orphanChildren = new IResourceDelta[length];
							orphanChildren[i] = child;
						}
					} else {
						if (rootInfo == null && childRootInfo == null) {
							// the non-java resource (or its parent folder) will be attached to the java project
							if (orphanChildren == null) orphanChildren = new IResourceDelta[length];
							orphanChildren[i] = child;
						}
					}
				} else {
					oneChildOnClasspath = true; // to avoid reporting child delta as non-java resource delta
				}

				// if child is a nested root
				// or if it is not a package fragment root of the current project
				// but it is a package fragment root of another project, traverse delta too
				if (isNestedRoot
						|| (childRootInfo == null && originalChildRootInfo != null)) {
					traverseDelta(child, IJavaElement.PACKAGE_FRAGMENT_ROOT, originalChildRootInfo, null); // binary output of childRootInfo.project cannot be this root
				}

				// if the child is a package fragment root of one or several other projects
				List<RootInfo> rootList;
				if ((rootList = otherRootsInfo(childPath, childKind)) != null) {
					Iterator<RootInfo> iterator = rootList.iterator();
					while (iterator.hasNext()) {
						originalChildRootInfo = iterator.next();
						this.currentElement = null; // ensure that 2 roots refering to the same resource don't share the current element (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=210746 )
						traverseDelta(child, IJavaElement.PACKAGE_FRAGMENT_ROOT, originalChildRootInfo, null); // binary output of childRootInfo.project cannot be this root
					}
				}
			}
			if (orphanChildren != null
					&& (oneChildOnClasspath // orphan children are siblings of a package fragment root
						|| res instanceof IProject)) { // non-java resource directly under a project

				// attach orphan children
				IProject rscProject = res.getProject();
				JavaProject adoptiveProject = (JavaProject)JavaCore.create(rscProject);
				if (adoptiveProject != null
						&& JavaProject.hasJavaNature(rscProject)) { // delta iff Java project (18698)
					for (int i = 0; i < length; i++) {
						if (orphanChildren[i] != null) {
							try {
								nonJavaResourcesChanged(adoptiveProject, orphanChildren[i]);
							} catch (JavaModelException e) {
								// ignore
							}
						}
					}
				}
			} // else resource delta will be added by parent
		} // else resource delta will be added by parent
	}

	private void validateClasspaths(IResourceDelta delta, Set<IPath> affectedProjects) {
		IResource resource = delta.getResource();
		boolean processChildren = false;
		switch (resource.getType()) {
			case IResource.ROOT :
				if (delta.getKind() == IResourceDelta.CHANGED) {
					processChildren = true;
				}
				break;
			case IResource.PROJECT :
				IProject project = (IProject)resource;
				int kind = delta.getKind();
				boolean isJavaProject = JavaProject.hasJavaNature(project);
				switch (kind) {
					case IResourceDelta.ADDED:
						processChildren = isJavaProject;
						affectedProjects.add(project.getFullPath());
						break;
					case IResourceDelta.CHANGED:
						processChildren = isJavaProject;
						if ((delta.getFlags() & IResourceDelta.OPEN) != 0) {
							// project opened or closed
							if (isJavaProject) {
								JavaProject javaProject = (JavaProject)JavaCore.create(project);
								this.state.addClasspathValidation(javaProject); // in case .classpath got modified while closed
							}
							affectedProjects.add(project.getFullPath());
						} else if ((delta.getFlags() & IResourceDelta.DESCRIPTION) != 0) {
							boolean wasJavaProject = this.state.findJavaProject(project.getName()) != null;
							if (wasJavaProject  != isJavaProject) {
								// project gained or lost Java nature
								JavaProject javaProject = (JavaProject)JavaCore.create(project);
								this.state.addClasspathValidation(javaProject); // add/remove classpath markers
								affectedProjects.add(project.getFullPath());
							}
						}
						break;
					case IResourceDelta.REMOVED:
						affectedProjects.add(project.getFullPath());
						break;
				}
				break;
			case IResource.FILE :
				/* check classpath or prefs files change */
				IFile file = (IFile) resource;
				String fileName = file.getName();
				RootInfo rootInfo = null;
				// https://bugs.eclipse.org/bugs/show_bug.cgi?id=229042
				// Mark a validation if a library with package fragment root in the project has changed
				if (fileName.equals(JavaProject.CLASSPATH_FILENAME)
						|| ((rootInfo = rootInfo(file.getFullPath(), delta.getKind())) != null && rootInfo.entryKind == IClasspathEntry.CPE_LIBRARY)) {
					JavaProject javaProject = (JavaProject)JavaCore.create(file.getProject());
					this.state.addClasspathValidation(javaProject);
					affectedProjects.add(file.getProject().getFullPath());
				}
				break;
		}
		if (processChildren) {
			IResourceDelta[] children = delta.getAffectedChildren();
			for (int i = 0; i < children.length; i++) {
				validateClasspaths(children[i], affectedProjects);
			}
		}
	}

	/*
	 * Validate the classpaths of the projects affected by the given delta.
	 * Create markers if necessary.
	 * Returns whether cycle markers should be recomputed.
	 */
	private boolean validateClasspaths(IResourceDelta delta) {
		Set<IPath> affectedProjects = new HashSet<>(5);
		validateClasspaths(delta, affectedProjects);
		boolean needCycleValidation = false;

		// validate classpaths of affected projects (dependent projects
		// or projects that reference a library in one of the projects that have changed)
		if (!affectedProjects.isEmpty()) {
			IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
			IProject[] projects = workspaceRoot.getProjects();
			int length = projects.length;
			for (int i = 0; i < length; i++){
				IProject project = projects[i];
				JavaProject javaProject = (JavaProject)JavaCore.create(project);
				try {
					IPath projectPath = project.getFullPath();
					IClasspathEntry[] classpath = javaProject.getResolvedClasspath(); // allowed to reuse model cache
					for (int j = 0, cpLength = classpath.length; j < cpLength; j++) {
						IClasspathEntry entry = classpath[j];
						switch (entry.getEntryKind()) {
							case IClasspathEntry.CPE_PROJECT:
								if (affectedProjects.contains(entry.getPath())) {
									this.state.addClasspathValidation(javaProject);
									needCycleValidation = true;
								}
								break;
							case IClasspathEntry.CPE_LIBRARY:
								IPath entryPath = entry.getPath();
								IPath libProjectPath = entryPath.removeLastSegments(entryPath.segmentCount()-1);
								if (!libProjectPath.equals(projectPath) // if library contained in another project
										&& affectedProjects.contains(libProjectPath)) {
									this.state.addClasspathValidation(javaProject);
								}
								break;
						}
					}
				} catch(JavaModelException e) {
						// project no longer exists
				}
			}
		}
		return needCycleValidation;
	}

	/*
	 * Update the current delta (i.e. add/remove/change the given element) and update the corresponding index.
	 * Returns whether the children of the given delta must be processed.
	 * @throws a JavaModelException if the delta doesn't correspond to a java element of the given type.
	 */
	@SuppressWarnings("unlikely-arg-type")
	public boolean updateCurrentDeltaAndIndex(IResourceDelta delta, int elementType, RootInfo rootInfo) {
		Openable element;
		switch (delta.getKind()) {
			case IResourceDelta.ADDED :
				IResource deltaRes = delta.getResource();
				element = createElement(deltaRes, elementType, rootInfo);
				if (element == null) {
					// resource might be containing shared roots (see bug 19058)
					this.state.updateRoots(deltaRes.getFullPath(), delta, this);
					return rootInfo != null && rootInfo.inclusionPatterns != null;
				}
				updateIndex(element, delta);
				elementAdded(element, delta, rootInfo);
				if (elementType == IJavaElement.PACKAGE_FRAGMENT_ROOT)
					this.state.addClasspathValidation(rootInfo.project);
				return elementType == IJavaElement.PACKAGE_FRAGMENT;
			case IResourceDelta.REMOVED :
				deltaRes = delta.getResource();
				element = createElement(deltaRes, elementType, rootInfo);
				if (element == null) {
					// resource might be containing shared roots (see bug 19058)
					this.state.updateRoots(deltaRes.getFullPath(), delta, this);
					return rootInfo != null && rootInfo.inclusionPatterns != null;
				}
				updateIndex(element, delta);
				elementRemoved(element, delta, rootInfo);
				if (elementType == IJavaElement.PACKAGE_FRAGMENT_ROOT)
					this.state.addClasspathValidation(rootInfo.project);

				if (deltaRes.getType() == IResource.PROJECT){
					// reset the corresponding project built state, since cannot reuse if added back
					if (JavaBuilder.DEBUG)
						System.out.println("Clearing last state for removed project : " + deltaRes); //$NON-NLS-1$
					this.manager.setLastBuiltState((IProject)deltaRes, null /*no state*/);

					// clean up previous session containers (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=89850)
					this.manager.previousSessionContainers.remove(element);
				}
				return elementType == IJavaElement.PACKAGE_FRAGMENT;
			case IResourceDelta.CHANGED :
				int flags = delta.getFlags();
				if (elementType == IJavaElement.PACKAGE_FRAGMENT_ROOT && (flags & IResourceDelta.LOCAL_CHANGED) != 0) {
					// external folder added or removed
					if (oldRootInfo(rootInfo.rootPath, rootInfo.project) == null) {
						// root just added to the classpath
						break;
					}
					deltaRes = delta.getResource();
					Object target = JavaModel.getExternalTarget(deltaRes.getLocation(), true/*check resource existence*/);
					element = createElement(deltaRes, elementType, rootInfo);
					updateIndex(element, delta);
					if (target != null) {
						// external folder added
						elementAdded(element, delta, rootInfo);
					} else {
						// external folder removed
						elementRemoved(element, delta, rootInfo);
					}
					this.state.addClasspathValidation(rootInfo.project);
				} else if (elementType == IJavaElement.JAVA_PROJECT) {
					if ((flags & IResourceDelta.OPEN) != 0) {
						// project has been opened or closed
						IProject res = (IProject)delta.getResource();
						element = createElement(res, elementType, rootInfo);
						if (element == null) {
							// resource might be containing shared roots (see bug 19058)
							this.state.updateRoots(res.getFullPath(), delta, this);
							return false;
						}
						if (res.isOpen()) {
							if (JavaProject.hasJavaNature(res)) {
								addToParentInfo(element);
								this.manager.getPerProjectInfo(res, true /*create info if needed*/).rememberExternalLibTimestamps();
								currentDelta().opened(element);
								this.state.updateRoots(element.getPath(), delta, this);

								// remember that the project's cache must be reset
								this.projectCachesToReset.add(element);

								this.manager.indexManager.indexAll(res);
							}
						} else {
							boolean wasJavaProject = this.state.findJavaProject(res.getName()) != null;
							if (wasJavaProject) {
								close(element);
								removeFromParentInfo(element);
								currentDelta().closed(element);
								this.manager.indexManager.discardJobs(element.getElementName());
								this.manager.indexManager.removeIndexFamily(res.getFullPath());
							}
						}
						return false; // when a project is open/closed don't process children
					}
					if ((flags & IResourceDelta.DESCRIPTION) != 0) {
						IProject res = (IProject)delta.getResource();
						boolean wasJavaProject = this.state.findJavaProject(res.getName()) != null;
						boolean isJavaProject = JavaProject.hasJavaNature(res);
						if (wasJavaProject != isJavaProject) {
							// project's nature has been added or removed
							element = createElement(res, elementType, rootInfo);
							if (element == null) return false; // note its resources are still visible as roots to other projects
							if (isJavaProject) {
								elementAdded(element, delta, rootInfo);
								this.manager.indexManager.indexAll(res);
							} else {
								elementRemoved(element, delta, rootInfo);
								this.manager.indexManager.discardJobs(element.getElementName());
								this.manager.indexManager.removeIndexFamily(res.getFullPath());
								// reset the corresponding project built state, since cannot reuse if added back
								if (JavaBuilder.DEBUG)
									System.out.println("Clearing last state for project loosing Java nature: " + res); //$NON-NLS-1$
								this.manager.setLastBuiltState(res, null /*no state*/);
							}
							return false; // when a project's nature is added/removed don't process children
						}
					}
					if ((flags & IResourceDelta.CONTENT) != 0 || (flags & IResourceDelta.ENCODING) != 0) {
						// content or encoding has changed
						element = createElement(delta.getResource(), elementType, rootInfo);
						if (element == null) return false;
						updateIndex(element, delta);
						contentChanged(element);
					}
				} else if ((flags & IResourceDelta.CONTENT) != 0 || (flags & IResourceDelta.ENCODING) != 0) {
					// content or encoding has changed
					element = createElement(delta.getResource(), elementType, rootInfo);
					if (element == null) return false;
					updateIndex(element, delta);
					contentChanged(element);
				}
				return true;
		}
		return true;
	}
	private void updateIndex(Openable element, IResourceDelta delta) {

		IndexManager indexManager = this.manager.indexManager;
		if (indexManager == null)
			return;

		switch (element.getElementType()) {
			case IJavaElement.JAVA_PROJECT :
				switch (delta.getKind()) {
					case IResourceDelta.ADDED :
						indexManager.indexAll(element.getJavaProject().getProject());
						break;
					case IResourceDelta.REMOVED :
						indexManager.removeIndexFamily(element.getJavaProject().getProject().getFullPath());
						// NB: Discarding index jobs belonging to this project was done during PRE_DELETE
						break;
					// NB: Update of index if project is opened, closed, or its java nature is added or removed
					//     is done in updateCurrentDeltaAndIndex
				}
				break;
			case IJavaElement.PACKAGE_FRAGMENT_ROOT :
				if (element instanceof JarPackageFragmentRoot) {
					JarPackageFragmentRoot root = (JarPackageFragmentRoot)element;
					// index jar file only once (if the root is in its declaring project)
					IPath jarPath = root.getPath();
					switch (delta.getKind()) {
						case IResourceDelta.ADDED:
							// index the new jar
							indexManager.indexLibrary(jarPath, root.getJavaProject().getProject(), root.getIndexPath());
							break;
						case IResourceDelta.CHANGED:
							// first remove the index so that it is forced to be re-indexed
							indexManager.removeIndex(jarPath);
							// then index the jar
							indexManager.indexLibrary(jarPath, root.getJavaProject().getProject(), root.getIndexPath());
							break;
						case IResourceDelta.REMOVED:
							// the jar was physically removed: remove the index
							indexManager.discardJobs(jarPath.toString());
							indexManager.removeIndex(jarPath);
							break;
					}
					break;
				}
				int kind = delta.getKind();
				if (kind == IResourceDelta.ADDED || kind == IResourceDelta.REMOVED || (kind == IResourceDelta.CHANGED && (delta.getFlags() & IResourceDelta.LOCAL_CHANGED) != 0)) {
					PackageFragmentRoot root = (PackageFragmentRoot)element;
					updateRootIndex(root, CharOperation.NO_STRINGS, delta);
					break;
				}
				// don't break as packages of the package fragment root can be indexed below
				// $FALL-THROUGH$
			case IJavaElement.PACKAGE_FRAGMENT :
				switch (delta.getKind()) {
					case IResourceDelta.CHANGED:
						if ((delta.getFlags() & IResourceDelta.LOCAL_CHANGED) == 0)
							break;
						// $FALL-THROUGH$
					case IResourceDelta.ADDED:
					case IResourceDelta.REMOVED:
						IPackageFragment pkg = null;
						if (element instanceof IPackageFragmentRoot) {
							PackageFragmentRoot root = (PackageFragmentRoot)element;
							pkg = root.getPackageFragment(CharOperation.NO_STRINGS);
						} else {
							pkg = (IPackageFragment)element;
						}
						RootInfo rootInfo = rootInfo(pkg.getParent().getPath(), delta.getKind());
						boolean isSource =
							rootInfo == null // if null, defaults to source
							|| rootInfo.entryKind == IClasspathEntry.CPE_SOURCE;
						IResourceDelta[] children = delta.getAffectedChildren();
						for (int i = 0, length = children.length; i < length; i++) {
							IResourceDelta child = children[i];
							IResource resource = child.getResource();
							// TODO (philippe) Why do this? Every child is added anyway as the delta is walked
							if (resource instanceof IFile) {
								String name = resource.getName();
								if (isSource) {
									if (org.eclipse.jdt.internal.core.util.Util.isJavaLikeFileName(name)) {
										Openable cu = (Openable)pkg.getCompilationUnit(name);
										updateIndex(cu, child);
									}
								} else if (org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(name)) {
									Openable classFile = (Openable)pkg.getClassFile(name);
									updateIndex(classFile, child);
								}
							}
						}
						break;
				}
				break;
			case IJavaElement.CLASS_FILE :
				IFile file = (IFile) delta.getResource();
				IJavaProject project = element.getJavaProject();
				PackageFragmentRoot root = element.getPackageFragmentRoot();
				IPath binaryFolderPath = root.isExternal() && !root.isArchive() ? root.resource().getFullPath() : root.getPath();
				// if the class file is part of the binary output, it has been created by
				// the java builder -> ignore
				try {
					if (binaryFolderPath.equals(project.getOutputLocation())) {
						break;
					}
				} catch (JavaModelException e) {
					// project doesn't exist: ignore
				}
				switch (delta.getKind()) {
					case IResourceDelta.CHANGED :
						// no need to index if the content has not changed
						int flags = delta.getFlags();
						if ((flags & IResourceDelta.CONTENT) == 0 && (flags & IResourceDelta.ENCODING) == 0)
							break;
						// $FALL-THROUGH$
					case IResourceDelta.ADDED :
						indexManager.addBinary(file, binaryFolderPath);
						break;
					case IResourceDelta.REMOVED :
						String containerRelativePath = Util.relativePath(file.getFullPath(), binaryFolderPath.segmentCount());
						indexManager.remove(containerRelativePath, binaryFolderPath);
						break;
				}
				break;
			case IJavaElement.COMPILATION_UNIT :
				file = (IFile) delta.getResource();
				switch (delta.getKind()) {
					case IResourceDelta.CHANGED :
						// no need to index if the content has not changed
						int flags = delta.getFlags();
						if ((flags & IResourceDelta.CONTENT) == 0 && (flags & IResourceDelta.ENCODING) == 0)
							break;
						// $FALL-THROUGH$
					case IResourceDelta.ADDED :
						indexManager.addSource(file, file.getProject().getFullPath(), getSourceElementParser(element));
						// Clean file from secondary types cache but do not update indexing secondary type cache as it will be updated through indexing itself
						this.manager.secondaryTypesRemoving(file, false);
						break;
					case IResourceDelta.REMOVED :
						indexManager.remove(Util.relativePath(file.getFullPath(), 1/*remove project segment*/), file.getProject().getFullPath());
						// Clean file from secondary types cache and update indexing secondary type cache as indexing cannot remove secondary types from cache
						this.manager.secondaryTypesRemoving(file, true);
						break;
				}
		}
	}
	/*
	 * Update Java Model given some delta
	 */
	public void updateJavaModel(IJavaElementDelta customDelta) {

		if (customDelta == null){
			for (int i = 0, length = this.javaModelDeltas.size(); i < length; i++){
				IJavaElementDelta delta = this.javaModelDeltas.get(i);
				this.modelUpdater.processJavaDelta(delta);
			}
		} else {
			this.modelUpdater.processJavaDelta(customDelta);
		}
	}
	/*
	 * Updates the index of the given root (assuming it's an addition or a removal).
	 * This is done recusively, pkg being the current package.
	 */
	private void updateRootIndex(PackageFragmentRoot root, String[] pkgName, IResourceDelta delta) {
		Openable pkg = root.getPackageFragment(pkgName);
		updateIndex(pkg, delta);
		IResourceDelta[] children = delta.getAffectedChildren();
		for (int i = 0, length = children.length; i < length; i++) {
			IResourceDelta child = children[i];
			IResource resource = child.getResource();
			if (resource instanceof IFolder) {
				String[] subpkgName = Util.arrayConcat(pkgName, resource.getName());
				updateRootIndex(root, subpkgName, child);
			}
		}
	}
}
