| /******************************************************************************* |
| * Copyright (c) 2005, 2017 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| *******************************************************************************/ |
| package org.eclipse.dltk.internal.core; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedOutputStream; |
| import java.io.DataInputStream; |
| import java.io.DataOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.net.URI; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| 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.ISaveContext; |
| import org.eclipse.core.resources.ISaveParticipant; |
| import org.eclipse.core.resources.ISavedState; |
| import org.eclipse.core.resources.IWorkspace; |
| import org.eclipse.core.resources.IWorkspaceRoot; |
| import org.eclipse.core.resources.IWorkspaceRunnable; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.AssertionFailedException; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IConfigurationElement; |
| import org.eclipse.core.runtime.IExtension; |
| import org.eclipse.core.runtime.IExtensionPoint; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.MultiStatus; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.Plugin; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.core.runtime.preferences.DefaultScope; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; |
| import org.eclipse.core.runtime.preferences.IPreferencesService; |
| import org.eclipse.core.runtime.preferences.InstanceScope; |
| import org.eclipse.dltk.compiler.problem.IProblem; |
| import org.eclipse.dltk.compiler.util.HashtableOfObjectToInt; |
| import org.eclipse.dltk.core.BuildpathContainerInitializer; |
| import org.eclipse.dltk.core.DLTKContentTypeManager; |
| import org.eclipse.dltk.core.DLTKCore; |
| import org.eclipse.dltk.core.DLTKLanguageManager; |
| import org.eclipse.dltk.core.IAccessRule; |
| import org.eclipse.dltk.core.IArchive; |
| import org.eclipse.dltk.core.IBuildpathAttribute; |
| import org.eclipse.dltk.core.IBuildpathContainer; |
| import org.eclipse.dltk.core.IBuildpathEntry; |
| import org.eclipse.dltk.core.IDLTKLanguageToolkit; |
| import org.eclipse.dltk.core.IModelElement; |
| import org.eclipse.dltk.core.IModelStatus; |
| import org.eclipse.dltk.core.IModelStatusConstants; |
| import org.eclipse.dltk.core.IParent; |
| import org.eclipse.dltk.core.IProblemRequestor; |
| 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.ISourceModuleInfoCache; |
| import org.eclipse.dltk.core.ModelException; |
| import org.eclipse.dltk.core.WorkingCopyOwner; |
| import org.eclipse.dltk.core.caching.IContentCache; |
| import org.eclipse.dltk.core.environment.EnvironmentPathUtils; |
| import org.eclipse.dltk.core.environment.IFileHandle; |
| import org.eclipse.dltk.core.search.indexing.IndexManager; |
| import org.eclipse.dltk.internal.core.builder.ScriptBuilder; |
| import org.eclipse.dltk.internal.core.caching.DLTKCoreCache; |
| import org.eclipse.dltk.internal.core.search.DLTKWorkspaceScope; |
| import org.eclipse.dltk.internal.core.search.ProjectIndexerManager; |
| import org.eclipse.dltk.internal.core.util.Messages; |
| import org.eclipse.dltk.internal.core.util.Util; |
| import org.eclipse.dltk.internal.core.util.WeakHashSet; |
| import org.eclipse.osgi.util.NLS; |
| import org.osgi.service.prefs.BackingStoreException; |
| |
| public class ModelManager implements ISaveParticipant { |
| private final static int CONTAINERS_FILE_VERSION = 1; |
| public final static String BP_VARIABLE_PREFERENCES_PREFIX = DLTKCore.PLUGIN_ID |
| + ".buildpathVariable."; //$NON-NLS-1$ |
| public final static String BP_CONTAINER_PREFERENCES_PREFIX = DLTKCore.PLUGIN_ID |
| + ".buildpathContainer."; //$NON-NLS-1$ |
| public final static String BP_USERLIBRARY_PREFERENCES_PREFIX = DLTKCore.PLUGIN_ID |
| + ".userLibrary."; //$NON-NLS-1$ |
| public final static String BP_ENTRY_IGNORE = "##<cp entry ignore>##"; //$NON-NLS-1$ |
| public final static IPath BP_ENTRY_IGNORE_PATH = new Path(BP_ENTRY_IGNORE); |
| public final static String TRUE = "true"; //$NON-NLS-1$ |
| /** |
| * Unique handle onto the Model |
| */ |
| final Model model = new Model(); |
| /** |
| * Buildpath variables pool |
| */ |
| public HashMap variables = new HashMap(5); |
| public HashSet variablesWithInitializer = new HashSet(5); |
| public HashSet readOnlyVariables = new HashSet(5); |
| public HashMap previousSessionVariables = new HashMap(5); |
| private ThreadLocal variableInitializationInProgress = new ThreadLocal(); |
| |
| /** |
| * Buildpath containers pool |
| */ |
| public HashMap containers = new HashMap(5); |
| public HashMap previousSessionContainers = new HashMap(5); |
| private ThreadLocal containerInitializationInProgress = new ThreadLocal(); |
| public boolean batchContainerInitializations = false; |
| public final Hashtable<String, BuildpathContainerInitializer> containerInitializersCache = new Hashtable<>( |
| 5); |
| /** |
| * Special value used for recognizing ongoing initialization and breaking |
| * initialization cycles |
| */ |
| public final static IPath VARIABLE_INITIALIZATION_IN_PROGRESS = new Path( |
| "Variable Initialization In Progress"); //$NON-NLS-1$ |
| |
| public final static IBuildpathContainer CONTAINER_INITIALIZATION_IN_PROGRESS = new IBuildpathContainer() { |
| @Override |
| public IBuildpathEntry[] getBuildpathEntries() { |
| return null; |
| } |
| |
| @Override |
| public String getDescription() { |
| return "Container Initialization In Progress"; //$NON-NLS-1$ |
| } |
| |
| @Override |
| public int getKind() { |
| return 0; |
| } |
| |
| @Override |
| public IPath getPath() { |
| return null; |
| } |
| |
| @Override |
| public String toString() { |
| return getDescription(); |
| } |
| |
| }; |
| /* |
| * A HashSet that contains the IScriptProject whose buildpath is being |
| * resolved. |
| */ |
| private ThreadLocal<HashSet<IScriptProject>> buildpathsBeingResolved = new ThreadLocal<>(); |
| |
| public static class PerProjectInfo { |
| static final IModelStatus NEED_RESOLUTION = new ModelStatus(); |
| |
| public IProject project; |
| public Object savedState; |
| public boolean triedRead; |
| public IBuildpathEntry[] rawBuildpath; |
| public IModelStatus rawBuildpathStatus; |
| public int rawTimeStamp = 0; |
| public boolean writtingRawClasspath = false; |
| public IBuildpathEntry[] resolvedBuildpath; |
| public IModelStatus unresolvedEntryStatus; |
| // reverse map from a package fragment root's path to the raw entry |
| public Map<IPath, IBuildpathEntry> rootPathToRawEntries; |
| // map from a package fragment root's path to the resolved entry |
| public Map<IPath, IBuildpathEntry> rootPathToResolvedEntries; |
| |
| public IEclipsePreferences preferences; |
| public Hashtable options; |
| |
| public PerProjectInfo(IProject project) { |
| this.triedRead = false; |
| this.savedState = null; |
| this.project = project; |
| } |
| |
| public synchronized IBuildpathEntry[] getResolvedBuildpath() { |
| if (this.unresolvedEntryStatus == NEED_RESOLUTION) |
| return null; |
| return this.resolvedBuildpath; |
| } |
| |
| public void rememberExternalLibTimestamps() { |
| IBuildpathEntry[] buildpath = this.resolvedBuildpath; |
| if (buildpath == null) |
| return; |
| Map<IPath, Long> externalTimeStamps = ModelManager |
| .getModelManager().deltaState.getExternalLibTimeStamps(); |
| for (int i = 0, length = buildpath.length; i < length; i++) { |
| IBuildpathEntry entry = buildpath[i]; |
| if (entry.getEntryKind() == IBuildpathEntry.BPE_LIBRARY) { |
| IPath path = entry.getPath(); |
| if (externalTimeStamps.get(path) == null) { |
| Object target = Model.getTarget( |
| ResourcesPlugin.getWorkspace().getRoot(), path, |
| true); |
| if (target instanceof IFileHandle) { |
| long timestamp = DeltaProcessor |
| .getTimeStamp((IFileHandle) target); |
| externalTimeStamps.put(path, new Long(timestamp)); |
| } |
| } |
| } |
| } |
| Map<IPath, Long> customTimeStamps = ModelManager |
| .getModelManager().deltaState.getCustomTimeStamps(); |
| // Save custom project fragments timestamps. |
| try { |
| IScriptProject scriptProject = DLTKCore.create(project); |
| IProjectFragment[] fragments = scriptProject |
| .getAllProjectFragments(); |
| for (int i = 0; i < fragments.length; i++) { |
| if (fragments[i] instanceof IProjectFragmentTimestamp) { |
| IProjectFragmentTimestamp stamp = (IProjectFragmentTimestamp) fragments[i]; |
| long timeStamp = stamp.getTimeStamp(); |
| customTimeStamps.put(fragments[i].getPath(), |
| new Long(timeStamp)); |
| } |
| } |
| } catch (ModelException e) { |
| if (DLTKCore.DEBUG) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| |
| public synchronized BuildpathChange resetResolvedBuildpath() { |
| return setResolvedBuildpath(null, null, null, null, rawTimeStamp, |
| true); |
| } |
| |
| protected BuildpathChange addBuildpathChange() { |
| // remember old info |
| ModelManager manager = ModelManager.getModelManager(); |
| BuildpathChange buildpathChange = manager.deltaState |
| .addBuildpathChange(this.project, this.rawBuildpath, |
| this.resolvedBuildpath); |
| return buildpathChange; |
| } |
| |
| public synchronized BuildpathChange setRawBuildpath( |
| IBuildpathEntry[] newRawBuildpath, |
| IModelStatus newRawBuildpathStatus) { |
| this.rawTimeStamp++; |
| return setBuildpath(newRawBuildpath, newRawBuildpathStatus, |
| null/* resolved classpath */, |
| null/* rootPathToRawEntries */, |
| null/* rootPathToResolvedEntries */, |
| null/* unresolved status */, |
| true/* add classpath change */); |
| } |
| |
| public synchronized BuildpathChange setResolvedBuildpath( |
| IBuildpathEntry[] newResolvedClasspath, |
| Map<IPath, IBuildpathEntry> newRootPathToRawEntries, |
| Map<IPath, IBuildpathEntry> newRootPathToResolvedEntries, |
| IModelStatus newUnresolvedEntryStatus, int timeStamp, |
| boolean addBuildpathChange) { |
| if (this.rawTimeStamp != timeStamp) |
| return null; |
| return setBuildpath(this.rawBuildpath, this.rawBuildpathStatus, |
| newResolvedClasspath, newRootPathToRawEntries, |
| newRootPathToResolvedEntries, newUnresolvedEntryStatus, |
| addBuildpathChange); |
| } |
| |
| private BuildpathChange setBuildpath(IBuildpathEntry[] newRawBuildpath, |
| IModelStatus newRawBuildpathStatus, |
| IBuildpathEntry[] newResolvedBuildpath, |
| Map<IPath, IBuildpathEntry> newRootPathToRawEntries, |
| Map<IPath, IBuildpathEntry> newRootPathToResolvedEntries, |
| IModelStatus newUnresolvedEntryStatus, |
| boolean addBuildpathChange) { |
| BuildpathChange classpathChange = addBuildpathChange |
| ? addBuildpathChange() |
| : null; |
| this.rawBuildpath = newRawBuildpath; |
| this.rawBuildpathStatus = newRawBuildpathStatus; |
| this.resolvedBuildpath = newResolvedBuildpath; |
| this.rootPathToRawEntries = newRootPathToRawEntries; |
| this.rootPathToResolvedEntries = newRootPathToResolvedEntries; |
| this.unresolvedEntryStatus = newUnresolvedEntryStatus; |
| return classpathChange; |
| } |
| |
| /** |
| * Reads the buildpath and caches the entries. Returns an array of raw |
| * buildpath entries. |
| */ |
| public synchronized IBuildpathEntry[] readAndCacheBuildpath( |
| ScriptProject javaProject) { |
| // read file entries and update status |
| IBuildpathEntry[] classpath; |
| IModelStatus status; |
| try { |
| classpath = javaProject.readFileEntriesWithException( |
| null/* |
| * not interested in unknown elements |
| */); |
| status = ModelStatus.VERIFIED_OK; |
| } catch (CoreException e) { |
| classpath = ScriptProject.INVALID_BUILDPATH; |
| status = new ModelStatus( |
| IModelStatusConstants.INVALID_BUILDPATH_FILE_FORMAT, |
| Messages.bind( |
| Messages.buildpath_cannotReadBuildpathFile, |
| javaProject.getElementName())); |
| } catch (IOException e) { |
| classpath = ScriptProject.INVALID_BUILDPATH; |
| if (Messages.file_badFormat.equals(e.getMessage())) |
| status = new ModelStatus( |
| IModelStatusConstants.INVALID_BUILDPATH_FILE_FORMAT, |
| Messages.bind(Messages.buildpath_xmlFormatError, |
| javaProject.getElementName(), |
| Messages.file_badFormat)); |
| else |
| status = new ModelStatus( |
| IModelStatusConstants.INVALID_BUILDPATH_FILE_FORMAT, |
| Messages.bind( |
| Messages.buildpath_cannotReadBuildpathFile, |
| javaProject.getElementName())); |
| } catch (/* BuildpathEntry. */AssertionFailedException e) { |
| classpath = ScriptProject.INVALID_BUILDPATH; |
| status = new ModelStatus( |
| IModelStatusConstants.INVALID_BUILDPATH_FILE_FORMAT, |
| Messages.bind( |
| Messages.buildpath_illegalEntryInBuildpathFile, |
| new String[] { javaProject.getElementName(), |
| e.getMessage() })); |
| } |
| |
| // store new raw buildpath and new status, and null out |
| // resolved info |
| setRawBuildpath(classpath, status); |
| |
| return classpath; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.append("Info for "); //$NON-NLS-1$ |
| buffer.append(this.project.getFullPath()); |
| buffer.append("\nRaw buildpath:\n"); //$NON-NLS-1$ |
| if (this.rawBuildpath == null) { |
| buffer.append(" <null>\n"); //$NON-NLS-1$ |
| } else { |
| for (int i = 0, length = this.rawBuildpath.length; i < length; i++) { |
| buffer.append(" "); //$NON-NLS-1$ |
| buffer.append(this.rawBuildpath[i]); |
| buffer.append('\n'); |
| } |
| } |
| buffer.append("Resolved buildpath:\n"); //$NON-NLS-1$ |
| IBuildpathEntry[] resolvedCP = this.resolvedBuildpath; |
| if (resolvedCP == null) { |
| buffer.append(" <null>\n"); //$NON-NLS-1$ |
| } else { |
| for (int i = 0, length = resolvedCP.length; i < length; i++) { |
| buffer.append(" "); //$NON-NLS-1$ |
| buffer.append(resolvedCP[i]); |
| buffer.append('\n'); |
| } |
| } |
| return buffer.toString(); |
| } |
| |
| public boolean writeAndCacheBuildpath(ScriptProject scriptProject, |
| final IBuildpathEntry[] newRawBuildpath) throws ModelException { |
| try { |
| this.writtingRawClasspath = true; |
| |
| // write .classpath |
| if (!scriptProject.writeFileEntries(newRawBuildpath)) { |
| return false; |
| } |
| // store new raw classpath, new output and new status, and null |
| // out resolved info |
| setRawBuildpath(newRawBuildpath, ModelStatus.VERIFIED_OK); |
| } finally { |
| this.writtingRawClasspath = false; |
| } |
| return true; |
| } |
| |
| } |
| |
| public static class PerWorkingCopyInfo implements IProblemRequestor { |
| int useCount = 0; |
| IProblemRequestor problemRequestor; |
| ISourceModule workingCopy; |
| |
| public PerWorkingCopyInfo(ISourceModule workingCopy, |
| IProblemRequestor problemRequestor) { |
| this.workingCopy = workingCopy; |
| this.problemRequestor = problemRequestor; |
| } |
| |
| @Override |
| public void acceptProblem(IProblem problem) { |
| if (this.problemRequestor == null) |
| return; |
| this.problemRequestor.acceptProblem(problem); |
| } |
| |
| @Override |
| public void beginReporting() { |
| if (this.problemRequestor == null) |
| return; |
| this.problemRequestor.beginReporting(); |
| } |
| |
| @Override |
| public void endReporting() { |
| if (this.problemRequestor == null) |
| return; |
| this.problemRequestor.endReporting(); |
| } |
| |
| public ISourceModule getWorkingCopy() { |
| return this.workingCopy; |
| } |
| |
| @Override |
| public boolean isActive() { |
| return this.problemRequestor != null |
| && this.problemRequestor.isActive(); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.append("Info for "); //$NON-NLS-1$ |
| buffer.append( |
| ((ModelElement) this.workingCopy).toStringWithAncestors()); |
| buffer.append("\nUse count = "); //$NON-NLS-1$ |
| buffer.append(this.useCount); |
| buffer.append("\nProblem requestor:\n "); //$NON-NLS-1$ |
| buffer.append(this.problemRequestor); |
| return buffer.toString(); |
| } |
| } |
| |
| public static boolean VERBOSE = DLTKCore.VERBOSE_MODEL_MANAGER; |
| public static boolean BP_RESOLVE_VERBOSE = DLTKCore.VERBOSE_BP_RESOLVE; |
| public static final boolean BP_RESOLVE_VERBOSE_ADVANCED = false; |
| /** |
| * Name of the extension point for contributing buildpath variable |
| * initializers |
| */ |
| public static final String BPVARIABLE_INITIALIZER_EXTPOINT_ID = "buildpathVariableInitializer"; //$NON-NLS-1$ |
| |
| /** |
| * Name of the extension point for contributing buildpath container |
| * initializers |
| */ |
| public static final String BPCONTAINER_INITIALIZER_EXTPOINT_ID = "buildpathContainerInitializer"; //$NON-NLS-1$ |
| public static boolean ZIP_ACCESS_VERBOSE = DLTKCore.VERBOSE_ZIP_ACCESS; |
| /** |
| * A cache of opened zip files per thread. (for a given thread, the object |
| * value is a HashMap from IPath to java.io.ZipFile) |
| */ |
| private ThreadLocal<Map<IPath, IArchive>> zipFiles = new ThreadLocal<>(); |
| |
| private UserLibraryManager userLibraryManager; |
| |
| public final static ISourceModule[] NO_WORKING_COPY = new ISourceModule[0]; |
| /** |
| * The singleton manager |
| */ |
| private static ModelManager MANAGER = new ModelManager(); |
| /** |
| * Infos cache. |
| */ |
| public ModelCache cache;// = new ModelCache(); |
| /* |
| * Temporary cache of newly opened elements |
| */ |
| private ThreadLocal temporaryCache = new ThreadLocal(); |
| /** |
| * Set of elements which are out of sync with their buffers. |
| */ |
| protected HashSet<Openable> elementsOutOfSynchWithBuffers = new HashSet<>( |
| 11); |
| /** |
| * Holds the state used for delta processing. |
| */ |
| public DeltaProcessingState deltaState = new DeltaProcessingState(); |
| public IndexManager indexManager; |
| /** |
| * Table from IProject to PerProjectInfo. NOTE: this object itself is used |
| * as a lock to synchronize creation/removal of per project infos |
| */ |
| protected Map perProjectInfos = new HashMap(5); |
| /** |
| * Table from WorkingCopyOwner to a table of ISourceModule (working copy |
| * handle) to PerWorkingCopyInfo. NOTE: this object itself is used as a lock |
| * to synchronize creation/removal of per working copy infos |
| */ |
| protected Map perWorkingCopyInfos = new HashMap(5); |
| public final IEclipsePreferences[] preferencesLookup = new IEclipsePreferences[2]; |
| static final int PREF_INSTANCE = 0; |
| static final int PREF_DEFAULT = 1; |
| // Preferences |
| HashSet<String> optionNames = new HashSet<>(20); |
| Hashtable<String, String> optionsCache; |
| /* |
| * Pools of symbols used in the model. Used as a replacement for |
| * String#intern() that could prevent garbage collection of strings on some |
| * VMs. |
| */ |
| private WeakHashSet stringSymbols = new WeakHashSet(5); |
| Map workspaceScope = null; |
| public static final String DELTA_LISTENER_PERF = DLTKCore.PLUGIN_ID |
| + "/perf/deltalistener"; //$NON-NLS-1$ |
| |
| private ExternalFoldersManager externalFoldersManager = new ExternalFoldersManager(); |
| private DLTKCoreCache coreCache = null; |
| |
| final boolean resolveReferencedLibrariesForContainers = false; |
| |
| public IContentCache getCoreCache() { |
| if (coreCache == null) { |
| coreCache = new DLTKCoreCache(); |
| } |
| return coreCache; |
| } |
| |
| /** |
| * Listener on properties changes. |
| */ |
| private IEclipsePreferences.IPreferenceChangeListener propertyListener; |
| private IEclipsePreferences.IPreferenceChangeListener resourcesPropertyListener; |
| |
| /** |
| * Constructs a new ModelManager |
| */ |
| private ModelManager() { |
| // singleton: prevent others from creating a new instance |
| if (Platform.isRunning()) |
| this.indexManager = new IndexManager(); |
| } |
| |
| /** |
| * Returns the handle to the active script model. |
| */ |
| public final Model getModel() { |
| return this.model; |
| } |
| |
| /** |
| * Returns the singleton ModelManager |
| */ |
| public final static ModelManager getModelManager() { |
| return MANAGER; |
| } |
| |
| public static DeltaProcessingState getDeltaState() { |
| return MANAGER.deltaState; |
| } |
| |
| /** |
| * Returns the set of elements which are out of synch with their buffers. |
| */ |
| protected HashSet<Openable> getElementsOutOfSynchWithBuffers() { |
| return this.elementsOutOfSynchWithBuffers; |
| } |
| |
| /** |
| * Returns the info for the element. |
| */ |
| public synchronized Object getInfo(IModelElement element) { |
| HashMap tempCache = (HashMap) this.temporaryCache.get(); |
| if (tempCache != null) { |
| Object result = tempCache.get(element); |
| if (result != null) { |
| return result; |
| } |
| } |
| return this.cache.getInfo(element); |
| } |
| |
| /** |
| * Returns the info for this element without disturbing the cache ordering. |
| */ |
| protected synchronized Object peekAtInfo(IModelElement element) { |
| HashMap tempCache = (HashMap) this.temporaryCache.get(); |
| if (tempCache != null) { |
| Object result = tempCache.get(element); |
| if (result != null) { |
| return result; |
| } |
| } |
| return this.cache.peekAtInfo(element); |
| } |
| |
| /* |
| * Removes all cached info for the given element (including all children) |
| * from the cache. Returns the info for the given element, or null if it was |
| * closed. |
| */ |
| public synchronized Object removeInfoAndChildren(ModelElement element) |
| throws ModelException { |
| Object info = this.cache.peekAtInfo(element); |
| if (info != null) { |
| boolean wasVerbose = false; |
| try { |
| if (VERBOSE) { |
| String elementType; |
| switch (element.getElementType()) { |
| case IModelElement.SCRIPT_PROJECT: |
| elementType = "project"; //$NON-NLS-1$ |
| break; |
| case IModelElement.PROJECT_FRAGMENT: |
| elementType = "root"; //$NON-NLS-1$ |
| break; |
| case IModelElement.SCRIPT_FOLDER: |
| elementType = "folder"; //$NON-NLS-1$ |
| break; |
| case IModelElement.BINARY_MODULE: |
| elementType = "binary module"; //$NON-NLS-1$ |
| break; |
| case IModelElement.SOURCE_MODULE: |
| elementType = "source module"; //$NON-NLS-1$ |
| break; |
| default: |
| elementType = "element"; //$NON-NLS-1$ |
| } |
| System.out.println( |
| Thread.currentThread() + " CLOSING " + elementType //$NON-NLS-1$ |
| + " " + element.toStringWithAncestors()); //$NON-NLS-1$ |
| wasVerbose = true; |
| VERBOSE = false; |
| } |
| element.closing(info); |
| if (element instanceof IParent |
| && info instanceof ModelElementInfo) { |
| IModelElement[] children = ((ModelElementInfo) info) |
| .getChildren(); |
| for (int i = 0, size = children.length; i < size; ++i) { |
| ModelElement child = (ModelElement) children[i]; |
| child.close(); |
| } |
| } |
| this.cache.removeInfo(element); |
| if (wasVerbose) { |
| System.out.println(this.cache.toStringFillingRation("-> ")); //$NON-NLS-1$ |
| } |
| } finally { |
| ModelManager.VERBOSE = wasVerbose; |
| } |
| return info; |
| } |
| return null; |
| } |
| |
| /* |
| * Puts the infos in the given map (keys are IModelElements and values are |
| * ModelElementInfos) in the model cache in an atomic way. First checks that |
| * the info for the opened element (or one of its ancestors) has not been |
| * added to the cache. If it is the case, another thread has opened the |
| * element (or one of its ancestors). So returns without updating the cache. |
| */ |
| protected synchronized void putInfos(IModelElement openedElement, |
| Map newElements) { |
| // remove children |
| Object existingInfo = this.cache.peekAtInfo(openedElement); |
| if (openedElement instanceof IParent |
| && existingInfo instanceof ModelElementInfo) { |
| IModelElement[] children = ((ModelElementInfo) existingInfo) |
| .getChildren(); |
| for (int i = 0, size = children.length; i < size; ++i) { |
| ModelElement child = (ModelElement) children[i]; |
| try { |
| child.close(); |
| } catch (ModelException e) { |
| // ignore |
| } |
| } |
| } |
| // Need to put any ArchiveProjectFragment in first. |
| // This is due to the way the LRU cache flushes entries. |
| // When a BinaryFolder is flused from the LRU cache, the entire |
| // archive is flushed by removing the ArchiveProjectFragment and all of |
| // its |
| // children (see ElementCache.close()). If we flush the BinaryFolder |
| // when its ArchiveProjectFragment is not in the cache and the root is |
| // about to be |
| // added (during the 'while' loop), we will end up in an inconsist |
| // state. |
| // Subsequent resolution against package in the archive would fail as a |
| // result. |
| for (Iterator it = newElements.entrySet().iterator(); it.hasNext();) { |
| Map.Entry entry = (Map.Entry) it.next(); |
| IModelElement element = (IModelElement) entry.getKey(); |
| if (element instanceof ArchiveProjectFragment |
| || element instanceof ExternalScriptFolder) { |
| Object info = entry.getValue(); |
| it.remove(); |
| this.cache.putInfo(element, info); |
| } |
| } |
| Iterator iterator = newElements.keySet().iterator(); |
| while (iterator.hasNext()) { |
| IModelElement element = (IModelElement) iterator.next(); |
| Object info = newElements.get(element); |
| this.cache.putInfo(element, info); |
| } |
| } |
| |
| /** |
| * Returns the temporary cache for newly opened elements for the current |
| * thread. Creates it if not already created. |
| */ |
| public HashMap getTemporaryCache() { |
| HashMap result = (HashMap) this.temporaryCache.get(); |
| if (result == null) { |
| result = new HashMap(); |
| this.temporaryCache.set(result); |
| } |
| return result; |
| } |
| |
| /* |
| * Resets the temporary cache for newly created elements to null. |
| */ |
| public void resetTemporaryCache() { |
| this.temporaryCache.set(null); |
| } |
| |
| /* |
| * Returns whether there is a temporary cache for the current thread. |
| */ |
| public boolean hasTemporaryCache() { |
| return this.temporaryCache.get() != null; |
| } |
| |
| /** |
| * Returns the model element corresponding to the given file, its project |
| * being the given project. Returns <code>null</code> if unable to associate |
| * the given file with a model element. |
| * |
| * Creating a model element has the side effect of creating and opening all |
| * of the element's parents if they are not yet open. |
| */ |
| public static IModelElement create(IFile file, IScriptProject project) { |
| if (file == null) { |
| return null; |
| } |
| if (project == null) { |
| project = DLTKCore.create(file.getProject()); |
| } |
| // FIXME at the moment we can create source modules only |
| if (Util.isValidSourceModule(project, file)) { |
| return createSourceModuleFrom(file, project); |
| } |
| return null; |
| } |
| |
| /** |
| * Creating an element has the side effect of creating and opening all of |
| * the element's parents if they are not yet open. |
| */ |
| public static IModelElement create(IResource resource, |
| IScriptProject project) { |
| if (resource == null) { |
| return null; |
| } |
| int type = resource.getType(); |
| switch (type) { |
| case IResource.PROJECT: |
| return DLTKCore.create((IProject) resource); |
| case IResource.FILE: |
| return create((IFile) resource, project); |
| case IResource.FOLDER: |
| return create((IFolder) resource, project); |
| case IResource.ROOT: |
| return DLTKCore.create((IWorkspaceRoot) resource); |
| default: |
| return null; |
| } |
| } |
| |
| public static IModelElement create(IFolder folder, IScriptProject project) { |
| if (folder == null) { |
| return null; |
| } |
| IModelElement element; |
| if (project == null) { |
| project = DLTKCore.create(folder.getProject()); |
| element = determineIfOnBuildpath(folder, project); |
| if (element == null) { |
| // walk all projects and find one that have the given folder on |
| // its buildpath |
| IScriptProject[] projects; |
| try { |
| projects = ModelManager.getModelManager().getModel() |
| .getScriptProjects(); |
| } catch (ModelException e) { |
| return null; |
| } |
| for (int i = 0, length = projects.length; i < length; i++) { |
| project = projects[i]; |
| element = determineIfOnBuildpath(folder, project); |
| if (element != null) |
| break; |
| } |
| } |
| } else { |
| element = determineIfOnBuildpath(folder, project); |
| } |
| return element; |
| } |
| |
| /** |
| * Creates and returns a source module element for the given file, its |
| * project being the given project. Returns <code>null</code> if unable to |
| * recognize the source module. |
| */ |
| public static ISourceModule createSourceModuleFrom(IFile file, |
| IScriptProject project) { |
| if (file == null) |
| return null; |
| if (project == null) { |
| project = DLTKCore.create(file.getProject()); |
| } |
| IScriptFolder folder = (IScriptFolder) determineIfOnBuildpath(file, |
| project); |
| if (folder == null) { |
| // not on buildpath - make the root its folder |
| IProjectFragment root = project |
| .getProjectFragment(file.getParent()); |
| folder = root.getScriptFolder(Path.EMPTY); |
| if (VERBOSE) { |
| System.out.println( |
| "WARNING : creating module element outside buildpath (" //$NON-NLS-1$ |
| + Thread.currentThread() + "): " //$NON-NLS-1$ |
| + file.getFullPath()); |
| } |
| } |
| return folder.getSourceModule(file.getName()); |
| } |
| |
| /** |
| * Returns the project fragment root represented by the resource, or the |
| * folder the given resource is located in, or <code>null</code> if the |
| * given resource is not on the buildpath of the given project. |
| */ |
| public static IModelElement determineIfOnBuildpath(IResource resource, |
| IScriptProject project) { |
| IPath resourcePath = resource.getFullPath(); |
| try { |
| IBuildpathEntry[] entries = ((ScriptProject) project) |
| .getResourceOnlyResolvedBuildpath(); |
| |
| for (int i = 0; i < entries.length; i++) { |
| IBuildpathEntry entry = entries[i]; |
| if (entry.getEntryKind() == IBuildpathEntry.BPE_PROJECT) |
| continue; |
| IPath rootPath = entry.getPath(); |
| if (rootPath.equals(resourcePath)) { |
| return project.getProjectFragment(resource); |
| } else if (rootPath.isPrefixOf(resourcePath)) { |
| BuildpathEntry bpe = (BuildpathEntry) entry; |
| if (!Util.isExcluded(resource, |
| bpe.fullInclusionPatternChars(), |
| bpe.fullExclusionPatternChars())) { |
| /* |
| * given we have a resource child of the root, it cannot |
| * be a ZIP fragment |
| */ |
| IProjectFragment root = ((ScriptProject) project) |
| .getFolderProjectFragment(rootPath); |
| if (root == null) |
| return null; |
| IPath folderPath = resourcePath |
| .removeFirstSegments(rootPath.segmentCount()); |
| if (resource.getType() == IResource.FILE) { |
| /* |
| * if the resource is a file, then remove the last |
| * segment which is the file name in the folder |
| */ |
| folderPath = folderPath.removeLastSegments(1); |
| } |
| return root.getScriptFolder(folderPath); |
| } |
| } |
| } |
| } catch (ModelException npe) { |
| return null; |
| } |
| return null; |
| } |
| |
| /* |
| * Returns the per-project info for the given project. If specified, create |
| * the info if the info doesn't exist. |
| */ |
| public PerProjectInfo getPerProjectInfo(IProject project, boolean create) { |
| synchronized (this.perProjectInfos) { // use the perProjectInfo |
| // collection as its own lock |
| PerProjectInfo info = (PerProjectInfo) this.perProjectInfos |
| .get(project); |
| if (info == null && create) { |
| info = new PerProjectInfo(project); |
| this.perProjectInfos.put(project, info); |
| } |
| return info; |
| } |
| } |
| |
| /* |
| * Returns the per-project info for the given project. If the info doesn't |
| * exist, check for the project existence and create the info. @throws |
| * ModelException if the project doesn't exist. |
| */ |
| public PerProjectInfo getPerProjectInfoCheckExistence(IProject project) |
| throws ModelException { |
| ModelManager.PerProjectInfo info = getPerProjectInfo(project, |
| false /* |
| * don't create info |
| */); |
| if (info == null) { |
| if (!ScriptProject.hasScriptNature(project)) { |
| throw ((ScriptProject) DLTKCore.create(project)) |
| .newNotPresentException(); |
| } |
| info = getPerProjectInfo(project, true /* create info */); |
| } |
| return info; |
| } |
| |
| /* |
| * Returns the per-working copy info for the given working copy at the given |
| * path. If it doesn't exist and if create, add a new per-working copy info |
| * with the given problem requestor. If recordUsage, increment the |
| * per-working copy info's use count. Returns null if it doesn't exist and |
| * not create. |
| */ |
| public PerWorkingCopyInfo getPerWorkingCopyInfo(SourceModule workingCopy, |
| boolean create, boolean recordUsage, |
| IProblemRequestor problemRequestor) { |
| synchronized (this.perWorkingCopyInfos) { |
| // use the perWorkingCopyInfo collection as its own lock |
| WorkingCopyOwner owner = workingCopy.getOwner(); |
| Map workingCopyToInfos = (Map) this.perWorkingCopyInfos.get(owner); |
| if (workingCopyToInfos == null && create) { |
| workingCopyToInfos = new HashMap(); |
| this.perWorkingCopyInfos.put(owner, workingCopyToInfos); |
| } |
| PerWorkingCopyInfo info = workingCopyToInfos == null ? null |
| : (PerWorkingCopyInfo) workingCopyToInfos.get(workingCopy); |
| if (info == null && create) { |
| info = new PerWorkingCopyInfo(workingCopy, problemRequestor); |
| workingCopyToInfos.put(workingCopy, info); |
| } |
| if (info != null && recordUsage) |
| info.useCount++; |
| return info; |
| } |
| } |
| |
| /* |
| * Returns all the working copies which have the given owner. Adds the |
| * working copies of the primary owner if specified. Returns null if it has |
| * none. |
| */ |
| public ISourceModule[] getWorkingCopies(WorkingCopyOwner owner, |
| boolean addPrimary) { |
| synchronized (this.perWorkingCopyInfos) { |
| ISourceModule[] primaryWCs = addPrimary |
| && owner != DefaultWorkingCopyOwner.PRIMARY |
| ? getWorkingCopies(DefaultWorkingCopyOwner.PRIMARY, |
| false) |
| : null; |
| Map workingCopyToInfos = (Map) this.perWorkingCopyInfos.get(owner); |
| if (workingCopyToInfos == null) |
| return primaryWCs; |
| int primaryLength = primaryWCs == null ? 0 : primaryWCs.length; |
| int size = workingCopyToInfos.size(); // note size is > 0 |
| // otherwise |
| // pathToPerWorkingCopyInfos |
| // would be null |
| ISourceModule[] result = new ISourceModule[primaryLength + size]; |
| int index = 0; |
| if (primaryWCs != null) { |
| for (int i = 0; i < primaryLength; i++) { |
| ISourceModule primaryWorkingCopy = primaryWCs[i]; |
| boolean validSrcModule = false; |
| IResource res = primaryWorkingCopy.getResource(); |
| if (res != null) { |
| validSrcModule = Util |
| .isValidSourceModule(primaryWorkingCopy, res); |
| } else { |
| validSrcModule = Util.isValidSourceModule( |
| primaryWorkingCopy, |
| primaryWorkingCopy.getPath()); |
| } |
| if (validSrcModule) { |
| ISourceModule workingCopy = new SourceModule( |
| (ModelElement) primaryWorkingCopy.getParent(), |
| primaryWorkingCopy.getElementName(), owner); |
| if (!workingCopyToInfos.containsKey(workingCopy)) |
| result[index++] = primaryWorkingCopy; |
| } else { |
| System.err.println("Not valid primary working copy:" //$NON-NLS-1$ |
| + primaryWorkingCopy.getElementName()); |
| } |
| } |
| if (index != primaryLength) |
| System.arraycopy(result, 0, |
| result = new ISourceModule[index + size], 0, index); |
| } |
| Iterator iterator = workingCopyToInfos.values().iterator(); |
| while (iterator.hasNext()) { |
| result[index++] = ((ModelManager.PerWorkingCopyInfo) iterator |
| .next()).getWorkingCopy(); |
| } |
| return result; |
| } |
| } |
| |
| public DeltaProcessor getDeltaProcessor() { |
| return this.deltaState.getDeltaProcessor(); |
| } |
| |
| public static ExternalFoldersManager getExternalManager() { |
| return MANAGER.externalFoldersManager; |
| } |
| |
| public IndexManager getIndexManager() { |
| return this.indexManager; |
| } |
| |
| public Object getLastBuiltState(IProject project, |
| IProgressMonitor monitor) { |
| if (!DLTKLanguageManager.hasScriptNature(project)) { |
| if (ScriptBuilder.DEBUG) |
| System.out.println(project + " is not a Java project"); //$NON-NLS-1$ |
| return null; // should never be requested on non-Java projects |
| } |
| PerProjectInfo info = getPerProjectInfo(project, |
| true/* |
| * create if missing |
| */); |
| if (!info.triedRead) { |
| info.triedRead = true; |
| try { |
| if (monitor != null) |
| monitor.subTask( |
| Messages.bind(Messages.build_readStateProgress, |
| project.getName())); |
| info.savedState = readState(project); |
| } catch (CoreException e) { |
| e.printStackTrace(); |
| } |
| } |
| return info.savedState; |
| } |
| |
| public String getOption(String optionName) { |
| if (DLTKCore.CORE_ENCODING.equals(optionName)) { |
| return DLTKCore.getEncoding(); |
| } |
| String propertyName = optionName; |
| if (this.optionNames.contains(propertyName)) { |
| IPreferencesService service = Platform.getPreferencesService(); |
| String value = service.get(optionName, null, |
| this.preferencesLookup); |
| return value == null ? null : value.trim(); |
| } |
| return null; |
| } |
| |
| /** |
| * Get workpsace eclipse preference for ScriptCore plugin. |
| */ |
| public IEclipsePreferences getInstancePreferences() { |
| return preferencesLookup[PREF_INSTANCE]; |
| } |
| |
| public void setOptions(Hashtable newOptions) { |
| try { |
| IEclipsePreferences defaultPreferences = getDefaultPreferences(); |
| IEclipsePreferences instancePreferences = getInstancePreferences(); |
| if (newOptions == null) { |
| instancePreferences.clear(); |
| } else { |
| Enumeration keys = newOptions.keys(); |
| while (keys.hasMoreElements()) { |
| String key = (String) keys.nextElement(); |
| if (!this.optionNames.contains(key)) |
| continue; // unrecognized option |
| if (key.equals(DLTKCore.CORE_ENCODING)) |
| continue; // skipped, contributed by resource prefs |
| String value = (String) newOptions.get(key); |
| String defaultValue = defaultPreferences.get(key, null); |
| if (defaultValue != null && defaultValue.equals(value)) { |
| instancePreferences.remove(key); |
| } else { |
| instancePreferences.put(key, value); |
| } |
| } |
| } |
| // persist options |
| instancePreferences.flush(); |
| // update cache |
| this.optionsCache = newOptions == null ? null |
| : new Hashtable(newOptions); |
| } catch (BackingStoreException e) { |
| // ignore |
| } |
| } |
| |
| public Hashtable<String, String> getOptions() { |
| // return cached options if already computed |
| if (this.optionsCache != null) |
| return new Hashtable<>(this.optionsCache); |
| if (!Platform.isRunning()) { |
| return this.optionsCache = getDefaultOptionsNoInitialization(); |
| } |
| // init |
| Hashtable<String, String> options = new Hashtable<>(10); |
| IPreferencesService service = Platform.getPreferencesService(); |
| // set options using preferences service lookup |
| for (String propertyName : optionNames) { |
| String propertyValue = service.get(propertyName, null, |
| this.preferencesLookup); |
| if (propertyValue != null) { |
| options.put(propertyName, propertyValue); |
| } |
| } |
| // get encoding through resource plugin |
| options.put(DLTKCore.CORE_ENCODING, DLTKCore.getEncoding()); |
| // store built map in cache |
| this.optionsCache = new Hashtable<>(options); |
| // return built map |
| return options; |
| } |
| |
| /* |
| * Reset project options stored in info cache. |
| */ |
| public void resetProjectOptions(ScriptProject scriptProject) { |
| synchronized (this.perProjectInfos) { // use the perProjectInfo |
| // collection as its own lock |
| IProject project = scriptProject.getProject(); |
| PerProjectInfo info = (PerProjectInfo) this.perProjectInfos |
| .get(project); |
| if (info != null) { |
| info.options = null; |
| } |
| } |
| } |
| |
| /* |
| * Reset project preferences stored in info cache. |
| */ |
| public void resetProjectPreferences(ScriptProject scriptProject) { |
| synchronized (this.perProjectInfos) { // use the perProjectInfo |
| // collection as its own lock |
| IProject project = scriptProject.getProject(); |
| PerProjectInfo info = (PerProjectInfo) this.perProjectInfos |
| .get(project); |
| if (info != null) { |
| info.preferences = null; |
| } |
| } |
| } |
| |
| public void setBuildpathBeingResolved(IScriptProject project, |
| boolean buildpathIsResolved) { |
| if (buildpathIsResolved) { |
| getBuildpathBeingResolved().add(project); |
| } else { |
| getBuildpathBeingResolved().remove(project); |
| } |
| } |
| |
| private HashSet<IScriptProject> getBuildpathBeingResolved() { |
| HashSet<IScriptProject> result = this.buildpathsBeingResolved.get(); |
| if (result == null) { |
| result = new HashSet<>(); |
| this.buildpathsBeingResolved.set(result); |
| } |
| return result; |
| } |
| |
| public boolean isBuildpathBeingResolved(IScriptProject project) { |
| return getBuildpathBeingResolved().contains(project); |
| } |
| |
| /* |
| * Discards the per working copy info for the given working copy (making it |
| * a compilation unit) if its use count was 1. Otherwise, just decrement the |
| * use count. If the working copy is primary, computes the delta between its |
| * state and the original compilation unit and register it. Close the |
| * working copy, its buffer and remove it from the shared working copy |
| * table. Ignore if no per-working copy info existed. NOTE: it must NOT be |
| * synchronized as it may interact with the element info cache (if useCount |
| * is decremented to 0), see bug 50667. Returns the new use count (or -1 if |
| * it didn't exist). |
| */ |
| public int discardPerWorkingCopyInfo(SourceModule workingCopy) |
| throws ModelException { |
| // create the delta builder (this remembers the current content of the |
| // working copy) |
| // outside the perWorkingCopyInfos lock (see bug 50667) |
| ModelElementDeltaBuilder deltaBuilder = null; |
| if (workingCopy.isPrimary() && workingCopy.hasUnsavedChanges()) { |
| deltaBuilder = new ModelElementDeltaBuilder(workingCopy); |
| } |
| PerWorkingCopyInfo info = null; |
| synchronized (this.perWorkingCopyInfos) { |
| WorkingCopyOwner owner = workingCopy.getOwner(); |
| Map workingCopyToInfos = (Map) this.perWorkingCopyInfos.get(owner); |
| if (workingCopyToInfos == null) |
| return -1; |
| info = (PerWorkingCopyInfo) workingCopyToInfos.get(workingCopy); |
| if (info == null) |
| return -1; |
| if (--info.useCount == 0) { |
| // remove per working copy info |
| workingCopyToInfos.remove(workingCopy); |
| if (workingCopyToInfos.isEmpty()) { |
| this.perWorkingCopyInfos.remove(owner); |
| } |
| } |
| } |
| if (info.useCount == 0) { // info cannot be null here (check was done |
| // above) |
| // remove infos + close buffer (since no longer working copy) |
| // outside the perWorkingCopyInfos lock (see bug 50667) |
| removeInfoAndChildren(workingCopy); |
| workingCopy.closeBuffer(); |
| // compute the delta if needed and register it if there are changes |
| if (deltaBuilder != null) { |
| getSourceModuleInfoCache().remove(workingCopy); |
| deltaBuilder.buildDeltas(); |
| if ((deltaBuilder.delta != null) && (deltaBuilder.delta |
| .getAffectedChildren().length > 0)) { |
| getDeltaProcessor().registerModelDelta(deltaBuilder.delta); |
| } |
| } |
| } |
| return info.useCount; |
| } |
| |
| public synchronized String intern(String s) { |
| // make sure to copy the string (so that it doesn't hold on the |
| // underlying char[] that might be much bigger than necessary) |
| return (String) this.stringSymbols.add(s); |
| } |
| |
| public void startup() throws CoreException { |
| try { |
| // initialize Model model cache |
| this.cache = new ModelCache(); |
| // request state folder creation (workaround 19885) |
| DLTKCore.getPlugin().getStateLocation(); |
| // Initialize eclipse preferences |
| initializePreferences(); |
| // Listen to preference changes |
| this.propertyListener = event -> ModelManager.this.optionsCache = null; |
| installPreferenceChangeListener(DLTKCore.PLUGIN_ID, |
| this.propertyListener); |
| // listen for encoding changes (see |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=255501 ) |
| this.resourcesPropertyListener = event -> { |
| if (ResourcesPlugin.PREF_ENCODING.equals(event.getKey())) { |
| ModelManager.this.optionsCache = null; |
| } |
| }; |
| installPreferenceChangeListener( |
| ResourcesPlugin.getPlugin().getBundle().getSymbolicName(), |
| this.resourcesPropertyListener); |
| long start = -1; |
| if (VERBOSE) |
| start = System.currentTimeMillis(); |
| loadContainers(); |
| if (VERBOSE) |
| traceContainers("Loaded", start); //$NON-NLS-1$ |
| final IWorkspace workspace = ResourcesPlugin.getWorkspace(); |
| workspace.addResourceChangeListener(this.deltaState, |
| IResourceChangeEvent.PRE_BUILD |
| | IResourceChangeEvent.POST_BUILD |
| | IResourceChangeEvent.POST_CHANGE |
| | IResourceChangeEvent.PRE_DELETE |
| | IResourceChangeEvent.PRE_CLOSE); |
| DLTKContentTypeManager.installListener(); |
| sourceModuleInfoCache = new SourceModuleInfoCache(); |
| sourceModuleInfoCache.start(); |
| startIndexing(); |
| // process deltas since last activated in indexer thread so that |
| // indexes are up-to-date. |
| // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=38658 |
| Job processSavedState = new Job(Messages.savedState_jobName) { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| try { |
| // add save participant and process delta atomically |
| // see |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=59937 |
| workspace.run(progress -> { |
| ISavedState savedState = workspace |
| .addSaveParticipant(DLTKCore.getPlugin(), |
| ModelManager.this); |
| if (savedState != null) { |
| // the event type coming from the saved |
| // state is always POST_AUTO_BUILD |
| // force it to be POST_CHANGE so that the |
| // delta processor can handle it |
| ModelManager.this.deltaState |
| .getDeltaProcessor().overridenEventType = IResourceChangeEvent.POST_CHANGE; |
| savedState.processResourceChangeEvents( |
| ModelManager.this.deltaState); |
| } |
| }, monitor); |
| } catch (CoreException e) { |
| return e.getStatus(); |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| processSavedState.setSystem(true); |
| processSavedState.setPriority(Job.SHORT); // process asap |
| processSavedState.schedule(); |
| } catch (RuntimeException e) { |
| shutdown(); |
| throw e; |
| } |
| } |
| |
| private static IEclipsePreferences getPluginPreferences(String pluginId) { |
| return InstanceScope.INSTANCE.getNode(pluginId); |
| } |
| |
| private static void installPreferenceChangeListener(String pluginId, |
| IPreferenceChangeListener listener) { |
| getPluginPreferences(pluginId).addPreferenceChangeListener(listener); |
| } |
| |
| private void startIndexing() { |
| if (indexManager != null) { |
| indexManager.reset(); |
| // create contributed indexers in a job, so |
| // dltk.core initialization completes earlier. |
| final Job startIndexing = new Job("DLTK indexing initialization") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| ProjectIndexerManager.startIndexing(); |
| return Status.OK_STATUS; |
| } |
| }; |
| startIndexing.setSystem(true); |
| startIndexing.setPriority(Job.BUILD); |
| startIndexing.schedule(); |
| } |
| } |
| |
| /** |
| * Update the buildpath variable cache |
| */ |
| public static class EclipsePreferencesListener |
| implements IEclipsePreferences.IPreferenceChangeListener { |
| /** |
| * @see org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener#preferenceChange(org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent) |
| */ |
| @Override |
| public void preferenceChange( |
| IEclipsePreferences.PreferenceChangeEvent event) { |
| String propertyName = event.getKey(); |
| if (propertyName.startsWith(BP_CONTAINER_PREFERENCES_PREFIX)) { |
| recreatePersistedContainer(propertyName, |
| (String) event.getNewValue(), false); |
| } else if (propertyName |
| .startsWith(BP_USERLIBRARY_PREFERENCES_PREFIX)) { |
| String libName = propertyName |
| .substring(BP_USERLIBRARY_PREFERENCES_PREFIX.length()); |
| UserLibraryManager manager = ModelManager |
| .getUserLibraryManager(); |
| manager.updateUserLibrary(libName, |
| (String) event.getNewValue()); |
| } |
| } |
| } |
| |
| /** |
| * Reads the build state for the relevant project. |
| */ |
| protected Object readState(IProject project) throws CoreException { |
| File file = getSerializationFile(project); |
| if (file != null && file.exists()) { |
| try { |
| DataInputStream in = new DataInputStream( |
| new BufferedInputStream(new FileInputStream(file))); |
| try { |
| String pluginID = in.readUTF(); |
| if (!pluginID.equals(DLTKCore.PLUGIN_ID)) |
| throw new IOException(Messages.build_wrongFileFormat); |
| String kind = in.readUTF(); |
| if (!kind.equals("STATE")) //$NON-NLS-1$ |
| throw new IOException(Messages.build_wrongFileFormat); |
| if (in.readBoolean()) |
| return ScriptBuilder.readState(project, in); |
| if (ScriptBuilder.DEBUG) |
| System.out.println( |
| "Saved state thinks last build failed for " //$NON-NLS-1$ |
| + project.getName()); |
| } finally { |
| in.close(); |
| } |
| } catch (Exception e) { |
| e.printStackTrace(); |
| throw new CoreException(new Status(IStatus.ERROR, |
| DLTKCore.PLUGIN_ID, Platform.PLUGIN_ERROR, |
| "Error reading last build state for project " //$NON-NLS-1$ |
| + project.getName(), |
| e)); |
| } |
| } else if (ScriptBuilder.DEBUG) { |
| if (file == null) |
| System.out.println("Project does not exist: " + project); //$NON-NLS-1$ |
| else |
| System.out.println("Build state file " + file.getPath() //$NON-NLS-1$ |
| + " does not exist"); //$NON-NLS-1$ |
| } |
| return null; |
| } |
| |
| public static void recreatePersistedContainer(String propertyName, |
| String containerString, boolean addToContainerValues) { |
| int containerPrefixLength = BP_CONTAINER_PREFERENCES_PREFIX.length(); |
| int index = propertyName.indexOf('|', containerPrefixLength); |
| if (containerString != null) |
| containerString = containerString.trim(); |
| if (index > 0) { |
| String projectName = propertyName |
| .substring(containerPrefixLength, index).trim(); |
| IScriptProject project = getModelManager().getModel() |
| .getScriptProject(projectName); |
| IPath containerPath = new Path( |
| propertyName.substring(index + 1).trim()); |
| recreatePersistedContainer(project, containerPath, containerString, |
| addToContainerValues); |
| } |
| } |
| |
| private static void recreatePersistedContainer(final IScriptProject project, |
| final IPath containerPath, String containerString, |
| boolean addToContainerValues) { |
| if (!project.getProject().isAccessible()) |
| return; // avoid leaking deleted project's persisted container |
| if (containerString == null) { |
| getModelManager().containerPut(project, containerPath, null); |
| } else { |
| IBuildpathEntry[] entries; |
| try { |
| entries = ((ScriptProject) project).decodeBuildpath( |
| containerString, |
| null/* |
| * not interested in unknown elements |
| */); |
| } catch (IOException e) { |
| Util.log(e, "Could not recreate persisted container: \n" //$NON-NLS-1$ |
| + containerString); |
| entries = ScriptProject.INVALID_BUILDPATH; |
| } |
| if (entries != ScriptProject.INVALID_BUILDPATH) { |
| final IBuildpathEntry[] containerEntries = entries; |
| IBuildpathContainer container = new IBuildpathContainer() { |
| @Override |
| public IBuildpathEntry[] getBuildpathEntries() { |
| return containerEntries; |
| } |
| |
| @Override |
| public String getDescription() { |
| return "Persisted container [" + containerPath //$NON-NLS-1$ |
| + " for project [" + project.getElementName() //$NON-NLS-1$ |
| + "]"; //$NON-NLS-1$ |
| } |
| |
| @Override |
| public int getKind() { |
| return 0; |
| } |
| |
| @Override |
| public IPath getPath() { |
| return containerPath; |
| } |
| |
| @Override |
| public String toString() { |
| return getDescription(); |
| } |
| |
| }; |
| if (addToContainerValues) { |
| getModelManager().containerPut(project, containerPath, |
| container); |
| } |
| Map projectContainers = (Map) getModelManager().previousSessionContainers |
| .get(project); |
| if (projectContainers == null) { |
| projectContainers = new HashMap(1); |
| getModelManager().previousSessionContainers.put(project, |
| projectContainers); |
| } |
| projectContainers.put(containerPath, container); |
| } |
| } |
| } |
| |
| /** |
| * Initialize preferences lookups for DLTKCore plugin. |
| */ |
| public void initializePreferences() { |
| // Create lookups |
| preferencesLookup[PREF_INSTANCE] = InstanceScope.INSTANCE |
| .getNode(DLTKCore.PLUGIN_ID); |
| preferencesLookup[PREF_DEFAULT] = DefaultScope.INSTANCE |
| .getNode(DLTKCore.PLUGIN_ID); |
| // Listen to instance preferences node removal from parent in order to |
| // refresh stored one |
| IEclipsePreferences.INodeChangeListener listener = new IEclipsePreferences.INodeChangeListener() { |
| @Override |
| public void added(IEclipsePreferences.NodeChangeEvent event) { |
| // do nothing |
| } |
| |
| @Override |
| public void removed(IEclipsePreferences.NodeChangeEvent event) { |
| if (event.getChild() == preferencesLookup[PREF_INSTANCE]) { |
| preferencesLookup[PREF_INSTANCE] = InstanceScope.INSTANCE |
| .getNode(DLTKCore.PLUGIN_ID); |
| preferencesLookup[PREF_INSTANCE] |
| .addPreferenceChangeListener( |
| new EclipsePreferencesListener()); |
| } |
| } |
| }; |
| ((IEclipsePreferences) preferencesLookup[PREF_INSTANCE].parent()) |
| .addNodeChangeListener(listener); |
| preferencesLookup[PREF_INSTANCE] |
| .addPreferenceChangeListener(new EclipsePreferencesListener()); |
| // Listen to default preferences node removal from parent in order to |
| // refresh stored one |
| listener = new IEclipsePreferences.INodeChangeListener() { |
| @Override |
| public void added(IEclipsePreferences.NodeChangeEvent event) { |
| // do nothing |
| } |
| |
| @Override |
| public void removed(IEclipsePreferences.NodeChangeEvent event) { |
| if (event.getChild() == preferencesLookup[PREF_DEFAULT]) { |
| preferencesLookup[PREF_DEFAULT] = DefaultScope.INSTANCE |
| .getNode(DLTKCore.PLUGIN_ID); |
| } |
| } |
| }; |
| ((IEclipsePreferences) preferencesLookup[PREF_DEFAULT].parent()) |
| .addNodeChangeListener(listener); |
| } |
| |
| public void shutdown() { |
| final IEclipsePreferences preferences = getPluginPreferences( |
| DLTKCore.PLUGIN_ID); |
| try { |
| preferences.flush(); |
| } catch (BackingStoreException e) { |
| Util.log(e, "Could not save DLTKCore preferences"); //$NON-NLS-1$ |
| } |
| // Stop listening to preferences changes |
| preferences.removePreferenceChangeListener(this.propertyListener); |
| getPluginPreferences( |
| ResourcesPlugin.getPlugin().getBundle().getSymbolicName()) |
| .removePreferenceChangeListener( |
| this.resourcesPropertyListener); |
| |
| if (coreCache != null) { |
| coreCache.stop(); |
| } |
| IWorkspace workspace = ResourcesPlugin.getWorkspace(); |
| workspace.removeResourceChangeListener(this.deltaState); |
| DLTKContentTypeManager.uninstallListener(); |
| workspace.removeSaveParticipant(DLTKCore.PLUGIN_ID); |
| |
| if (sourceModuleInfoCache != null) { |
| sourceModuleInfoCache.stop(); |
| } |
| if (this.indexManager != null) { // no more indexing |
| this.indexManager.shutdown(); |
| } |
| // wait for the initialization job to finish |
| try { |
| Job.getJobManager().join(DLTKCore.PLUGIN_ID, null); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| } |
| |
| public void removePerProjectInfo(ScriptProject scriptProject) { |
| synchronized (this.perProjectInfos) { // use the perProjectInfo |
| // collection as its own lock |
| IProject project = scriptProject.getProject(); |
| PerProjectInfo info = (PerProjectInfo) this.perProjectInfos |
| .get(project); |
| if (info != null) { |
| this.perProjectInfos.remove(project); |
| } |
| } |
| } |
| |
| public synchronized IPath variableGet(String variableName) { |
| // check initialization in progress first |
| HashSet initializations = variableInitializationInProgress(); |
| if (initializations.contains(variableName)) { |
| return VARIABLE_INITIALIZATION_IN_PROGRESS; |
| } |
| return (IPath) this.variables.get(variableName); |
| } |
| |
| private synchronized IPath variableGetDefaultToPreviousSession( |
| String variableName) { |
| IPath variablePath = (IPath) this.variables.get(variableName); |
| if (variablePath == null) |
| return getPreviousSessionVariable(variableName); |
| return variablePath; |
| } |
| |
| /* |
| * Returns the set of variable names that are being initialized in the |
| * current thread. |
| */ |
| private HashSet variableInitializationInProgress() { |
| HashSet initializations = (HashSet) this.variableInitializationInProgress |
| .get(); |
| if (initializations == null) { |
| initializations = new HashSet(); |
| this.variableInitializationInProgress.set(initializations); |
| } |
| return initializations; |
| } |
| |
| public synchronized String[] variableNames() { |
| int length = this.variables.size(); |
| String[] result = new String[length]; |
| Iterator vars = this.variables.keySet().iterator(); |
| int index = 0; |
| while (vars.hasNext()) { |
| result[index++] = (String) vars.next(); |
| } |
| return result; |
| } |
| |
| public synchronized void variablePut(String variableName, |
| IPath variablePath) { |
| |
| // set/unset the initialization in progress |
| HashSet initializations = variableInitializationInProgress(); |
| if (variablePath == VARIABLE_INITIALIZATION_IN_PROGRESS) { |
| initializations.add(variableName); |
| |
| // do not write out intermediate initialization value |
| return; |
| } else { |
| initializations.remove(variableName); |
| |
| // update cache - do not only rely on listener refresh |
| if (variablePath == null) { |
| // if path is null, record that the variable was removed to |
| // avoid asking the initializer to initialize it again |
| // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=112609 |
| this.variables.put(variableName, BP_ENTRY_IGNORE_PATH); |
| // clean other variables caches |
| this.variablesWithInitializer.remove(variableName); |
| } else { |
| this.variables.put(variableName, variablePath); |
| } |
| // discard obsoleted information about previous session |
| this.previousSessionVariables.remove(variableName); |
| } |
| } |
| |
| public void variablePreferencesPut(String variableName, |
| IPath variablePath) { |
| String variableKey = BP_VARIABLE_PREFERENCES_PREFIX + variableName; |
| if (variablePath == null) { |
| getInstancePreferences().remove(variableKey); |
| } else { |
| getInstancePreferences().put(variableKey, variablePath.toString()); |
| } |
| try { |
| getInstancePreferences().flush(); |
| } catch (BackingStoreException e) { |
| // ignore exception |
| } |
| } |
| |
| /* |
| * Optimize startup case where 1 variable is initialized at a time with the |
| * same value as on shutdown. |
| */ |
| public boolean variablePutIfInitializingWithSameValue( |
| String[] variableNames, IPath[] variablePaths) { |
| if (variableNames.length != 1) |
| return false; |
| String variableName = variableNames[0]; |
| IPath oldPath = variableGetDefaultToPreviousSession(variableName); |
| if (oldPath == null) |
| return false; |
| IPath newPath = variablePaths[0]; |
| if (!oldPath.equals(newPath)) |
| return false; |
| variablePut(variableName, newPath); |
| return true; |
| } |
| |
| /** |
| * Sets the last built state for the given project, or null to reset it. |
| */ |
| public void setLastBuiltState(IProject project, Object state) { |
| if (DLTKLanguageManager.hasScriptNature(project)) { |
| // should never be requested on non-script projects |
| PerProjectInfo info = getPerProjectInfo(project, |
| true /* |
| * create if missing |
| */); |
| info.triedRead = true; // no point trying to re-read once using |
| // setter |
| info.savedState = state; |
| } |
| if (state == null) { // delete state file to ensure a full build |
| // happens if the workspace crashes |
| try { |
| File file = getSerializationFile(project); |
| if (file != null && file.exists()) |
| file.delete(); |
| } catch (SecurityException se) { |
| // could not delete file: cannot do much more |
| } |
| } |
| } |
| |
| /** |
| * Returns the File to use for saving and restoring the last built state for |
| * the given project. |
| */ |
| private File getSerializationFile(IProject project) { |
| if (!project.exists()) |
| return null; |
| IPath workingLocation = project.getWorkingLocation(DLTKCore.PLUGIN_ID); |
| return workingLocation.append("state.dat").toFile(); //$NON-NLS-1$ |
| } |
| |
| // If modified, also modify the method getDefaultOptionsNoInitialization() |
| public Hashtable getDefaultOptions() { |
| Hashtable defaultOptions = new Hashtable(10); |
| // see |
| // DLTKCorePreferenceInitializer#initializeDefaultPluginPreferences() |
| // for changing default settings |
| // If modified, also modify the method |
| // getDefaultOptionsNoInitialization() |
| IEclipsePreferences defaultPreferences = getDefaultPreferences(); |
| // initialize preferences to their default |
| Iterator iterator = this.optionNames.iterator(); |
| while (iterator.hasNext()) { |
| String propertyName = (String) iterator.next(); |
| String value = defaultPreferences.get(propertyName, null); |
| if (value != null) |
| defaultOptions.put(propertyName, value); |
| } |
| // get encoding through resource plugin |
| defaultOptions.put(DLTKCore.CORE_ENCODING, DLTKCore.getEncoding()); |
| return defaultOptions; |
| } |
| |
| /** |
| * Get default eclipse preference for DLTKCore plugin. |
| */ |
| public IEclipsePreferences getDefaultPreferences() { |
| return preferencesLookup[PREF_DEFAULT]; |
| } |
| |
| // Do not modify without modifying getDefaultOptions() |
| private Hashtable<String, String> getDefaultOptionsNoInitialization() { |
| System.err.println( |
| "Add language dependent compiler options. Or implement it in another whan in DLTK way..."); //$NON-NLS-1$ |
| Map defaultOptionsMap = new HashMap(); // compiler defaults |
| return new Hashtable(defaultOptionsMap); |
| } |
| |
| public IBuildpathContainer getBuildpathContainer(IPath containerPath, |
| IScriptProject project) throws ModelException { |
| IBuildpathContainer container = containerGet(project, containerPath); |
| if (container == null) { |
| if (this.batchContainerInitializations) { |
| // avoid deep recursion while initializaing container on |
| // workspace restart |
| // (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=60437) |
| this.batchContainerInitializations = false; |
| return initializeAllContainers(project, containerPath); |
| } |
| return initializeContainer(project, containerPath); |
| } |
| return container; |
| } |
| |
| public synchronized IBuildpathContainer containerGet(IScriptProject project, |
| IPath containerPath) { |
| // check initialization in progress first |
| HashSet projectInitializations = containerInitializationInProgress( |
| project); |
| if (projectInitializations.contains(containerPath)) { |
| return CONTAINER_INITIALIZATION_IN_PROGRESS; |
| } |
| Map projectContainers = (Map) this.containers.get(project); |
| if (projectContainers == null) { |
| return null; |
| } |
| IBuildpathContainer container = (IBuildpathContainer) projectContainers |
| .get(containerPath); |
| return container; |
| } |
| |
| /* |
| * Returns the set of container paths for the given project that are being |
| * initialized in the current thread. |
| */ |
| private HashSet containerInitializationInProgress(IScriptProject project) { |
| Map initializations = (Map) this.containerInitializationInProgress |
| .get(); |
| if (initializations == null) { |
| initializations = new HashMap(); |
| this.containerInitializationInProgress.set(initializations); |
| } |
| HashSet projectInitializations = (HashSet) initializations.get(project); |
| if (projectInitializations == null) { |
| projectInitializations = new HashSet(); |
| initializations.put(project, projectInitializations); |
| } |
| return projectInitializations; |
| } |
| |
| /* |
| * Initialize all container at the same time as the given container. Return |
| * the container for the given path and project. |
| */ |
| private IBuildpathContainer initializeAllContainers( |
| IScriptProject scriptProjectToInit, IPath containerToInit) |
| throws ModelException { |
| /* |
| * if (BP_RESOLVE_VERBOSE) { Util.verbose( "CPContainer INIT - batching |
| * containers initialization\n" + //$NON-NLS-1$ " project to init: " + |
| * scriptProjectToInit.getElementName() + '\n' + //$NON-NLS-1$ " |
| * container path to init: " + containerToInit); //$NON-NLS-1$ } |
| */ |
| // collect all container paths |
| final HashMap allContainerPaths = new HashMap(); |
| IProject[] projects = ResourcesPlugin.getWorkspace().getRoot() |
| .getProjects(); |
| for (int i = 0, length = projects.length; i < length; i++) { |
| IProject project = projects[i]; |
| if (!DLTKLanguageManager.hasScriptNature(project)) |
| continue; |
| ScriptProject scriptProject = new ScriptProject(project, |
| getModel()); |
| HashSet paths = null; |
| IBuildpathEntry[] rawBuildpath = scriptProject.getRawBuildpath(); |
| for (int j = 0, length2 = rawBuildpath.length; j < length2; j++) { |
| IBuildpathEntry entry = rawBuildpath[j]; |
| IPath path = entry.getPath(); |
| if (entry.getEntryKind() == IBuildpathEntry.BPE_CONTAINER |
| && containerGet(scriptProject, path) == null) { |
| if (paths == null) { |
| paths = new HashSet(); |
| allContainerPaths.put(scriptProject, paths); |
| } |
| paths.add(path); |
| } |
| } |
| } |
| HashSet containerPaths = (HashSet) allContainerPaths |
| .get(scriptProjectToInit); |
| if (containerPaths == null) { |
| containerPaths = new HashSet(); |
| allContainerPaths.put(scriptProjectToInit, containerPaths); |
| } |
| containerPaths.add(containerToInit); |
| // end block |
| // mark all containers as being initialized |
| this.containerInitializationInProgress.set(allContainerPaths); |
| // initialize all containers |
| boolean ok = false; |
| try { |
| // if possible run inside an IWokspaceRunnable with AVOID_UPATE to |
| // avoid unwanted builds |
| // (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=118507) |
| IWorkspaceRunnable runnable = monitor -> { |
| Set keys = allContainerPaths.keySet(); |
| int length = keys.size(); |
| IScriptProject[] scriptProjects = new IScriptProject[length]; // clone |
| // as |
| // the |
| // following |
| // will |
| // have |
| // a |
| // side |
| // effect |
| keys.toArray(scriptProjects); |
| for (int i = 0; i < length; i++) { |
| IScriptProject scriptProject = scriptProjects[i]; |
| HashSet pathSet = (HashSet) allContainerPaths |
| .get(scriptProject); |
| if (pathSet == null) |
| continue; |
| int length2 = pathSet.size(); |
| IPath[] paths = new IPath[length2]; |
| pathSet.toArray(paths); // clone as the following will |
| // have a side effect |
| for (int j = 0; j < length2; j++) { |
| IPath path = paths[j]; |
| initializeContainer(scriptProject, path); |
| } |
| } |
| }; |
| IWorkspace workspace = ResourcesPlugin.getWorkspace(); |
| if (workspace.isTreeLocked()) |
| runnable.run(null/* no progress available */); |
| else |
| workspace.run(runnable, null/* don't take any lock */, |
| IWorkspace.AVOID_UPDATE, |
| null/* |
| * no progress available here |
| */); |
| ok = true; |
| } catch (CoreException e) { |
| // ignore |
| System.err.println("Exception while initializing all containers"); //$NON-NLS-1$ |
| // Util.log(e, "Exception while initializing all containers"); |
| // //$NON-NLS-1$ |
| } finally { |
| if (!ok) { |
| // if we're being traversed by an exception, ensure that that |
| // containers are |
| // no longer marked as initialization in progress |
| // (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=66437) |
| this.containerInitializationInProgress.set(null); |
| } |
| } |
| return containerGet(scriptProjectToInit, containerToInit); |
| } |
| |
| IBuildpathContainer initializeContainer(IScriptProject project, |
| IPath containerPath) throws ModelException { |
| IBuildpathContainer container = null; |
| final BuildpathContainerInitializer initializer = DLTKCore |
| .getBuildpathContainerInitializer(containerPath.segment(0)); |
| if (initializer != null) { |
| if (BP_RESOLVE_VERBOSE) { |
| verbose_triggering_container_initialization(project, |
| containerPath, initializer); |
| } |
| // PerformanceStats stats = null; |
| containerPut(project, containerPath, |
| CONTAINER_INITIALIZATION_IN_PROGRESS); // avoid |
| // initialization |
| // cycles |
| boolean ok = false; |
| try { |
| // let OperationCanceledException go through |
| // (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=59363) |
| initializer.initialize(containerPath, project); |
| // retrieve value (if initialization was successful) |
| container = containerGet(project, containerPath); |
| if (container == CONTAINER_INITIALIZATION_IN_PROGRESS) |
| return null; // break cycle |
| ok = true; |
| } catch (CoreException e) { |
| if (e instanceof ModelException) { |
| throw (ModelException) e; |
| } else { |
| throw new ModelException(e); |
| } |
| } catch (RuntimeException e) { |
| if (ModelManager.BP_RESOLVE_VERBOSE) { |
| e.printStackTrace(); |
| } |
| throw e; |
| } catch (Error e) { |
| if (ModelManager.BP_RESOLVE_VERBOSE) { |
| e.printStackTrace(); |
| } |
| throw e; |
| } finally { |
| if (!ok) { |
| // just remove initialization in progress and keep previous |
| // session container so as to avoid a full build |
| // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=92588 |
| containerRemoveInitializationInProgress(project, |
| containerPath); |
| if (BP_RESOLVE_VERBOSE) { |
| verbose_container_initialization_failed(project, |
| containerPath, container, initializer); |
| } |
| } |
| } |
| if (BP_RESOLVE_VERBOSE) { |
| verbose_container_value_after_initialization(project, |
| containerPath, container); |
| } |
| } else { |
| if (BP_RESOLVE_VERBOSE) { |
| Util.verbose("CPContainer INIT - no initializer found\n" + //$NON-NLS-1$ |
| " project: " + project.getElementName() + '\n' + //$NON-NLS-1$ |
| " container path: " + containerPath); //$NON-NLS-1$ |
| } |
| } |
| return container; |
| } |
| |
| private void verbose_triggering_container_initialization( |
| IScriptProject project, IPath containerPath, |
| final BuildpathContainerInitializer initializer) { |
| Util.verbose("BPContainer INIT - triggering initialization\n" + //$NON-NLS-1$ |
| " project: " + project.getElementName() + '\n' + //$NON-NLS-1$ |
| " container path: " + containerPath + '\n' + //$NON-NLS-1$ |
| " initializer: " + initializer + '\n' + //$NON-NLS-1$ |
| " invocation stack trace:"); //$NON-NLS-1$ |
| new Exception("<Fake exception>").printStackTrace(System.out); //$NON-NLS-1$ |
| } |
| |
| private void verbose_container_value_after_initialization( |
| IScriptProject project, IPath containerPath, |
| IBuildpathContainer container) { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.append("CPContainer INIT - after resolution\n"); //$NON-NLS-1$ |
| buffer.append(" project: " + project.getElementName() + '\n'); //$NON-NLS-1$ |
| buffer.append(" container path: " + containerPath + '\n'); //$NON-NLS-1$ |
| if (container != null) { |
| buffer.append(" container: " + container.getDescription() + " {\n"); //$NON-NLS-2$//$NON-NLS-1$ |
| IBuildpathEntry[] entries = container.getBuildpathEntries(); |
| if (entries != null) { |
| for (int i = 0; i < entries.length; i++) { |
| buffer.append(" " + entries[i] + '\n'); //$NON-NLS-1$ |
| } |
| } |
| buffer.append(" }");//$NON-NLS-1$ |
| } else { |
| buffer.append(" container: {unbound}");//$NON-NLS-1$ |
| } |
| Util.verbose(buffer.toString()); |
| } |
| |
| private void verbose_container_initialization_failed(IScriptProject project, |
| IPath containerPath, IBuildpathContainer container, |
| final BuildpathContainerInitializer initializer) { |
| if (container == CONTAINER_INITIALIZATION_IN_PROGRESS) { |
| Util.verbose( |
| "CPContainer INIT - FAILED (initializer did not initialize container)\n" //$NON-NLS-1$ |
| + " project: " + project.getElementName() + '\n' |
| + " container path: " //$NON-NLS-1$ |
| + containerPath + '\n' + " initializer: " //$NON-NLS-1$ |
| + initializer); |
| } else { |
| Util.verbose("CPContainer INIT - FAILED (see exception above)\n" + //$NON-NLS-1$ |
| " project: " //$NON-NLS-1$ |
| + project.getElementName() + '\n' + " container path: " //$NON-NLS-1$ |
| + containerPath + '\n' + " initializer: " + initializer); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Returns a persisted container from previous session if any. Note that it |
| * is not the original container from previous session (i.e. it did not get |
| * serialized) but rather a summary of its entries recreated for CP |
| * initialization purpose. As such it should not be stored into container |
| * caches. |
| */ |
| public IBuildpathContainer getPreviousSessionContainer(IPath containerPath, |
| IScriptProject project) { |
| Map previousContainerValues = (Map) this.previousSessionContainers |
| .get(project); |
| if (previousContainerValues != null) { |
| IBuildpathContainer previousContainer = (IBuildpathContainer) previousContainerValues |
| .get(containerPath); |
| if (previousContainer != null) { |
| if (ModelManager.BP_RESOLVE_VERBOSE) { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.append( |
| "CPContainer INIT - reentering access to project container during its initialization, will see previous value\n"); //$NON-NLS-1$ |
| buffer.append( |
| " project: " + project.getElementName() + '\n'); //$NON-NLS-1$ |
| buffer.append(" container path: " + containerPath + '\n'); //$NON-NLS-1$ |
| buffer.append(" previous value: "); //$NON-NLS-1$ |
| buffer.append(previousContainer.getDescription()); |
| buffer.append(" {\n"); //$NON-NLS-1$ |
| IBuildpathEntry[] entries = previousContainer |
| .getBuildpathEntries(); |
| if (entries != null) { |
| for (int j = 0; j < entries.length; j++) { |
| buffer.append(" "); //$NON-NLS-1$ |
| buffer.append(entries[j]); |
| buffer.append('\n'); |
| } |
| } |
| buffer.append(" }"); //$NON-NLS-1$ |
| Util.verbose(buffer.toString()); |
| new Exception("<Fake exception>") //$NON-NLS-1$ |
| .printStackTrace(System.out); |
| } |
| return previousContainer; |
| } |
| } |
| return null; // break cycle if none found |
| } |
| |
| /** |
| * Returns a persisted container from previous session if any |
| */ |
| public IPath getPreviousSessionVariable(String variableName) { |
| IPath previousPath = (IPath) this.previousSessionVariables |
| .get(variableName); |
| if (previousPath != null) { |
| // if (BP_RESOLVE_VERBOSE_ADVANCED) |
| // verbose_reentering_variable_access(variableName, previousPath); |
| return previousPath; |
| } |
| return null; // break cycle |
| } |
| |
| public synchronized void containerPut(IScriptProject project, |
| IPath containerPath, IBuildpathContainer container) { |
| // set/unset the initialization in progress |
| if (container == CONTAINER_INITIALIZATION_IN_PROGRESS) { |
| HashSet projectInitializations = containerInitializationInProgress( |
| project); |
| projectInitializations.add(containerPath); |
| // do not write out intermediate initialization value |
| return; |
| } else { |
| containerRemoveInitializationInProgress(project, containerPath); |
| Map projectContainers = (Map) this.containers.get(project); |
| if (projectContainers == null) { |
| projectContainers = new HashMap(1); |
| this.containers.put(project, projectContainers); |
| } |
| if (container == null) { |
| projectContainers.remove(containerPath); |
| } else { |
| projectContainers.put(containerPath, container); |
| } |
| // discard obsoleted information about previous session |
| Map previousContainers = (Map) this.previousSessionContainers |
| .get(project); |
| if (previousContainers != null) { |
| previousContainers.remove(containerPath); |
| } |
| } |
| // container values are persisted in preferences during save operations, |
| // see #saving(ISaveContext) |
| } |
| |
| /* |
| * The given project is being removed. Remove all containers for this |
| * project from the cache. |
| */ |
| public synchronized void containerRemove(IScriptProject project) { |
| Map initializations = (Map) this.containerInitializationInProgress |
| .get(); |
| if (initializations != null) { |
| initializations.remove(project); |
| } |
| this.containers.remove(project); |
| } |
| |
| void verbose_missbehaving_container(IScriptProject project, |
| IPath containerPath, IBuildpathEntry[] classpathEntries) { |
| Util.verbose( |
| "CPContainer GET - missbehaving container (returning null classpath entry)\n" //$NON-NLS-1$ |
| + " project: " + project.getElementName() + '\n' |
| + " container path: " + containerPath + '\n' |
| + " classpath entries: {\n" |
| + Util.toString(classpathEntries, o -> { |
| StringBuffer buffer = new StringBuffer(" "); //$NON-NLS-1$ |
| if (o == null) { |
| buffer.append("<null>"); //$NON-NLS-1$ |
| return buffer.toString(); |
| } |
| buffer.append(o); |
| return buffer.toString(); |
| }) + "\n }" //$NON-NLS-1$ |
| ); |
| } |
| |
| void verbose_missbehaving_container_null_entries(IScriptProject project, |
| IPath containerPath) { |
| Util.verbose( |
| "CPContainer GET - missbehaving container (returning null as classpath entries)\n" //$NON-NLS-1$ |
| + " project: " + project.getElementName() + '\n' |
| + " container path: " + containerPath + '\n' |
| + " classpath entries: <null>"); |
| } |
| |
| private void containerRemoveInitializationInProgress(IScriptProject project, |
| IPath containerPath) { |
| HashSet projectInitializations = containerInitializationInProgress( |
| project); |
| projectInitializations.remove(containerPath); |
| if (projectInitializations.size() == 0) { |
| Map initializations = (Map) this.containerInitializationInProgress |
| .get(); |
| initializations.remove(project); |
| } |
| } |
| |
| /* |
| * Optimize startup case where a container for 1 project is initialized at a |
| * time with the same entries as on shutdown. |
| */ |
| public boolean containerPutIfInitializingWithSameEntries( |
| IPath containerPath, IScriptProject[] projects, |
| IBuildpathContainer[] respectiveContainers) { |
| int projectLength = projects.length; |
| if (projectLength != 1) |
| return false; |
| final IBuildpathContainer container = respectiveContainers[0]; |
| if (container == null) |
| return false; |
| final IScriptProject project = projects[0]; |
| if (!containerInitializationInProgress(project).contains(containerPath)) |
| return false; |
| IBuildpathContainer previousSessionContainer = getPreviousSessionContainer( |
| containerPath, project); |
| final IBuildpathEntry[] newEntries = container.getBuildpathEntries(); |
| if (previousSessionContainer == null) |
| if (newEntries.length == 0) { |
| containerPut(project, containerPath, container); |
| return true; |
| } else { |
| return false; |
| } |
| final IBuildpathEntry[] oldEntries = previousSessionContainer |
| .getBuildpathEntries(); |
| if (oldEntries.length != newEntries.length) |
| return false; |
| for (int i = 0, length = newEntries.length; i < length; i++) { |
| if (!newEntries[i].equals(oldEntries[i])) { |
| if (BP_RESOLVE_VERBOSE) { |
| Util.verbose("CPContainer SET - missbehaving container\n" + //$NON-NLS-1$ |
| " container path: " //$NON-NLS-1$ |
| + containerPath + '\n' + " projects: {" //$NON-NLS-1$ |
| + Util.toString(projects, |
| o -> ((IScriptProject) o).getElementName()) |
| + "}\n values on previous session: {\n" + //$NON-NLS-1$ |
| Util.toString(respectiveContainers, o -> { |
| StringBuffer buffer = new StringBuffer( |
| " "); //$NON-NLS-1$ |
| if (o == null) { |
| buffer.append("<null>"); //$NON-NLS-1$ |
| return buffer.toString(); |
| } |
| buffer.append(container.getDescription()); |
| buffer.append(" {\n"); //$NON-NLS-1$ |
| for (int j = 0; j < oldEntries.length; j++) { |
| buffer.append(" "); //$NON-NLS-1$ |
| buffer.append(oldEntries[j]); |
| buffer.append('\n'); |
| } |
| buffer.append(" }"); //$NON-NLS-1$ |
| return buffer.toString(); |
| }) + "}\n new values: {\n" + //$NON-NLS-1$ |
| Util.toString(respectiveContainers, o -> { |
| StringBuffer buffer = new StringBuffer( |
| " "); //$NON-NLS-1$ |
| if (o == null) { |
| buffer.append("<null>"); //$NON-NLS-1$ |
| return buffer.toString(); |
| } |
| buffer.append(container.getDescription()); |
| buffer.append(" {\n"); //$NON-NLS-1$ |
| for (int j = 0; j < newEntries.length; j++) { |
| buffer.append(" "); //$NON-NLS-1$ |
| buffer.append(newEntries[j]); |
| buffer.append('\n'); |
| } |
| buffer.append(" }"); //$NON-NLS-1$ |
| return buffer.toString(); |
| }) + "\n }"); //$NON-NLS-1$ |
| } |
| return false; |
| } |
| } |
| containerPut(project, containerPath, container); |
| return true; |
| } |
| |
| /** |
| * Returns the open ZipFile at the given path. If the ZipFile does not yet |
| * exist, it is created, opened, and added to the cache of open ZipFiles. |
| * |
| * NOTE: closeZipFile() must be called for the resulting ZipFile, when the |
| * client is done using it. |
| * |
| * The path must be a file system path if representing an external zip, or |
| * it must be an absolute workspace relative path if representing a zip |
| * inside the workspace. |
| * |
| * @param archiveProjectFragment |
| * |
| * @exception CoreException |
| * If unable to create/open the ZipFile |
| */ |
| public IArchive getArchive(IPath path, |
| IProjectFragment archiveProjectFragment) throws CoreException { |
| Map<IPath, IArchive> map; |
| IArchive zipFile; |
| if ((map = this.zipFiles.get()) != null |
| && (zipFile = map.get(path)) != null) { |
| return zipFile; |
| } |
| File localFile = null; |
| IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); |
| IResource file = root.findMember(path); |
| if (file != null) { |
| // internal resource |
| URI location; |
| if (file.getType() != IResource.FILE |
| || (location = file.getLocationURI()) == null) { |
| throw new CoreException(new Status(IStatus.ERROR, |
| DLTKCore.PLUGIN_ID, -1, |
| Messages.bind(Messages.file_notFound, path.toString()), |
| null)); |
| } |
| localFile = Util.toLocalFile(location, |
| null/* |
| * no progress availaible |
| */); |
| if (localFile == null) |
| throw new CoreException(new Status(IStatus.ERROR, |
| DLTKCore.PLUGIN_ID, -1, |
| Messages.bind(Messages.file_notFound, path.toString()), |
| null)); |
| } else { |
| // external resource -> it is ok to use toFile() |
| if (EnvironmentPathUtils.isFull(path)) { |
| path = EnvironmentPathUtils.getLocalPath(path); |
| } |
| localFile = path.toFile(); |
| } |
| try { |
| if (ZIP_ACCESS_VERBOSE) { |
| System.out.println("(" + Thread.currentThread() //$NON-NLS-1$ |
| + ") [ModelManager.getZipFile(IPath)] Creating ZipFile on " //$NON-NLS-1$ |
| + localFile); |
| } |
| zipFile = openArchive(archiveProjectFragment, localFile); |
| |
| if (map != null) { |
| map.put(path, zipFile); |
| } |
| return zipFile; |
| } catch (IOException e) { |
| throw new CoreException(new Status(IStatus.ERROR, |
| DLTKCore.PLUGIN_ID, -1, Messages.status_IOException, e)); |
| } |
| } |
| |
| public IArchive openArchive(IProjectFragment archiveProjectFragment, |
| File localFile) throws IOException { |
| final IDLTKLanguageToolkit toolkit = DLTKLanguageManager |
| .getLanguageToolkit(archiveProjectFragment); |
| if (toolkit != null) { |
| return toolkit.openArchive(localFile); |
| } else { |
| return new ZipArchiveFile(localFile); |
| } |
| } |
| |
| /** |
| * Starts caching ZipFiles. Ignores if there are already clients. |
| */ |
| public void cacheZipFiles() { |
| if (this.zipFiles.get() != null) |
| return; |
| this.zipFiles.set(new HashMap<IPath, IArchive>()); |
| } |
| |
| public void closeArchive(IArchive zipFile) { |
| if (zipFile == null) |
| return; |
| if (this.zipFiles.get() != null) { |
| return; // zip file will be closed by call to flushZipFiles |
| } |
| try { |
| if (ModelManager.ZIP_ACCESS_VERBOSE) { |
| System.out.println("(" + Thread.currentThread() //$NON-NLS-1$ |
| + ") [ModelManager.closeZipFile(ZipFile)] Closing ZipFile on " //$NON-NLS-1$ |
| + zipFile.getName()); |
| } |
| zipFile.close(); |
| } catch (IOException e) { |
| // problem occured closing zip file: cannot do much more |
| } |
| } |
| |
| @Override |
| public void doneSaving(ISaveContext context) { |
| // nothing |
| } |
| |
| @Override |
| public void prepareToSave(ISaveContext context) throws CoreException { |
| // TODO Auto-generated method stub |
| } |
| |
| @Override |
| public void rollback(ISaveContext context) { |
| // TODO Auto-generated method stub |
| } |
| |
| private void traceContainers(String action, long start) { |
| Long delta = System.currentTimeMillis() - start; |
| Long length = getContainersFile().length(); |
| String pattern = "{0} {1} bytes in containers.dat in {2}ms"; //$NON-NLS-1$ |
| String message = NLS.bind(pattern, |
| new Object[] { action, length, delta }); |
| System.out.println(message); |
| } |
| |
| @Override |
| public void saving(ISaveContext context) throws CoreException { |
| // save variable and container values on snapshot/full save |
| long start = -1; |
| if (VERBOSE) |
| start = System.currentTimeMillis(); |
| savesContainers(); |
| if (VERBOSE) |
| traceContainers("Saved", start); //$NON-NLS-1$ |
| if (context.getKind() == ISaveContext.FULL_SAVE) { |
| // will need delta since this save (see |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=38658) |
| context.needDelta(); |
| // clean up indexes on workspace full save |
| // (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=52347) |
| IndexManager manager = this.indexManager; |
| if (manager != null |
| // don't force initialization of workspace scope as we could |
| // be |
| // shutting down |
| // (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=93941) |
| && this.workspaceScope != null) { |
| manager.cleanUpIndexes(); |
| } |
| } |
| IProject savedProject = context.getProject(); |
| if (savedProject != null) { |
| if (!ScriptProject.hasScriptNature(savedProject)) |
| return; // ignore |
| PerProjectInfo info = getPerProjectInfo(savedProject, |
| true /* |
| * create info |
| */); |
| saveState(info, context); |
| info.rememberExternalLibTimestamps(); |
| return; |
| } |
| ArrayList vStats = null; // lazy initialized |
| ArrayList values = null; |
| synchronized (this.perProjectInfos) { |
| values = new ArrayList(this.perProjectInfos.values()); |
| } |
| Iterator iterator = values.iterator(); |
| while (iterator.hasNext()) { |
| try { |
| PerProjectInfo info = (PerProjectInfo) iterator.next(); |
| saveState(info, context); |
| info.rememberExternalLibTimestamps(); |
| } catch (CoreException e) { |
| if (vStats == null) |
| vStats = new ArrayList(); |
| vStats.add(e.getStatus()); |
| } |
| } |
| if (vStats != null) { |
| IStatus[] stats = new IStatus[vStats.size()]; |
| vStats.toArray(stats); |
| throw new CoreException( |
| new MultiStatus(DLTKCore.PLUGIN_ID, IStatus.ERROR, stats, |
| Messages.build_cannotSaveStates, null)); |
| } |
| // save external libs timestamps |
| this.deltaState.saveExternalLibTimeStamps(); |
| } |
| |
| private File getContainersFile() { |
| return DLTKCore.getPlugin().getStateLocation().append("Containers.dat") //$NON-NLS-1$ |
| .toFile(); |
| } |
| |
| private void saveState(PerProjectInfo info, ISaveContext context) |
| throws CoreException { |
| // passed this point, save actions are non trivial |
| if (context.getKind() == ISaveContext.SNAPSHOT) |
| return; |
| // save built state |
| if (info.triedRead) |
| saveBuiltState(info); |
| } |
| |
| /** |
| * Saves the built state for the project. |
| */ |
| private void saveBuiltState(PerProjectInfo info) throws CoreException { |
| if (ScriptBuilder.DEBUG) |
| System.out.println(Messages.bind(Messages.build_saveStateProgress, |
| info.project.getName())); |
| File file = getSerializationFile(info.project); |
| if (file == null) |
| return; |
| long t = System.currentTimeMillis(); |
| try { |
| DataOutputStream out = new DataOutputStream( |
| new BufferedOutputStream(new FileOutputStream(file))); |
| try { |
| out.writeUTF(DLTKCore.PLUGIN_ID); |
| out.writeUTF("STATE"); //$NON-NLS-1$ |
| if (info.savedState == null) { |
| out.writeBoolean(false); |
| } else { |
| out.writeBoolean(true); |
| ScriptBuilder.writeState(info.savedState, out); |
| } |
| } finally { |
| out.close(); |
| } |
| } catch (RuntimeException e) { |
| try { |
| file.delete(); |
| } catch (SecurityException se) { |
| // could not delete file: cannot do much more |
| } |
| throw new CoreException(new Status(IStatus.ERROR, |
| DLTKCore.PLUGIN_ID, Platform.PLUGIN_ERROR, |
| Messages.bind(Messages.build_cannotSaveState, |
| info.project.getName()), |
| e)); |
| } catch (IOException e) { |
| try { |
| file.delete(); |
| } catch (SecurityException se) { |
| // could not delete file: cannot do much more |
| } |
| throw new CoreException(new Status(IStatus.ERROR, |
| DLTKCore.PLUGIN_ID, Platform.PLUGIN_ERROR, |
| Messages.bind(Messages.build_cannotSaveState, |
| info.project.getName()), |
| e)); |
| } |
| if (ScriptBuilder.DEBUG) { |
| t = System.currentTimeMillis() - t; |
| System.out.println(Messages.bind(Messages.build_saveStateComplete, |
| String.valueOf(t))); |
| } |
| } |
| |
| private void savesContainers() throws CoreException { |
| File file = getContainersFile(); |
| DataOutputStream out = null; |
| try { |
| out = new DataOutputStream( |
| new BufferedOutputStream(new FileOutputStream(file))); |
| out.writeInt(CONTAINERS_FILE_VERSION); |
| new ContainersSaveHelper(out).save(); |
| // old code retained for performance comparisons |
| // containers |
| IScriptProject[] projects = getModel().getScriptProjects(); |
| int length = projects.length; |
| out.writeInt(length); |
| for (int i = 0; i < length; i++) { |
| IScriptProject project = projects[i]; |
| // clone while iterating (see |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=59638) |
| Map projectContainers = containerClone(project); |
| out.writeUTF(project.getElementName()); |
| if (projectContainers == null) { |
| out.writeInt(0); |
| continue; |
| } |
| HashMap containersToSave = new HashMap(); |
| for (Iterator iterator = projectContainers.keySet() |
| .iterator(); iterator.hasNext();) { |
| IPath containerPath = (IPath) iterator.next(); |
| IBuildpathContainer container = (IBuildpathContainer) projectContainers |
| .get(containerPath); |
| String containerString = null; |
| try { |
| if (container == null) { |
| // container has not been initialized yet, use |
| // previous session value |
| // (see |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=73969 |
| // ) |
| container = getPreviousSessionContainer( |
| containerPath, project); |
| } |
| if (container != null) { |
| IBuildpathEntry[] entries = container |
| .getBuildpathEntries(); |
| containerString = ((ScriptProject) project) |
| .encodeBuildpath(entries, false, |
| null/* |
| * not interested in unknown |
| * elements |
| */); |
| } |
| } catch (ModelException e) { |
| // could not encode entry: will not persist |
| Util.log(e, |
| "Could not persist container " + containerPath //$NON-NLS-1$ |
| + " for project " //$NON-NLS-1$ |
| + project.getElementName()); |
| } |
| if (containerString != null) |
| containersToSave.put(containerPath, containerString); |
| } |
| out.writeInt(containersToSave.size()); |
| Iterator iterator = containersToSave.keySet().iterator(); |
| while (iterator.hasNext()) { |
| IPath containerPath = (IPath) iterator.next(); |
| out.writeUTF(containerPath.toPortableString()); |
| String containerString = (String) containersToSave |
| .get(containerPath); |
| out.writeInt(containerString.length()); |
| out.writeBytes(containerString); |
| } |
| } |
| } catch (IOException e) { |
| IStatus status = new Status(IStatus.ERROR, DLTKCore.PLUGIN_ID, |
| IStatus.ERROR, |
| "Problems while saving variables and containers", e); //$NON-NLS-1$ |
| throw new CoreException(status); |
| } finally { |
| if (out != null) { |
| try { |
| out.close(); |
| } catch (IOException e) { |
| // nothing we can do: ignore |
| } |
| } |
| } |
| } |
| |
| protected synchronized void resetZIPTypeCache() { |
| this.cache.resetZIPTypeCache(); |
| } |
| |
| public DLTKWorkspaceScope getWorkspaceScope(IDLTKLanguageToolkit toolkit) { |
| if (this.workspaceScope == null) { |
| this.workspaceScope = new HashMap(); |
| } |
| if (this.workspaceScope.containsKey(toolkit)) { |
| return (DLTKWorkspaceScope) this.workspaceScope.get(toolkit); |
| } else { |
| DLTKWorkspaceScope scope = new DLTKWorkspaceScope(toolkit); |
| this.workspaceScope.put(toolkit, scope); |
| return scope; |
| } |
| } |
| |
| private final class ContainersSaveHelper { |
| private final HashtableOfObjectToInt buildpathEntryIds; |
| // -> int |
| private final DataOutputStream out; |
| private final HashtableOfObjectToInt stringIds; // Strings -> int |
| |
| ContainersSaveHelper(DataOutputStream out) { |
| super(); |
| this.buildpathEntryIds = new HashtableOfObjectToInt(); |
| this.out = out; |
| this.stringIds = new HashtableOfObjectToInt(); |
| } |
| |
| void save() throws IOException, ModelException { |
| saveProjects(ModelManager.this.getModel().getScriptProjects()); |
| } |
| |
| private void saveAccessRule(BuildpathAccessRule rule) |
| throws IOException { |
| saveInt(rule.problemId); |
| savePath(rule.getPattern()); |
| } |
| |
| private void saveAccessRules(IAccessRule[] rules) throws IOException { |
| int count = rules == null ? 0 : rules.length; |
| saveInt(count); |
| for (int i = 0; i < count; ++i) |
| saveAccessRule((BuildpathAccessRule) rules[i]); |
| } |
| |
| private void saveAttribute(IBuildpathAttribute attribute) |
| throws IOException { |
| saveString(attribute.getName()); |
| saveString(attribute.getValue()); |
| } |
| |
| private void saveAttributes(IBuildpathAttribute[] attributes) |
| throws IOException { |
| int count = attributes == null ? 0 : attributes.length; |
| saveInt(count); |
| for (int i = 0; i < count; ++i) |
| saveAttribute(attributes[i]); |
| } |
| |
| private void saveBuildpathEntries(IBuildpathEntry[] entries) |
| throws IOException { |
| int count = entries == null ? 0 : entries.length; |
| saveInt(count); |
| for (int i = 0; i < count; ++i) |
| saveBuildpathEntry(entries[i]); |
| } |
| |
| private void saveBuildpathEntry(IBuildpathEntry entry) |
| throws IOException { |
| if (saveNewId(entry, this.buildpathEntryIds)) { |
| saveInt(entry.getContentKind()); |
| saveInt(entry.getEntryKind()); |
| savePath(entry.getPath()); |
| savePaths(entry.getInclusionPatterns()); |
| savePaths(entry.getExclusionPatterns()); |
| this.out.writeBoolean(entry.isExported()); |
| this.out.writeBoolean(entry.isExternal()); |
| saveAccessRules(entry.getAccessRules()); |
| this.out.writeBoolean(entry.combineAccessRules()); |
| saveAttributes(entry.getExtraAttributes()); |
| } |
| } |
| |
| private void saveContainers(IScriptProject project, Map containerMap) |
| throws IOException { |
| saveInt(containerMap.size()); |
| for (Iterator i = containerMap.entrySet().iterator(); i |
| .hasNext();) { |
| Entry entry = (Entry) i.next(); |
| IPath path = (IPath) entry.getKey(); |
| IBuildpathContainer container = (IBuildpathContainer) entry |
| .getValue(); |
| IBuildpathEntry[] cpEntries = null; |
| if (container == null) { |
| // container has not been initialized yet, use previous |
| // session value |
| // (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=73969) |
| container = ModelManager.this |
| .getPreviousSessionContainer(path, project); |
| } |
| if (container != null) |
| cpEntries = container.getBuildpathEntries(); |
| savePath(path); |
| saveBuildpathEntries(cpEntries); |
| } |
| } |
| |
| private void saveInt(int value) throws IOException { |
| this.out.writeInt(value); |
| } |
| |
| private boolean saveNewId(Object key, HashtableOfObjectToInt map) |
| throws IOException { |
| int id = map.get(key); |
| if (id == -1) { |
| int newId = map.size(); |
| map.put(key, newId); |
| saveInt(newId); |
| return true; |
| } else { |
| saveInt(id); |
| return false; |
| } |
| } |
| |
| private void savePath(IPath path) throws IOException { |
| if (path == null) { |
| this.out.writeBoolean(true); |
| } else { |
| this.out.writeBoolean(false); |
| saveString(path.toPortableString()); |
| } |
| } |
| |
| private void savePaths(IPath[] paths) throws IOException { |
| int count = paths == null ? 0 : paths.length; |
| saveInt(count); |
| for (int i = 0; i < count; ++i) |
| savePath(paths[i]); |
| } |
| |
| private void saveProjects(IScriptProject[] projects) |
| throws IOException, ModelException { |
| int count = projects.length; |
| saveInt(count); |
| for (int i = 0; i < count; ++i) { |
| IScriptProject project = projects[i]; |
| saveString(project.getElementName()); |
| Map containerMap = (Map) ModelManager.this.containers |
| .get(project); |
| if (containerMap == null) { |
| containerMap = Collections.EMPTY_MAP; |
| } else { |
| // clone while iterating |
| // (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=59638) |
| containerMap = new HashMap(containerMap); |
| } |
| saveContainers(project, containerMap); |
| } |
| } |
| |
| private void saveString(String string) throws IOException { |
| if (saveNewId(string, this.stringIds)) |
| this.out.writeUTF(string); |
| } |
| } |
| |
| private synchronized Map containerClone(IScriptProject project) { |
| Map originalProjectContainers = (Map) this.containers.get(project); |
| if (originalProjectContainers == null) |
| return null; |
| Map projectContainers = new HashMap(originalProjectContainers.size()); |
| projectContainers.putAll(originalProjectContainers); |
| return projectContainers; |
| } |
| |
| public void loadContainers() throws CoreException { |
| // backward compatibility, load variables and containers from |
| // preferences into cache |
| loadVariablesAndContainers(getDefaultPreferences()); |
| loadVariablesAndContainers(getInstancePreferences()); |
| // load variables and containers from saved file into cache |
| File file = getContainersFile(); |
| DataInputStream in = null; |
| try { |
| in = new DataInputStream( |
| new BufferedInputStream(new FileInputStream(file))); |
| switch (in.readInt()) { |
| case 1: |
| new ContainersLoadHelper(in).load(); |
| break; |
| } |
| } catch (IOException e) { |
| if (file.exists()) |
| Util.log(e, "Unable to read variable and containers file"); //$NON-NLS-1$ |
| } catch (RuntimeException e) { |
| if (file.exists()) |
| Util.log(e, |
| "Unable to read variable and containers file (file is corrupt)"); //$NON-NLS-1$ |
| } finally { |
| if (in != null) { |
| try { |
| in.close(); |
| } catch (IOException e) { |
| // nothing we can do: ignore |
| } |
| } |
| } |
| // override persisted values for containers which have a registered |
| // initializer |
| containersReset(getRegisteredContainerIDs()); |
| } |
| |
| private void loadVariablesAndContainers(IEclipsePreferences preferences) { |
| try { |
| // only get variable from preferences not set to their default |
| String[] propertyNames = preferences.keys(); |
| int variablePrefixLength = BP_VARIABLE_PREFERENCES_PREFIX.length(); |
| for (int i = 0; i < propertyNames.length; i++) { |
| String propertyName = propertyNames[i]; |
| if (propertyName.startsWith(BP_VARIABLE_PREFERENCES_PREFIX)) { |
| String varName = propertyName |
| .substring(variablePrefixLength); |
| String propertyValue = preferences.get(propertyName, null); |
| if (propertyValue != null) { |
| String pathString = propertyValue.trim(); |
| |
| if (BP_ENTRY_IGNORE.equals(pathString)) { |
| // cleanup old preferences |
| preferences.remove(propertyName); |
| continue; |
| } |
| |
| // add variable to table |
| IPath varPath = new Path(pathString); |
| this.variables.put(varName, varPath); |
| this.previousSessionVariables.put(varName, varPath); |
| } |
| } else if (propertyName |
| .startsWith(BP_CONTAINER_PREFERENCES_PREFIX)) { |
| String propertyValue = preferences.get(propertyName, null); |
| if (propertyValue != null) { |
| // cleanup old preferences |
| preferences.remove(propertyName); |
| |
| // recreate container |
| recreatePersistedContainer(propertyName, propertyValue, |
| true/* add to container values */); |
| } |
| } |
| } |
| } catch (BackingStoreException e1) { |
| // TODO (frederic) see if it's necessary to report this failure... |
| } |
| } |
| |
| private static final class PersistedBuildpathContainer |
| implements IBuildpathContainer { |
| private final IPath containerPath; |
| private final IBuildpathEntry[] entries; |
| private final IScriptProject project; |
| |
| PersistedBuildpathContainer(IScriptProject project, IPath containerPath, |
| IBuildpathEntry[] entries) { |
| super(); |
| this.containerPath = containerPath; |
| this.entries = entries; |
| this.project = project; |
| } |
| |
| @Override |
| public IBuildpathEntry[] getBuildpathEntries() { |
| return entries; |
| } |
| |
| @Override |
| public String getDescription() { |
| return "Persisted container [" + containerPath //$NON-NLS-1$ |
| + " for project [" + project.getElementName() //$NON-NLS-1$ |
| + "]]"; //$NON-NLS-1$ |
| } |
| |
| @Override |
| public int getKind() { |
| return 0; |
| } |
| |
| @Override |
| public IPath getPath() { |
| return containerPath; |
| } |
| |
| @Override |
| public String toString() { |
| return getDescription(); |
| } |
| |
| } |
| |
| private final class ContainersLoadHelper { |
| private static final int ARRAY_INCREMENT = 200; |
| private IBuildpathEntry[] allBuildpathEntries; |
| private int allBuildpathEntryCount; |
| private final Map allPaths; // String -> IPath |
| private String[] allStrings; |
| private int allStringsCount; |
| private final DataInputStream in; |
| |
| ContainersLoadHelper(DataInputStream in) { |
| super(); |
| this.allBuildpathEntries = null; |
| this.allBuildpathEntryCount = 0; |
| this.allPaths = new HashMap(); |
| this.allStrings = null; |
| this.allStringsCount = 0; |
| this.in = in; |
| } |
| |
| void load() throws IOException { |
| loadProjects(ModelManager.this.getModel()); |
| } |
| |
| private IAccessRule loadAccessRule() throws IOException { |
| int problemId = loadInt(); |
| IPath pattern = loadPath(); |
| return new BuildpathAccessRule(pattern.toString().toCharArray(), |
| problemId); |
| } |
| |
| private IAccessRule[] loadAccessRules() throws IOException { |
| int count = loadInt(); |
| if (count == 0) |
| return BuildpathEntry.NO_ACCESS_RULES; |
| IAccessRule[] rules = new IAccessRule[count]; |
| for (int i = 0; i < count; ++i) |
| rules[i] = loadAccessRule(); |
| return rules; |
| } |
| |
| private IBuildpathAttribute loadAttribute() throws IOException { |
| String name = loadString(); |
| String value = loadString(); |
| return new BuildpathAttribute(name, value); |
| } |
| |
| private IBuildpathAttribute[] loadAttributes() throws IOException { |
| int count = loadInt(); |
| if (count == 0) |
| return BuildpathEntry.NO_EXTRA_ATTRIBUTES; |
| IBuildpathAttribute[] attributes = new IBuildpathAttribute[count]; |
| for (int i = 0; i < count; ++i) |
| attributes[i] = loadAttribute(); |
| return attributes; |
| } |
| |
| private boolean loadBoolean() throws IOException { |
| return this.in.readBoolean(); |
| } |
| |
| private IBuildpathEntry[] loadBuildpathEntries() throws IOException { |
| int count = loadInt(); |
| IBuildpathEntry[] entries = new IBuildpathEntry[count]; |
| for (int i = 0; i < count; ++i) |
| entries[i] = loadBuildpathEntry(); |
| return entries; |
| } |
| |
| private IBuildpathEntry loadBuildpathEntry() throws IOException { |
| int id = loadInt(); |
| if (id < 0 || id > this.allBuildpathEntryCount) |
| throw new IOException("Unexpected buildpathentry id"); //$NON-NLS-1$ |
| if (id < this.allBuildpathEntryCount) |
| return this.allBuildpathEntries[id]; |
| int contentKind = loadInt(); |
| int entryKind = loadInt(); |
| IPath path = loadPath(); |
| IPath[] inclusionPatterns = loadPaths(); |
| IPath[] exclusionPatterns = loadPaths(); |
| boolean isExported = loadBoolean(); |
| boolean isExternal = loadBoolean(); |
| IAccessRule[] accessRules = loadAccessRules(); |
| boolean combineAccessRules = loadBoolean(); |
| IBuildpathAttribute[] extraAttributes = loadAttributes(); |
| IBuildpathEntry entry = new BuildpathEntry(contentKind, entryKind, |
| path, isExported, inclusionPatterns, exclusionPatterns, |
| accessRules, combineAccessRules, extraAttributes, |
| isExternal); |
| IBuildpathEntry[] array = this.allBuildpathEntries; |
| if (array == null || id == array.length) { |
| array = new IBuildpathEntry[id + ARRAY_INCREMENT]; |
| if (id != 0) |
| System.arraycopy(this.allBuildpathEntries, 0, array, 0, id); |
| this.allBuildpathEntries = array; |
| } |
| array[id] = entry; |
| this.allBuildpathEntryCount = id + 1; |
| return entry; |
| } |
| |
| private void loadContainers(IScriptProject project) throws IOException { |
| boolean projectIsAccessible = project.getProject().isAccessible(); |
| int count = loadInt(); |
| for (int i = 0; i < count; ++i) { |
| IPath path = loadPath(); |
| IBuildpathEntry[] entries = loadBuildpathEntries(); |
| if (!projectIsAccessible) |
| // avoid leaking deleted project's persisted container, |
| // but still read the container as it is is part of the file |
| // format |
| continue; |
| IBuildpathContainer container = new PersistedBuildpathContainer( |
| project, path, entries); |
| ModelManager.this.containerPut(project, path, container); |
| Map oldContainers = (Map) ModelManager.this.previousSessionContainers |
| .get(project); |
| if (oldContainers == null) { |
| oldContainers = new HashMap(); |
| ModelManager.this.previousSessionContainers.put(project, |
| oldContainers); |
| } |
| oldContainers.put(path, container); |
| } |
| } |
| |
| private int loadInt() throws IOException { |
| return this.in.readInt(); |
| } |
| |
| private IPath loadPath() throws IOException { |
| if (loadBoolean()) |
| return null; |
| String portableString = loadString(); |
| IPath path = (IPath) this.allPaths.get(portableString); |
| if (path == null) { |
| path = Path.fromPortableString(portableString); |
| this.allPaths.put(portableString, path); |
| } |
| return path; |
| } |
| |
| private IPath[] loadPaths() throws IOException { |
| int count = loadInt(); |
| IPath[] pathArray = new IPath[count]; |
| for (int i = 0; i < count; ++i) |
| pathArray[i] = loadPath(); |
| return pathArray; |
| } |
| |
| private void loadProjects(IScriptModel model) throws IOException { |
| int count = loadInt(); |
| for (int i = 0; i < count; ++i) { |
| String projectName = loadString(); |
| loadContainers(model.getScriptProject(projectName)); |
| } |
| } |
| |
| private String loadString() throws IOException { |
| int id = loadInt(); |
| if (id < 0 || id > this.allStringsCount) |
| throw new IOException("Unexpected string id"); //$NON-NLS-1$ |
| if (id < this.allStringsCount) |
| return this.allStrings[id]; |
| String string = this.in.readUTF(); |
| String[] array = this.allStrings; |
| if (array == null || id == array.length) { |
| array = new String[id + ARRAY_INCREMENT]; |
| if (id != 0) |
| System.arraycopy(this.allStrings, 0, array, 0, id); |
| this.allStrings = array; |
| } |
| array[id] = string; |
| this.allStringsCount = id + 1; |
| return string; |
| } |
| } |
| |
| /** |
| * Returns the name of the container IDs for which an CP container |
| * initializer is registered through an extension point |
| */ |
| public static String[] getRegisteredContainerIDs() { |
| |
| Plugin dltkCorePlugin = DLTKCore.getPlugin(); |
| if (dltkCorePlugin == null) |
| return null; |
| |
| ArrayList containerIDList = new ArrayList(5); |
| IExtensionPoint extension = Platform.getExtensionRegistry() |
| .getExtensionPoint(DLTKCore.PLUGIN_ID, |
| ModelManager.BPCONTAINER_INITIALIZER_EXTPOINT_ID); |
| if (extension != null) { |
| IExtension[] extensions = extension.getExtensions(); |
| for (int i = 0; i < extensions.length; i++) { |
| IConfigurationElement[] configElements = extensions[i] |
| .getConfigurationElements(); |
| for (int j = 0; j < configElements.length; j++) { |
| String idAttribute = configElements[j].getAttribute("id"); //$NON-NLS-1$ |
| if (idAttribute != null) |
| containerIDList.add(idAttribute); |
| } |
| } |
| } |
| String[] containerIDs = new String[containerIDList.size()]; |
| containerIDList.toArray(containerIDs); |
| return containerIDs; |
| } |
| |
| private synchronized void containersReset(String[] containerIDs) { |
| for (int i = 0; i < containerIDs.length; i++) { |
| String containerID = containerIDs[i]; |
| Iterator projectIterator = this.containers.keySet().iterator(); |
| while (projectIterator.hasNext()) { |
| IScriptProject project = (IScriptProject) projectIterator |
| .next(); |
| Map projectContainers = (Map) this.containers.get(project); |
| if (projectContainers != null) { |
| Iterator containerIterator = projectContainers.keySet() |
| .iterator(); |
| while (containerIterator.hasNext()) { |
| IPath containerPath = (IPath) containerIterator.next(); |
| if (containerPath.segment(0).equals(containerID)) { // registered |
| // container |
| projectContainers.put(containerPath, null); // reset |
| // container |
| // value, |
| // but |
| // leave |
| // entry |
| // in |
| // Map |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Flushes ZipFiles cache if there are no more clients. |
| */ |
| public void flushZipFiles() { |
| Thread currentThread = Thread.currentThread(); |
| Map<IPath, IArchive> map = this.zipFiles.get(); |
| if (map == null) |
| return; |
| this.zipFiles.set(null); |
| for (IArchive zipFile : map.values()) { |
| try { |
| if (ModelManager.ZIP_ACCESS_VERBOSE) { |
| System.out.println("(" + currentThread //$NON-NLS-1$ |
| + ") [ModelManager.flushZipFiles()] Closing ZipFile on " //$NON-NLS-1$ |
| + zipFile.getName()); |
| } |
| zipFile.close(); |
| } catch (IOException e) { |
| // problem occured closing zip file: cannot do much more |
| } |
| } |
| } |
| |
| private SourceModuleInfoCache sourceModuleInfoCache = null; |
| |
| public ISourceModuleInfoCache getSourceModuleInfoCache() { |
| return sourceModuleInfoCache; |
| } |
| |
| public static UserLibraryManager getUserLibraryManager() { |
| if (MANAGER.userLibraryManager == null) { |
| UserLibraryManager libraryManager = new UserLibraryManager(); |
| synchronized (MANAGER) { |
| if (MANAGER.userLibraryManager == null) { // ensure another |
| // library manager |
| // was not set while |
| // creating the |
| // instance above |
| MANAGER.userLibraryManager = libraryManager; |
| } |
| } |
| } |
| return MANAGER.userLibraryManager; |
| } |
| } |