/******************************************************************************* * Copyright (c) 2000, 2007 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 * *******************************************************************************/ package org.eclipse.dltk.internal.core; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.resources.ResourcesPlugin; 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.Path; import org.eclipse.core.runtime.PerformanceStats; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.core.DLTKLanguageManager; import org.eclipse.dltk.core.ElementChangedEvent; import org.eclipse.dltk.core.IBuildpathEntry; import org.eclipse.dltk.core.IDLTKLanguageToolkit; import org.eclipse.dltk.core.IElementChangedListener; import org.eclipse.dltk.core.IModelElement; import org.eclipse.dltk.core.IModelElementDelta; import org.eclipse.dltk.core.IProjectFragment; import org.eclipse.dltk.core.IProjectFragmentTimestamp; import org.eclipse.dltk.core.IScriptFolder; import org.eclipse.dltk.core.IScriptModel; import org.eclipse.dltk.core.IScriptProject; import org.eclipse.dltk.core.ISourceElementParser; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.core.environment.EnvironmentManager; import org.eclipse.dltk.core.environment.IEnvironment; import org.eclipse.dltk.core.environment.IFileHandle; import org.eclipse.dltk.core.search.indexing.IndexManager; import org.eclipse.dltk.core.search.indexing.SourceIndexerRequestor; import org.eclipse.dltk.internal.core.ModelManager.PerProjectInfo; import org.eclipse.dltk.internal.core.builder.ScriptBuilder; import org.eclipse.dltk.internal.core.search.DLTKWorkspaceScope; import org.eclipse.dltk.internal.core.search.ProjectIndexerManager; import org.eclipse.dltk.internal.core.util.Util; /** * This class is used by <code>ModelManager</code> to convert * <code>IResourceDelta</code>s into <code>IModelElementDelta</code>s. It also * does some processing on the <code>ModelElement</code>s involved (e.g. closing * them or updating buildpaths). */ public class DeltaProcessor { static class RootInfo { char[][] inclusionPatterns; char[][] exclusionPatterns; IPath rootPath; ScriptProject project; int entryKind; IProjectFragment root; RootInfo(ScriptProject project, IPath rootPath, char[][] inclusionPatterns, char[][] exclusionPatterns, int entryKind) { this.project = project; this.rootPath = rootPath; this.inclusionPatterns = inclusionPatterns; this.exclusionPatterns = exclusionPatterns; this.entryKind = entryKind; } IProjectFragment getProjectFragment(IResource resource) { if (this.root == null) { if (resource != null) { this.root = this.project.getProjectFragment(resource); } else { Object target = Model .getTarget( ResourcesPlugin.getWorkspace().getRoot(), this.rootPath, false/* * don't check existence */); if (target instanceof IResource) { this.root = this.project .getProjectFragment((IResource) target); } else { this.root = this.project .getProjectFragment(this.rootPath.toOSString()); } } } 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(); } } // private final static int IGNORE = 0; // private final static int SOURCE = 1; // private final static int BINARY = 2; private final static int NON_SCRIPT_RESOURCE = -1; public static boolean DEBUG = false; public static boolean VERBOSE = false; public static boolean PERF = false; public static final int DEFAULT_CHANGE_EVENT = 0; // must not collide with // ElementChangedEvent // event masks /* * Cache SourceElementParser for the project being visited */ private ISourceElementParser sourceElementParserCache; private SourceIndexerRequestor sourceRequestorCache; /* * Answer a combination of the lastModified stamp and the size. Used for * detecting external JAR changes */ public static long getTimeStamp(IFileHandle file) { long lmodif = 0; // if (file instanceof EFSFileHandle) { // lmodif = ((EFSFileHandle) file).lastModified(); // } else { lmodif = file.lastModified(); // } return lmodif + file.length(); } /* * The global state of delta processing. */ private DeltaProcessingState state; /* * The script model manager */ ModelManager manager; /* * The <code>ModelElementDelta</code> corresponding to the * <code>IResourceDelta</code> being translated. */ private ModelElementDelta currentDelta; /* * The model element that was last created (see createElement(IResource)). * This is used as a stack of model elements (using getParent() to pop it, * and using the various get(...) to push it. */ private Openable currentElement; /* * Queue of deltas created explicily by the script Model that have yet to be * fired. */ public ArrayList<IModelElementDelta> modelDeltas = new ArrayList<IModelElementDelta>(); /* * Queue of reconcile deltas on working copies that have yet to be fired. * This is a table form IWorkingCopy to IModelElementDelta */ public HashMap reconcileDeltas = new HashMap(); /* * Turns delta firing on/off. By default it is on. */ private boolean isFiring = true; /* * Used to update the Model for <code>IModelElementDelta</code>s. */ private final ModelUpdater modelUpdater = new ModelUpdater(); /* A set of IDLTKProject whose caches need to be reset */ private HashSet<IScriptProject> projectCachesToReset = new HashSet<IScriptProject>(); /* * A table from IScriptProject to an array of IProjectFragment. This table * contains the pkg fragment roots of the project that are being deleted. */ public Map<IScriptProject, IProjectFragment[]> oldRoots; /* A set of IDylanProject whose package fragment roots need to be refreshed */ private HashSet<IScriptProject> rootsToRefresh = new HashSet<IScriptProject>(); /** {@link Runnable}s that should be called after model is updated */ private final ArrayList<Runnable> postActions = new ArrayList<Runnable>(); /* * Type of event that should be processed no matter what the real event type * is. */ public int overridenEventType = -1; public DeltaProcessor(DeltaProcessingState state, ModelManager manager) { this.state = state; this.manager = manager; } /* * Adds the dependents of the given project to the list of the projects to * update. */ private void addDependentProjects(IScriptProject project, HashMap projectDependencies, HashSet result) { IScriptProject[] dependents = (IScriptProject[]) projectDependencies .get(project); if (dependents == null) { return; } for (int i = 0, length = dependents.length; i < length; i++) { IScriptProject dependent = dependents[i]; if (result.contains(dependent)) { continue; // no need to go further as the project is already } // known result.add(dependent); this.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 { ModelElementInfo info = (ModelElementInfo) parent .getElementInfo(); info.addChild(child); } catch (ModelException e) { // do nothing - we already checked if open } } } /* * Adds the given project and its dependents to the list of the roots to * refresh. */ private void addToRootsToRefreshWithDependents(IScriptProject scriptProject) { this.rootsToRefresh.add(scriptProject); this.addDependentProjects(scriptProject, this.state.projectDependencies, this.rootsToRefresh); } /* * Check all external archive (referenced by given roots, projects or model) * status and issue a corresponding root delta. Also triggers index updates */ public void checkExternalChanges(IModelElement[] elementsToRefresh, IProgressMonitor monitor) throws ModelException { try { for (int i = 0, length = elementsToRefresh.length; i < length; i++) { this.state.addForRefresh(elementsToRefresh[i]); } boolean hasDelta = false; final Set<IModelElement> refreshedElementsCopy = this.state .removeExternalElementsToRefresh(); if (refreshedElementsCopy != null) { hasDelta = this.createExternalArchiveDelta( refreshedElementsCopy, null); hasDelta |= this.createCustomElementDelta(null, refreshedElementsCopy); } else { return; } if (monitor != null && monitor.isCanceled()) { return; } if (hasDelta) { // force buildpath marker refresh of affected projects Model.flushExternalFileCache(); // flush zip type cache ModelManager.getModelManager().resetZIPTypeCache(); IModelElementDelta[] projectDeltas = this.currentDelta .getAffectedChildren(); final int length = projectDeltas.length; final IProject[] projectsToTouch = new IProject[length]; for (int i = 0; i < length; i++) { IModelElementDelta delta = projectDeltas[i]; ScriptProject scriptProject = (ScriptProject) delta .getElement(); projectsToTouch[i] = scriptProject.getProject(); } // touch the projects to force them to be recompiled while // taking the workspace lock // so that there is no concurrency with the 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 < length; i++) { IProject project = projectsToTouch[i]; // touch to force a build of this project if (DLTKCore.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 ModelException(e); } if (this.currentDelta != null) { // if delta has not been // fired while creating // markers this.fire(this.currentDelta, DEFAULT_CHANGE_EVENT); } } } finally { this.currentDelta = null; if (monitor != null) { monitor.done(); } } } /* * Process the given delta and look for projects being added, opened, closed * or with a script 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 * buildpath related markers can be updated. */ private void checkProjectsBeingAddedOrRemoved(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.getOldScriptProjectNames(); // 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 // script project: // - if the project is added or changed this is a noop for // projectsBeingDeleted // - if the project is closed, it has already lost its script nature IProject project = (IProject) resource; ScriptProject scriptProject = (ScriptProject) DLTKCore .create(project); switch (delta.getKind()) { case IResourceDelta.ADDED: this.manager.batchContainerInitializations = true; // remember project and its dependents this.addToRootsToRefreshWithDependents(scriptProject); // workaround for bug 15168 circular errors not reported if (DLTKLanguageManager.hasScriptNature(project)) { addToParentInfo(scriptProject); readRawBuildpath(scriptProject); // ensure project references are updated checkProjectReferenceChange(project, scriptProject); // and external folders as well // checkExternalFolderChange(project, scriptProject); } this.state.rootsAreStale = true; break; case IResourceDelta.CHANGED: if ((delta.getFlags() & IResourceDelta.OPEN) != 0) { this.manager.batchContainerInitializations = true; // project opened or closed: remember project and its // dependents this.addToRootsToRefreshWithDependents(scriptProject); // workaround for bug 15168 circular errors not reported if (project.isOpen()) { if (DLTKLanguageManager.hasScriptNature(project)) { addToParentInfo(scriptProject); readRawBuildpath(scriptProject); // ensure project references are updated checkProjectReferenceChange(project, scriptProject); } } else { try { scriptProject.close(); } catch (ModelException e) { // script project doesn't exist: ignore } this.removeFromParentInfo(scriptProject); this.manager.removePerProjectInfo(scriptProject); this.manager.containerRemove(scriptProject); } this.state.rootsAreStale = true; } else if ((delta.getFlags() & IResourceDelta.DESCRIPTION) != 0) { boolean wasScriptProject = this.state.findProject(project .getName()) != null; boolean isScriptProject = DLTKLanguageManager .hasScriptNature(project); if (wasScriptProject != isScriptProject) { this.manager.batchContainerInitializations = true; // script nature added or removed: remember project and // its dependents this.addToRootsToRefreshWithDependents(scriptProject); // workaround for bug 15168 circular errors not reported if (isScriptProject) { this.addToParentInfo(scriptProject); readRawBuildpath(scriptProject); // ensure project references are updated (see // https://bugs.eclipse.org/bugs/show_bug.cgi?id= // 172666) checkProjectReferenceChange(project, scriptProject); } else { // remove buildpath cache so that initializeRoots() // will not consider the project has a buildpath this.manager.removePerProjectInfo(scriptProject); // remove container cache for this project this.manager.containerRemove(scriptProject); // close project try { scriptProject.close(); } catch (ModelException e) { // script project doesn't exist: ignore } this.removeFromParentInfo(scriptProject); } this.state.rootsAreStale = true; } else { // in case the project was removed then added then // changed (see bug 19799) if (isScriptProject) { // need nature check - 18698 this.addToParentInfo(scriptProject); children = delta.getAffectedChildren(); } } } else { // workaround for bug 15168 circular errors not reported // in case the project was removed then added then changed if (DLTKLanguageManager.hasScriptNature(project)) { // need // nature // check // - // 18698 this.addToParentInfo(scriptProject); children = delta.getAffectedChildren(); } } break; case IResourceDelta.REMOVED: this.manager.batchContainerInitializations = true; // remove buildpath cache so that initializeRoots() will not // consider the project has a buildpath this.manager.removePerProjectInfo(scriptProject); // remove container cache for this project this.manager.containerRemove(scriptProject); this.state.rootsAreStale = true; break; } // in all cases, refresh the external jars for this project this.state.addForRefresh(scriptProject); break; case IResource.FILE: IFile file = (IFile) resource; /* buildpath file change */ if (file.getName().equals(ScriptProject.BUILDPATH_FILENAME)) { this.manager.batchContainerInitializations = true; switch (delta.getKind()) { 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: scriptProject = (ScriptProject) DLTKCore.create(file .getProject()); readRawBuildpath(scriptProject); break; } this.state.rootsAreStale = true; } break; } if (children != null) { for (int i = 0; i < children.length; i++) { this.checkProjectsBeingAddedOrRemoved(children[i]); } } } private void checkProjectReferenceChange(IProject project, ScriptProject javaProject) { BuildpathChange change = this.state.getBuildpathChange(project); this.state.addProjectReferenceChange(javaProject, change == null ? null : change.oldResolvedBuildpath); } private void readRawBuildpath(ScriptProject 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.readAndCacheBuildpath(javaProject); } catch (ModelException e) { if (VERBOSE) { e.printStackTrace(); } } } /* * Closes the given element, which removes it from the cache of open * elements. */ private void close(Openable element) { try { element.close(); } catch (ModelException 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() == IModelElement.SOURCE_MODULE) { SourceModule cu = (SourceModule) 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) this.currentDelta().changed(element, IModelElementDelta.F_PRIMARY_RESOURCE); } else { this.close(element); int flags = IModelElementDelta.F_CONTENT; if (element instanceof ArchiveProjectFragment) { flags |= IModelElementDelta.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.getScriptProject()); } if (isPrimary) { flags |= IModelElementDelta.F_PRIMARY_RESOURCE; } this.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(); IModelElement element = null; switch (elementType) { case IModelElement.SCRIPT_PROJECT: // note that non-script resources rooted at the project level will // also enter this code with // an elementType SCRIPT_PROJECT (see #elementType(...)). if (resource instanceof IProject) { this.popUntilPrefixOf(path); if (this.currentElement != null && this.currentElement.getElementType() == IModelElement.SCRIPT_PROJECT && ((IScriptProject) 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 (DLTKLanguageManager.hasScriptNature(proj)) { element = DLTKCore.create(proj); } else { // script project may have been been closed or removed // (look // for // element amongst old script project s list). element = this.state.findProject(proj.getName()); } } break; case IModelElement.PROJECT_FRAGMENT: element = rootInfo == null ? DLTKCore.create(resource) : rootInfo .getProjectFragment(resource); break; case IModelElement.SCRIPT_FOLDER: if (rootInfo != null) { if (rootInfo.project.contains(resource)) { IProjectFragment root = rootInfo.getProjectFragment(null); // create package handle IPath pkgPath = path.removeFirstSegments(rootInfo.rootPath .segmentCount()); element = root.getScriptFolder(pkgPath); } } else { // find the element that encloses the resource this.popUntilPrefixOf(path); if (this.currentElement == null) { element = DLTKCore.create(resource); } else { // find the root IProjectFragment root = this.currentElement .getProjectFragment(); if (root == null) { element = DLTKCore.create(resource); } else if (((ScriptProject) root.getScriptProject()) .contains(resource)) { // create package handle IPath pkgPath = path.removeFirstSegments(root.getPath() .segmentCount()); element = root.getScriptFolder(pkgPath); } } } break; case IModelElement.SOURCE_MODULE: case IModelElement.BINARY_MODULE: // find the element that encloses the resource this.popUntilPrefixOf(path); if (this.currentElement == null) { element = rootInfo == null ? DLTKCore.create(resource) : ModelManager.create(resource, rootInfo.project); } else { // find the package IScriptFolder pkgFragment = null; switch (this.currentElement.getElementType()) { case IModelElement.PROJECT_FRAGMENT: IProjectFragment root = (IProjectFragment) this.currentElement; IPath rootPath = root.getPath(); IPath pkgPath = path.removeLastSegments(1); IPath pkgName = pkgPath.removeFirstSegments(rootPath .segmentCount()); pkgFragment = root.getScriptFolder(pkgName); break; case IModelElement.SCRIPT_FOLDER: Openable pkg = this.currentElement; if (pkg.getPath().equals(path.removeLastSegments(1))) { pkgFragment = (IScriptFolder) pkg; } // else case of package x which is a prefix of // x.y break; case IModelElement.SOURCE_MODULE: case IModelElement.BINARY_MODULE: pkgFragment = (IScriptFolder) this.currentElement .getParent(); break; } if (pkgFragment == null) { element = rootInfo == null ? DLTKCore.create(resource) : ModelManager.create(resource, rootInfo.project); } else { if (elementType == IModelElement.SOURCE_MODULE) { // create compilation unit handle // fileName validation has been done in // elementType(IResourceDelta, int, boolean) String fileName = path.lastSegment(); element = pkgFragment.getSourceModule(fileName); } else { // create binary module handle // fileName validation has been done in // elementType(IResourceDelta, int, boolean) // String fileName = path.lastSegment(); throw new RuntimeException("not implemented"); //$NON-NLS-1$ // element = pkgFragment.getClassFile(fileName); } } } break; } if (element == null) { return null; } this.currentElement = (Openable) element; return this.currentElement; } enum ZipStatus { EXTERNAL_ZIP_ADDED, EXTERNAL_ZIP_CHANGED, EXTERNAL_ZIP_REMOVED, EXTERNAL_ZIP_UNCHANGED, INTERNAL_ZIP_IGNORE } /* * Check if external archives have changed and create the corresponding * deltas. Returns whether at least on delta was created. */ private boolean createExternalArchiveDelta( Set<IModelElement> refreshedElements, IProgressMonitor monitor) { if (refreshedElements == null) { return false; } Map<IPath, ZipStatus> externalArchivesStatus = new HashMap<IPath, ZipStatus>(); boolean hasDelta = false; // find JARs to refresh Set<IPath> archivePathsToRefresh = new HashSet<IPath>(); // modification exception (see // https://bugs.eclipse.org/bugs/show_bug.cgi?id=63534) for (IModelElement element : refreshedElements) { switch (element.getElementType()) { case IModelElement.PROJECT_FRAGMENT: archivePathsToRefresh.add(element.getPath()); break; case IModelElement.SCRIPT_PROJECT: ScriptProject scriptProject = (ScriptProject) element; if (!DLTKLanguageManager.hasScriptNature(scriptProject .getProject())) { // project is not accessible or has lost its script // nature break; } IEnvironment environment = EnvironmentManager .getEnvironment(scriptProject); if (environment != null && !environment.isConnected()) { // Project environment is not connected. break; } IBuildpathEntry[] buildpath; try { buildpath = scriptProject.getResolvedBuildpath(); for (int j = 0, cpLength = buildpath.length; j < cpLength; j++) { if (buildpath[j].getEntryKind() == IBuildpathEntry.BPE_LIBRARY) { archivePathsToRefresh.add(buildpath[j].getPath()); } } } catch (ModelException e) { // project doesn't exist -> ignore } break; case IModelElement.SCRIPT_MODEL: for (String projectName : this.state.getOldScriptProjectNames()) { IProject project = ResourcesPlugin.getWorkspace().getRoot() .getProject(projectName); if (!DLTKLanguageManager.hasScriptNature(project)) { // project is not accessible or has lost its Script // nature continue; } scriptProject = (ScriptProject) DLTKCore.create(project); try { buildpath = scriptProject.getResolvedBuildpath(); } catch (ModelException e2) { // project doesn't exist -> ignore continue; } for (int k = 0, cpLength = buildpath.length; k < cpLength; k++) { if (buildpath[k].getEntryKind() == IBuildpathEntry.BPE_LIBRARY) { archivePathsToRefresh.add(buildpath[k].getPath()); } } } break; } } // perform refresh IWorkspaceRoot wksRoot = ResourcesPlugin.getWorkspace().getRoot(); for (String projectName : this.state.getOldScriptProjectNames()) { if (monitor != null && monitor.isCanceled()) { break; } IProject project = wksRoot.getProject(projectName); if (!DLTKLanguageManager.hasScriptNature(project)) { // project is not accessible or has lost its Script nature continue; } ScriptProject scriptProject = (ScriptProject) DLTKCore .create(project); IBuildpathEntry[] entries; try { entries = scriptProject.getResolvedBuildpath(); } catch (ModelException e1) { // project does not exist -> ignore continue; } for (int j = 0; j < entries.length; j++) { IBuildpathEntry entry = entries[j]; if (entry.getEntryKind() == IBuildpathEntry.BPE_LIBRARY) { IPath entryPath = entry.getPath(); if (!archivePathsToRefresh.contains(entryPath)) { continue; // not supposed to be refreshed } ZipStatus status = externalArchivesStatus.get(entryPath); if (status == null) { // compute shared status Object targetLibrary = Model.getTarget(wksRoot, entryPath, true); if (targetLibrary == null) { // missing JAR if (this.state.getExternalLibTimeStamps().remove( entryPath) != null) { externalArchivesStatus.put(entryPath, ZipStatus.EXTERNAL_ZIP_REMOVED); // the jar was physically removed: remove the // index this.manager.indexManager .removeIndex(entryPath); ProjectIndexerManager.removeLibrary( scriptProject, entryPath); } } else if (targetLibrary instanceof IFileHandle) { // external // JAR IFileHandle externalFile = (IFileHandle) targetLibrary; // check timestamp to figure if JAR has changed in // some way Long oldTimestamp = this.state .getExternalLibTimeStamps().get(entryPath); long newTimeStamp = getTimeStamp(externalFile); if (oldTimestamp != null) { if (newTimeStamp == 0) { // file doesn't // exist externalArchivesStatus.put(entryPath, ZipStatus.EXTERNAL_ZIP_REMOVED); this.state.getExternalLibTimeStamps() .remove(entryPath); // remove the index this.manager.indexManager .removeIndex(entryPath); ProjectIndexerManager.removeLibrary( scriptProject, entryPath); } else if (oldTimestamp.longValue() != newTimeStamp) { externalArchivesStatus.put(entryPath, ZipStatus.EXTERNAL_ZIP_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 ProjectIndexerManager.indexLibrary( scriptProject, entryPath); } else { externalArchivesStatus.put(entryPath, ZipStatus.EXTERNAL_ZIP_UNCHANGED); } } else { if (newTimeStamp == 0) { // jar still doesn't // exist externalArchivesStatus.put(entryPath, ZipStatus.EXTERNAL_ZIP_UNCHANGED); } else { externalArchivesStatus.put(entryPath, ZipStatus.EXTERNAL_ZIP_ADDED); this.state.getExternalLibTimeStamps().put( entryPath, new Long(newTimeStamp)); // index the new jar ProjectIndexerManager.indexLibrary( scriptProject, entryPath); } } } else { // internal ZIP externalArchivesStatus.put(entryPath, ZipStatus.INTERNAL_ZIP_IGNORE); } } // according to computed status, generate a delta status = externalArchivesStatus.get(entryPath); if (status != null) { if (status == ZipStatus.EXTERNAL_ZIP_ADDED) { IProjectFragment root = scriptProject .getProjectFragment(entryPath); if (root == null) { logFragmentNotFound(scriptProject, entryPath); continue; } if (VERBOSE) { System.out .println("- External ZIP ADDED, affecting root: " + root.getElementName()); //$NON-NLS-1$ } this.elementAdded((Openable) root, null, null); // in case it contains a chained jar scriptProject.resetResolvedBuildpath(); // https://bugs.eclipse.org/bugs/show_bug.cgi?id=185733 this.state.addBuildpathValidation(scriptProject); hasDelta = true; } else if (status == ZipStatus.EXTERNAL_ZIP_CHANGED) { IProjectFragment root = scriptProject .getProjectFragment(entryPath); if (root == null) { logFragmentNotFound(scriptProject, entryPath); continue; } if (VERBOSE) { System.out .println("- External ZIP CHANGED, affecting root: " + root.getElementName()); //$NON-NLS-1$ } this.contentChanged((Openable) root); hasDelta = true; } else if (status == ZipStatus.EXTERNAL_ZIP_REMOVED) { IProjectFragment root = scriptProject .getProjectFragment(entryPath); if (root == null) { logFragmentNotFound(scriptProject, entryPath); continue; } if (VERBOSE) { System.out .println("- External ZIP REMOVED, affecting root: " + root.getElementName()); //$NON-NLS-1$ } this.elementRemoved((Openable) root, null, null); // in case it contains a chained jar scriptProject.resetResolvedBuildpath(); // https://bugs.eclipse.org/bugs/show_bug.cgi?id=185733 this.state.addBuildpathValidation(scriptProject); hasDelta = true; } } } } } // Check for external project fragment changes via timestamps. return hasDelta; } private static void logFragmentNotFound(ScriptProject scriptProject, IPath entryPath) { if (DEBUG) System.out.println("ProjectFragment \"" + entryPath + "\" not found in " + scriptProject.getElementName()); } private ModelElementDelta currentDelta() { if (this.currentDelta == null) { this.currentDelta = new ModelElementDelta(this.manager.getModel()); } 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()); ScriptProject scriptProject = (ScriptProject) DLTKCore .create(project); // remember roots of this project if (this.oldRoots == null) { this.oldRoots = new HashMap<IScriptProject, IProjectFragment[]>(); } if (scriptProject.isOpen()) { this.oldRoots.put(scriptProject, scriptProject.getProjectFragments()); } else { // compute roots without opening project this.oldRoots.put(scriptProject, scriptProject .computeProjectFragments( scriptProject.getResolvedBuildpath(), false, null /* * no reverse map */)); } scriptProject.close(); // workaround for bug 15168 circular errors not reported this.state.getOldScriptProjectNames(); // foce list to be computed this.removeFromParentInfo(scriptProject); // remove preferences from per project info this.manager.resetProjectPreferences(scriptProject); } catch (ModelException e) { // script project doesn't exist: ignore } } /* * Processing for an element that has been added:<ul> <li>If the element is * a project, do nothing, and do not process children, as when a project is * created it does not yet have any natures - specifically a script 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 ZIP change */ private void elementAdded(Openable element, IResourceDelta delta, RootInfo rootInfo) { int elementType = element.getElementType(); if (elementType == IModelElement.SCRIPT_PROJECT) { // project add is handled by DylanProject.configure() because // when a project is created, it does not yet have a script nature if (delta != null && DLTKLanguageManager.hasScriptNature((IProject) delta .getResource())) { this.addToParentInfo(element); if ((delta.getFlags() & IResourceDelta.MOVED_FROM) != 0) { Openable movedFromElement = (Openable) element.getModel() .getScriptProject( delta.getMovedFromPath().lastSegment()); this.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. this.close(element); this.currentDelta().added(element); } this.state.updateRoots(element.getPath(), delta, this); // refresh pkg fragment roots and caches of the project (and its // dependents) final IScriptProject project = (IScriptProject) element; this.rootsToRefresh.add(project); this.projectCachesToReset.add(project); } } else { if (delta == null || (delta.getFlags() & IResourceDelta.MOVED_FROM) == 0) { // regular element addition if (this.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) this.currentDelta().changed(element, IModelElementDelta.F_PRIMARY_RESOURCE); } else { this.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 script 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. this.close(element); this.currentDelta().added(element); } } else { // element is moved this.addToParentInfo(element); this.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 RootInfo movedFromInfo = this.enclosingRootInfo(movedFromPath, IResourceDelta.REMOVED); int movedFromType = this.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 != IModelElement.SCRIPT_PROJECT && movedFromType == IModelElement.SCRIPT_PROJECT ? null : // outside buildpath this.createElement(movedFromRes, movedFromType, movedFromInfo); if (movedFromElement == null) { // moved from outside buildpath this.currentDelta().added(element); } else { this.currentDelta().movedTo(element, movedFromElement); } } switch (elementType) { case IModelElement.PROJECT_FRAGMENT: // when a root is added, and is on the buildpath, the // project // must be updated ScriptProject project = (ScriptProject) element .getScriptProject(); // refresh pkg fragment roots and caches of the project (and // its // dependents) this.rootsToRefresh.add(project); this.projectCachesToReset.add(project); break; case IModelElement.SCRIPT_FOLDER: // reset project's package fragment cache project = (ScriptProject) element.getScriptProject(); 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 ZIP 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 (this.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) this.currentDelta().changed(element, IModelElementDelta.F_PRIMARY_RESOURCE); } else { this.close(element); this.removeFromParentInfo(element); this.currentDelta().removed(element); } } else { // element is moved this.close(element); this.removeFromParentInfo(element); IPath movedToPath = delta.getMovedToPath(); IResource res = delta.getResource(); IResource movedToRes; switch (res.getType()) { case IResource.PROJECT: movedToRes = res.getWorkspace().getRoot() .getProject(movedToPath.lastSegment()); break; case IResource.FOLDER: movedToRes = res.getWorkspace().getRoot() .getFolder(movedToPath); break; case IResource.FILE: movedToRes = res.getWorkspace().getRoot().getFile(movedToPath); break; default: return; } // find the element type of the moved from element RootInfo movedToInfo = this.enclosingRootInfo(movedToPath, IResourceDelta.ADDED); int movedToType = this.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 != IModelElement.SCRIPT_PROJECT && movedToType == IModelElement.SCRIPT_PROJECT ? null : // outside // buildpath this.createElement(movedToRes, movedToType, movedToInfo); if (movedToElement == null) { // moved outside buildpath this.currentDelta().removed(element); } else { this.currentDelta().movedFrom(element, movedToElement); } } switch (elementType) { case IModelElement.SCRIPT_MODEL: this.manager.indexManager.reset(); break; case IModelElement.SCRIPT_PROJECT: { this.state.updateRoots(element.getPath(), delta, this); // refresh pkg fragment roots and caches of the project (and its // dependents) final IScriptProject project = (IScriptProject) element; this.rootsToRefresh.add(project); this.projectCachesToReset.add(project); break; } case IModelElement.PROJECT_FRAGMENT: { IScriptProject project = element.getScriptProject(); // refresh pkg fragment roots and caches of the project (and its // dependents) this.rootsToRefresh.add(project); this.projectCachesToReset.add(project); break; } case IModelElement.SCRIPT_FOLDER: { // reset package fragment cache IScriptProject project = element.getScriptProject(); this.projectCachesToReset.add(project); break; } } } /* * Returns the type of the model element the given delta matches to. Returns * NON_SCRIPT_RESOURCE if unknown (e.g. a non-script resource or excluded * file) */ private int elementType(IResource res, int kind, int parentType, RootInfo rootInfo) { switch (parentType) { case IModelElement.SCRIPT_MODEL: // case of a movedTo or movedFrom project (other cases are // handled // in processResourceDelta(...) return IModelElement.SCRIPT_PROJECT; case NON_SCRIPT_RESOURCE: case IModelElement.SCRIPT_PROJECT: if (rootInfo == null) { rootInfo = this.enclosingRootInfo(res.getFullPath(), kind); } if (rootInfo != null && rootInfo.isRootOfProject(res.getFullPath())) { return IModelElement.PROJECT_FRAGMENT; } // not yet in a package fragment root or root of another project // or package fragment to be included (see below) // -> let it go through case IModelElement.PROJECT_FRAGMENT: case IModelElement.SCRIPT_FOLDER: if (rootInfo == null) { rootInfo = this.enclosingRootInfo(res.getFullPath(), kind); } if (rootInfo == null) { return NON_SCRIPT_RESOURCE; } if (Util.isExcluded(res, rootInfo.inclusionPatterns, rootInfo.exclusionPatterns)) { return NON_SCRIPT_RESOURCE; } if (res.getType() == IResource.FOLDER) { if (parentType == NON_SCRIPT_RESOURCE && !Util.isExcluded(res.getParent(), rootInfo.inclusionPatterns, rootInfo.exclusionPatterns)) { // parent is a non-script resource because it doesn't // have a // valid package name (see // https://bugs.eclipse.org/bugs/show_bug.cgi?id=130982) return NON_SCRIPT_RESOURCE; } if (Util.isValidFolderNameForPackage(res.getParent(), res.getName())) { return IModelElement.SCRIPT_FOLDER; } return NON_SCRIPT_RESOURCE; } // String fileName = res.getName(); IProject project = res.getProject(); IScriptProject scriptProject = DLTKCore.create(project); if (scriptProject != null) { if (Util.isValidSourceModule(scriptProject, res)) { return IModelElement.SOURCE_MODULE; } // FIXME: Add support of checking files without extensions here. } else if (this.rootInfo(res.getFullPath(), kind) != null) { // case of proj=src=bin and resource is a jar file on the // buildpath return IModelElement.PROJECT_FRAGMENT; } else { return NON_SCRIPT_RESOURCE; } default: return NON_SCRIPT_RESOURCE; } } /* * Flushes all deltas without firing them. */ public void flush() { this.modelDeltas = new ArrayList<IModelElementDelta>(); } /* * 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 = this.rootInfo(path, kind); if (rootInfo != null) { return rootInfo; } path = path.removeLastSegments(1); } return null; } /* * Fire script 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(IModelElementDelta customDelta, int eventType) { if (!this.isFiring) { return; } if (DEBUG) { System.out .println("-----------------------------------------------------------------------------------------------------------------------");//$NON-NLS-1$ } IModelElementDelta deltaToNotify; if (customDelta == null) { deltaToNotify = this.mergeDeltas(this.modelDeltas); } 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); // } if (this.manager.workspaceScope != null) { Iterator wsIter = this.manager.workspaceScope.values() .iterator(); for (; wsIter.hasNext();) { DLTKWorkspaceScope workspaceScope = (DLTKWorkspaceScope) wsIter .next(); if (workspaceScope != null) { workspaceScope.processDelta(deltaToNotify); } } } } // 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: this.firePostChangeDelta(deltaToNotify, listeners, listenerMask, listenerCount); this.fireReconcileDelta(listeners, listenerMask, listenerCount); break; case ElementChangedEvent.POST_CHANGE: this.firePostChangeDelta(deltaToNotify, listeners, listenerMask, listenerCount); this.fireReconcileDelta(listeners, listenerMask, listenerCount); break; } } private void firePostChangeDelta(IModelElementDelta 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 this.flush(); this.notifyListeners(deltaToNotify, ElementChangedEvent.POST_CHANGE, listeners, listenerMask, listenerCount); } } private void fireReconcileDelta(IElementChangedListener[] listeners, int[] listenerMask, int listenerCount) { IModelElementDelta deltaToNotify = this .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(); this.notifyListeners(deltaToNotify, ElementChangedEvent.POST_RECONCILE, listeners, listenerMask, listenerCount); } } /* * Returns whether a given delta contains some information relevant to the * Model, 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() { public boolean visit(IResourceDelta delta) /* * throws * CoreException */{ switch (delta.getKind()) { case IResourceDelta.ADDED: case IResourceDelta.REMOVED: throw new FoundRelevantDeltaException(); case IResourceDelta.CHANGED: // if any flag is set but SYNC or MARKER, this // delta // should be considered if (delta.getAffectedChildren().length == 0 // only // check // leaf // delta // nodes && (delta.getFlags() & ~(IResourceDelta.SYNC | IResourceDelta.MARKERS)) != 0) { throw new FoundRelevantDeltaException(); } } return true; } }); } 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(IModelElement element, int elementType) { if (elementType == IModelElement.SOURCE_MODULE) { SourceModule cu = (SourceModule) element; return cu.isPrimary() && cu.isWorkingCopy(); } return false; } /* * Merges all awaiting deltas. */ private IModelElementDelta mergeDeltas(Collection deltas) { if (deltas.size() == 0) { return null; } if (deltas.size() == 1) { return (IModelElementDelta) 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(); ModelElementDelta rootDelta = new ModelElementDelta(this.manager.model); boolean insertedTree = false; while (iterator.hasNext()) { ModelElementDelta delta = (ModelElementDelta) iterator.next(); if (VERBOSE) { System.out.println(delta.toString()); } IModelElement element = delta.getElement(); if (this.manager.model.equals(element)) { IModelElementDelta[] children = delta.getAffectedChildren(); for (int j = 0; j < children.length; j++) { ModelElementDelta projectDelta = (ModelElementDelta) 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(IModelElementDelta 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 script element change notification"); //$NON-NLS-1$ } public void run() throws Exception { PerformanceStats stats = null; if (PERF) { stats = PerformanceStats.getStats( ModelManager.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) { // TODO implement if (DLTKCore.DEBUG) { System.out.println("notify type hierarchies"); //$NON-NLS-1$ } } /* * 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 nonScriptResourcesChanged(Openable element, IResourceDelta delta) throws ModelException { // reset non-script resources if element was open if (element.isOpen()) { ModelElementInfo info = (ModelElementInfo) element.getElementInfo(); switch (element.getElementType()) { case IModelElement.SCRIPT_MODEL: ((ModelInfo) info).foreignResources = null; this.currentDelta().addResourceDelta(delta); return; case IModelElement.SCRIPT_PROJECT: ((ProjectElementInfo) info).setForeignResources(null); // if a package fragment root is the project, clear it too ScriptProject project = (ScriptProject) element; IProjectFragment projectRoot = project .getProjectFragment(project.getProject()); if (projectRoot.isOpen()) { ((ProjectFragmentInfo) ((Openable) projectRoot) .getElementInfo()).setForeignResources(null); } break; case IModelElement.SCRIPT_FOLDER: ((ScriptFolderInfo) info).setForeignResources(null); break; case IModelElement.PROJECT_FRAGMENT: ((ProjectFragmentInfo) info).setForeignResources(null); } } ModelElementDelta current = this.currentDelta(); ModelElementDelta 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, IModelElementDelta.F_CONTENT); } elementDelta.addResourceDelta(delta); } /* * 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 void popUntilPrefixOf(IPath path) { while (this.currentElement != null) { IPath currentElementPath = null; if (this.currentElement instanceof IProjectFragment) { currentElementPath = ((IProjectFragment) this.currentElement) .getPath(); } else { IResource currentElementResource = this.currentElement .getResource(); if (currentElementResource != null) { currentElementPath = currentElementResource.getFullPath(); } } if (currentElementPath != null) { if (this.currentElement instanceof IScriptFolder && ((IScriptFolder) this.currentElement).isRootFolder() && 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>IModelElementDelta</code>, rooted in * the relevant <code>Model</code>s. */ private IModelElementDelta processResourceDelta(IResourceDelta changes) { try { IScriptModel model = this.manager.getModel(); if (!model.isOpen()) { // force opening of script model so that model element delta are // reported try { model.open(null); } catch (ModelException e) { if (VERBOSE) { e.printStackTrace(); } return null; } } this.state.initializeRoots(); this.currentElement = null; // get the workspace delta, and start processing there. IResourceDelta[] deltas = changes.getAffectedChildren(); for (int i = 0; i < deltas.length; i++) { IResourceDelta delta = deltas[i]; IResource res = delta.getResource(); // find out the element type RootInfo rootInfo = null; int elementType; IProject proj = (IProject) res; boolean wasDylanProject = this.state .findProject(proj.getName()) != null; boolean isDylanProject = DLTKLanguageManager .hasScriptNature(proj); if (!wasDylanProject && !isDylanProject) { elementType = NON_SCRIPT_RESOURCE; } else { rootInfo = this.enclosingRootInfo(res.getFullPath(), delta.getKind()); if (rootInfo != null && rootInfo.isRootOfProject(res.getFullPath())) { elementType = IModelElement.PROJECT_FRAGMENT; } else { elementType = IModelElement.SCRIPT_PROJECT; } } // traverse delta this.traverseDelta(delta, elementType, rootInfo); if (elementType == NON_SCRIPT_RESOURCE || (wasDylanProject != isDylanProject && (delta .getKind()) == IResourceDelta.CHANGED)) { /* * project has changed nature (description or open/closed) */ try { // add child as non script resource this.nonScriptResourcesChanged((Model) model, delta); } catch (ModelException e) { // script model could not be opened } } } this.refreshProjectFragments(); this.resetProjectCaches(); this.executePostActions(); return this.currentDelta; } finally { this.currentDelta = null; this.rootsToRefresh.clear(); this.projectCachesToReset.clear(); this.postActions.clear(); } } private void executePostActions() { if (postActions.size() == 0) { return; } for (Iterator<Runnable> i = postActions.iterator(); i.hasNext();) { i.next().run(); } } /* * Traverse the set of projects which have changed namespace, and reset * their caches and their dependents */ private void resetProjectCaches() { if (this.projectCachesToReset.size() == 0) { return; } ModelManager.getModelManager().resetZIPTypeCache(); Iterator iterator = this.projectCachesToReset.iterator(); HashMap projectDepencies = this.state.projectDependencies; HashSet affectedDependents = new HashSet(); while (iterator.hasNext()) { ScriptProject project = (ScriptProject) iterator.next(); project.resetCaches(); this.addDependentProjects(project, projectDepencies, affectedDependents); } // reset caches of dependent projects iterator = affectedDependents.iterator(); while (iterator.hasNext()) { ScriptProject project = (ScriptProject) iterator.next(); project.resetCaches(); } } /* * Refresh package fragment roots of projects that were affected */ private void refreshProjectFragments() { Iterator iterator = this.rootsToRefresh.iterator(); while (iterator.hasNext()) { ScriptProject project = (ScriptProject) iterator.next(); project.updateProjectFragments(); } } /* * Registers the given delta with this delta processor. */ public void registerModelDelta(IModelElementDelta delta) { this.modelDeltas.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 { ModelElementInfo info = (ModelElementInfo) parent .getElementInfo(); info.removeChild(child); } catch (ModelException e) { // do nothing - we already checked if open } } } /* * Notification that some resource changes have happened on the platform, * and that the script Model should update any required internal structures * such that its elements remain consistent. Translates * <code>IResourceDeltas</code> into <code>IModelElementDeltas</code>. * * @see IResourceDelta * * @see IResource */ public void resourceChanged(IResourceChangeEvent event) { int eventType = this.overridenEventType == -1 ? event.getType() : this.overridenEventType; IResource resource = event.getResource(); IResourceDelta delta = event.getDelta(); switch (eventType) { case IResourceChangeEvent.PRE_DELETE: if (resource.getType() == IResource.PROJECT && DLTKLanguageManager.hasScriptNature((IProject) resource)) { this.deleting((IProject) resource); } return; case IResourceChangeEvent.POST_CHANGE: Set<IModelElement> elementsToRefresh = this.state .removeExternalElementsToRefresh(); if (this.isAffectedBy(delta) || elementsToRefresh != null) { // avoid populating for SYNC or MARKER deltas try { try { this.stopDeltas(); this.checkProjectsBeingAddedOrRemoved(delta); // generate external archive change deltas if (elementsToRefresh != null) { // Call archive or custom deltas only if project are // correctly connected this.createExternalArchiveDelta(elementsToRefresh, null); this.createCustomElementDelta(null, elementsToRefresh); } // generate buildpath change deltas HashMap<IProject, BuildpathChange> buildpathChanges = this.state .removeAllBuildpathChanges(); if (buildpathChanges.size() > 0) { boolean hasDelta = this.currentDelta != null; ModelElementDelta javaDelta = currentDelta(); Iterator<BuildpathChange> changes = buildpathChanges .values().iterator(); while (changes.hasNext()) { BuildpathChange change = changes.next(); int result = change.generateDelta(javaDelta, false/* don't add buildpath change */); if ((result & BuildpathChange.HAS_DELTA) != 0) { hasDelta = true; // need to recompute root infos this.state.rootsAreStale = true; change.requestIndexing(); this.state .addBuildpathValidation(change.project); } if ((result & BuildpathChange.HAS_PROJECT_CHANGE) != 0) { this.state.addProjectReferenceChange( change.project, change.oldResolvedBuildpath); } if ((result & BuildpathChange.HAS_LIBRARY_CHANGE) != 0) { this.state.addExternalFolderChange( change.project, change.oldResolvedBuildpath); } } // 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; } IModelElementDelta translatedDelta = this .processResourceDelta(delta); if (translatedDelta != null) { this.registerModelDelta(translatedDelta); } } finally { this.sourceElementParserCache = null; // don't hold // onto parser // longer than // necessary this.startDeltas(); } IElementChangedListener[] listeners; int listenerCount; synchronized (this.state) { listeners = this.state.elementChangedListeners; listenerCount = this.state.elementChangedListenerCount; } this.notifyTypeHierarchies(listeners, listenerCount); this.fire(null, ElementChangedEvent.POST_CHANGE); } finally { // workaround for bug 15168 circular errors not reported this.state.resetOldScriptProjectNames(); this.oldRoots = null; } } return; case IResourceChangeEvent.PRE_BUILD: if (!this.isAffectedBy(delta)) { return; // avoid populating for SYNC or MARKER deltas } // create classpath markers if necessary boolean needCycleValidation = validateBuildpaths(delta); BuildpathValidation[] validations = this.state .removeBuildpathValidations(); if (validations != null) { for (int i = 0, length = validations.length; i < length; i++) { BuildpathValidation 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 (ModelException e) { // project doesn't exist any longer, continue with next // one } } } if (needCycleValidation || projectRefChanges != null) { // update all cycle markers since the project references changes // may have affected cycles try { ScriptProject.validateCycles(null); } catch (ModelException e) { // a project no longer exists } } Model.flushExternalFileCache(); ScriptBuilder.buildStarting(); // does not fire any deltas return; case IResourceChangeEvent.POST_BUILD: ScriptBuilder.buildFinished(); return; } } private static boolean isVirtualProjectFragment(IProjectFragment fragment) throws ModelException { if (fragment == null) { return false; } return fragment.isExternal() && fragment.getRawBuildpathEntry() == null; } @SuppressWarnings("serial") private static class FragmentCache extends HashMap<ScriptProject, IProjectFragment[]> { public IProjectFragment[] getAllFragments(ScriptProject project) throws ModelException { IProjectFragment[] fragments = get(project); if (fragments == null) { fragments = project.getAllProjectFragments(); put(project, fragments); } return fragments; } } /** * Create delta for custom user project fragments. * * @param refreshedElementsCopy */ private boolean createCustomElementDelta(IProgressMonitor monitor, Set<IModelElement> refreshedElements) { if (refreshedElements == null) { return false; } boolean hasDelta = false; final FragmentCache fragmentCache = new FragmentCache(); HashSet<IProjectFragment> fragmentsToRefresh = new HashSet<IProjectFragment>(); for (IModelElement element : refreshedElements) { switch (element.getElementType()) { case IModelElement.PROJECT_FRAGMENT: IProjectFragment fragment = (IProjectFragment) element; try { if (isVirtualProjectFragment(fragment)) { fragmentsToRefresh.add(fragment); } } catch (ModelException e1) { if (DLTKCore.DEBUG) { e1.printStackTrace(); } } break; case IModelElement.SCRIPT_PROJECT: ScriptProject scriptProject = (ScriptProject) element; if (!DLTKLanguageManager.hasScriptNature(scriptProject .getProject())) { // project is not accessible or has lost its script // nature break; } IEnvironment environment = EnvironmentManager .getEnvironment(scriptProject); if (environment != null && !environment.isConnected()) { break; // Project environment is not connected. } try { IProjectFragment[] fragments = scriptProject .getProjectFragments(); for (int i = 0; i < fragments.length; i++) { if (isVirtualProjectFragment(fragments[i])) { fragmentsToRefresh.add(fragments[i]); } } fragments = fragmentCache.getAllFragments(scriptProject); for (int i = 0; i < fragments.length; i++) { if (isVirtualProjectFragment(fragments[i])) { fragmentsToRefresh.add(fragments[i]); } } } catch (ModelException e1) { if (DLTKCore.DEBUG) { e1.printStackTrace(); } } break; case IModelElement.SCRIPT_MODEL: for (String projectName : this.state.getOldScriptProjectNames()) { IProject project = ResourcesPlugin.getWorkspace().getRoot() .getProject(projectName); if (!DLTKLanguageManager.hasScriptNature(project)) { // project is not accessible or has lost its Script // nature continue; } scriptProject = (ScriptProject) DLTKCore.create(project); try { IProjectFragment[] fragments = scriptProject .getProjectFragments(); for (int i = 0; i < fragments.length; i++) { if (isVirtualProjectFragment(fragments[i])) { fragmentsToRefresh.add(fragments[i]); } } } catch (ModelException e1) { if (DLTKCore.DEBUG) { e1.printStackTrace(); } } } break; } } if (fragmentsToRefresh.isEmpty()) { return false; } final Set<String> projectsToRefresh = new HashSet<String>(); for (IProjectFragment fragment : fragmentsToRefresh) { projectsToRefresh.add(fragment.getScriptProject().getElementName()); } final Map<IPath, Long> customTimeStamps = this.state .getCustomTimeStamps(); // perform refresh IWorkspaceRoot wksRoot = ResourcesPlugin.getWorkspace().getRoot(); for (String projectName : this.state.getOldScriptProjectNames()) { if (!projectsToRefresh.contains(projectName)) { continue; } if (monitor != null && monitor.isCanceled()) { break; } IProject project = wksRoot.getProject(projectName); if (!DLTKLanguageManager.hasScriptNature(project)) { // project is not accessible or has lost its Script nature continue; } ScriptProject scriptProject = (ScriptProject) DLTKCore .create(project); try { final Set<IProjectFragment> oldFragments = new HashSet<IProjectFragment>(); Collections.addAll(oldFragments, scriptProject.getProjectFragments()); final Set<IProjectFragment> newFragments = new HashSet<IProjectFragment>(); Collections.addAll(newFragments, fragmentCache.getAllFragments(scriptProject)); final Set<IProjectFragment> allFragments = new HashSet<IProjectFragment>(); allFragments.addAll(oldFragments); allFragments.addAll(newFragments); allFragments.retainAll(fragmentsToRefresh); for (IProjectFragment fragment : allFragments) { if (!(fragment instanceof IProjectFragmentTimestamp)) { continue; } final boolean isNew = newFragments.contains(fragment); final boolean isOld = oldFragments.contains(fragment); if (isNew && !isOld) { long newTimeStamp = ((IProjectFragmentTimestamp) fragment) .getTimeStamp(); if (newTimeStamp == 0) continue; customTimeStamps.put(fragment.getPath(), new Long( newTimeStamp)); // index new library ProjectIndexerManager.indexLibrary(scriptProject, fragment.getPath()); if (fragment instanceof Openable) { this.elementAdded((Openable) fragment, null, null); } hasDelta = true; } else if (isOld && !isNew) { // TODO it's shared and can be used in other projects // customTimeStamps.remove(fragment.getPath()); // ProjectIndexerManager.removeLibrary(scriptProject, // fragment.getPath()); if (fragment instanceof Openable) { this.elementRemoved((Openable) fragment, null, null); } hasDelta = true; } else { final Long oldTimestamp = customTimeStamps.get(fragment .getPath()); final long newTimeStamp = ((IProjectFragmentTimestamp) fragment) .getTimeStamp(); if (newTimeStamp != 0 && (oldTimestamp == null || newTimeStamp != oldTimestamp .longValue())) { customTimeStamps.put(fragment.getPath(), new Long( newTimeStamp)); // index changed library ProjectIndexerManager.indexLibrary(scriptProject, fragment.getPath()); if (fragment instanceof Openable) { this.contentChanged((Openable) fragment); } hasDelta = true; } } } } catch (ModelException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } } return hasDelta; } /* * 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>IModelElementDelta</code>s. */ private void traverseDelta(IResourceDelta delta, int elementType, RootInfo rootInfo) { 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 = this .updateCurrentDeltaAndIndex( delta, elementType == IModelElement.PROJECT_FRAGMENT ? IModelElement.SCRIPT_PROJECT : // case // of // prj=src elementType, rootInfo); } else if (rootInfo != null) { processChildren = this.updateCurrentDeltaAndIndex(delta, elementType, rootInfo); } else { // not yet inside a package fragment root processChildren = true; } // process children if needed if (processChildren) { IResourceDelta[] children = delta.getAffectedChildren(); boolean oneChildOnBuildpath = 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(); // find out whether the child is a package fragment root of the // current project IPath childPath = childRes.getFullPath(); int childKind = child.getKind(); RootInfo childRootInfo = this.rootInfo(childPath, childKind); if (childRootInfo != null && !childRootInfo.isRootOfProject(childPath)) { // package fragment root of another project (dealt with // later) childRootInfo = null; } // compute child type int childType = this.elementType(childRes, childKind, elementType, rootInfo == null ? childRootInfo : rootInfo); boolean isNestedRoot = rootInfo != null && childRootInfo != null; if (!isNestedRoot) { // do not treat as non-script rsc if // nested root this.traverseDelta(child, childType, rootInfo == null ? childRootInfo : rootInfo); // traverse delta for child in the same project if (childType == NON_SCRIPT_RESOURCE) { if (rootInfo != null) { // if inside a package fragment // root if (!isValidParent) { continue; } if (parent == null) { /* * find the parent of the non-script resource to * attach to */ if (this.currentElement == null || !rootInfo.project .equals(this.currentElement .getScriptProject())) { /* * note if currentElement is the IModel, * getScriptProject() is null force the * currentProject to be used */ this.currentElement = rootInfo.project; } if (elementType == IModelElement.SCRIPT_PROJECT || (elementType == IModelElement.PROJECT_FRAGMENT && res instanceof IProject)) { // NB: attach non-script resource to project // (not to its package fragment root) parent = rootInfo.project; } else { parent = this.createElement(res, elementType, rootInfo); } if (parent == null) { isValidParent = false; continue; } } // add child as non script resource try { this.nonScriptResourcesChanged(parent, child); } catch (ModelException e) { // ignore } } else { // the non-script resource (or its parent folder) // will // be attached to the dltk project if (orphanChildren == null) { orphanChildren = new IResourceDelta[length]; } orphanChildren[i] = child; } } else { oneChildOnBuildpath = true; } } else { oneChildOnBuildpath = true; // to avoid reporting child // delta as non-script 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 && (childRootInfo = this .rootInfo(childPath, childKind)) != null)) { this.traverseDelta(child, IModelElement.PROJECT_FRAGMENT, childRootInfo); // 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 = this.otherRootsInfo(childPath, childKind)) != null) { Iterator iterator = rootList.iterator(); while (iterator.hasNext()) { childRootInfo = (RootInfo) iterator.next(); this.traverseDelta(child, IModelElement.PROJECT_FRAGMENT, childRootInfo); // binary // output // of // childRootInfo.project // cannot // be // this // root } } } if (orphanChildren != null && (oneChildOnBuildpath // orphan // children are // siblings of a // package // fragment root || res instanceof IProject)) { // non-script resource // directly under a project // attach orphan children IProject rscProject = res.getProject(); ScriptProject adoptiveProject = (ScriptProject) DLTKCore .create(rscProject); if (adoptiveProject != null && DLTKLanguageManager.hasScriptNature(rscProject)) { // delta // iff // script // project // (18698) for (int i = 0; i < length; i++) { if (orphanChildren[i] != null) { try { this.nonScriptResourcesChanged(adoptiveProject, orphanChildren[i]); } catch (ModelException e) { // ignore } } } } } // else resource delta will be added by parent } // else resource delta will be added by parent } private void validateBuildpaths(IResourceDelta delta, HashSet<IPath> affectedProjects) { IResource resource = delta.getResource(); boolean processChildren = false; switch (resource.getType()) { case IResource.ROOT: if (delta.getKind() == IResourceDelta.CHANGED) { processChildren = true; } break; case IResource.PROJECT: IProject project = (IProject) resource; int kind = delta.getKind(); boolean isDylanProject = DLTKLanguageManager .hasScriptNature(project); switch (kind) { case IResourceDelta.ADDED: processChildren = isDylanProject; affectedProjects.add(project.getFullPath()); break; case IResourceDelta.CHANGED: processChildren = isDylanProject; if ((delta.getFlags() & IResourceDelta.OPEN) != 0) { // project opened or closed if (isDylanProject) { ScriptProject scriptProject = (ScriptProject) DLTKCore .create(project); this.state.addBuildpathValidation(scriptProject); // in case .buildpath got modified while closed } affectedProjects.add(project.getFullPath()); } else if ((delta.getFlags() & IResourceDelta.DESCRIPTION) != 0) { boolean wasScriptProject = this.state.findProject(project .getName()) != null; if (wasScriptProject != isDylanProject) { // project gained or lost script nature ScriptProject scriptProject = (ScriptProject) DLTKCore .create(project); // add/remove buildpath markers this.state.addBuildpathValidation(scriptProject); affectedProjects.add(project.getFullPath()); } } break; case IResourceDelta.REMOVED: affectedProjects.add(project.getFullPath()); break; } break; case IResource.FILE: /* check buildpath file change */ IFile file = (IFile) resource; if (file.getName().equals(ScriptProject.BUILDPATH_FILENAME)) { ScriptProject scriptProject = (ScriptProject) DLTKCore .create(file.getProject()); this.state.addBuildpathValidation(scriptProject); affectedProjects.add(file.getProject().getFullPath()); break; } break; } if (processChildren) { IResourceDelta[] children = delta.getAffectedChildren(); for (int i = 0; i < children.length; i++) { validateBuildpaths(children[i], affectedProjects); } } } /* * Validate the buildpaths of the projects affected by the given delta. * Create markers if necessary. Returns whether cycle markers should be * recomputed. */ private boolean validateBuildpaths(IResourceDelta delta) { HashSet<IPath> affectedProjects = new HashSet<IPath>(5); validateBuildpaths(delta, affectedProjects); boolean needCycleValidation = false; // validate buildpaths 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]; ScriptProject scriptProject = (ScriptProject) DLTKCore .create(project); try { IPath projectPath = project.getFullPath(); IBuildpathEntry[] buildpath = scriptProject .getResolvedBuildpath(); /* * allowed to reuse model cache */ for (int j = 0, cpLength = buildpath.length; j < cpLength; j++) { IBuildpathEntry entry = buildpath[j]; switch (entry.getEntryKind()) { case IBuildpathEntry.BPE_PROJECT: if (affectedProjects.contains(entry.getPath())) { this.state .addBuildpathValidation(scriptProject); needCycleValidation = true; } break; case IBuildpathEntry.BPE_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 .addBuildpathValidation(scriptProject); } break; } } } catch (ModelException e) { // project no longer exist } } } return needCycleValidation; } /* * Update the current delta (ie. add/remove/change the given element) and * update the correponding index. Returns whether the children of the given * delta must be processed. @throws a ModelException if the delta doesn't * correspond to a model 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 = this.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; } this.updateIndex(element, delta); this.elementAdded(element, delta, rootInfo); if (elementType == IModelElement.PROJECT_FRAGMENT) { this.state.addBuildpathValidation(rootInfo.project); } return elementType == IModelElement.SCRIPT_FOLDER; case IResourceDelta.REMOVED: deltaRes = delta.getResource(); element = this.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; } this.updateIndex(element, delta); this.elementRemoved(element, delta, rootInfo); if (elementType == IModelElement.PROJECT_FRAGMENT) { this.state.addBuildpathValidation(rootInfo.project); } if (deltaRes.getType() == IResource.PROJECT) { // reset the corresponding project built state, since cannot // reuse if added back if (DLTKCore.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 == IModelElement.SCRIPT_FOLDER; case IResourceDelta.CHANGED: int flags = delta.getFlags(); if ((flags & IResourceDelta.CONTENT) != 0 || (flags & IResourceDelta.ENCODING) != 0) { // content or encoding has changed element = this.createElement(delta.getResource(), elementType, rootInfo); if (element == null) { return false; } this.updateIndex(element, delta); this.contentChanged(element); } else if (elementType == IModelElement.SCRIPT_PROJECT) { if ((flags & IResourceDelta.OPEN) != 0) { // project has been opened or closed final IProject res = (IProject) delta.getResource(); element = this.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 (DLTKLanguageManager.hasScriptNature(res)) { this.addToParentInfo(element); this.currentDelta().opened(element); this.state.updateRoots(element.getPath(), delta, this); // refresh pkg fragment roots and caches of the // project (and its dependents) final IScriptProject project = (IScriptProject) element; this.rootsToRefresh.add(project); this.projectCachesToReset.add(project); this.postActions.add(new Runnable() { public void run() { ProjectIndexerManager.indexProject(res); } }); } } else { boolean wasDylanProject = this.state.findProject(res .getName()) != null; if (wasDylanProject) { this.close(element); this.removeFromParentInfo(element); this.currentDelta().closed(element); this.manager.indexManager.discardJobs(element .getElementName()); final IPath projectPath = res.getFullPath(); this.manager.indexManager .removeIndexFamily(projectPath); ProjectIndexerManager.removeProject(projectPath); } } return false; // when a project is open/closed don't // process children } if ((flags & IResourceDelta.DESCRIPTION) != 0) { IProject res = (IProject) delta.getResource(); boolean wasDylanProject = this.state.findProject(res .getName()) != null; boolean isDylanProject = DLTKLanguageManager .hasScriptNature(res); if (wasDylanProject != isDylanProject) { // project's nature has been added or removed element = this .createElement(res, elementType, rootInfo); if (element == null) { return false; // note its resources are still } // visible as roots to other // projects if (isDylanProject) { this.elementAdded(element, delta, rootInfo); ProjectIndexerManager.indexProject(res); } else { this.elementRemoved(element, delta, rootInfo); this.manager.indexManager.discardJobs(element .getElementName()); final IPath projectPath = res.getFullPath(); this.manager.indexManager .removeIndexFamily(projectPath); ProjectIndexerManager.removeProject(projectPath); // reset the corresponding project built state, // since cannot reuse if added back if (DLTKCore.DEBUG) { System.out .println("Clearing last state for project loosing script 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 IModelElement.SCRIPT_PROJECT: switch (delta.getKind()) { case IResourceDelta.ADDED: final IScriptProject scriptProject = element.getScriptProject(); this.postActions.add(new Runnable() { public void run() { ProjectIndexerManager.indexProject(scriptProject); } }); break; case IResourceDelta.REMOVED: final IPath projectPath = element.getScriptProject() .getProject().getFullPath(); indexManager.removeIndexFamily(projectPath); ProjectIndexerManager.removeProject(projectPath); // 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 // script // nature is added or removed // is done in updateCurrentDeltaAndIndex } break; case IModelElement.PROJECT_FRAGMENT: if (element instanceof ArchiveProjectFragment || element instanceof ExternalProjectFragment) { IProjectFragment root = (IProjectFragment) element; // index External or zip fragment only once (if the root is // in its declaring // project) IPath jarPath = root.getPath(); final IScriptProject scriptProject = root.getScriptProject(); switch (delta.getKind()) { case IResourceDelta.ADDED: // index the new jar ProjectIndexerManager.indexLibrary(scriptProject, jarPath); break; case IResourceDelta.CHANGED: // first remove the index so that it is forced to be // re-indexed indexManager.removeIndex(jarPath); // then index the jar ProjectIndexerManager.indexLibrary(scriptProject, jarPath); break; case IResourceDelta.REMOVED: // the jar was physically removed: remove the index indexManager.discardJobs(jarPath.toString()); indexManager.removeIndex(jarPath); ProjectIndexerManager.removeLibrary(scriptProject, jarPath); break; } break; } int kind = delta.getKind(); if (kind == IResourceDelta.ADDED || kind == IResourceDelta.REMOVED) { IProjectFragment root = (IProjectFragment) element; this.updateRootIndex(root, Path.EMPTY, delta); break; } // don't break as packages of the package fragment root can be // indexed below case IModelElement.SCRIPT_FOLDER: switch (delta.getKind()) { case IResourceDelta.ADDED: case IResourceDelta.REMOVED: IScriptFolder pkg = null; if (element instanceof IProjectFragment) { IProjectFragment root = (IProjectFragment) element; pkg = root.getScriptFolder(Path.EMPTY); } else { pkg = (IScriptFolder) element; } RootInfo rootInfo = this.rootInfo(pkg.getParent().getPath(), delta.getKind()); boolean isSource = rootInfo == null // if null, defaults // to // source || rootInfo.entryKind == IBuildpathEntry.BPE_SOURCE; IResourceDelta[] children = delta.getAffectedChildren(); for (int i = 0, length = children.length; i < length; i++) { IResourceDelta child = children[i]; IResource resource = child.getResource(); // TODO (philippe) Why do this? Every child is added // anyway // as the delta is walked if (resource instanceof IFile) { String name = resource.getName(); if (isSource) { if (Util.isValidSourceModule(pkg, resource)) { Openable cu = (Openable) pkg .getSourceModule(name); this.updateIndex(cu, child); } } } } break; } break; case IModelElement.BINARY_MODULE: IFile file = (IFile) delta.getResource(); IPath binaryFolderPath = element.getProjectFragment().getPath(); 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; } // case IResourceDelta.ADDED: // indexManager.addExternal(file, binaryFolderPath); // break; case IResourceDelta.REMOVED: String containerRelativePath = Util.relativePath( file.getFullPath(), binaryFolderPath.segmentCount()); indexManager.remove(containerRelativePath, binaryFolderPath); break; } break; case IModelElement.SOURCE_MODULE: 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; } case IResourceDelta.ADDED: if (ProjectIndexerManager.isIndexerEnabled(file.getProject())) { IDLTKLanguageToolkit toolkit = null; toolkit = DLTKLanguageManager.getLanguageToolkit(element); ProjectIndexerManager.indexSourceModule( (ISourceModule) element, toolkit); if (DLTKCore.DEBUG) { System.err .println("update index: some actions are required to perform here...."); //$NON-NLS-1$ } } // 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: final IProject project = file.getProject(); /* remove project segment */ final String path = Util.relativePath(file.getFullPath(), 1); indexManager.remove(path, project.getFullPath()); ProjectIndexerManager.removeSourceModule( DLTKCore.create(project), path); // 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); // System.err // .println("this.manager.secondaryTypesRemoving(file, true);"); break; } } } private ISourceElementParser getSourceElementParser(Openable element) { if (this.sourceElementParserCache == null) { this.sourceElementParserCache = this.manager.indexManager .getSourceElementParser(element.getScriptProject()); } return this.sourceElementParserCache; } private SourceIndexerRequestor getSourceRequestor(Openable element) { if (this.sourceRequestorCache == null) { this.sourceRequestorCache = this.manager.indexManager .getSourceRequestor(element.getScriptProject()); } return this.sourceRequestorCache; } /* * Update Model given some delta */ public void updateModel(IModelElementDelta customDelta) { if (customDelta == null) { for (int i = 0, length = this.modelDeltas.size(); i < length; i++) { IModelElementDelta delta = this.modelDeltas.get(i); this.modelUpdater.processDelta(delta); } } else { this.modelUpdater.processDelta(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(IProjectFragment root, IPath pkgPath, IResourceDelta delta) { Openable pkg = (Openable) root.getScriptFolder(pkgPath); this.updateIndex(pkg, delta); IResourceDelta[] children = delta.getAffectedChildren(); for (int i = 0, length = children.length; i < length; i++) { IResourceDelta child = children[i]; IResource resource = child.getResource(); if (resource instanceof IFolder) { this.updateRootIndex(root, pkgPath.append(resource.getName()), child); } } } public void clearCustomTimestampsFor(IModelElement[] elements) { final Map<IPath, Long> timeStamps = state.getCustomTimeStamps(); for (IModelElement e : elements) { // TODO test if it actually works timeStamps.remove(e.getPath()); } } // /* // * Check whether .buildpath files are affected by the given delta. // * Creates/removes problem markers if needed. Remember the affected // projects // * in the given set. // */ // private void updateBuildpathMarkers(IResourceDelta delta, // HashSet affectedProjects, Map preferredBuildpaths) { // 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 isScriptProject = DLTKLanguageManager // .hasScriptNature(project); // switch (kind) { // case IResourceDelta.ADDED: // processChildren = isScriptProject; // affectedProjects.add(project.getFullPath()); // break; // case IResourceDelta.CHANGED: // processChildren = isScriptProject; // if ((delta.getFlags() & IResourceDelta.OPEN) != 0) { // // project opened or closed: remember project and its // // dependents // affectedProjects.add(project.getFullPath()); // if (isScriptProject) { // ScriptProject scriptProject = (ScriptProject) DLTKCore // .create(project); // scriptProject.updateBuildpathMarkers(preferredBuildpaths); // in // // case // // .buildpath // // got // // modified // // while // // closed // } // } else if ((delta.getFlags() & IResourceDelta.DESCRIPTION) != 0) { // boolean wasScriptProject = this.state.findProject(project // .getName()) != null; // if (wasScriptProject && !isScriptProject) { // // project no longer has Script nature, discard Script // // related obsolete markers // affectedProjects.add(project.getFullPath()); // // flush buildpath markers // ScriptProject scriptProject = (ScriptProject) DLTKCore // .create(project); // scriptProject.flushBuildpathProblemMarkers(true, // flush // // cycle // // markers // true // flush buildpath format markers // ); // // // remove problems and tasks created by the builder // ScriptBuilder.removeProblemsAndTasksFor(project); // } // } else if (isScriptProject) { // // check if all entries exist // try { // ScriptProject scriptProject = (ScriptProject) DLTKCore // .create(project); // scriptProject.getResolvedBuildpath( // true/* ignoreUnresolvedEntry */, // true/* generateMarkerOnError */, false/* // * don't // * returnResolutionInProgress // */); // } catch (ModelException e) { // // project doesn't exist: ignore // } // } // break; // case IResourceDelta.REMOVED: // affectedProjects.add(project.getFullPath()); // break; // } // break; // case IResource.FILE: // /* check buildpath file change */ // IFile file = (IFile) resource; // if (file.getName().equals(ScriptProject.BUILDPATH_FILENAME)) { // affectedProjects.add(file.getProject().getFullPath()); // ScriptProject scriptProject = (ScriptProject) DLTKCore.create(file // .getProject()); // scriptProject.updateBuildpathMarkers(preferredBuildpaths); // break; // } // // /* check custom preference file change */ // // if (file.getName().equals(ScriptProject.PREF_FILENAME)) { // // reconcilePreferenceFileUpdate(delta, file, project); // // break; // // } // break; // } // if (processChildren) { // IResourceDelta[] children = delta.getAffectedChildren(); // for (int i = 0; i < children.length; i++) { // updateBuildpathMarkers(children[i], affectedProjects, // preferredBuildpaths); // } // } // } // /* // * Update the .buildpath format, missing entries and cycle markers for the // * projects affected by the given delta. // */ // private void updateBuildpathMarkers(IResourceDelta delta, // DeltaProcessingState.ProjectUpdateInfo[] updates) { // // Map preferredBuildpaths = new HashMap(5); // HashSet affectedProjects = new HashSet(5); // // // read .buildpath files that have changed, and create markers if format // // is wrong or if an entry cannot be found // Model.flushExternalFileCache(); // updateBuildpathMarkers(delta, affectedProjects, preferredBuildpaths); // // // update .buildpath format markers for 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]; // ScriptProject scriptProject = (ScriptProject) DLTKCore // .create(project); // if (preferredBuildpaths.get(scriptProject) == null) { // not // // already // // updated // try { // IPath projectPath = project.getFullPath(); // IBuildpathEntry[] buildpath = scriptProject // .getResolvedBuildpath( // true/* ignoreUnresolvedEntry */, // false/* don't generateMarkerOnError */, // false/* // * don't // * returnResolutionInProgress // */); // allowed // // to // // reuse // // model // // cache // for (int j = 0, cpLength = buildpath.length; j < cpLength; j++) { // IBuildpathEntry entry = buildpath[j]; // switch (entry.getEntryKind()) { // case IBuildpathEntry.BPE_PROJECT: // if (affectedProjects.contains(entry.getPath())) { // scriptProject.updateBuildpathMarkers(null); // } // break; // case IBuildpathEntry.BPE_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)) { // scriptProject.updateBuildpathMarkers(null); // } // break; // } // } // } catch (ModelException e) { // // project no longer exists // } // } // } // } // if (!affectedProjects.isEmpty() || updates != null) { // // update all cycle markers since the given delta may have affected // // cycles // if (updates != null) { // for (int i = 0, length = updates.length; i < length; i++) { // DeltaProcessingState.ProjectUpdateInfo info = updates[i]; // if (!preferredBuildpaths.containsKey(info.project)) // preferredBuildpaths.put(info.project, // info.newResolvedPath); // } // } // try { // ScriptProject.updateAllCycleMarkers(preferredBuildpaths); // } catch (ModelException e) { // // project no longer exist // } // } // } }