/******************************************************************************* * Copyright (c) 2004, 2012 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.core; import org.eclipse.core.resources.IFolder; import org.eclipse.jdt.core.JavaCore; import org.eclipse.core.resources.IResourceDelta; 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.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.PerformanceStats; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.jdt.core.ElementChangedEvent; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IElementChangedListener; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaElementDelta; import org.eclipse.jdt.core.IJavaModel; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.SourceElementParser; import org.eclipse.jdt.internal.core.builder.JavaBuilder; import org.eclipse.jdt.internal.core.hierarchy.TypeHierarchy; import org.eclipse.jdt.internal.core.util.Util; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; /** * 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> */ @SuppressWarnings({"rawtypes", "unchecked"}) public class DeltaProcessor { public static final int DEFAULT_CHANGE_EVENT = 0; // must not collide with ElementChangedEvent event masks 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; /* * Used to update the JavaModel for <code>IJavaElementDelta</code>s. */ private final ModelUpdater modelUpdater = new ModelUpdater(); /* * Queue of deltas created explicily by the Java Model that * have yet to be fired. */ public ArrayList 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 HashMap reconcileDeltas = new HashMap(); /* A set of IJavaProject whose caches need to be reset */ public HashSet 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 oldRoots; /* * Type of event that should be processed no matter what the real event type is. */ public int overridenEventType = -1; /* * The Java model manager */ JavaModelManager manager; /* * The global state of delta processing. */ private DeltaProcessingState state; /* * 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; /* * Turns delta firing on/off. By default it is on. */ private boolean isFiring = true; /* * Cache SourceElementParser for the project being visited */ private SourceElementParser sourceElementParserCache; public DeltaProcessor(DeltaProcessingState state, JavaModelManager manager) { this.state = state; this.manager = manager; } /* * 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(); } /* * Adds the dependents of the given project to the list of the projects * to update. */ private void addDependentProjects(IJavaProject project, HashMap projectDependencies, HashSet result) { IJavaProject[] dependents = (IJavaProject[])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); // // this.state.rootsAreStale = true; // break; // } // // break; // case IResource.FOLDER: // if (delta.getKind() == IResourceDelta.CHANGED) { // look for .jar file change to update classpath // children = delta.getAffectedChildren(); // } // 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 overide 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; // } // break; // // } // if (children != null) { // for (int i = 0; i < children.length; i++) { // checkProjectsAndClasspathChanges(children[i]); // } // } } 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) { // ClasspathChange change = this.state.getClasspathChange(project); // this.state.addProjectReferenceChange(javaProject, change == null ? null : change.oldResolvedClasspath); } 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 = (IPath)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(); 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 // HashSet projects = JavaModelManager.getJavaModelManager().getExternalWorkingCopyProjects(); // if (projects != null) { // hasExternalWorkingCopyProject = true; // Iterator iterator = projects.iterator(); // while (iterator.hasNext()) { // JavaProject project = (JavaProject) iterator.next(); // project.resetCaches(); // } // } // } // } // HashSet 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){ // WorkspaceJob touchJob = new WorkspaceJob(Messages.updating_external_archives_jobName) { // // public IStatus runInWorkspace(IProgressMonitor progressMonitor) throws CoreException { // try { // if (progressMonitor != null) // progressMonitor.beginTask("", projectsToTouch.length); //$NON-NLS-1$ // touchProjects(projectsToTouch, progressMonitor); // } // finally { // if (progressMonitor != null) // progressMonitor.done(); // } // return Status.OK_STATUS; // } // // public boolean belongsTo(Object family) { // return ResourcesPlugin.FAMILY_MANUAL_REFRESH == family; // } // }; // touchJob.schedule(); // } // 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() { // 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(); // } throw new UnsupportedOperationException(); } protected void touchProjects(final IProject[] projectsToTouch, IProgressMonitor progressMonitor) throws CoreException { for (int i = 0; i < projectsToTouch.length; i++) { IProgressMonitor monitor = progressMonitor == null ? null : new SubProgressMonitor(progressMonitor, 1); 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(monitor); } } /* * 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(HashSet refreshedElements, IProgressMonitor monitor) { HashMap externalArchivesStatus = new HashMap(); boolean hasDelta = false; // find JARs to refresh HashSet archivePathsToRefresh = new HashSet(); Iterator iterator = refreshedElements.iterator(); while (iterator.hasNext()) { IJavaElement element = (IJavaElement)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 projectNames = this.state.getOldJavaProjecNames().iterator(); // while (projectNames.hasNext()) { // String projectName = (String) 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; // } // } throw new UnsupportedOperationException(); // break; } } // // perform refresh // Iterator projectNames = this.state.getOldJavaProjecNames().iterator(); // IWorkspaceRoot wksRoot = ResourcesPlugin.getWorkspace().getRoot(); // while (projectNames.hasNext()) { // // if (monitor != null && monitor.isCanceled()) break; // // String projectName = (String) 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 = (String)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 =(Long) 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, new Long(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, new Long(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 = (String)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$ // } // 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, // 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 // } throw new UnsupportedOperationException(); } /* * 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; } } } /* * 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; // movedToRes = movedToPath.toFile(); 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); //TODO: this is quick fix for https://jira.codenvycorp.com/browse/IDEX-4221 //we do it because we need clear cache for deleted project but we don't know how do it right way //so we clean all cache totally this.state.roots.clear(); // 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; } } /* * 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 scopes = this.manager.searchScopes.keySet().iterator(); while (scopes.hasNext()) { AbstractSearchScope scope = (AbstractSearchScope)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(org.eclipse.core.resources.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 deltas) { if (deltas.size() == 0) return null; if (deltas.size() == 1) return (IJavaElementDelta)deltas.iterator().next(); if (VERBOSE) { System.out.println( "MERGING " + deltas.size() + " DELTAS [" + Thread.currentThread() + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } Iterator 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() { public void handleException(Throwable exception) { Util.log(exception, "Exception occurred in listener of Java element change notification"); //$NON-NLS-1$ } 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() { public void handleException(Throwable exception) { Util.log(exception, "Exception occurred in listener of Java element change notification"); //$NON-NLS-1$ } 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).nonJavaResources = null; // if (!ExternalFoldersManager.isInternalPathForExternalFolder(delta.getFullPath())) // currentDelta().addResourceDelta(delta); // return; // case IJavaElement.JAVA_PROJECT : // ((org.eclipse.jdt.internal.core.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()) { // ((org.eclipse.jdt.internal.core.PackageFragmentRootInfo) projectRoot.getElementInfo()).setNonJavaResources(null); // } // break; // case IJavaElement.PACKAGE_FRAGMENT : // ((org.eclipse.jdt.internal.core.PackageFragmentInfo) info).setNonJavaResources(null); // break; // case IJavaElement.PACKAGE_FRAGMENT_ROOT : // ((org.eclipse.jdt.internal.core.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); throw new UnsupportedOperationException(); } /* * Returns the old root info for the given path and project. */ private RootInfo oldRootInfo(IPath path, JavaProject project) { RootInfo oldInfo = (RootInfo)this.state.oldRoots.get(path); if (oldInfo == null) return null; if (oldInfo.project.equals(project)) return oldInfo; ArrayList oldInfos = (ArrayList)this.state.oldOtherRoots.get(path); if (oldInfos == null) return null; for (int i = 0, length = oldInfos.size(); i < length; i++) { oldInfo = (RootInfo)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 ArrayList otherRootsInfo(IPath path, int kind) { if (kind == IResourceDelta.REMOVED) { return (ArrayList)this.state.oldOtherRoots.get(path); } return (ArrayList)this.state.otherRoots.get(path); } private OutputsInfo outputsInfo(RootInfo rootInfo, File 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; throw new UnsupportedOperationException(); } 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 = (IResourceDelta[])changes.getAffectedChildren(IResourceDelta.ADDED | IResourceDelta.REMOVED | // IResourceDelta.CHANGED, IContainer.INCLUDE_HIDDEN); // for (int i = 0; i < deltas.length; i++) { // IResourceDelta delta = deltas[i]; // File res = delta.getFile(); // // // 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(changes, IJavaElement.JAVA_PROJECT, null, null); updateCurrentDeltaAndIndex(changes, IJavaElement.COMPILATION_UNIT, 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.size() == 0) return; // JavaModelManager.getJavaModelManager().resetJarTypeCache(); Iterator iterator = this.projectCachesToReset.iterator(); HashMap projectDepencies = this.state.projectDependencies; HashSet 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 = (IResourceDelta)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: HashSet 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 HashMap classpathChanges = this.state.removeAllClasspathChanges(); // if (classpathChanges.size() > 0) { // boolean hasDelta = this.currentDelta != null; // JavaElementDelta javaDelta = currentDelta(); // Iterator changes = classpathChanges.values().iterator(); // while (changes.hasNext()) { // ClasspathChange change = (ClasspathChange) 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) { // this.state.addProjectReferenceChange(change.project, change.oldResolvedClasspath); // } // 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(); } IElementChangedListener[] listeners; int listenerCount; synchronized (this.state) { listeners = this.state.elementChangedListeners; listenerCount = this.state.elementChangedListenerCount; } notifyTypeHierarchies(listeners, listenerCount); fire(null, ElementChangedEvent.POST_CHANGE); } 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 // ProjectReferenceChange[] projectRefChanges = this.state.removeProjectReferenceChanges(); // if (projectRefChanges != null) { // for (int i = 0, length = projectRefChanges.length; i < length; i++) { // try { // projectRefChanges[i].updateProjectReferencesIfNecessary(); // } catch(JavaModelException e) { // // project doesn't exist any longer, continue with next one // if (!e.isDoesNotExist()) // Util.log(e, "Exception while updating project references"); //$NON-NLS-1$ // } // } // } // // if (needCycleValidation || projectRefChanges != null) { // // 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; } } /* * 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 (RootInfo)this.state.oldRoots.get(path); } return (RootInfo)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 = (IResourceDelta[])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 ArrayList rootList; if ((rootList = otherRootsInfo(childPath, childKind)) != null) { Iterator iterator = rootList.iterator(); while (iterator.hasNext()) { originalChildRootInfo = (RootInfo)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, HashSet 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); // } // } } /* * 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. */ 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.getFullPath(), 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); // 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 = (IResourceDelta[])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: IFile 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, element.getJavaProject().getPath(), 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*/), element.getJavaProject().getPath()); // 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; } } } // /* // * 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) { // HashSet 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 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 = (IJavaElementDelta)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 = (IResourceDelta[])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); } } } /* * 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; } 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 public JavaProject project; final char[][] inclusionPatterns; final char[][] exclusionPatterns; final IPath rootPath; final int entryKind; IPackageFragmentRoot root; IPackageFragmentRoot cache; RootInfo(JavaProject project, IPath rootPath, char[][] inclusionPatterns, char[][] exclusionPatterns, int entryKind) { this.project = project; this.rootPath = rootPath; this.inclusionPatterns = inclusionPatterns; this.exclusionPatterns = exclusionPatterns; this.entryKind = entryKind; 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); // } else { tRoot = this.project.getPackageFragmentRoot(this.rootPath.toOSString()); // } return tRoot; } public IPackageFragmentRoot getPackageFragmentRoot(IResource resource) { if (this.root == null) { if (resource != null) { this.root = this.project.getPackageFragmentRoot(resource); } 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); } 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(); } } }