| /******************************************************************************* |
| * Copyright (c) 2000, 2018 IBM Corporation and others. |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| *******************************************************************************/ |
| 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.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.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.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); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuffer buffer = new StringBuffer("project="); //$NON-NLS-1$ |
| if (this.project == null) { |
| buffer.append("null"); //$NON-NLS-1$ |
| } else { |
| buffer.append(this.project.getElementName()); |
| } |
| buffer.append("\npath="); //$NON-NLS-1$ |
| if (this.rootPath == null) { |
| buffer.append("null"); //$NON-NLS-1$ |
| } else { |
| buffer.append(this.rootPath.toString()); |
| } |
| buffer.append("\nincluding="); //$NON-NLS-1$ |
| if (this.inclusionPatterns == null) { |
| buffer.append("null"); //$NON-NLS-1$ |
| } else { |
| for (int i = 0, length = this.inclusionPatterns.length; i < length; i++) { |
| buffer.append(new String(this.inclusionPatterns[i])); |
| if (i < length - 1) { |
| buffer.append("|"); //$NON-NLS-1$ |
| } |
| } |
| } |
| buffer.append("\nexcluding="); //$NON-NLS-1$ |
| if (this.exclusionPatterns == null) { |
| buffer.append("null"); //$NON-NLS-1$ |
| } else { |
| for (int i = 0, length = this.exclusionPatterns.length; i < length; i++) { |
| buffer.append(new String(this.exclusionPatterns[i])); |
| if (i < length - 1) { |
| buffer.append("|"); //$NON-NLS-1$ |
| } |
| } |
| } |
| return buffer.toString(); |
| } |
| } |
| |
| // private final static int IGNORE = 0; |
| // private final static int SOURCE = 1; |
| // private final static int BINARY = 2; |
| |
| private final static 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 |
| |
| /* |
| * 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<>(); |
| /* |
| * 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<>(); |
| |
| /* |
| * 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<>(); |
| /** {@link Runnable}s that should be called after model is updated */ |
| private final ArrayList<Runnable> postActions = new ArrayList<>(); |
| /* |
| * 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 = progressMonitor -> { |
| 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 " //$NON-NLS-1$ |
| + project.getName() + " due to external jar file change"); //$NON-NLS-1$ |
| } |
| 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<>(); |
| boolean hasDelta = false; |
| // find JARs to refresh |
| Set<IPath> archivePathsToRefresh = new HashSet<>(); |
| // 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, Long.valueOf(newTimeStamp)); |
| // first remove the index so that it is |
| // forced to be re-indexed |
| this.manager.indexManager.removeIndex(entryPath); |
| // then index the jar |
| 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, Long.valueOf(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: " //$NON-NLS-1$ |
| + root.getElementName()); |
| } |
| 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: " //$NON-NLS-1$ |
| + root.getElementName()); |
| } |
| 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: " //$NON-NLS-1$ |
| + root.getElementName()); |
| } |
| 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<>(); |
| } |
| 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<>(); |
| } |
| |
| /* |
| * 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 [" //$NON-NLS-1$ |
| + Thread.currentThread() + "]:"); //$NON-NLS-1$ |
| System.out.println(deltaToNotify == null ? "<NONE>" //$NON-NLS-1$ |
| : deltaToNotify.toString()); |
| } |
| 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 [" //$NON-NLS-1$ |
| + Thread.currentThread() + "]:"); //$NON-NLS-1$ |
| System.out.println(deltaToNotify == null ? "<NONE>" //$NON-NLS-1$ |
| : deltaToNotify.toString()); |
| } |
| 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(delta -> { |
| 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<IModelElementDelta> deltas) { |
| if (deltas.size() == 0) { |
| return null; |
| } |
| if (deltas.size() == 1) { |
| return deltas.iterator().next(); |
| } |
| if (VERBOSE) { |
| System.out.println("MERGING " + deltas.size() + " DELTAS [" //$NON-NLS-1$ //$NON-NLS-2$ |
| + Thread.currentThread() + "]"); //$NON-NLS-1$ |
| } |
| Iterator<IModelElementDelta> 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() { |
| @Override |
| public void handleException(Throwable exception) { |
| Util.log(exception, "Exception occurred in listener of script element change notification"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| 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(" -> " //$NON-NLS-1$ |
| + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ |
| } |
| } |
| } |
| } |
| |
| 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<IScriptProject> iterator = this.projectCachesToReset.iterator(); |
| HashMap projectDepencies = this.state.projectDependencies; |
| HashSet<IScriptProject> 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<IScriptProject> 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.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<>(); |
| 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<>(); |
| 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<>(); |
| Collections.addAll(oldFragments, scriptProject.getProjectFragments()); |
| final Set<IProjectFragment> newFragments = new HashSet<>(); |
| Collections.addAll(newFragments, fragmentCache.getAllFragments(scriptProject)); |
| final Set<IProjectFragment> allFragments = new HashSet<>(); |
| 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(), Long.valueOf(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(), Long.valueOf(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<>(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 : " //$NON-NLS-1$ |
| + deltaRes); |
| } |
| 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(() -> 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: " //$NON-NLS-1$ |
| + res); |
| } |
| 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(() -> 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; |
| } |
| } |
| } |
| |
| /* |
| * 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()); |
| } |
| } |
| } |