/******************************************************************************* * 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.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.StringReader; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ProjectScope; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.AssertionFailedException; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.content.IContentDescription; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.IScopeContext; import org.eclipse.dltk.compiler.CharOperation; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.core.DLTKLanguageManager; 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.IModelElementMemento; import org.eclipse.dltk.core.IModelMarker; import org.eclipse.dltk.core.IModelProvider; import org.eclipse.dltk.core.IModelStatus; import org.eclipse.dltk.core.IModelStatusConstants; import org.eclipse.dltk.core.IProjectFragment; import org.eclipse.dltk.core.IProjectFragmentFactory; import org.eclipse.dltk.core.IRegion; import org.eclipse.dltk.core.IScriptFolder; import org.eclipse.dltk.core.IScriptProject; import org.eclipse.dltk.core.IScriptProjectFilenames; import org.eclipse.dltk.core.ISearchableEnvironment; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.core.IType; import org.eclipse.dltk.core.ITypeHierarchy; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.core.WorkingCopyOwner; import org.eclipse.dltk.internal.core.ModelManager.PerProjectInfo; import org.eclipse.dltk.internal.core.util.MementoTokenizer; import org.eclipse.dltk.internal.core.util.Messages; import org.eclipse.dltk.internal.core.util.Util; import org.eclipse.dltk.utils.CorePrinter; import org.osgi.service.prefs.BackingStoreException; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; public class ScriptProject extends Openable implements IScriptProject, IScriptProjectFilenames { /** * Value of the project's raw buildpath if the .buildpath file contains * invalid entries. */ public static final IBuildpathEntry[] INVALID_BUILDPATH = new IBuildpathEntry[0]; /** * Whether the underlying file system is case sensitive. */ protected static final boolean IS_CASE_SENSITIVE = !new File("Temp") //$NON-NLS-1$ .equals(new File("temp")); //$NON-NLS-1$ /** * An empty array of strings indicating that a project doesn't have any * prerequesite projects. */ protected static final String[] NO_PREREQUISITES = new String[0]; /* * Value of project's resolved buildpath while it is being resolved */ private static final IBuildpathEntry[] RESOLUTION_IN_PROGRESS = new IBuildpathEntry[0]; /* * For testing purpose only */ private static ArrayList<?> BP_RESOLUTION_BP_LISTENERS; /* * For testing purpose only */ private static void breakpoint(int bp, ScriptProject project) { } /** * The platform project this <code>IDylanProject</code> is based on */ protected IProject project; private IDLTKLanguageToolkit toolkit = null; public ScriptProject(IProject project, ModelElement parent) { super(parent); this.project = project; toolkit = DLTKLanguageManager.findToolkit(project); } @Override public IDLTKLanguageToolkit getLanguageToolkit() { if (toolkit == null) { toolkit = DLTKLanguageManager.findToolkit(project); } return toolkit; } /** * The path is known to match a source/library folder entry. * * @param path * IPath * @return IProjectFragment */ public IProjectFragment getFolderProjectFragment(IPath path) { if (path.segmentCount() == 1) { // default project root return getProjectFragment(this.project); } return getProjectFragment( this.project.getWorkspace().getRoot().getFolder(path)); } @Override public IProject getProject() { return this.project; } @Override public IProjectFragment getProjectFragment(IResource resource) { switch (resource.getType()) { case IResource.FILE: if (org.eclipse.dltk.compiler.util.Util.isArchiveFileName( DLTKLanguageManager.getLanguageToolkit(this), resource.getName())) { return createArchiveFragment(resource); } else { return null; } case IResource.FOLDER: return new ProjectFragment(resource, this); case IResource.PROJECT: return new ProjectFragment(resource, this); default: return null; } } private IProjectFragment createArchiveFragment(IResource resource) { IDLTKLanguageToolkit toolkit = DLTKLanguageManager .getLanguageToolkit(this); if (toolkit != null) { if (toolkit.languageSupportZIPBuildpath()) { return new ArchiveProjectFragment(resource, this); } } return null; } @Override public IProjectFragment getProjectFragment(String path) { return getProjectFragment( canonicalizedPath(Path.fromPortableString(path))); } /* * no path canonicalization */ public IProjectFragment getProjectFragment0(IPath archivePath) { IDLTKLanguageToolkit toolkit = DLTKLanguageManager .getLanguageToolkit(this); if (toolkit != null) { if (toolkit.languageSupportZIPBuildpath()) { return new ArchiveProjectFragment(archivePath, this); } } return null; } /** * @param path * IPath * @return A handle to the package fragment root identified by the given * path. This method is handle-only and the element may or may not * exist. Returns <code>null</code> if unable to generate a handle * from the path (for example, an absolute path that has less than 1 * segment. The path may be relative or absolute. */ @Override public IProjectFragment getProjectFragment(IPath path) { boolean isSpecial = !path.isEmpty() && path.segment(0) .startsWith(IBuildpathEntry.BUILDPATH_SPECIAL); if (!path.isAbsolute() && !isSpecial) { path = getPath().append(path); } final int segmentCount = path.segmentCount(); // TODO (alex) getProjectFragment(IPath) doesn't work for external paths switch (segmentCount) { case 0: return null; case 1: if (path.equals(getPath())) { // see // https://bugs.eclipse.org/bugs/show_bug.cgi?id=75814 // default root return getProjectFragment(this.project); } default: // a path ending with .jar/.zip is still ambiguous and could // still // resolve to a source/lib folder // thus will try to guess based on existing resource if (isSpecial && path.segment(0) .startsWith(IBuildpathEntry.BUILTIN_EXTERNAL_ENTRY_STR)) { return new BuiltinProjectFragment(path, this); } if (org.eclipse.dltk.compiler.util.Util.isArchiveFileName( DLTKLanguageManager.getLanguageToolkit(this), path.lastSegment())) { IResource resource = this.project.getWorkspace().getRoot() .findMember(path); if (resource != null && resource.getType() == IResource.FOLDER) { return getProjectFragment(resource); } return getProjectFragment0(path); } else if (segmentCount == 1) { // lib being another project return getProjectFragment(this.project.getWorkspace().getRoot() .getProject(path.lastSegment())); } else { // lib being a folder IResource folder = this.project.getWorkspace().getRoot() .findMember(path); if (folder != null) { IProjectFragment projectFragment = getProjectFragment( folder); return projectFragment; } // No folders with such path exist in workspace, lets // getAllFragments and check. IProjectFragment[] fragments; try { fragments = getProjectFragments(); for (int i = 0; i < fragments.length; i++) { if (fragments[i].getPath().equals(path)) { return fragments[i]; } } } catch (ModelException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } return null; } } } /* * Returns the cached resolved buildpath, or compute it ignoring unresolved * entries and cache it. */ public IBuildpathEntry[] getResolvedBuildpath() throws ModelException { PerProjectInfo perProjectInfo = getPerProjectInfo(); IBuildpathEntry[] resolvedClasspath = perProjectInfo .getResolvedBuildpath(); if (resolvedClasspath == null) { resolveBuildpath(perProjectInfo, false/* * don't use previous session values */, true/* add classpath change */); resolvedClasspath = perProjectInfo.getResolvedBuildpath(); if (resolvedClasspath == null) { // another thread reset the resolved classpath, use a temporary // PerProjectInfo PerProjectInfo temporaryInfo = newTemporaryInfo(); resolveBuildpath(temporaryInfo, false/* * don't use previous session values */, true/* * add classpath change */); resolvedClasspath = temporaryInfo.getResolvedBuildpath(); } } return resolvedClasspath; } /* * Internal variant which can create marker on project for invalid entries * and caches the resolved buildpath on perProjectInfo. If requested, return * a special buildpath (RESOLUTION_IN_PROGRESS) if the buildpath is being * resolved. */ @Override public IBuildpathEntry[] getResolvedBuildpath(boolean ignoreUnresolvedEntry) throws ModelException { if (ModelManager.getModelManager().isBuildpathBeingResolved(this)) { if (ModelManager.BP_RESOLVE_VERBOSE_ADVANCED) verbose_reentering_classpath_resolution(); return RESOLUTION_IN_PROGRESS; } PerProjectInfo perProjectInfo = getPerProjectInfo(); // use synchronized block to ensure consistency IBuildpathEntry[] resolvedClasspath; IModelStatus unresolvedEntryStatus; synchronized (perProjectInfo) { resolvedClasspath = perProjectInfo.getResolvedBuildpath(); unresolvedEntryStatus = perProjectInfo.unresolvedEntryStatus; } if (resolvedClasspath == null || (unresolvedEntryStatus != null && !unresolvedEntryStatus.isOK())) { // force resolution to // ensure initializers // are run again resolveBuildpath(perProjectInfo, false/* * don't use previous session values */, true/* add classpath change */); synchronized (perProjectInfo) { resolvedClasspath = perProjectInfo.getResolvedBuildpath(); unresolvedEntryStatus = perProjectInfo.unresolvedEntryStatus; } if (resolvedClasspath == null) { // another thread reset the resolved classpath, use a temporary // PerProjectInfo PerProjectInfo temporaryInfo = newTemporaryInfo(); resolveBuildpath(temporaryInfo, false/* * don't use previous session values */, true/* * add classpath change */); resolvedClasspath = temporaryInfo.getResolvedBuildpath(); unresolvedEntryStatus = temporaryInfo.unresolvedEntryStatus; } } if (!ignoreUnresolvedEntry && unresolvedEntryStatus != null && !unresolvedEntryStatus.isOK()) throw new ModelException(unresolvedEntryStatus); return resolvedClasspath; } private void verbose_reentering_classpath_resolution() { Util.verbose( "CPResolution: reentering raw classpath resolution, will use empty classpath instead" //$NON-NLS-1$ + " project: " + getElementName() + '\n' + " invocation stack trace:"); new Exception("<Fake exception>").printStackTrace(System.out); //$NON-NLS-1$ } /** * This is a helper method returning the expanded buildpath for the project, * as a list of buildpath entries, where all buildpath variable entries have * been resolved and substituted with their final target entries. All * project exports have been appended to project entries. * * @return IBuildpathEntry[] * @throws ModelException */ public IBuildpathEntry[] getExpandedBuildpath() throws ModelException { List<IBuildpathEntry> accumulatedEntries = new ArrayList<>(); computeExpandedBuildpath(null, new HashSet<String>(5), accumulatedEntries); return accumulatedEntries .toArray(new IBuildpathEntry[accumulatedEntries.size()]); } /** * This is a helper method returning the expanded buildpath for the project, * as a list of buildpath entries, where all buildpath variable entries have * been resolved and substituted with their final target entries. All * project exports have been appended to project entries. * * @param ignoreUnresolvedVariable * boolean * @return IBuildpathEntry[] * @throws ModelException */ @Deprecated public IBuildpathEntry[] getExpandedBuildpath( boolean ignoreUnresolvedVariable) throws ModelException { return getExpandedBuildpath(); } /** * Internal computation of an expanded buildpath. It will eliminate * duplicates, and produce copies of exported or restricted buildpath * entries to avoid possible side-effects ever after. */ private void computeExpandedBuildpath(BuildpathEntry referringEntry, HashSet<String> rootIDs, List<IBuildpathEntry> accumulatedEntries) throws ModelException { String projectRootId = this.rootID(); if (rootIDs.contains(projectRootId)) { return; // break cycles if any } rootIDs.add(projectRootId); IBuildpathEntry[] resolvedBuildpath = getResolvedBuildpath(); IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); boolean isInitialProject = referringEntry == null; for (int i = 0, length = resolvedBuildpath.length; i < length; i++) { BuildpathEntry entry = (BuildpathEntry) resolvedBuildpath[i]; if (isInitialProject || entry.isExported()) { String rootID = entry.rootID(); if (rootIDs.contains(rootID)) { continue; } // combine restrictions along the project chain BuildpathEntry combinedEntry = entry .combineWith(referringEntry); accumulatedEntries.add(combinedEntry); // recurse in project to get all its indirect exports (only // consider exported entries from there on) if (entry.getEntryKind() == IBuildpathEntry.BPE_PROJECT) { IResource member = workspaceRoot .findMember(entry.getPath()); if (member != null && member.getType() == IResource.PROJECT) { // double // check // if // bound // to // project // (23977) IProject projRsc = (IProject) member; if (ScriptProject.hasScriptNature(projRsc)) { ScriptProject scriptProject = (ScriptProject) DLTKCore .create(projRsc); scriptProject.computeExpandedBuildpath( combinedEntry, rootIDs, accumulatedEntries); } } } else { rootIDs.add(rootID); } } } } /** * Computes the project fragments identified by the given entry. Only works * with resolved entry * * @param resolvedEntry * IBuildpathEntry * @return IProjectFragment[] */ public IProjectFragment[] computeProjectFragments( IBuildpathEntry resolvedEntry) { try { return computeProjectFragments( new IBuildpathEntry[] { resolvedEntry }, false, // don't // retrieve // exported // roots null /* no reverse map */ ); } catch (ModelException e) { return new IProjectFragment[] {}; } } @Override public int getElementType() { return SCRIPT_PROJECT; } @Override protected boolean buildStructure(OpenableElementInfo info, IProgressMonitor pm, Map newElements, IResource underlyingResource) throws ModelException { // check whether the dltk project can be opened if (!underlyingResource.isAccessible()) { throw newNotPresentException(); } // cannot refresh bp markers on opening (emulate cp check on startup) // since can create deadlocks (see bug 37274) IBuildpathEntry[] resolvedBuildpath = getResolvedBuildpath(); // compute the project fragements IProjectFragment[] children = computeProjectFragments(resolvedBuildpath, false, null); setProjectInfoChildren(info, children); // remember the timestamps of external libraries the first time they are // looked up getPerProjectInfo().rememberExternalLibTimestamps(); return true; } private void setProjectInfoChildren(OpenableElementInfo info, IProjectFragment[] children) { List<IModelElement> fragments = new ArrayList<>(); Collections.addAll(fragments, children); // Call for extra model providers IDLTKLanguageToolkit toolkit = DLTKLanguageManager .getLanguageToolkit(this); if (toolkit != null) { IModelProvider[] providers = ModelProviderManager .getProviders(toolkit.getNatureId()); if (providers != null) { for (int i = 0; i < providers.length; i++) { providers[i].provideModelChanges(this, fragments); } } } info.setChildren( fragments.toArray(new IModelElement[fragments.size()])); } public ModelManager.PerProjectInfo getPerProjectInfo() throws ModelException { return ModelManager.getModelManager() .getPerProjectInfoCheckExistence(this.project); } /** * Returns (local/all) the project fragments identified by the given * project's buildpath. Note: this follows project buildpath references to * find required project contributions, eliminating duplicates silently. * Only works with resolved entries * * @param resolvedBuildpath * IBuildpathEntry[] * @param retrieveExportedRoots * boolean * @return IProjectFragment[] * @throws ModelException */ public IProjectFragment[] computeProjectFragments( IBuildpathEntry[] resolvedBuildpath, boolean retrieveExportedRoots, Map<IProjectFragment, BuildpathEntry> rootToResolvedEntries) throws ModelException { List<IProjectFragment> accumulatedRoots = new ArrayList<>(); computeProjectFragments(resolvedBuildpath, accumulatedRoots, new HashSet<String>(5), // rootIDs null, // inside original project true, // check existency retrieveExportedRoots, rootToResolvedEntries); return accumulatedRoots .toArray(new IProjectFragment[accumulatedRoots.size()]); } /** * Returns (local/all) the package fragment roots identified by the given * project's buildpath. Note: this follows project buildpath references to * find required project contributions, eliminating duplicates silently. * Only works with resolved entries * * @param resolvedBuildpath * IBuildpathEntry[] * @param accumulatedRoots * List * @param rootIDs * HashSet * @param referringEntry * project entry referring to this CP or null if initial project * @param checkExistency * boolean * @param retrieveExportedRoots * boolean * @throws ModelException */ public void computeProjectFragments(IBuildpathEntry[] resolvedBuildpath, List<IProjectFragment> accumulatedRoots, Set<String> rootIDs, IBuildpathEntry referringEntry, boolean checkExistency, boolean retrieveExportedRoots, Map<IProjectFragment, BuildpathEntry> rootToResolvedEntries) throws ModelException { if (referringEntry == null) { rootIDs.add(rootID()); } for (int i = 0, length = resolvedBuildpath.length; i < length; i++) { computeProjectFragments(resolvedBuildpath[i], accumulatedRoots, rootIDs, referringEntry, checkExistency, retrieveExportedRoots, rootToResolvedEntries); } } /** * Returns the package fragment roots identified by the given entry. In case * it refers to a project, it will follow its buildpath so as to find * exported roots as well. Only works with resolved entry * * @param resolvedEntry * IBuildpathEntry * @param accumulatedRoots * List * @param rootIDs * HashSet * @param referringEntry * the BP entry (project) referring to this entry, or null if * initial project * @param checkExistency * boolean * @param retrieveExportedRoots * boolean * @throws ModelException */ public void computeProjectFragments(IBuildpathEntry resolvedEntry, List<IProjectFragment> accumulatedRoots, Set<String> rootIDs, IBuildpathEntry referringEntry, boolean checkExistency, boolean retrieveExportedRoots, Map<IProjectFragment, BuildpathEntry> rootToResolvedEntries) throws ModelException { String rootID = ((BuildpathEntry) resolvedEntry).rootID(); if (rootIDs.contains(rootID)) return; IPath projectPath = this.project.getFullPath(); IPath entryPath = resolvedEntry.getPath(); IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); IProjectFragment root = null; switch (resolvedEntry.getEntryKind()) { // source folder case IBuildpathEntry.BPE_SOURCE: if (projectPath.isPrefixOf(entryPath)) { if (checkExistency) { Object target = Model.getTarget(workspaceRoot, entryPath, checkExistency); if (target == null) return; if (target instanceof IFolder || target instanceof IProject) { root = getProjectFragment((IResource) target); } } else { root = getFolderProjectFragment(entryPath); } } break; // internal/external ZIP or folder case IBuildpathEntry.BPE_LIBRARY: if (referringEntry != null && !resolvedEntry.isExported()) return; root = delegatedCreateProjectFragment(resolvedEntry); if (root != null) { break; } if (checkExistency) { if (entryPath.segmentCount() > 0 && entryPath.segment(0).startsWith( IBuildpathEntry.BUILTIN_EXTERNAL_ENTRY_STR) && BuiltinProjectFragment.isSupported(this)) { root = new BuiltinProjectFragment(entryPath, this); break; } Object target = Model.getTarget(workspaceRoot, entryPath, checkExistency); if (target == null) return; if (!resolvedEntry.isExternal()) { if (target instanceof IResource) { // internal target root = getProjectFragment((IResource) target); } } else {// external target // This is external folder or zip. if (Model.isFile(target) && (org.eclipse.dltk.compiler.util.Util .isArchiveFileName( DLTKLanguageManager .getLanguageToolkit(this), entryPath.lastSegment()))) { // root = new ArchiveProjectFragment(entryPath, this); root = getProjectFragment0(entryPath); } else { root = new ExternalProjectFragment(entryPath, this, true, true); } } } else { root = getProjectFragment(entryPath); } break; // recurse into required project case IBuildpathEntry.BPE_PROJECT: if (!retrieveExportedRoots) return; if (referringEntry != null && !resolvedEntry.isExported()) return; IResource member = workspaceRoot.findMember(entryPath); if (member != null && member.getType() == IResource.PROJECT) {// double /* * check if bound to project (23977) */ IProject requiredProjectRsc = (IProject) member; if (ScriptProject.hasScriptNature(requiredProjectRsc)) { rootIDs.add(rootID); ScriptProject requiredProject = (ScriptProject) DLTKCore .create(requiredProjectRsc); requiredProject.computeProjectFragments( requiredProject.getResolvedBuildpath(), accumulatedRoots, rootIDs, rootToResolvedEntries == null ? resolvedEntry : ((BuildpathEntry) resolvedEntry) .combineWith( (BuildpathEntry) referringEntry), checkExistency, retrieveExportedRoots, rootToResolvedEntries); } } break; } if (root != null) { accumulatedRoots.add(root); rootIDs.add(rootID); if (rootToResolvedEntries != null) rootToResolvedEntries.put(root, ((BuildpathEntry) resolvedEntry) .combineWith((BuildpathEntry) referringEntry)); } } private IProjectFragment delegatedCreateProjectFragment( IBuildpathEntry resolvedEntry) { final IDLTKLanguageToolkit toolkit = getLanguageToolkit(); if (toolkit != null) { final IProjectFragmentFactory[] factories = ModelProviderManager .getProjectFragmentFactories(toolkit.getNatureId()); if (factories != null) { for (IProjectFragmentFactory factory : factories) { final IProjectFragment fragment = factory.create(this, resolvedEntry); if (fragment != null) { return fragment; } } } } return null; } public String[] projectPrerequisites(IBuildpathEntry[] entries) throws ModelException { ArrayList<String> prerequisites = new ArrayList<>(); for (int i = 0, length = entries.length; i < length; i++) { IBuildpathEntry entry = entries[i]; if (entry.getEntryKind() == IBuildpathEntry.BPE_PROJECT) { prerequisites.add(entry.getPath().lastSegment()); } } int size = prerequisites.size(); if (size == 0) { return NO_PREREQUISITES; } else { String[] result = new String[size]; prerequisites.toArray(result); return result; } } /** * Returns a default build path. This is the root of the project */ protected IBuildpathEntry[] defaultBuildpath() { return new IBuildpathEntry[] { DLTKCore.newSourceEntry(this.project.getFullPath()) }; } /* * Resolve the given perProjectInfo's raw buildpath and store the resolved * buildpath in the perProjectInfo. */ public void resolveBuildpath(PerProjectInfo perProjectInfo, boolean usePreviousSession, boolean addBuildpathChange) throws ModelException { if (BP_RESOLUTION_BP_LISTENERS != null) breakpoint(1, this); ModelManager manager = ModelManager.getModelManager(); boolean isBuildpathBeingResolved = manager .isBuildpathBeingResolved(this); try { if (!isBuildpathBeingResolved) { manager.setBuildpathBeingResolved(this, true); } // get raw info inside a synchronized block to ensure that it is // consistent IBuildpathEntry[] classpath; int timeStamp; synchronized (perProjectInfo) { classpath = perProjectInfo.rawBuildpath; if (classpath == null) classpath = perProjectInfo.readAndCacheBuildpath(this); timeStamp = perProjectInfo.rawTimeStamp; } ResolvedBuildpath result = resolveBuildpath(classpath, usePreviousSession, true/* * resolve chained libraries */); if (BP_RESOLUTION_BP_LISTENERS != null) breakpoint(2, this); // store resolved info along with the raw info to ensure consistency perProjectInfo.setResolvedBuildpath(result.resolvedClasspath, result.rawReverseMap, result.rootPathToResolvedEntries, usePreviousSession ? PerProjectInfo.NEED_RESOLUTION : result.unresolvedEntryStatus, timeStamp, addBuildpathChange); } finally { if (!isBuildpathBeingResolved) { manager.setBuildpathBeingResolved(this, false); } if (BP_RESOLUTION_BP_LISTENERS != null) breakpoint(3, this); } } /** * Answers an ID which is used to distinguish project/entries during project * fragment root computations * * @return String */ public String rootID() { return "[PRJ]" + this.project.getFullPath(); //$NON-NLS-1$ } @Override protected Object createElementInfo() { return new ProjectElementInfo(); } @Override public IResource getResource() { return project; } @Override public IPath getPath() { return project.getFullPath(); } /** * Returns a canonicalized path from the given external path. Note that the * return path contains the same number of segments and it contains a device * only if the given path contained one. * * @param externalPath * IPath * @see java.io.File for the definition of a canonicalized path * @return IPath */ public static IPath canonicalizedPath(IPath externalPath) { if (externalPath == null) return null; if (IS_CASE_SENSITIVE) { return externalPath; } // if not external path, return original path IWorkspace workspace = ResourcesPlugin.getWorkspace(); if (workspace == null) return externalPath; // protection during shutdown (30487) if (workspace.getRoot().findMember(externalPath) != null) { return externalPath; } // TODO Check this return externalPath; } /** * Convenience method that returns the specific type of info for a project. */ protected ProjectElementInfo getProjectElementInfo() throws ModelException { return (ProjectElementInfo) getElementInfo(); } /* * Resets this project's caches */ public void resetCaches() { ProjectElementInfo info = (ProjectElementInfo) ModelManager .getModelManager().peekAtInfo(this); if (info != null) { info.resetCaches(); } } public BuildpathChange resetResolvedBuildpath() { try { return getPerProjectInfo().resetResolvedBuildpath(); } catch (ModelException e) { // project doesn't exist return null; } } /* * Resolve the given raw classpath. */ public IBuildpathEntry[] resolveBuildpath(IBuildpathEntry[] rawClasspath) throws ModelException { return resolveBuildpath(rawClasspath, false/* * don't use previous session */, true/* * resolve chained libraries */).resolvedClasspath; } static class ResolvedBuildpath { IBuildpathEntry[] resolvedClasspath; IModelStatus unresolvedEntryStatus = ModelStatus.VERIFIED_OK; HashMap<IPath, IBuildpathEntry> rawReverseMap = new HashMap<>(); Map<IPath, IBuildpathEntry> rootPathToResolvedEntries = new HashMap<>(); } public ResolvedBuildpath resolveBuildpath(IBuildpathEntry[] rawClasspath, boolean usePreviousSession, boolean resolveChainedLibraries) throws ModelException { ModelManager manager = ModelManager.getModelManager(); ExternalFoldersManager externalFoldersManager = ModelManager .getExternalManager(); ResolvedBuildpath result = new ResolvedBuildpath(); Map<String, Boolean> knownDrives = new HashMap<>(); Map referencedEntriesMap = new HashMap(); List<IPath> rawLibrariesPath = new ArrayList<>(); LinkedHashSet<IBuildpathEntry> resolvedEntries = new LinkedHashSet<>(); if (resolveChainedLibraries) { for (int index = 0; index < rawClasspath.length; index++) { IBuildpathEntry currentEntry = rawClasspath[index]; if (currentEntry .getEntryKind() == IBuildpathEntry.BPE_LIBRARY) { rawLibrariesPath.add(BuildpathEntry.resolveDotDot( getProject().getLocation(), currentEntry.getPath())); } } } int length = rawClasspath.length; for (int i = 0; i < length; i++) { IBuildpathEntry rawEntry = rawClasspath[i]; IBuildpathEntry resolvedEntry = rawEntry; switch (rawEntry.getEntryKind()) { case IBuildpathEntry.BPE_VARIABLE: try { resolvedEntry = DLTKCore.getResolvedBuildpathEntry( rawEntry /* usePreviousSession */); } catch (/* ClasspathEntry. */AssertionFailedException e) { // Catch the assertion failure and set status instead // see bug // https://bugs.eclipse.org/bugs/show_bug.cgi?id=55992 result.unresolvedEntryStatus = new ModelStatus( IModelStatusConstants.INVALID_PATH, e.getMessage()); break; } if (resolvedEntry == null) { result.unresolvedEntryStatus = new ModelStatus( IModelStatusConstants.BP_VARIABLE_PATH_UNBOUND, this, rawEntry.getPath()); } else { // If the entry is already present in the rawReversetMap, it // means the entry and the chained libraries // have already been processed. So, skip it. if (resolveChainedLibraries && resolvedEntry .getEntryKind() == IBuildpathEntry.BPE_LIBRARY && result.rawReverseMap .get(resolvedEntry.getPath()) == null) { // resolve Class-Path: in manifest BuildpathEntry[] extraEntries = ((BuildpathEntry) resolvedEntry) .resolvedChainedLibraries(); for (int j = 0, length2 = extraEntries.length; j < length2; j++) { if (!rawLibrariesPath .contains(extraEntries[j].getPath())) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=305037 // referenced entries for variable entries could // also be persisted with extra attributes, so // addAsChainedEntry = true addToResult(rawEntry, extraEntries[j], result, resolvedEntries, externalFoldersManager, referencedEntriesMap, true, knownDrives); } } } addToResult(rawEntry, resolvedEntry, result, resolvedEntries, externalFoldersManager, referencedEntriesMap, false, knownDrives); } break; case IBuildpathEntry.BPE_CONTAINER: IBuildpathContainer container = usePreviousSession ? manager.getPreviousSessionContainer( rawEntry.getPath(), this) : DLTKCore.getBuildpathContainer(rawEntry.getPath(), this); if (container == null) { result.unresolvedEntryStatus = new ModelStatus( IModelStatusConstants.BP_CONTAINER_PATH_UNBOUND, this, rawEntry.getPath()); break; } IBuildpathEntry[] containerEntries = container .getBuildpathEntries(); if (containerEntries == null) { if (ModelManager.BP_RESOLVE_VERBOSE /* || ModelManager.BP_RESOLVE_VERBOSE_FAILURE */) { ModelManager.getModelManager() .verbose_missbehaving_container_null_entries( this, rawEntry.getPath()); } break; } // container was bound for (int j = 0, containerLength = containerEntries.length; j < containerLength; j++) { BuildpathEntry cEntry = (BuildpathEntry) containerEntries[j]; if (cEntry == null) { if (ModelManager.BP_RESOLVE_VERBOSE /* || ModelManager.CP_RESOLVE_VERBOSE_FAILURE */) { ModelManager.getModelManager() .verbose_missbehaving_container(this, rawEntry.getPath(), containerEntries); } break; } // if container is exported or restricted, then its nested // entries must in turn be exported (21749) and/or propagate // restrictions cEntry = cEntry.combineWith((BuildpathEntry) rawEntry); if (cEntry.getEntryKind() == IBuildpathEntry.BPE_LIBRARY) { // resolve ".." in library path cEntry = cEntry .resolvedDotDot(getProject().getLocation()); // https://bugs.eclipse.org/bugs/show_bug.cgi?id=313965 // Do not resolve if the system attribute is set to // false if (resolveChainedLibraries && ModelManager .getModelManager().resolveReferencedLibrariesForContainers && result.rawReverseMap .get(cEntry.getPath()) == null) { // resolve Class-Path: in manifest BuildpathEntry[] extraEntries = cEntry .resolvedChainedLibraries(); for (int k = 0, length2 = extraEntries.length; k < length2; k++) { if (!rawLibrariesPath .contains(extraEntries[k].getPath())) { addToResult(rawEntry, extraEntries[k], result, resolvedEntries, externalFoldersManager, referencedEntriesMap, false, knownDrives); } } } } addToResult(rawEntry, cEntry, result, resolvedEntries, externalFoldersManager, referencedEntriesMap, false, knownDrives); } break; case IBuildpathEntry.BPE_LIBRARY: // resolve ".." in library path resolvedEntry = ((BuildpathEntry) rawEntry) .resolvedDotDot(getProject().getLocation()); if (resolveChainedLibraries && result.rawReverseMap .get(resolvedEntry.getPath()) == null) { // resolve Class-Path: in manifest BuildpathEntry[] extraEntries = ((BuildpathEntry) resolvedEntry) .resolvedChainedLibraries(); for (int k = 0, length2 = extraEntries.length; k < length2; k++) { if (!rawLibrariesPath .contains(extraEntries[k].getPath())) { addToResult(rawEntry, extraEntries[k], result, resolvedEntries, externalFoldersManager, referencedEntriesMap, true, knownDrives); } } } addToResult(rawEntry, resolvedEntry, result, resolvedEntries, externalFoldersManager, referencedEntriesMap, false, knownDrives); break; default: addToResult(rawEntry, resolvedEntry, result, resolvedEntries, externalFoldersManager, referencedEntriesMap, false, knownDrives); break; } } result.resolvedClasspath = new IBuildpathEntry[resolvedEntries.size()]; resolvedEntries.toArray(result.resolvedClasspath); return result; } private void addToResult(IBuildpathEntry rawEntry, IBuildpathEntry resolvedEntry, ResolvedBuildpath result, LinkedHashSet<IBuildpathEntry> resolvedEntries, ExternalFoldersManager externalFoldersManager, Map oldChainedEntriesMap, boolean addAsChainedEntry, Map<String, Boolean> knownDrives) { IPath resolvedPath; // If it's already been resolved, do not add to resolvedEntries if (result.rawReverseMap .get(resolvedPath = resolvedEntry.getPath()) == null) { result.rawReverseMap.put(resolvedPath, rawEntry); result.rootPathToResolvedEntries.put(resolvedPath, resolvedEntry); resolvedEntries.add(resolvedEntry); if (addAsChainedEntry) { IBuildpathEntry chainedEntry = null; chainedEntry = (BuildpathEntry) oldChainedEntriesMap .get(resolvedPath); if (chainedEntry != null) { // This is required to keep the attributes if any added by // the user in // the previous session such as source attachment path etc. copyFromOldChainedEntry((BuildpathEntry) resolvedEntry, (BuildpathEntry) chainedEntry); } } } if (resolvedEntry.getEntryKind() == IBuildpathEntry.BPE_LIBRARY && ExternalFoldersManager.isExternalFolderPath(resolvedPath)) { externalFoldersManager.addFolder(resolvedPath, true/* scheduleForCreation */); // no-op // if // not // an // external // folder // or // if // already // registered } // https://bugs.eclipse.org/bugs/show_bug.cgi?id=336046 // The source attachment path could be external too and in which case, // must be added. IPath sourcePath = resolvedEntry.getSourceAttachmentPath(); if (sourcePath != null && driveExists(sourcePath, knownDrives) && ExternalFoldersManager.isExternalFolderPath(sourcePath)) { externalFoldersManager.addFolder(sourcePath, true); } } private void copyFromOldChainedEntry(BuildpathEntry resolvedEntry, BuildpathEntry chainedEntry) { IPath path = chainedEntry.getSourceAttachmentPath(); if (path != null) { resolvedEntry.sourceAttachmentPath = path; } path = chainedEntry.getSourceAttachmentRootPath(); if (path != null) { resolvedEntry.sourceAttachmentRootPath = path; } IBuildpathAttribute[] attributes = chainedEntry.getExtraAttributes(); if (attributes != null) { resolvedEntry.extraAttributes = attributes; } } /* * File#exists() takes lot of time for an unmapped drive. Hence, cache the * info. https://bugs.eclipse.org/bugs/show_bug.cgi?id=338649 */ private boolean driveExists(IPath sourcePath, Map<String, Boolean> knownDrives) { String drive = sourcePath.getDevice(); if (drive == null) return true; Boolean good = knownDrives.get(drive); if (good == null) { if (new File(drive).exists()) { knownDrives.put(drive, Boolean.TRUE); return true; } else { knownDrives.put(drive, Boolean.FALSE); return false; } } return good.booleanValue(); } /** * Reset the collection of project fragments (local ones) - only if opened. */ public void updateProjectFragments() { if (this.isOpen()) { try { ProjectElementInfo info = getProjectElementInfo(); computeChildren(info); info.resetCaches(); // discard caches (hold onto roots and pkg // fragments) } catch (ModelException e) { try { close(); // could not do better } catch (ModelException ex) { // ignore } } } } /** * Computes the collection of project fragments (local ones) and set it on * the given info. Need to check *all* project fragments in order to reset * NameLookup * * @param info * ProjectElementInfo * @throws ModelException */ public void computeChildren(ProjectElementInfo info) throws ModelException { IBuildpathEntry[] buildpath = getResolvedBuildpath(); ProjectElementInfo.ProjectCache projectCache = info.projectCache; if (projectCache != null) { IProjectFragment[] newRoots = computeProjectFragments(buildpath, true, null /* * no reverse map */); checkIdentical: { // compare all pkg fragment root lists IProjectFragment[] oldRoots = projectCache.allProjectFragmentCache; if (oldRoots.length == newRoots.length) { for (int i = 0, length = oldRoots.length; i < length; i++) { if (!oldRoots[i].equals(newRoots[i])) { break checkIdentical; } } return; // no need to update } } } info.setForeignResources(null); IProjectFragment[] fragments = computeProjectFragments(buildpath, false, null); setProjectInfoChildren(info, fragments); } @Override public IBuildpathEntry[] getRawBuildpath() throws ModelException { PerProjectInfo perProjectInfo = getPerProjectInfo(); IBuildpathEntry[] classpath = perProjectInfo.rawBuildpath; if (classpath != null) return classpath; classpath = perProjectInfo.readAndCacheBuildpath(this); if (classpath == ScriptProject.INVALID_BUILDPATH) return defaultBuildpath(); return classpath; } /** * Record a new marker denoting a buildpath problem */ void createBuildpathProblemMarker(IModelStatus status) { IMarker marker = null; int severity; String[] arguments = CharOperation.NO_STRINGS; boolean isCycleProblem = false, isBuildpathFileFormatProblem = false; if (DLTKCore.PLUGIN_ID.equals(status.getPlugin())) { switch (status.getCode()) { case IModelStatusConstants.BUILDPATH_CYCLE: isCycleProblem = true; if (DLTKCore.ERROR.equals( getOption(DLTKCore.CORE_CIRCULAR_BUILDPATH, true))) { severity = IMarker.SEVERITY_ERROR; } else { severity = IMarker.SEVERITY_WARNING; } break; case IModelStatusConstants.INVALID_BUILDPATH_FILE_FORMAT: isBuildpathFileFormatProblem = true; severity = IMarker.SEVERITY_ERROR; break; default: IPath path = status.getPath(); if (path != null) { arguments = new String[] { path.toString() }; } if (DLTKCore.ERROR.equals( getOption(DLTKCore.CORE_INCOMPLETE_BUILDPATH, true))) { severity = IMarker.SEVERITY_ERROR; } else { severity = IMarker.SEVERITY_WARNING; } break; } } else { severity = status.getSeverity() == IStatus.ERROR ? IMarker.SEVERITY_ERROR : IMarker.SEVERITY_WARNING; } try { marker = this.project .createMarker(IModelMarker.BUILDPATH_PROBLEM_MARKER); marker.setAttributes( new String[] { IMarker.MESSAGE, IMarker.SEVERITY, IMarker.LOCATION, IModelMarker.CYCLE_DETECTED, IModelMarker.BUILDPATH_FILE_FORMAT, IModelMarker.ID, IModelMarker.ARGUMENTS, }, new Object[] { status.getMessage(), Integer.valueOf(severity), Messages.buildpath_buildPath, Boolean.toString(isCycleProblem), Boolean.toString(isBuildpathFileFormatProblem), Integer.valueOf(status.getCode()), Util.getProblemArgumentsForMarker(arguments), }); } catch (CoreException e) { // could not create marker: cannot do much if (ModelManager.VERBOSE) { e.printStackTrace(); } } } /** * Remove all markers denoting buildpath problems */ protected void flushBuildpathProblemMarkers(boolean flushCycleMarkers, boolean flushBuildpathFormatMarkers) { try { if (this.project.isAccessible()) { IMarker[] markers = this.project.findMarkers( IModelMarker.BUILDPATH_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); for (int i = 0, length = markers.length; i < length; i++) { IMarker marker = markers[i]; if (flushCycleMarkers && flushBuildpathFormatMarkers) { marker.delete(); } else { String cycleAttr = (String) marker .getAttribute(IModelMarker.CYCLE_DETECTED); String buildpathFileFormatAttr = (String) marker .getAttribute( IModelMarker.BUILDPATH_FILE_FORMAT); if ((flushCycleMarkers == (cycleAttr != null && cycleAttr.equals("true"))) //$NON-NLS-1$ && (flushBuildpathFormatMarkers == (buildpathFileFormatAttr != null && buildpathFileFormatAttr .equals("true")))) { //$NON-NLS-1$ marker.delete(); } } } } } catch (CoreException e) { // could not flush markers: not much we can do if (ModelManager.VERBOSE) { e.printStackTrace(); } } } /* * Reads and decode an XML buildpath string */ public IBuildpathEntry[] decodeBuildpath(String xmlBuildpath, Map unknownElements) throws IOException, AssertionFailedException { ArrayList<IBuildpathEntry> paths = new ArrayList<>(); StringReader reader = new StringReader(xmlBuildpath); Element cpElement; try { DocumentBuilder parser = DocumentBuilderFactory.newInstance() .newDocumentBuilder(); cpElement = parser.parse(new InputSource(reader)) .getDocumentElement(); } catch (SAXException e) { throw new IOException(Messages.file_badFormat); } catch (ParserConfigurationException e) { throw new IOException(Messages.file_badFormat); } finally { reader.close(); } if (!cpElement.getNodeName().equalsIgnoreCase("buildpath")) { //$NON-NLS-1$ throw new IOException(Messages.file_badFormat); } NodeList list = cpElement.getElementsByTagName("buildpathentry"); //$NON-NLS-1$ int length = list.getLength(); for (int i = 0; i < length; ++i) { Node node = list.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { IBuildpathEntry entry = BuildpathEntry .elementDecode((Element) node, this, unknownElements); if (entry != null) { paths.add(entry); } } } // return a new empty buildpath is it size is 0, to differenciate from // an INVALID_BUILDPATH int pathSize = paths.size(); IBuildpathEntry[] entries = new IBuildpathEntry[pathSize]; paths.toArray(entries); return entries; } @Override public IBuildpathEntry decodeBuildpathEntry(String encodedEntry) { try { if (encodedEntry == null) return null; StringReader reader = new StringReader(encodedEntry); Element node; try { DocumentBuilder parser = DocumentBuilderFactory.newInstance() .newDocumentBuilder(); node = parser.parse(new InputSource(reader)) .getDocumentElement(); } catch (SAXException e) { return null; } catch (ParserConfigurationException e) { return null; } finally { reader.close(); } if (!node.getNodeName().equalsIgnoreCase("buildpathentry") //$NON-NLS-1$ || node.getNodeType() != Node.ELEMENT_NODE) { return null; } return BuildpathEntry.elementDecode(node, this, null/* * not interested in unknown elements */); } catch (IOException e) { // bad format return null; } } /** * Retrieve a shared property on a project. If the property is not defined, * answers null. Note that it is orthogonal to IResource persistent * properties, and client code has to decide which form of storage to use * appropriately. Shared properties produce real resource files which can be * shared through a VCM onto a server. Persistent properties are not * shareable. * * @param key * String * @see IScriptProject#setSharedProperty(String, String) * @return String * @throws CoreException */ public String getSharedProperty(String key) throws CoreException { String property = null; IFile rscFile = this.project.getFile(key); if (rscFile.exists()) { byte[] bytes = Util.getResourceContentsAsByteArray(rscFile); property = new String(bytes, StandardCharsets.UTF_8); // .buildpath always encoded with UTF-8 } else { // when a project is imported, we get a first delta for the addition // of the .project, but the .buildpath is not accessible // so default to using java.io.File // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=96258 URI location = rscFile.getLocationURI(); if (location != null) { File file = Util.toLocalFile(location, null/* * no progress monitor available */); if (file != null && file.exists()) { byte[] bytes; try { bytes = org.eclipse.dltk.compiler.util.Util .getFileByteContent(file); } catch (IOException e) { return null; } property = new String(bytes, StandardCharsets.UTF_8); // .buildpath always encoded with UTF-8 } } } return property; } @Override public IProjectFragment[] getProjectFragments() throws ModelException { Object[] children; int length; IProjectFragment[] roots; System.arraycopy(children = getChildren(), 0, roots = new IProjectFragment[length = children.length], 0, length); return roots; } /** * Saves the buildpath in a shareable format (VCM-wise) only when necessary, * that is, if it is semantically different from the existing one in file. * Will never write an identical one. * * @param newBuildpath * IBuildpathEntry[] * @param newOutputLocation * IPath * @return boolean Return whether the .buildpath file was modified. * @throws ModelException */ public boolean writeFileEntries(IBuildpathEntry[] newBuildpath) throws ModelException { if (!this.project.isAccessible()) return false; Map unknownElements = new HashMap(); IBuildpathEntry[] fileEntries = readFileEntries(unknownElements); if (fileEntries != INVALID_BUILDPATH && areBuildpathsEqual(newBuildpath, fileEntries) && this.project.getFile(BUILDPATH_FILENAME).exists()) { // no need to save it, it is the same return false; } // actual file saving try { setSharedProperty(BUILDPATH_FILENAME, encodeBuildpath(newBuildpath, true, unknownElements)); return true; } catch (CoreException e) { throw new ModelException(e); } } /** * Record a shared persistent property onto a project. Note that it is * orthogonal to IResource persistent properties, and client code has to * decide which form of storage to use appropriately. Shared properties * produce real resource files which can be shared through a VCM onto a * server. Persistent properties are not shareable. * * shared properties end up in resource files, and thus cannot be modified * during delta notifications (a CoreException would then be thrown). * * @param key * String * @param value * String * @see IScriptProject#getSharedProperty(String key) * @throws CoreException */ public void setSharedProperty(String key, String value) throws CoreException { IFile rscFile = this.project.getFile(key); byte[] bytes = value.getBytes(StandardCharsets.UTF_8); // .buildpath always encoded with UTF-8 InputStream inputStream = new ByteArrayInputStream(bytes); // update the resource content if (rscFile.exists()) { if (rscFile.isReadOnly()) { // provide opportunity to checkout read-only .buildpath file // (23984) ResourcesPlugin.getWorkspace() .validateEdit(new IFile[] { rscFile }, null); } rscFile.setContents(inputStream, IResource.FORCE, null); } else { rscFile.create(inputStream, IResource.FORCE, null); } } /** * Returns the XML String encoding of the buildpath. */ protected String encodeBuildpath(IBuildpathEntry[] buildpath, boolean indent, Map unknownElements) throws ModelException { try { ByteArrayOutputStream s = new ByteArrayOutputStream(); OutputStreamWriter writer = new OutputStreamWriter(s, StandardCharsets.UTF_8); XMLWriter xmlWriter = new XMLWriter(writer, this, true/* * print XML version */); xmlWriter.startTag(BuildpathEntry.TAG_BUILDPATH, indent); for (int i = 0; i < buildpath.length; ++i) { ((BuildpathEntry) buildpath[i]).elementEncode(xmlWriter, this.project.getFullPath(), indent, true, unknownElements); } xmlWriter.endTag(BuildpathEntry.TAG_BUILDPATH, indent, true/* * insert new line */); writer.flush(); writer.close(); return s.toString("UTF8");//$NON-NLS-1$ } catch (IOException e) { throw new ModelException(e, IModelStatusConstants.IO_EXCEPTION); } } @Override public String encodeBuildpathEntry(IBuildpathEntry buildpathEntry) { try { ByteArrayOutputStream s = new ByteArrayOutputStream(); OutputStreamWriter writer = new OutputStreamWriter(s, "UTF8"); //$NON-NLS-1$ XMLWriter xmlWriter = new XMLWriter(writer, this, false/* * don't print XML version */); ((BuildpathEntry) buildpathEntry).elementEncode(xmlWriter, this.project.getFullPath(), true/* indent */, true/* * insert new line */, null/* * not interested in unknown elements */); writer.flush(); writer.close(); return s.toString("UTF8");//$NON-NLS-1$ } catch (IOException e) { return null; // never happens since all is done in memory } } /** * @see IScriptProject */ public boolean hasBuildpathCycle(IBuildpathEntry[] preferredBuildpath) { HashSet<IPath> cycleParticipants = new HashSet<>(); HashMap<ScriptProject, IBuildpathEntry[]> preferredBuildpaths = new HashMap<>( 1); preferredBuildpaths.put(this, preferredBuildpath); updateCycleParticipants(new ArrayList<IPath>(2), cycleParticipants, ResourcesPlugin.getWorkspace().getRoot(), new HashSet<IPath>(2), preferredBuildpaths); return !cycleParticipants.isEmpty(); } public boolean hasCycleMarker() { return this.getCycleMarker() != null; } @Override public int hashCode() { return this.project.hashCode(); } private boolean hasUTF8BOM(byte[] bytes) { if (bytes.length > IContentDescription.BOM_UTF_8.length) { for (int i = 0, length = IContentDescription.BOM_UTF_8.length; i < length; i++) { if (IContentDescription.BOM_UTF_8[i] != bytes[i]) return false; } return true; } return false; } /* * Returns the cycle marker associated with this project or null if none. */ public IMarker getCycleMarker() { try { if (this.project.isAccessible()) { IMarker[] markers = this.project.findMarkers( IModelMarker.BUILDPATH_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); for (int i = 0, length = markers.length; i < length; i++) { IMarker marker = markers[i]; String cycleAttr = (String) marker .getAttribute(IModelMarker.CYCLE_DETECTED); if (cycleAttr != null && cycleAttr.equals("true")) { //$NON-NLS-1$ return marker; } } } } catch (CoreException e) { // could not get markers: return null } return null; } @Override public String getOption(String optionName, boolean inheritCoreOptions) { String propertyName = optionName; if (ModelManager.getModelManager().optionNames.contains(propertyName)) { IEclipsePreferences projectPreferences = getEclipsePreferences(); String javaCoreDefault = inheritCoreOptions ? DLTKCore.getOption(propertyName) : null; if (projectPreferences == null) return javaCoreDefault; String value = projectPreferences.get(propertyName, javaCoreDefault); return value == null ? null : value.trim(); } return null; } @Override public Map<String, String> getOptions(boolean inheritCoreOptions) { // initialize to the defaults from DLTKCore options pool Map<String, String> options = inheritCoreOptions ? DLTKCore.getOptions() : new Hashtable<>(5); // Get project specific options ModelManager.PerProjectInfo perProjectInfo = null; Hashtable<String, String> projectOptions = null; HashSet<String> optionNames = ModelManager .getModelManager().optionNames; try { perProjectInfo = getPerProjectInfo(); projectOptions = perProjectInfo.options; if (projectOptions == null) { // get eclipse preferences IEclipsePreferences projectPreferences = getEclipsePreferences(); if (projectPreferences == null) return options; // cannot do better (non-script project) // create project options String[] propertyNames = projectPreferences.keys(); projectOptions = new Hashtable<>(propertyNames.length); for (int i = 0; i < propertyNames.length; i++) { String propertyName = propertyNames[i]; String value = projectPreferences.get(propertyName, null); if (value != null && optionNames.contains(propertyName)) { projectOptions.put(propertyName, value.trim()); } } // cache project options perProjectInfo.options = projectOptions; } } catch (ModelException jme) { projectOptions = new Hashtable<>(); } catch (BackingStoreException e) { projectOptions = new Hashtable<>(); } // Inherit from DLTKCore options if specified if (inheritCoreOptions) { for (String propertyName : projectOptions.keySet()) { String propertyValue = projectOptions.get(propertyName); if (propertyValue != null && optionNames.contains(propertyName)) { options.put(propertyName, propertyValue.trim()); } } return options; } return projectOptions; } @Override public void setOption(String optionName, String optionValue) { if (!ModelManager.getModelManager().optionNames.contains(optionName)) return; // unrecognized option if (optionValue == null) return; // invalid value IEclipsePreferences projectPreferences = getEclipsePreferences(); String defaultValue = DLTKCore.getOption(optionName); if (optionValue.equals(defaultValue)) { // set default value => remove preference projectPreferences.remove(optionName); } else { projectPreferences.put(optionName, optionValue); } // Dump changes try { projectPreferences.flush(); } catch (BackingStoreException e) { // problem with pref store - quietly ignore } } @Override public void setOptions(Map newOptions) { IEclipsePreferences projectPreferences = getEclipsePreferences(); try { if (newOptions == null) { projectPreferences.clear(); } else { Iterator keys = newOptions.keySet().iterator(); while (keys.hasNext()) { String key = (String) keys.next(); if (!ModelManager.getModelManager().optionNames .contains(key)) continue; // unrecognized option // no filtering for encoding (custom encoding for project is // allowed) String value = (String) newOptions.get(key); projectPreferences.put(key, value); } // reset to default all options not in new map // @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=26255 // @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=49691 String[] pNames = projectPreferences.keys(); int ln = pNames.length; for (int i = 0; i < ln; i++) { String key = pNames[i]; if (!newOptions.containsKey(key)) { projectPreferences.remove(key); // old preferences => // remove from // preferences table } } } // persist options projectPreferences.flush(); // flush cache immediately try { getPerProjectInfo().options = null; } catch (ModelException e) { // do nothing } } catch (BackingStoreException e) { // problem with pref store - quietly ignore } } /** * @see IScriptProject#setRawClasspath(IBuildpathEntry[],IPath,IProgressMonitor) */ @Override public void setRawBuildpath(IBuildpathEntry[] entries, IProgressMonitor monitor) throws ModelException { setRawBuildpath(entries, true, monitor); // need to save } protected void setRawBuildpath(IBuildpathEntry[] newRawBuildpath, boolean canModifyResources, IProgressMonitor monitor) throws ModelException { try { if (newRawBuildpath == null) { // are we already with the default // buildpath newRawBuildpath = defaultBuildpath(); } SetBuildpathOperation op = new SetBuildpathOperation(this, newRawBuildpath, canModifyResources); op.runOperation(monitor); } catch (ModelException e) { ModelManager.getModelManager().getDeltaProcessor().flush(); throw e; } } /** * Returns the project custom preference pool. Project preferences may * include custom encoding. * * @return IEclipsePreferences */ public IEclipsePreferences getEclipsePreferences() { // Get cached preferences if exist ModelManager.PerProjectInfo perProjectInfo = ModelManager .getModelManager().getPerProjectInfo(this.project, true); if (perProjectInfo.preferences != null) return perProjectInfo.preferences; // Init project preferences IScopeContext context = new ProjectScope(getProject()); final IEclipsePreferences eclipsePreferences = context .getNode(DLTKCore.PLUGIN_ID); perProjectInfo.preferences = eclipsePreferences; // Listen to node removal from parent in order to reset cache (see bug // 68993) IEclipsePreferences.INodeChangeListener nodeListener = new IEclipsePreferences.INodeChangeListener() { @Override public void added(IEclipsePreferences.NodeChangeEvent event) { // do nothing } @Override public void removed(IEclipsePreferences.NodeChangeEvent event) { if (event.getChild() == eclipsePreferences) { ModelManager.getModelManager() .resetProjectPreferences(ScriptProject.this); } } }; ((IEclipsePreferences) eclipsePreferences.parent()) .addNodeChangeListener(nodeListener); // Listen to preference changes IEclipsePreferences.IPreferenceChangeListener preferenceListener = event -> ModelManager .getModelManager().resetProjectOptions(ScriptProject.this); eclipsePreferences.addPreferenceChangeListener(preferenceListener); return eclipsePreferences; } /** * If a cycle is detected, then cycleParticipants contains all the paths of * projects involved in this cycle (directly and indirectly), no cycle if * the set is empty (and started empty) * * @param prereqChain * ArrayList * @param cycleParticipants * HashSet * @param workspaceRoot * IWorkspaceRoot * @param traversed * HashSet * @param preferredBuildpaths * Map */ public void updateCycleParticipants(ArrayList<IPath> prereqChain, HashSet<IPath> cycleParticipants, IWorkspaceRoot workspaceRoot, HashSet<IPath> traversed, Map<ScriptProject, IBuildpathEntry[]> preferredBuildpaths) { IPath path = this.getPath(); prereqChain.add(path); traversed.add(path); try { IBuildpathEntry[] buildpath = null; if (preferredBuildpaths != null) buildpath = preferredBuildpaths.get(this); if (buildpath == null) buildpath = getResolvedBuildpath(); for (int i = 0, length = buildpath.length; i < length; i++) { IBuildpathEntry entry = buildpath[i]; if (entry.getEntryKind() == IBuildpathEntry.BPE_PROJECT) { IPath prereqProjectPath = entry.getPath(); int index = cycleParticipants.contains(prereqProjectPath) ? 0 : prereqChain.indexOf(prereqProjectPath); if (index >= 0) { // refer to cycle, or in cycle itself for (int size = prereqChain .size(); index < size; index++) { cycleParticipants.add(prereqChain.get(index)); } } else { if (!traversed.contains(prereqProjectPath)) { IResource member = workspaceRoot .findMember(prereqProjectPath); if (member != null && member.getType() == IResource.PROJECT) { ScriptProject scriptProject = (ScriptProject) DLTKCore .create((IProject) member); scriptProject.updateCycleParticipants( prereqChain, cycleParticipants, workspaceRoot, traversed, preferredBuildpaths); } } } } } } catch (ModelException e) { // project doesn't exist: ignore } prereqChain.remove(path); } /** * Returns true if this handle represents the same project as the given * handle. Two handles represent the same project if they are identical or * if they represent a project with the same underlying resource and * occurrence counts. * * @see ModelElement#equals(Object) */ @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof ScriptProject)) return false; ScriptProject other = (ScriptProject) o; return this.project.equals(other.getProject()); } @Override public boolean exists() { return project.exists() && DLTKLanguageManager.hasScriptNature(project); } @Override public String getElementName() { return project.getName(); } /* * Returns whether the given resource is accessible through the children or * the non-script resources of this project. Returns true if the resource is * not in the project. Assumes that the resource is a folder or a file. */ public boolean contains(IResource resource) { IBuildpathEntry[] buildpath; try { buildpath = getResolvedBuildpath(); } catch (ModelException e) { return false; } IPath fullPath = resource.getFullPath(); IBuildpathEntry innerMostEntry = null; for (int j = 0, cpLength = buildpath.length; j < cpLength; j++) { IBuildpathEntry entry = buildpath[j]; IPath entryPath = entry.getPath(); if ((innerMostEntry == null || innerMostEntry.getPath().isPrefixOf(entryPath)) && entryPath.isPrefixOf(fullPath)) { innerMostEntry = entry; } } if (innerMostEntry != null) { return true; } return false; } @Override public boolean isValid() { IDLTKLanguageToolkit toolkit = DLTKLanguageManager .getLanguageToolkit(this); return toolkit != null; } @Override public void printNode(CorePrinter output) { output.formatPrint("ScriptProject:" + getElementName()); //$NON-NLS-1$ output.indent(); try { IModelElement modelElements[] = this.getChildren(); for (int i = 0; i < modelElements.length; ++i) { IModelElement element = modelElements[i]; if (element instanceof ModelElement) { ((ModelElement) element).printNode(output); } else { output.print("Unknown element:" + element); //$NON-NLS-1$ } } } catch (ModelException ex) { output.formatPrint(ex.getLocalizedMessage()); } output.dedent(); } /* * Reads the buildpath file entries of this project's .buildpath file. This * includes the output entry. As a side effect, unknown elements are stored * in the given map (if not null) Throws exceptions if the file cannot be * accessed or is malformed. */ public IBuildpathEntry[] readFileEntriesWithException(Map unknownElements) throws CoreException, IOException, AssertionFailedException { IFile rscFile = this.project.getFile(BUILDPATH_FILENAME); byte[] bytes; if (rscFile.exists()) { bytes = Util.getResourceContentsAsByteArray(rscFile); } else { // when a project is imported, we get a first delta for the addition // of the .project, but the .buildpath is not accessible // so default to using java.io.File // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=96258 URI location = rscFile.getLocationURI(); if (location == null) throw new IOException( "Cannot obtain a location URI for " + rscFile); //$NON-NLS-1$ File file = Util.toLocalFile(location, null/* * no progress monitor available */); if (file == null) throw new IOException("Unable to fetch file from " + location); //$NON-NLS-1$ try { bytes = org.eclipse.dltk.compiler.util.Util .getFileByteContent(file); } catch (IOException e) { if (!file.exists()) return defaultBuildpath(); throw e; } } if (hasUTF8BOM(bytes)) { // see // https://bugs.eclipse.org/bugs/show_bug.cgi?id=240034 int length = bytes.length - IContentDescription.BOM_UTF_8.length; System.arraycopy(bytes, IContentDescription.BOM_UTF_8.length, bytes = new byte[length], 0, length); } String xmlBuildpath = new String(bytes, StandardCharsets.UTF_8); // .buildpath always encoded with UTF-8 return decodeBuildpath(xmlBuildpath, unknownElements); } /** * Compare current buildpath with given one to see if any different. * * @param newBuildpath * IBuildpathEntry[] * @param otherBuildpath * IBuildpathEntry[] * @return boolean */ public static boolean areBuildpathsEqual(IBuildpathEntry[] newBuildpath, IBuildpathEntry[] otherBuildpath) { if (otherBuildpath == null /* || otherBuildpath.length == 0 */) return false; int length = newBuildpath.length; if (length != otherBuildpath.length) return false; for (int i = 0; i < length; i++) { if (!newBuildpath[i].equals(otherBuildpath[i])) return false; } return true; } /* * Detect cycles in the buildpath of the workspace's projects and create * markers if necessary. @param preferredBuildpaths Map @throws * ModelException */ public static void validateCycles( Map<ScriptProject, IBuildpathEntry[]> preferredBuildpaths) throws ModelException { // long start = System.currentTimeMillis(); IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); IProject[] rscProjects = workspaceRoot.getProjects(); int length = rscProjects.length; ScriptProject[] projects = new ScriptProject[length]; HashSet<IPath> cycleParticipants = new HashSet<>(); HashSet<IPath> traversed = new HashSet<>(); // compute cycle participants ArrayList<IPath> prereqChain = new ArrayList<>(); for (int i = 0; i < length; i++) { if (DLTKLanguageManager.hasScriptNature(rscProjects[i])) { ScriptProject project = (projects[i] = (ScriptProject) DLTKCore .create(rscProjects[i])); if (!traversed.contains(project.getPath())) { prereqChain.clear(); project.updateCycleParticipants(prereqChain, cycleParticipants, workspaceRoot, traversed, preferredBuildpaths); } } } // System.out.println("updateAllCycleMarkers: " + // (System.currentTimeMillis() - start) + " ms"); for (int i = 0; i < length; i++) { ScriptProject project = projects[i]; if (project != null) { if (cycleParticipants.contains(project.getPath())) { IMarker cycleMarker = project.getCycleMarker(); String circularCPOption = project .getOption(DLTKCore.CORE_CIRCULAR_BUILDPATH, true); int circularCPSeverity = DLTKCore.ERROR .equals(circularCPOption) ? IMarker.SEVERITY_ERROR : IMarker.SEVERITY_WARNING; if (cycleMarker != null) { // update existing cycle marker if needed try { int existingSeverity = ((Integer) cycleMarker .getAttribute(IMarker.SEVERITY)).intValue(); if (existingSeverity != circularCPSeverity) { cycleMarker.setAttribute(IMarker.SEVERITY, circularCPSeverity); } } catch (CoreException e) { throw new ModelException(e); } } else { // create new marker project.createBuildpathProblemMarker(new ModelStatus( IModelStatusConstants.BUILDPATH_CYCLE, project)); } } else { project.flushBuildpathProblemMarkers(true, false); } } } } // Search operations /** * @see IScriptProject */ @Override public IModelElement findElement(IPath path) throws ModelException { return findElement(path, DefaultWorkingCopyOwner.PRIMARY); } /** * @see IScriptProject */ @Override public IModelElement findElement(IPath path, WorkingCopyOwner owner) throws ModelException { if (path == null) { // throw new ModelException( // new ModelStatus(IModelStatusConstants.INVALID_PATH, path)); // } return null; } try { String extension = path.getFileExtension(); if (extension == null) { String packageName = path.toString();// .replace(IPath.SEPARATOR, // '.'); NameLookup lookup = newNameLookup( (WorkingCopyOwner) null/* * no need to look at working * copies for pkgs */); IScriptFolder[] pkgFragments = lookup .findScriptFolders(packageName, false); if (pkgFragments == null) { return null; } else { // try to return one that is a child of this project for (int i = 0, length = pkgFragments.length; i < length; i++) { IScriptFolder pkgFragment = pkgFragments[i]; if (this.equals(pkgFragment.getParent().getParent())) { return pkgFragment; } } // default to the first one return pkgFragments[0]; } } else if (Util.isValidSourceModule(this, path)) { IPath packagePath = path.removeLastSegments(1); String packageName = packagePath.toString();// .replace(IPath. // SEPARATOR, // '.'); String typeName = path.lastSegment(); typeName = typeName.substring(0, typeName.length() - extension.length() - 1); String qualifiedName = null; if (packageName.length() > 0) { qualifiedName = packageName + IScriptFolder.PACKAGE_DELIMETER_STR + typeName; } else { qualifiedName = typeName; } // lookup type NameLookup lookup = newNameLookup(owner); NameLookup.Answer answer = lookup.findType(qualifiedName, false, NameLookup.ACCEPT_ALL, true/* * consider secondary types */, false/* * do NOT wait for indexes */, false/* * don't check restrictions */, null); if (answer != null) { return answer.type.getParent(); } else { return null; } } else { // unsupported extension return null; } } catch (ModelException e) { if (e.getStatus() .getCode() == IModelStatusConstants.ELEMENT_DOES_NOT_EXIST) { return null; } else { throw e; } } } /** * @see IScriptProject */ @Override public IScriptFolder findScriptFolder(IPath path) throws ModelException { return findScriptFolder0(ScriptProject.canonicalizedPath(path)); } /* * non path canonicalizing version */ private IScriptFolder findScriptFolder0(IPath path) throws ModelException { /* no need to look at working copies for pkgs */ NameLookup lookup = newNameLookup((WorkingCopyOwner) null); return lookup.findScriptFolder(path); } /** * @see IScriptProject */ @Override public IProjectFragment findProjectFragment(IPath path) throws ModelException { return findProjectFragment0(ScriptProject.canonicalizedPath(path)); } /* * no path canonicalization */ public IProjectFragment findProjectFragment0(IPath path) throws ModelException { IProjectFragment[] allRoots = this.getAllProjectFragments(); if (!path.isAbsolute()) { if (path.segmentCount() == 0 || !path.segment(0) .startsWith(IBuildpathEntry.BUILDPATH_SPECIAL)) { throw new IllegalArgumentException( Messages.path_mustBeAbsolute); } } for (int i = 0; i < allRoots.length; i++) { IProjectFragment buildpathRoot = allRoots[i]; if (buildpathRoot.getPath().equals(path)) { return buildpathRoot; } } return null; } /** * @see IScriptProject */ @Override public IProjectFragment[] findProjectFragments(IBuildpathEntry entry) { try { IBuildpathEntry[] buildpath = this.getRawBuildpath(); for (int i = 0, length = buildpath.length; i < length; i++) { if (buildpath[i].equals(entry)) { // entry may need to be // resolved return computeProjectFragments( resolveBuildpath(new IBuildpathEntry[] { entry }), false, // don't retrieve exported roots null); /* no reverse map */ } } } catch (ModelException e) { // project doesn't exist: return an empty array } return new IProjectFragment[] {}; } /** * @see IScriptProject#findType(String) */ @Override public IType findType(String fullyQualifiedName) throws ModelException { return findType(fullyQualifiedName, DefaultWorkingCopyOwner.PRIMARY); } /** * @see IScriptProject#findType(String, IProgressMonitor) */ @Override public IType findType(String fullyQualifiedName, IProgressMonitor progressMonitor) throws ModelException { return findType(fullyQualifiedName, DefaultWorkingCopyOwner.PRIMARY, progressMonitor); } /* * Internal findType with instanciated name lookup */ IType findType(String fullyQualifiedName, Object lookup, boolean considerSecondaryTypes, IProgressMonitor progressMonitor) throws ModelException { if (DLTKCore.DEBUG) { System.err.println("Search Need to be implemented"); //$NON-NLS-1$ } return null; } /** * @see IScriptProject#findType(String, String) */ @Override public IType findType(String packageName, String typeQualifiedName) throws ModelException { return findType(packageName, typeQualifiedName, DefaultWorkingCopyOwner.PRIMARY); } /** * @see IScriptProject#findType(String, String, IProgressMonitor) */ @Override public IType findType(String packageName, String typeQualifiedName, IProgressMonitor progressMonitor) throws ModelException { return findType(packageName, typeQualifiedName, DefaultWorkingCopyOwner.PRIMARY, progressMonitor); } /* * Internal findType with instanciated name lookup */ IType findType(String packageName, String typeQualifiedName, Object lookup, boolean considerSecondaryTypes, IProgressMonitor progressMonitor) throws ModelException { if (DLTKCore.DEBUG) { System.err.println("Search Need to be implemented"); //$NON-NLS-1$ } return null; } /** * @see IScriptProject#findType(String, String, WorkingCopyOwner) */ @Override public IType findType(String packageName, String typeQualifiedName, WorkingCopyOwner owner) throws ModelException { if (DLTKCore.DEBUG) { System.err.println("Search Need to be implemented"); //$NON-NLS-1$ } return null; } /** * @see IScriptProject#findType(String, String, WorkingCopyOwner, * IProgressMonitor) */ @Override public IType findType(String packageName, String typeQualifiedName, WorkingCopyOwner owner, IProgressMonitor progressMonitor) throws ModelException { if (DLTKCore.DEBUG) { System.err.println("Search Need to be implemented"); //$NON-NLS-1$ } return null; } @Override public IType findType(String fullyQualifiedName, WorkingCopyOwner owner) throws ModelException { if (DLTKCore.DEBUG) { System.err.println("Search Need to be implemented"); //$NON-NLS-1$ } return null; } @Override public IType findType(String fullyQualifiedName, WorkingCopyOwner owner, IProgressMonitor progressMonitor) throws ModelException { if (DLTKCore.DEBUG) { System.err.println("Search Need to be implemented"); //$NON-NLS-1$ } return null; } @Override public IBuildpathEntry[] readRawBuildpath() { // Read buildpath file without creating markers nor logging problems IBuildpathEntry[] buildpath = readFileEntries( null/* * not interested in unknown elements */); if (buildpath == ScriptProject.INVALID_BUILDPATH) return defaultBuildpath(); return buildpath; } /* * Reads the buildpath file entries of this project's .buildpath file. This * includes the output entry. As a side effect, unknown elements are stored * in the given map (if not null) */ private IBuildpathEntry[] readFileEntries(Map unkwownElements) { try { return readFileEntriesWithException(unkwownElements); } catch (CoreException e) { Util.log(e, "Exception while reading " //$NON-NLS-1$ + getPath().append(BUILDPATH_FILENAME)); return ScriptProject.INVALID_BUILDPATH; } catch (IOException e) { Util.log(e, "Exception while reading " //$NON-NLS-1$ + getPath().append(BUILDPATH_FILENAME)); return ScriptProject.INVALID_BUILDPATH; } catch (AssertionFailedException e) { Util.log(e, "Exception while reading " //$NON-NLS-1$ + getPath().append(BUILDPATH_FILENAME)); return ScriptProject.INVALID_BUILDPATH; } } /** * Returns an array of non-java resources contained in the receiver. */ @Override public Object[] getForeignResources() throws ModelException { return ((ProjectElementInfo) getElementInfo()) .getForeignResources(this); } @Override public boolean isOnBuildpath(IModelElement element) { IBuildpathEntry[] rawBuildpath; try { rawBuildpath = getRawBuildpath(); } catch (ModelException e) { return false; // not a script project } int elementType = element.getElementType(); boolean isFolderPath = false; // boolean isSource = false; boolean isProjectFragment = false; switch (elementType) { case IModelElement.SCRIPT_MODEL: return false; case IModelElement.SCRIPT_PROJECT: break; case IModelElement.PROJECT_FRAGMENT: isProjectFragment = true; break; case IModelElement.SCRIPT_FOLDER: isFolderPath = !((IProjectFragment) element.getParent()) .isArchive(); break; case IModelElement.SOURCE_MODULE: // isSource = true; break; default: // isSource = element.getAncestor(IModelElement.SOURCE_MODULE) != // null; break; } IPath elementPath = element.getPath(); // first look at unresolved entries int length = rawBuildpath.length; for (int i = 0; i < length; i++) { IBuildpathEntry entry = rawBuildpath[i]; switch (entry.getEntryKind()) { case IBuildpathEntry.BPE_LIBRARY: case IBuildpathEntry.BPE_PROJECT: case IBuildpathEntry.BPE_SOURCE: if (isOnBuildpathEntry(elementPath, isFolderPath, isProjectFragment, entry)) return true; break; } } // no need to go further for compilation units and elements inside a // compilation unit // it can only be in a source folder, thus on the raw buildpath // if (isSource) // return false; // then look at resolved entries for (int i = 0; i < length; i++) { IBuildpathEntry rawEntry = rawBuildpath[i]; switch (rawEntry.getEntryKind()) { case IBuildpathEntry.BPE_CONTAINER: IBuildpathContainer container; try { container = DLTKCore .getBuildpathContainer(rawEntry.getPath(), this); } catch (ModelException e) { break; } if (container == null) break; IBuildpathEntry[] containerEntries = container .getBuildpathEntries(); if (containerEntries == null) break; // container was bound for (int j = 0, containerLength = containerEntries.length; j < containerLength; j++) { IBuildpathEntry resolvedEntry = containerEntries[j]; if (isOnBuildpathEntry(elementPath, isFolderPath, isProjectFragment, resolvedEntry)) return true; } break; } } // Check for all fragments for custom elements try { IProjectFragment[] allProjectFragments = getAllProjectFragments(); for (IProjectFragment fragment : allProjectFragments) { if (fragment.isExternal()) { IPath path = fragment.getPath(); if (path.isPrefixOf(elementPath)) { return true; } } } } catch (ModelException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } return false; } /* * @see IScriptProject */ @Override public boolean isOnBuildpath(IResource resource) { IPath exactPath = resource.getFullPath(); IPath path = exactPath; // ensure that folders are only excluded if all of their children are // excluded int resourceType = resource.getType(); boolean isFolderPath = resourceType == IResource.FOLDER || resourceType == IResource.PROJECT; IBuildpathEntry[] buildpath; try { buildpath = this.getResolvedBuildpath(); } catch (ModelException e) { return false; // not a script project } for (int i = 0; i < buildpath.length; i++) { IBuildpathEntry entry = buildpath[i]; IPath entryPath = entry.getPath(); if (entryPath.equals(exactPath)) { // package fragment roots must // match exactly entry pathes // (no exclusion there) return true; } if (entryPath.isPrefixOf(path) && !Util.isExcluded(path, ((BuildpathEntry) entry).fullInclusionPatternChars(), ((BuildpathEntry) entry).fullExclusionPatternChars(), isFolderPath)) { return true; } } return false; } private boolean isOnBuildpathEntry(IPath elementPath, boolean isFolderPath, boolean isProjectFragment, IBuildpathEntry entry) { IPath entryPath = entry.getPath(); if (isProjectFragment) { // package fragment roots must match exactly entry pathes (no // exclusion there) if (entryPath.equals(elementPath)) return true; } else { if (entryPath.isPrefixOf(elementPath) && !Util.isExcluded( elementPath, ((BuildpathEntry) entry).fullInclusionPatternChars(), ((BuildpathEntry) entry).fullExclusionPatternChars(), isFolderPath)) return true; } return false; } @Override public IModelElement getHandleFromMemento(String token, MementoTokenizer memento, WorkingCopyOwner owner) { switch (token.charAt(0)) { case JEM_PROJECTFRAGMENT: String rootPath = IProjectFragment.DEFAULT_PACKAGE_ROOT; token = null; while (memento.hasMoreTokens()) { token = memento.nextToken(); char firstChar = token.charAt(0); if (firstChar != JEM_SCRIPTFOLDER && firstChar != JEM_COUNT && firstChar != JEM_USER_ELEMENT) { rootPath += token; } else { break; } } ModelElement root = null; if (rootPath.indexOf(JEM_SKIP_DELIMETER) != -1) { root = getExternalScriptFolderOrContainerFolder(rootPath); } else { root = (ModelElement) getProjectFragment(new Path(rootPath)); } if (token != null && token.charAt(0) == JEM_SCRIPTFOLDER && root != null) { return root.getHandleFromMemento(token, memento, owner); } else if (token != null && token.charAt(0) == JEM_USER_ELEMENT && root != null) { return root.getHandleFromMemento(token, memento, owner); } else if (root != null) { return root.getHandleFromMemento(memento, owner); } return null; case JEM_USER_ELEMENT: // We need to construct project children and return appropriate // element from it. token = null; String name = ""; while (memento.hasMoreTokens()) { token = memento.nextToken(); char firstChar = token.charAt(0); if (ModelElement.JEM_USER_ELEMENT_ENDING .indexOf(firstChar) == -1 && firstChar != JEM_COUNT) { name += token; } else { break; } } try { IModelElement[] children = getProjectFragments(); for (int i = 0; i < children.length; i++) { if (name.equals(children[i].getElementName()) && children[i] instanceof IModelElementMemento) { IModelElementMemento childMemento = (IModelElementMemento) children[i]; return childMemento.getHandleFromMemento(token, memento, owner); } } } catch (ModelException e) { DLTKCore.error("Incorrect handle resolving", e); } } return null; } private ModelElement getExternalScriptFolderOrContainerFolder( String rootPath) { IProjectFragment[] allRoots; try { allRoots = this.getProjectFragments(); } catch (ModelException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } return null; } for (int i = 0; i < allRoots.length; i++) { IProjectFragment buildpathRoot = allRoots[i]; if (buildpathRoot.getElementName().equals(rootPath)) { return (ModelElement) buildpathRoot; } } return null; } @Override protected char getHandleMementoDelimiter() { return JEM_SCRIPTPROJECT; } @Override public IResource getUnderlyingResource() throws ModelException { if (!exists()) throw newNotPresentException(); return this.project; } /* * Returns a new search name environment for this project. This name * environment first looks in the given working copies. */ public ISearchableEnvironment newSearchableNameEnvironment( ISourceModule[] workingCopies) throws ModelException { return new SearchableEnvironment(this, workingCopies); } /* * Returns a new search name environment for this project. This name * environment first looks in the working copies of the given owner. */ public SearchableEnvironment newSearchableNameEnvironment( WorkingCopyOwner owner) throws ModelException { return new SearchableEnvironment(this, owner); } /* * Returns a PerProjectInfo that doesn't register buildpath change and that * should be used as a temporary info. */ public PerProjectInfo newTemporaryInfo() { return new PerProjectInfo(this.project.getProject()) { @Override protected BuildpathChange addBuildpathChange() { return null; } }; } /** * @see IJavaProject */ @Override public ITypeHierarchy newTypeHierarchy(IRegion region, IProgressMonitor monitor) throws ModelException { return newTypeHierarchy(region, DefaultWorkingCopyOwner.PRIMARY, monitor); } /** * @see IJavaProject */ @Override public ITypeHierarchy newTypeHierarchy(IRegion region, WorkingCopyOwner owner, IProgressMonitor monitor) throws ModelException { if (region == null) { throw new IllegalArgumentException(Messages.hierarchy_nullRegion); } ISourceModule[] workingCopies = ModelManager.getModelManager() .getWorkingCopies(owner, true/* add primary working copies */); CreateTypeHierarchyOperation op = new CreateTypeHierarchyOperation( region, workingCopies, null, true); op.runOperation(monitor); return op.getResult(); } /** * @see IJavaProject */ @Override public ITypeHierarchy newTypeHierarchy(IType type, IRegion region, IProgressMonitor monitor) throws ModelException { return newTypeHierarchy(type, region, DefaultWorkingCopyOwner.PRIMARY, monitor); } /** * @see IJavaProject */ @Override public ITypeHierarchy newTypeHierarchy(IType type, IRegion region, WorkingCopyOwner owner, IProgressMonitor monitor) throws ModelException { if (type == null) { throw new IllegalArgumentException( Messages.hierarchy_nullFocusType); } if (region == null) { throw new IllegalArgumentException(Messages.hierarchy_nullRegion); } ISourceModule[] workingCopies = ModelManager.getModelManager() .getWorkingCopies(owner, true/* add primary working copies */); CreateTypeHierarchyOperation op = new CreateTypeHierarchyOperation( region, workingCopies, type, true/* compute subtypes */); op.runOperation(monitor); return op.getResult(); } /** * Returns the buildpath entry that refers to the given path or * <code>null</code> if there is no reference to the path. * * @param path * IPath * @return IBuildpathEntry * @throws ModelException */ public IBuildpathEntry getBuildpathEntryFor(IPath path) throws ModelException { getResolvedBuildpath(); // force resolution PerProjectInfo perProjectInfo = getPerProjectInfo(); if (perProjectInfo == null) return null; Map<IPath, IBuildpathEntry> rootPathToResolvedEntries = perProjectInfo.rootPathToResolvedEntries; if (rootPathToResolvedEntries == null) return null; IBuildpathEntry classpathEntry = rootPathToResolvedEntries.get(path); if (classpathEntry == null) { path = getProject().getWorkspace().getRoot().getLocation() .append(path); classpathEntry = rootPathToResolvedEntries.get(path); } return classpathEntry; } /* * Returns a new name lookup. This name lookup first looks in the given * working copies. */ public NameLookup newNameLookup(ISourceModule[] workingCopies) throws ModelException { return getProjectElementInfo().newNameLookup(this, workingCopies); } /* * Returns a new name lookup. This name lookup first looks in the working * copies of the given owner. */ public NameLookup newNameLookup(WorkingCopyOwner owner) throws ModelException { ModelManager manager = ModelManager.getModelManager(); ISourceModule[] workingCopies = owner == null ? null : manager.getWorkingCopies(owner, true/* * add primary WCs */); return newNameLookup(workingCopies); } @Override public IProjectFragment[] getAllProjectFragments() throws ModelException { return getAllProjectFragments(null /* no reverse map */); } public IProjectFragment[] getAllProjectFragments( Map<IProjectFragment, BuildpathEntry> rootToResolvedEntries) throws ModelException { IProjectFragment[] computed = computeProjectFragments( getResolvedBuildpath(), true/* retrieveExportedRoots */, rootToResolvedEntries); // Add all user project fragments List<IModelElement> fragments = new ArrayList<>(); Collections.addAll(fragments, computed); // Call for extra model providers IDLTKLanguageToolkit toolkit = DLTKLanguageManager .getLanguageToolkit(this); if (toolkit != null) { IModelProvider[] providers = ModelProviderManager .getProviders(toolkit.getNatureId()); if (providers != null) { for (int i = 0; i < providers.length; i++) { providers[i].provideModelChanges(this, fragments); } } } return fragments.toArray(new IProjectFragment[fragments.size()]); } public static boolean hasScriptNature(IProject p) { return DLTKLanguageManager.hasScriptNature(p); } @Override public IScriptFolder[] getScriptFolders() throws ModelException { IProjectFragment[] roots = getProjectFragments(); return getScriptFoldersInFragments(roots); } public IScriptFolder[] getScriptFoldersInFragments( IProjectFragment[] roots) { ArrayList<IScriptFolder> frags = new ArrayList<>(); for (int i = 0; i < roots.length; i++) { IProjectFragment root = roots[i]; try { IModelElement[] rootFragments = root.getChildren(); for (int j = 0; j < rootFragments.length; j++) { frags.add((IScriptFolder) rootFragments[j]); } } catch (ModelException e) { // do nothing } } return frags.toArray(new IScriptFolder[frags.size()]); } /** * Because resources could only be in project, not came from containers. * Lets skip container buildpath entries then checking. * * This should remove deadlock then we came here from editor initialization * and try to lock already locked workspace. */ public IBuildpathEntry[] getResourceOnlyResolvedBuildpath() throws ModelException { IBuildpathEntry[] rawBuildpath = getRawBuildpath(); List<IBuildpathEntry> rawEntries = new ArrayList<>(); for (IBuildpathEntry entry : rawBuildpath) { if (entry.getEntryKind() != IBuildpathEntry.BPE_CONTAINER) { rawEntries.add(entry); } } rawBuildpath = rawEntries .toArray(new IBuildpathEntry[rawEntries.size()]); return resolveBuildpath(rawBuildpath); } }