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 - DeltaProcessor exhibits O(N^2) behavior, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=354332 Terry Parker - DeltaProcessor misses state changes in archive files, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=357425 Terry Parker - [performance] Low hit rates in JavaModel caches - https://bugs.eclipse.org/421165
/******************************************************************************* * 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 JavaModelManager to convert IResourceDeltas into IJavaElementDeltas. It also does some processing on the JavaElements involved (e.g. closing them or updating classpaths).

High level summary of what the delta processor does:

  • reacts to resource deltas
  • fires corresponding Java element deltas
  • deltas also contain non-Java resources changes
  • updates the model to reflect the Java element changes
  • notifies type hierarchies of the changes
  • triggers indexing of the changed elements
  • refresh external archives (delta, model update, indexing)
  • is thread safe (one delta processor instance per thread, see DeltaProcessingState#resourceChanged(...))
  • handles .classpath changes (updates package fragment roots, update project references, validate classpath (.classpath format, resolved classpath, cycles))
/** * 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$ StringBuffer buffer = new StringBuffer(); 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.canonicalizedPath(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() { StringBuffer buffer = new StringBuffer("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 (((JavaProject)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 = (Openable)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 = (JavaProject) 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 = (JavaProject) 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 = (JavaProject) 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 = (JavaProject) 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 ((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 (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 } } } 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); } } } }