package org.rubypeople.rdt.internal.core; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.eclipse.core.resources.ICommand; 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.IProjectDescription; import org.eclipse.core.resources.IProjectNature; 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.Path; import org.eclipse.core.runtime.Preferences; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.IScopeContext; import org.osgi.service.prefs.BackingStoreException; import org.rubypeople.rdt.core.ILoadpathContainer; import org.rubypeople.rdt.core.ILoadpathEntry; import org.rubypeople.rdt.core.IRegion; import org.rubypeople.rdt.core.IRubyElement; import org.rubypeople.rdt.core.IRubyModelMarker; import org.rubypeople.rdt.core.IRubyModelStatus; import org.rubypeople.rdt.core.IRubyModelStatusConstants; import org.rubypeople.rdt.core.IRubyProject; import org.rubypeople.rdt.core.IRubyScript; import org.rubypeople.rdt.core.ISourceFolder; import org.rubypeople.rdt.core.ISourceFolderRoot; import org.rubypeople.rdt.core.IType; import org.rubypeople.rdt.core.ITypeHierarchy; import org.rubypeople.rdt.core.RubyCore; import org.rubypeople.rdt.core.RubyModelException; import org.rubypeople.rdt.core.WorkingCopyOwner; import org.rubypeople.rdt.core.compiler.CategorizedProblem; import org.rubypeople.rdt.core.search.CollectingSearchRequestor; import org.rubypeople.rdt.core.search.IRubySearchConstants; import org.rubypeople.rdt.core.search.IRubySearchScope; import org.rubypeople.rdt.core.search.SearchEngine; import org.rubypeople.rdt.core.search.SearchMatch; import org.rubypeople.rdt.core.search.SearchParticipant; import org.rubypeople.rdt.core.search.SearchPattern; import org.rubypeople.rdt.internal.compiler.util.ObjectVector; import org.rubypeople.rdt.internal.core.util.MementoTokenizer; import org.rubypeople.rdt.internal.core.util.Messages; import org.rubypeople.rdt.internal.core.util.Util; 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 RubyProject extends Openable implements IProjectNature, IRubyElement, IRubyProject { protected IProject project; protected boolean scratched; /** * Name of file containing custom project preferences */ private static final String PREF_FILENAME = ".rprefs"; //$NON-NLS-1$ /* * Value of project's resolved loadpath while it is being resolved */ private static final ILoadpathEntry[] RESOLUTION_IN_PROGRESS = new ILoadpathEntry[0]; static final String LOADPATH_FILENAME = ".loadpath"; static final ILoadpathEntry[] INVALID_LOADPATH = new ILoadpathEntry[0]; /** * Whether the underlying file system is case sensitive. */ protected static final boolean IS_CASE_SENSITIVE = !new File("Temp").equals(new File("temp")); //$NON-NLS-1$ //$NON-NLS-2$ /** * 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 loadpath while it is being resolved */ public RubyProject() { super(null); } /** * @param aProject */ public RubyProject(IProject aProject, RubyElement parent) { super(parent); setProject(aProject); } /** * Configure the project with Ruby nature. */ public void configure() throws CoreException { // register Ruby builder addToBuildSpec(RubyCore.BUILDER_ID); } /** * Adds a builder to the build spec for the given project. */ protected boolean addToBuildSpec(String builderID) throws CoreException { IProjectDescription description = this.project.getDescription(); int commandIndex = getRubyCommandIndex(description.getBuildSpec()); if (commandIndex == -1) { // Add a Ruby command to the build spec ICommand command = description.newCommand(); command.setBuilderName(builderID); setRubyCommand(description, command); return true; } return false; } /** * Find the specific Ruby command amongst the given build spec and return * its index or -1 if not found. */ private int getRubyCommandIndex(ICommand[] buildSpec) { for (int i = 0; i < buildSpec.length; ++i) { if (buildSpec[i].getBuilderName().equals(RubyCore.BUILDER_ID)) { return i; } } return -1; } /** * Update the Ruby command in the build spec (replace existing one if * present, add one first if none). */ private void setRubyCommand(IProjectDescription description, ICommand newCommand) throws CoreException { ICommand[] oldBuildSpec = description.getBuildSpec(); int oldRubyCommandIndex = getRubyCommandIndex(oldBuildSpec); ICommand[] newCommands; if (oldRubyCommandIndex == -1) { // Add a Ruby build spec before other builders (1FWJK7I) newCommands = new ICommand[oldBuildSpec.length + 1]; System.arraycopy(oldBuildSpec, 0, newCommands, 1, oldBuildSpec.length); newCommands[0] = newCommand; } else { oldBuildSpec[oldRubyCommandIndex] = newCommand; newCommands = oldBuildSpec; } // Commit the spec change into the project description.setBuildSpec(newCommands); this.project.setDescription(description, null); } /** * /** Removes the Java nature from the project. */ public void deconfigure() throws CoreException { // deregister Ruby builder removeFromBuildSpec(RubyCore.BUILDER_ID); } /** * Removes the given builder from the build spec for the given project. */ protected void removeFromBuildSpec(String builderID) throws CoreException { IProjectDescription description = this.project.getDescription(); ICommand[] commands = description.getBuildSpec(); for (int i = 0; i < commands.length; ++i) { if (commands[i].getBuilderName().equals(builderID)) { ICommand[] newCommands = new ICommand[commands.length - 1]; System.arraycopy(commands, 0, newCommands, 0, i); System.arraycopy(commands, i + 1, newCommands, i, commands.length - i - 1); description.setBuildSpec(newCommands); this.project.setDescription(description, null); return; } } } /** * Returns true if this handle represents the same Ruby 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 RubyElement#equals(Object) */ public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof RubyProject)) return false; RubyProject other = (RubyProject) o; return this.project.equals(other.getProject()); } public int hashCode() { if (this.project == null) { return super.hashCode() * 10 + 1; } return this.project.hashCode() * 10 + 2; } public boolean exists() { return hasRubyNature(this.project); } public RubyModelManager.PerProjectInfo getPerProjectInfo() throws RubyModelException { return RubyModelManager.getRubyModelManager().getPerProjectInfoCheckExistence(this.project); } private IPath getPluginWorkingLocation() { return this.project.getWorkingLocation(RubyCore.PLUGIN_ID); } public IProject getProject() { return project; } /** * @see IRubyElement */ public IPath getPath() { return this.project.getFullPath(); } protected IProject getProject(String name) { return RubyCore.getWorkspace().getRoot().getProject(name); } public void setProject(IProject aProject) { project = aProject; } public IResource getResource() { return this.project; } public String[] getRequiredProjectNames() throws RubyModelException { return this.projectPrerequisites(getResolvedLoadpath(true/* ignoreUnresolvedEntry */, false/* * don't * generateMarkerOnError */, false/* * don't * returnResolutionInProgress */)); } public String[] projectPrerequisites(ILoadpathEntry[] entries) throws RubyModelException { ArrayList prerequisites = new ArrayList(); // need resolution entries = getResolvedLoadpath(entries, null, true, false, null/* * no * reverse * map */); for (int i = 0, length = entries.length; i < length; i++) { ILoadpathEntry entry = entries[i]; if (entry.getEntryKind() == ILoadpathEntry.CPE_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; } } /** * @see IRubyElement */ public IResource getUnderlyingResource() throws RubyModelException { if (!exists()) throw newNotPresentException(); return this.project; } /** * Returns the project custom preference pool. Project preferences may * include custom encoding. * * @return IEclipsePreferences */ public IEclipsePreferences getEclipsePreferences() { if (!RubyProject.hasRubyNature(this.project)) return null; // Get cached preferences if exist RubyModelManager.PerProjectInfo perProjectInfo = RubyModelManager.getRubyModelManager().getPerProjectInfo(this.project, true); if (perProjectInfo.preferences != null) return perProjectInfo.preferences; // Init project preferences IScopeContext context = new ProjectScope(getProject()); final IEclipsePreferences eclipsePreferences = context.getNode(RubyCore.PLUGIN_ID); updatePreferences(eclipsePreferences); perProjectInfo.preferences = eclipsePreferences; // Listen to node removal from parent in order to reset cache (see bug // 68993) IEclipsePreferences.INodeChangeListener nodeListener = new IEclipsePreferences.INodeChangeListener() { public void added(IEclipsePreferences.NodeChangeEvent event) { // do nothing } public void removed(IEclipsePreferences.NodeChangeEvent event) { if (event.getChild() == eclipsePreferences) { RubyModelManager.getRubyModelManager().resetProjectPreferences(RubyProject.this); } } }; ((IEclipsePreferences) eclipsePreferences.parent()).addNodeChangeListener(nodeListener); // Listen to preference changes IEclipsePreferences.IPreferenceChangeListener preferenceListener = new IEclipsePreferences.IPreferenceChangeListener() { public void preferenceChange(IEclipsePreferences.PreferenceChangeEvent event) { RubyModelManager.getRubyModelManager().resetProjectOptions(RubyProject.this); } }; eclipsePreferences.addPreferenceChangeListener(preferenceListener); return eclipsePreferences; } /* * (non-Javadoc) * * @see org.rubypeople.rdt.core.IRubyElement#getElementName() */ public String getElementName() { if (project == null) { return super.getElementName(); } return project.getName(); } /* * (non-Javadoc) * * @see org.rubypeople.rdt.internal.core.parser.RubyElement#getElementType() */ public int getElementType() { return IRubyElement.RUBY_PROJECT; } /* * (non-Javadoc) * * @see org.rubypeople.rdt.core.IRubyElement#hasChildren() */ public boolean hasChildren() { return true; } /* * (non-Javadoc) * * @see org.rubypeople.rdt.core.IRubyProject#findType(java.lang.String) */ public IType findType(String fullyQualifiedName) { return findType(fullyQualifiedName, null); } /* * (non-Javadoc) * * @see org.rubypeople.rdt.core.IRubyProject#findType(java.lang.String, org.eclipse.core.runtime.IProgressMonitor) */ public IType findType(String fullyQualifiedName, IProgressMonitor monitor) { try { SearchEngine engine = new SearchEngine(); SearchPattern pattern = SearchPattern.createPattern(IRubyElement.TYPE, fullyQualifiedName, IRubySearchConstants.DECLARATIONS, SearchPattern.R_EXACT_MATCH); SearchParticipant[] participants = new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()}; IRubySearchScope scope = SearchEngine.createWorkspaceScope(); CollectingSearchRequestor requestor = new CollectingSearchRequestor(); engine.search(pattern, participants, scope, requestor, monitor); List<SearchMatch> matches = requestor.getResults(); for (SearchMatch match : matches) { IType type = (IType) match.getElement(); if (type.getFullyQualifiedName().equals(fullyQualifiedName)) return type; } } catch (CoreException e) { RubyCore.log(e); } return null; } /** * @param project * @return */ public static boolean hasRubyNature(IProject project) { try { return project.hasNature(RubyCore.NATURE_ID); } catch (CoreException e) { // project does not exist or is not open } return false; } /** * @see Openable */ protected boolean buildStructure(OpenableElementInfo info, IProgressMonitor pm, Map newElements, IResource underlyingResource) throws RubyModelException { // check whether the ruby project can be opened if (!hasRubyNature((IProject) underlyingResource)) { throw new RubyModelException(new RubyModelStatus(IRubyModelStatusConstants.PROJECT_HAS_NO_RUBY_NATURE, this)); } // cannot refresh cp markers on opening (emulate cp check on startup) // since can create deadlocks (see bug 37274) ILoadpathEntry[] resolvedClasspath = getResolvedLoadpath(true/* ignoreUnresolvedEntry */, false/* * don't * generateMarkerOnError */, false/* * don't * returnResolutionInProgress */); // compute the src folder roots info.setChildren(computeSourceFolderRoots(resolvedClasspath, false, null /* * no * reverse * map */)); // remember the timestamps of external libraries the first time they are // looked up getPerProjectInfo().rememberExternalLibTimestamps(); return true; } /** * Returns (local/all) the package fragment roots identified by the given * project's classpath. Note: this follows project classpath references to * find required project contributions, eliminating duplicates silently. * Only works with resolved entries * * @param resolvedClasspath * ILoadpathEntry[] * @param retrieveExportedRoots * boolean * @return IPackageFragmentRoot[] * @throws RubyModelException */ public ISourceFolderRoot[] computeSourceFolderRoots(ILoadpathEntry[] resolvedClasspath, boolean retrieveExportedRoots, Map rootToResolvedEntries) throws RubyModelException { ObjectVector accumulatedRoots = new ObjectVector(); computeSourceFolderRoots(resolvedClasspath, accumulatedRoots, new HashSet(5), // rootIDs null, // inside original project true, // check existency retrieveExportedRoots, rootToResolvedEntries); ISourceFolderRoot[] rootArray = new ISourceFolderRoot[accumulatedRoots.size()]; accumulatedRoots.copyInto(rootArray); return rootArray; } /** * Returns (local/all) the package fragment roots identified by the given * project's classpath. Note: this follows project classpath references to * find required project contributions, eliminating duplicates silently. * Only works with resolved entries * * @param resolvedClasspath * IClasspathEntry[] * @param accumulatedRoots * ObjectVector * @param rootIDs * HashSet * @param referringEntry * project entry referring to this CP or null if initial project * @param checkExistency * boolean * @param retrieveExportedRoots * boolean * @throws RubyModelException */ public void computeSourceFolderRoots(ILoadpathEntry[] resolvedClasspath, ObjectVector accumulatedRoots, HashSet rootIDs, ILoadpathEntry referringEntry, boolean checkExistency, boolean retrieveExportedRoots, Map rootToResolvedEntries) throws RubyModelException { if (referringEntry == null) { rootIDs.add(rootID()); } for (int i = 0, length = resolvedClasspath.length; i < length; i++) { computeSourceFolderRoots(resolvedClasspath[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 classpath so as to find * exported roots as well. Only works with resolved entry * * @param resolvedEntry * IClasspathEntry * @param accumulatedRoots * ObjectVector * @param rootIDs * HashSet * @param referringEntry * the CP entry (project) referring to this entry, or null if * initial project * @param checkExistency * boolean * @param retrieveExportedRoots * boolean * @throws RubyModelException */ public void computeSourceFolderRoots(ILoadpathEntry resolvedEntry, ObjectVector accumulatedRoots, HashSet rootIDs, ILoadpathEntry referringEntry, boolean checkExistency, boolean retrieveExportedRoots, Map rootToResolvedEntries) throws RubyModelException { String rootID = ((LoadpathEntry) resolvedEntry).rootID(); if (rootIDs.contains(rootID)) return; IPath projectPath = this.project.getFullPath(); IPath entryPath = resolvedEntry.getPath(); IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); ISourceFolderRoot root = null; switch (resolvedEntry.getEntryKind()) { // source folder case ILoadpathEntry.CPE_SOURCE: if (projectPath.isPrefixOf(entryPath)) { if (checkExistency) { Object target = RubyModel.getTarget(entryPath, checkExistency); if (target == null) return; if (target instanceof IFolder || target instanceof IProject) { root = getSourceFolderRoot((IResource) target); } } else { root = getFolderSourceFolderRoot(entryPath); } } break; // internal/external JAR or folder case ILoadpathEntry.CPE_LIBRARY: if (referringEntry != null && !resolvedEntry.isExported()) return; if (checkExistency) { Object target = RubyModel.getTarget(entryPath, checkExistency); if (target == null) return; if (target instanceof IResource) { // internal target root = getSourceFolderRoot((IResource) target); } else { // external target if (RubyModel.isFolder(target)) { root = new ExternalSourceFolderRoot(entryPath, this); } } } else { root = getSourceFolderRoot(entryPath); } break; // recurse into required project case ILoadpathEntry.CPE_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 (RubyProject.hasRubyNature(requiredProjectRsc)) { // special // builder // binary // output rootIDs.add(rootID); RubyProject requiredProject = (RubyProject) RubyCore.create(requiredProjectRsc); requiredProject.computeSourceFolderRoots(requiredProject.getResolvedLoadpath(true/* ignoreUnresolvedEntry */, false/* * don't * generateMarkerOnError */, false/* * don't * returnResolutionInProgress */), accumulatedRoots, rootIDs, rootToResolvedEntries == null ? resolvedEntry : ((LoadpathEntry) resolvedEntry).combineWith((LoadpathEntry) referringEntry), // only // combine // if // need // to // build // the // reverse // map checkExistency, retrieveExportedRoots, rootToResolvedEntries); } break; } } if (root != null) { accumulatedRoots.add(root); rootIDs.add(rootID); if (rootToResolvedEntries != null) rootToResolvedEntries.put(root, ((LoadpathEntry) resolvedEntry).combineWith((LoadpathEntry) referringEntry)); } } /** * @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. */ public ISourceFolderRoot getSourceFolderRoot(IPath path) { if (!path.isAbsolute()) { path = getPath().append(path); } int segmentCount = path.segmentCount(); 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 getSourceFolderRoot(this.project); } default: if (segmentCount == 1) { // lib being another project return getSourceFolderRoot(this.project.getWorkspace().getRoot().getProject(path.lastSegment())); } else { // lib being a folder return getSourceFolderRoot(this.project.getWorkspace().getRoot().getFolder(path)); } } } public boolean contains(IResource resource) { // XXX Check the paths to see if this is true or not! return true; } /** * Answers an ID which is used to distinguish project/entries during package * fragment root computations * * @return String */ public String rootID() { return "[PRJ]" + this.project.getFullPath(); //$NON-NLS-1$ } /** * Returns a new element info for this element. */ protected Object createElementInfo() { return new RubyProjectElementInfo(); } /** * @see org.rubypeople.rdt.core.IRubyProject#getOption(String, boolean) */ public String getOption(String optionName, boolean inheritRubyCoreOptions) { String propertyName = optionName; if (RubyModelManager.getRubyModelManager().optionNames.contains(propertyName)) { IEclipsePreferences projectPreferences = getEclipsePreferences(); String javaCoreDefault = inheritRubyCoreOptions ? RubyCore.getOption(propertyName) : null; if (projectPreferences == null) return javaCoreDefault; String value = projectPreferences.get(propertyName, javaCoreDefault); return value == null ? null : value.trim(); } return null; } /** * @see org.rubypeople.rdt.core.IRubyProject#getOptions(boolean) */ public Map getOptions(boolean inheritRubyCoreOptions) { // initialize to the defaults from RubyCore options pool Map<String, String> options = inheritRubyCoreOptions ? RubyCore.getOptions() : new Hashtable<String, String>(5); // Get project specific options RubyModelManager.PerProjectInfo perProjectInfo = null; Hashtable<String, String> projectOptions = null; HashSet<String> optionNames = RubyModelManager.getRubyModelManager().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-Ruby project) // create project options String[] propertyNames = projectPreferences.keys(); projectOptions = new Hashtable<String, String>(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 (RubyModelException jme) { projectOptions = new Hashtable<String, String>(); } catch (BackingStoreException e) { projectOptions = new Hashtable<String, String>(); } // Inherit from RubyCore options if specified if (inheritRubyCoreOptions) { Iterator<Map.Entry<String, String>> entries = projectOptions.entrySet().iterator(); while (entries.hasNext()) { Map.Entry<String, String> entry = entries.next(); String propertyName = entry.getKey(); String propertyValue = entry.getValue(); if (propertyValue != null && optionNames.contains(propertyName)) { options.put(propertyName, propertyValue.trim()); } } return options; } return projectOptions; } /* * Update eclipse preferences from old preferences. */ private void updatePreferences(IEclipsePreferences preferences) { Preferences oldPreferences = loadPreferences(); if (oldPreferences != null) { String[] propertyNames = oldPreferences.propertyNames(); for (int i = 0; i < propertyNames.length; i++) { String propertyName = propertyNames[i]; String propertyValue = oldPreferences.getString(propertyName); if (!"".equals(propertyValue)) { //$NON-NLS-1$ preferences.put(propertyName, propertyValue); } } try { // save immediately old preferences preferences.flush(); } catch (BackingStoreException e) { // fails silently } } } /** * load preferences from a shareable format (VCM-wise) */ private Preferences loadPreferences() { Preferences preferences = new Preferences(); IPath projectMetaLocation = getPluginWorkingLocation(); if (projectMetaLocation != null) { File prefFile = projectMetaLocation.append(PREF_FILENAME).toFile(); if (prefFile.exists()) { // load preferences from file InputStream in = null; try { in = new BufferedInputStream(new FileInputStream(prefFile)); preferences.load(in); } catch (IOException e) { // problems loading preference store // - quietly ignore } finally { if (in != null) { try { in.close(); } catch (IOException e) { // ignore problems with // close } } } // one shot read, delete old preferences prefFile.delete(); return preferences; } } return null; } /* * Resets this project's caches */ public void resetCaches() { RubyProjectElementInfo info = (RubyProjectElementInfo) RubyModelManager.getRubyModelManager().peekAtInfo(this); if (info != null) { info.resetCaches(); } } /** * Returns an array of non-ruby resources contained in the receiver. */ public Object[] getNonRubyResources() throws RubyModelException { return ((RubyProjectElementInfo) getElementInfo()).getNonRubyResources(this); } public ISourceFolder[] getSourceFolders() throws RubyModelException { ISourceFolderRoot[] roots = getSourceFolderRoots(); return getSourceFoldersInRoots(roots); } /** * Returns all the source folders found in the specified * source folder roots. * @param roots ISourceFolderRoot[] * @return ISourceFolder[] */ public ISourceFolder[] getSourceFoldersInRoots(ISourceFolderRoot[] roots) { ArrayList frags = new ArrayList(); for (int i = 0; i < roots.length; i++) { ISourceFolderRoot root = roots[i]; try { IRubyElement[] rootFragments = root.getChildren(); for (int j = 0; j < rootFragments.length; j++) { frags.add(rootFragments[j]); } } catch (RubyModelException e) { // do nothing } } ISourceFolder[] fragments = new ISourceFolder[frags.size()]; frags.toArray(fragments); return fragments; } /* * Internal variant allowing to parameterize problem creation/logging */ public ILoadpathEntry[] getRawLoadpath(boolean createMarkers, boolean logProblems) throws RubyModelException { RubyModelManager.PerProjectInfo perProjectInfo = null; ILoadpathEntry[] classpath; if (createMarkers) { this.flushLoadpathProblemMarkers(false/* cycle */, true/* format */); classpath = this.readLoadpathFile(createMarkers, logProblems); } else { perProjectInfo = getPerProjectInfo(); classpath = perProjectInfo.rawLoadpath; if (classpath != null) return classpath; classpath = this.readLoadpathFile(createMarkers, logProblems); } if (classpath == null) { return defaultLoadpath(); } /* * Disable validate: classpath can contain CP variables and container * that need to be resolved if (classpath != INVALID_CLASSPATH && * !JavaConventions.validateClasspath(this, classpath, * outputLocation).isOK()) { classpath = INVALID_CLASSPATH; } */ if (!createMarkers) { perProjectInfo.rawLoadpath = classpath; perProjectInfo.outputLocation = null; } return classpath; } /** * Returns a default load path. This is the root of the project */ protected ILoadpathEntry[] defaultLoadpath() { return new ILoadpathEntry[] { RubyCore.newSourceEntry(this.project.getFullPath()) }; } /** * @see IRubyProject */ public ILoadpathEntry[] readRawLoadpath() { // Read loadpath file without creating markers nor logging problems return this.readLoadpathFile(false, false); } /** * Reads the .classpath file from disk and returns the list of entries it * contains (including output location entry) Returns null if .classfile is * not present. Returns INVALID_CLASSPATH if it has a format problem. */ protected ILoadpathEntry[] readLoadpathFile(boolean createMarker, boolean logProblems) { return readLoadpathFile(createMarker, logProblems, null/* * not * interested in * unknown * elements */); } protected ILoadpathEntry[] readLoadpathFile(boolean createMarker, boolean logProblems, Map unknownElements) { try { String xmlClasspath = getSharedProperty(LOADPATH_FILENAME); if (xmlClasspath == null) { if (createMarker && this.project.isAccessible()) { this.createLoadpathProblemMarker(new RubyModelStatus(IRubyModelStatusConstants.INVALID_LOADPATH_FILE_FORMAT, Messages.bind(Messages.classpath_cannotReadClasspathFile, this.getElementName()))); } return null; } return decodeLoadpath(xmlClasspath, createMarker, logProblems, unknownElements); } catch (CoreException e) { // file does not exist (or not accessible) if (createMarker && this.project.isAccessible()) { this.createLoadpathProblemMarker(new RubyModelStatus(IRubyModelStatusConstants.INVALID_LOADPATH_FILE_FORMAT, Messages.bind(Messages.classpath_cannotReadClasspathFile, this.getElementName()))); } if (logProblems) { Util.log(e, "Exception while retrieving " + this.getPath() //$NON-NLS-1$ + "/.loadpath, will revert to default loadpath"); //$NON-NLS-1$ } } return null; } /** * Reads and decode an XML classpath string */ protected ILoadpathEntry[] decodeLoadpath(String xmlClasspath, boolean createMarker, boolean logProblems, Map unknownElements) { ArrayList paths = new ArrayList(); try { if (xmlClasspath == null) return null; StringReader reader = new StringReader(xmlClasspath); 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(LoadpathEntry.TAG_LOADPATH)) { throw new IOException(Messages.file_badFormat); } NodeList list = cpElement.getElementsByTagName(LoadpathEntry.TAG_LOADPATHENTRY); int length = list.getLength(); for (int i = 0; i < length; ++i) { Node node = list.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { ILoadpathEntry entry = LoadpathEntry.elementDecode((Element) node, this, unknownElements); if (entry != null) { paths.add(entry); } } } } catch (IOException e) { // bad format if (createMarker && this.project.isAccessible()) { this.createLoadpathProblemMarker(new RubyModelStatus(IRubyModelStatusConstants.INVALID_LOADPATH_FILE_FORMAT, Messages.bind(Messages.classpath_xmlFormatError, new String[] { this.getElementName(), e.getMessage() }))); } if (logProblems) { Util.log(e, "Exception while retrieving " + this.getPath() //$NON-NLS-1$ + "/.classpath, will mark classpath as invalid"); //$NON-NLS-1$ } return INVALID_LOADPATH; } catch (AssertionFailedException e) { // failed creating CP entries from file if (createMarker && this.project.isAccessible()) { this.createLoadpathProblemMarker(new RubyModelStatus(IRubyModelStatusConstants.INVALID_LOADPATH_FILE_FORMAT, Messages.bind(Messages.classpath_illegalEntryInClasspathFile, new String[] { this.getElementName(), e.getMessage() }))); } if (logProblems) { Util.log(e, "Exception while retrieving " + this.getPath() //$NON-NLS-1$ + "/.classpath, will mark classpath as invalid"); //$NON-NLS-1$ } return INVALID_LOADPATH; } // return an empty classpath is it size is 0, to differenciate from a // null classpath int pathSize = paths.size(); ILoadpathEntry[] entries = new ILoadpathEntry[pathSize]; paths.toArray(entries); return entries; } /** * 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 JavaProject#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); try { property = new String(bytes, org.rubypeople.rdt.core.util.Util.UTF_8); // .classpath // always // encoded // with // UTF-8 } catch (UnsupportedEncodingException e) { Util.log(e, "Could not read .classpath with UTF-8 encoding"); //$NON-NLS-1$ // fallback to default property = new String(bytes); } } else { // when a project is imported, we get a first delta for the addition // of the .project, but the .classpath 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.rubypeople.rdt.core.util.Util.getFileByteContent(file); } catch (IOException e) { return null; } try { property = new String(bytes, org.rubypeople.rdt.core.util.Util.UTF_8); // .classpath // always // encoded // with // UTF-8 } catch (UnsupportedEncodingException e) { Util.log(e, "Could not read .classpath with UTF-8 encoding"); //$NON-NLS-1$ // fallback to default property = new String(bytes); } } } } return property; } /** * @see IRubyProject */ public ILoadpathEntry[] getResolvedLoadpath(boolean ignoreUnresolvedEntry, boolean generateMarkerOnError) throws RubyModelException { return getResolvedLoadpath(ignoreUnresolvedEntry, generateMarkerOnError, true // returnResolutionInProgress ); } public ILoadpathEntry[] getResolvedLoadpath(boolean ignoreUnresolvedEntry, boolean generateMarkerOnError, boolean returnResolutionInProgress) throws RubyModelException { RubyModelManager manager = RubyModelManager.getRubyModelManager(); RubyModelManager.PerProjectInfo perProjectInfo = null; if (ignoreUnresolvedEntry && !generateMarkerOnError) { perProjectInfo = getPerProjectInfo(); if (perProjectInfo != null) { // resolved path is cached on its info ILoadpathEntry[] infoPath = perProjectInfo.resolvedLoadpath; if (infoPath != null) { return infoPath; } else if (returnResolutionInProgress && manager.isLoadpathBeingResolved(this)) { if (RubyModelManager.CP_RESOLVE_VERBOSE) { Util.verbose("CPResolution: reentering raw loadpath resolution, will use empty loadpath instead" + //$NON-NLS-1$ " project: " + getElementName() + '\n' + //$NON-NLS-1$ " invocation stack trace:"); //$NON-NLS-1$ new Exception("<Fake exception>").printStackTrace(System.out); //$NON-NLS-1$ } return RESOLUTION_IN_PROGRESS; } } } Map<IPath, ILoadpathEntry> rawReverseMap = perProjectInfo == null ? null : new HashMap<IPath, ILoadpathEntry>(5); ILoadpathEntry[] resolvedPath = null; boolean nullOldResolvedCP = perProjectInfo != null && perProjectInfo.resolvedLoadpath == null; try { // protect against misbehaving clients (see // https://bugs.eclipse.org/bugs/show_bug.cgi?id=61040) if (nullOldResolvedCP) manager.setLoadpathBeingResolved(this, true); resolvedPath = getResolvedLoadpath(getRawLoadpath(generateMarkerOnError, !generateMarkerOnError), null, ignoreUnresolvedEntry, generateMarkerOnError, rawReverseMap); } finally { if (nullOldResolvedCP) perProjectInfo.resolvedLoadpath = null; } if (perProjectInfo != null) { if (perProjectInfo.rawLoadpath == null // .loadpath file could not // be read && generateMarkerOnError && RubyProject.hasRubyNature(this.project)) { // flush .loadpath format markers (bug 39877), but only when // file cannot be read (bug 42366) this.flushLoadpathProblemMarkers(false, true); this.createLoadpathProblemMarker(new RubyModelStatus(IRubyModelStatusConstants.INVALID_LOADPATH_FILE_FORMAT, Messages.bind(Messages.classpath_cannotReadClasspathFile, this.getElementName()))); } perProjectInfo.resolvedLoadpath = resolvedPath; perProjectInfo.resolvedPathToRawEntries = rawReverseMap; manager.setLoadpathBeingResolved(this, false); } return resolvedPath; } /** * Internal variant which can process any arbitrary classpath * * @param classpathEntries * IClasspathEntry[] * @param projectOutputLocation * IPath * @param ignoreUnresolvedEntry * boolean * @param generateMarkerOnError * boolean * @param rawReverseMap * Map * @return IClasspathEntry[] * @throws JavaModelException */ public ILoadpathEntry[] getResolvedLoadpath(ILoadpathEntry[] classpathEntries, IPath projectOutputLocation, // only // set // if // needing // full // classpath // validation // (and // markers) boolean ignoreUnresolvedEntry, // if unresolved entries are met, // should it trigger initializations boolean generateMarkerOnError, Map<IPath, ILoadpathEntry> rawReverseMap) // can be null // if not // interested in // reverse // mapping throws RubyModelException { IRubyModelStatus status; if (generateMarkerOnError) { flushLoadpathProblemMarkers(false, false); } int length = classpathEntries.length; ArrayList<ILoadpathEntry> resolvedEntries = new ArrayList<ILoadpathEntry>(); for (int i = 0; i < length; i++) { ILoadpathEntry rawEntry = classpathEntries[i]; IPath resolvedPath; status = null; /* validation if needed */ if (generateMarkerOnError || !ignoreUnresolvedEntry) { status = LoadpathEntry.validateLoadpathEntry(this, rawEntry, false /* * ignore * src * attach */, false /* * do * not * recurse * in * containers, * done * later * to * accumulate */); if (generateMarkerOnError && !status.isOK()) { if (status.getCode() == IRubyModelStatusConstants.INVALID_CLASSPATH && ((LoadpathEntry) rawEntry).isOptional()) continue; // ignore this entry createLoadpathProblemMarker(status); } } switch (rawEntry.getEntryKind()) { case ILoadpathEntry.CPE_VARIABLE: ILoadpathEntry resolvedEntry = null; try { resolvedEntry = RubyCore.getResolvedLoadpathEntry(rawEntry); } catch (AssertionFailedException e) { // Catch the assertion failure and throw java model // exception instead // see bug // https://bugs.eclipse.org/bugs/show_bug.cgi?id=55992 // if ignoredUnresolvedEntry is false, status is set by by // ClasspathEntry.validateClasspathEntry // called above as validation was needed if (!ignoreUnresolvedEntry) throw new RubyModelException(status); } if (resolvedEntry == null) { if (!ignoreUnresolvedEntry) throw new RubyModelException(status); } else { if (rawReverseMap != null) { if (rawReverseMap.get(resolvedPath = resolvedEntry.getPath()) == null) rawReverseMap.put(resolvedPath, rawEntry); } resolvedEntries.add(resolvedEntry); } break; case ILoadpathEntry.CPE_CONTAINER: ILoadpathContainer container = RubyCore.getLoadpathContainer(rawEntry.getPath(), this); if (container == null) { if (!ignoreUnresolvedEntry) throw new RubyModelException(status); break; } ILoadpathEntry[] containerEntries = container.getLoadpathEntries(); if (containerEntries == null) break; // container was bound for (int j = 0, containerLength = containerEntries.length; j < containerLength; j++) { LoadpathEntry cEntry = (LoadpathEntry) containerEntries[j]; if (generateMarkerOnError) { IRubyModelStatus containerStatus = LoadpathEntry.validateLoadpathEntry(this, cEntry, false, true /* recurse */); if (!containerStatus.isOK()) createLoadpathProblemMarker(containerStatus); } // if container is exported or restricted, then its nested // entries must in turn be exported (21749) and/or propagate // restrictions cEntry = cEntry.combineWith((LoadpathEntry) rawEntry); if (rawReverseMap != null) { if (rawReverseMap.get(resolvedPath = cEntry.getPath()) == null) rawReverseMap.put(resolvedPath, rawEntry); } resolvedEntries.add(cEntry); } break; default: if (rawReverseMap != null) { if (rawReverseMap.get(resolvedPath = rawEntry.getPath()) == null) rawReverseMap.put(resolvedPath, rawEntry); } resolvedEntries.add(rawEntry); } } ILoadpathEntry[] resolvedPath = new ILoadpathEntry[resolvedEntries.size()]; resolvedEntries.toArray(resolvedPath); if (generateMarkerOnError && projectOutputLocation != null) { status = LoadpathEntry.validateLoadpath(this, resolvedPath, projectOutputLocation); if (!status.isOK()) createLoadpathProblemMarker(status); } return resolvedPath; } public ISourceFolderRoot getFolderSourceFolderRoot(IPath path) { if (path.segmentCount() == 1) { // default project root return getSourceFolderRoot(this.project); } return getSourceFolderRoot(this.project.getWorkspace().getRoot().getFolder(path)); } public ISourceFolderRoot getSourceFolderRoot(IResource resource) { switch (resource.getType()) { case IResource.FILE: return null; case IResource.FOLDER: return new SourceFolderRoot(resource, this); case IResource.PROJECT: return new SourceFolderRoot(resource, this); default: return null; } } /** * Record a new marker denoting a classpath problem */ void createLoadpathProblemMarker(IRubyModelStatus status) { IMarker marker = null; int severity; String[] arguments = new String[0]; boolean isCycleProblem = false, isClasspathFileFormatProblem = false; switch (status.getCode()) { case IRubyModelStatusConstants.CLASSPATH_CYCLE: isCycleProblem = true; if (RubyCore.ERROR.equals(getOption(RubyCore.CORE_CIRCULAR_CLASSPATH, true))) { severity = IMarker.SEVERITY_ERROR; } else { severity = IMarker.SEVERITY_WARNING; } break; case IRubyModelStatusConstants.INVALID_LOADPATH_FILE_FORMAT: isClasspathFileFormatProblem = true; severity = IMarker.SEVERITY_ERROR; break; case IRubyModelStatusConstants.INCOMPATIBLE_JDK_LEVEL: String setting = getOption(RubyCore.CORE_INCOMPATIBLE_JDK_LEVEL, true); if (RubyCore.ERROR.equals(setting)) { severity = IMarker.SEVERITY_ERROR; } else if (RubyCore.WARNING.equals(setting)) { severity = IMarker.SEVERITY_WARNING; } else { return; // setting == IGNORE } break; default: IPath path = status.getPath(); if (path != null) arguments = new String[] { path.toString() }; if (RubyCore.ERROR.equals(getOption(RubyCore.CORE_INCOMPLETE_CLASSPATH, true))) { severity = IMarker.SEVERITY_ERROR; } else { severity = IMarker.SEVERITY_WARNING; } break; } try { marker = this.project.createMarker(IRubyModelMarker.BUILDPATH_PROBLEM_MARKER); marker.setAttributes(new String[] { IMarker.MESSAGE, IMarker.SEVERITY, IMarker.LOCATION, IRubyModelMarker.CYCLE_DETECTED, IRubyModelMarker.CLASSPATH_FILE_FORMAT, IRubyModelMarker.ID, IRubyModelMarker.ARGUMENTS, IRubyModelMarker.CATEGORY_ID, }, new Object[] { status.getMessage(), new Integer(severity), Messages.classpath_buildPath, isCycleProblem ? "true" : "false",//$NON-NLS-1$ //$NON-NLS-2$ isClasspathFileFormatProblem ? "true" : "false",//$NON-NLS-1$ //$NON-NLS-2$ Integer.valueOf(status.getCode()), Util.getProblemArgumentsForMarker(arguments), new Integer(CategorizedProblem.CAT_BUILDPATH) }); } catch (CoreException e) { // could not create marker: cannot do much if (RubyModelManager.VERBOSE) { e.printStackTrace(); } } } /** * Remove all markers denoting classpath problems */ // TODO (philippe) should improve to use a bitmask instead of booleans // (CYCLE, FORMAT, VALID) protected void flushLoadpathProblemMarkers(boolean flushCycleMarkers, boolean flushClasspathFormatMarkers) { try { if (this.project.isAccessible()) { IMarker[] markers = this.project.findMarkers(IRubyModelMarker.BUILDPATH_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); for (int i = 0, length = markers.length; i < length; i++) { IMarker marker = markers[i]; if (flushCycleMarkers && flushClasspathFormatMarkers) { marker.delete(); } else { String cycleAttr = (String) marker.getAttribute(IRubyModelMarker.CYCLE_DETECTED); String classpathFileFormatAttr = (String) marker.getAttribute(IRubyModelMarker.CLASSPATH_FILE_FORMAT); if ((flushCycleMarkers == (cycleAttr != null && cycleAttr.equals("true"))) //$NON-NLS-1$ && (flushClasspathFormatMarkers == (classpathFileFormatAttr != null && classpathFileFormatAttr.equals("true")))) { //$NON-NLS-1$ marker.delete(); } } } } } catch (CoreException e) { // could not flush markers: not much we can do if (RubyModelManager.VERBOSE) { e.printStackTrace(); } } } /** * 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 (JavaModelManager.VERBOSE) { // System.out.println("JAVA MODEL - Canonicalizing " + // externalPath.toString()); // } if (IS_CASE_SENSITIVE) { // if (JavaModelManager.VERBOSE) { // System.out.println("JAVA MODEL - Canonical path is original path // (file system 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) { // if (JavaModelManager.VERBOSE) { // System.out.println("JAVA MODEL - Canonical path is original path // (member of workspace)"); // } return externalPath; } IPath canonicalPath = null; try { canonicalPath = new Path(new File(externalPath.toOSString()).getCanonicalPath()); } catch (IOException e) { // default to original path // if (JavaModelManager.VERBOSE) { // System.out.println("JAVA MODEL - Canonical path is original path // (IOException)"); // } return externalPath; } IPath result; int canonicalLength = canonicalPath.segmentCount(); if (canonicalLength == 0) { // the java.io.File canonicalization failed // if (JavaModelManager.VERBOSE) { // System.out.println("JAVA MODEL - Canonical path is original path // (canonical path is empty)"); // } return externalPath; } else if (externalPath.isAbsolute()) { result = canonicalPath; } else { // if path is relative, remove the first segments that were added by // the java.io.File canonicalization // e.g. 'lib/classes.zip' was converted to // 'd:/myfolder/lib/classes.zip' int externalLength = externalPath.segmentCount(); if (canonicalLength >= externalLength) { result = canonicalPath.removeFirstSegments(canonicalLength - externalLength); } else { // if (JavaModelManager.VERBOSE) { // System.out.println("JAVA MODEL - Canonical path is original // path (canonical path is " + canonicalPath.toString() + ")"); // } return externalPath; } } // keep device only if it was specified (this is because // File.getCanonicalPath() converts '/lib/classed.zip' to // 'd:/lib/classes/zip') if (externalPath.getDevice() == null) { result = result.setDevice(null); } // if (JavaModelManager.VERBOSE) { // System.out.println("JAVA MODEL - Canonical path is " + // result.toString()); // } return result; } /** * @see IRubyProject */ public ILoadpathEntry[] getRawLoadpath() throws RubyModelException { // Do not create marker but log problems while getting raw loadpath return getRawLoadpath(false, true); } public ISourceFolderRoot[] getSourceFolderRoots() throws RubyModelException { Object[] children; int length; ISourceFolderRoot[] roots; System.arraycopy(children = getChildren(), 0, roots = new ISourceFolderRoot[length = children.length], 0, length); return roots; } public boolean isOnLoadpath(IRubyElement element) { ILoadpathEntry[] rawClasspath; try { rawClasspath = getRawLoadpath(); } catch (RubyModelException e) { return false; // not a Ruby project } int elementType = element.getElementType(); boolean isPackageFragmentRoot = false; boolean isFolderPath = false; boolean isSource = false; switch (elementType) { case IRubyElement.RUBY_MODEL: return false; case IRubyElement.RUBY_PROJECT: break; case IRubyElement.SOURCE_FOLDER_ROOT: isPackageFragmentRoot = true; break; case IRubyElement.SOURCE_FOLDER: isFolderPath = !((ISourceFolderRoot) element.getParent()).isArchive(); break; case IRubyElement.SCRIPT: isSource = true; break; default: isSource = element.getAncestor(IRubyElement.SCRIPT) != null; break; } IPath elementPath = element.getPath(); // first look at unresolved entries int length = rawClasspath.length; for (int i = 0; i < length; i++) { ILoadpathEntry entry = rawClasspath[i]; switch (entry.getEntryKind()) { case ILoadpathEntry.CPE_LIBRARY: case ILoadpathEntry.CPE_PROJECT: case ILoadpathEntry.CPE_SOURCE: if (isOnLoadpathEntry(elementPath, isFolderPath, isPackageFragmentRoot, 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 classpath if (isSource) return false; // then look at resolved entries for (int i = 0; i < length; i++) { ILoadpathEntry rawEntry = rawClasspath[i]; switch (rawEntry.getEntryKind()) { case ILoadpathEntry.CPE_CONTAINER: ILoadpathContainer container; try { container = RubyCore.getLoadpathContainer(rawEntry.getPath(), this); } catch (RubyModelException e) { break; } if (container == null) break; ILoadpathEntry[] containerEntries = container.getLoadpathEntries(); if (containerEntries == null) break; // container was bound for (int j = 0, containerLength = containerEntries.length; j < containerLength; j++) { ILoadpathEntry resolvedEntry = containerEntries[j]; if (isOnLoadpathEntry(elementPath, isFolderPath, isPackageFragmentRoot, resolvedEntry)) return true; } break; case ILoadpathEntry.CPE_VARIABLE: ILoadpathEntry resolvedEntry = RubyCore.getResolvedLoadpathEntry(rawEntry); if (resolvedEntry == null) break; if (isOnLoadpathEntry(elementPath, isFolderPath, isPackageFragmentRoot, resolvedEntry)) return true; break; } } return false; } private boolean isOnLoadpathEntry(IPath elementPath, boolean isFolderPath, boolean isPackageFragmentRoot, ILoadpathEntry entry) { IPath entryPath = entry.getPath(); if (isPackageFragmentRoot) { // 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, ((LoadpathEntry) entry).fullInclusionPatternChars(), ((LoadpathEntry) entry).fullExclusionPatternChars(), isFolderPath)) return true; } return false; } public ILoadpathEntry[] getResolvedLoadpath(boolean ignoreUnresolvedEntry) throws RubyModelException { return getResolvedLoadpath(ignoreUnresolvedEntry, false, // don't // generateMarkerOnError true // returnResolutionInProgress ); } public ISourceFolderRoot[] computeSourceFolderRoots(ILoadpathEntry resolvedEntry) { try { return computeSourceFolderRoots(new ILoadpathEntry[] { resolvedEntry }, false, // don't // retrieve // exported // roots null /* no reverse map */ ); } catch (RubyModelException e) { return new ISourceFolderRoot[] {}; } } public ILoadpathEntry[] getExpandedLoadpath(boolean ignoreUnresolvedVariable) throws RubyModelException { return getExpandedLoadpath(ignoreUnresolvedVariable, false/* * don't * create * markers */, null, null); } private ILoadpathEntry[] getExpandedLoadpath(boolean ignoreUnresolvedVariable, boolean generateMarkerOnError, Map preferredClasspaths, Map preferredOutputs) throws RubyModelException { ObjectVector accumulatedEntries = new ObjectVector(); computeExpandedLoadpath(null, ignoreUnresolvedVariable, generateMarkerOnError, new HashSet(5), accumulatedEntries, preferredClasspaths, preferredOutputs); ILoadpathEntry[] expandedPath = new ILoadpathEntry[accumulatedEntries.size()]; accumulatedEntries.copyInto(expandedPath); return expandedPath; } private void computeExpandedLoadpath(LoadpathEntry referringEntry, boolean ignoreUnresolvedVariable, boolean generateMarkerOnError, HashSet rootIDs, ObjectVector accumulatedEntries, Map preferredClasspaths, Map preferredOutputs) throws RubyModelException { String projectRootId = this.rootID(); if (rootIDs.contains(projectRootId)) { return; // break cycles if any } rootIDs.add(projectRootId); ILoadpathEntry[] preferredClasspath = preferredClasspaths != null ? (ILoadpathEntry[]) preferredClasspaths.get(this) : null; IPath preferredOutput = preferredOutputs != null ? (IPath) preferredOutputs.get(this) : null; ILoadpathEntry[] immediateClasspath = preferredClasspath != null ? getResolvedLoadpath(preferredClasspath, preferredOutput, ignoreUnresolvedVariable, generateMarkerOnError, null /* * no * reverse * map */) : getResolvedLoadpath(ignoreUnresolvedVariable, generateMarkerOnError, false/* * don't * returnResolutionInProgress */); IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); boolean isInitialProject = referringEntry == null; for (int i = 0, length = immediateClasspath.length; i < length; i++) { LoadpathEntry entry = (LoadpathEntry) immediateClasspath[i]; if (isInitialProject || entry.isExported()) { String rootID = entry.rootID(); if (rootIDs.contains(rootID)) { continue; } // combine restrictions along the project chain LoadpathEntry 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() == ILoadpathEntry.CPE_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 (RubyProject.hasRubyNature(projRsc)) { RubyProject javaProject = (RubyProject) RubyCore.create(projRsc); javaProject.computeExpandedLoadpath(combinedEntry, ignoreUnresolvedVariable, false /* * no * marker * when * recursing * in * prereq */, rootIDs, accumulatedEntries, preferredClasspaths, preferredOutputs); } } } else { rootIDs.add(rootID); } } } } public void updateSourceFolderRoots() { if (this.isOpen()) { try { RubyProjectElementInfo info = getRubyProjectElementInfo(); computeChildren(info); info.resetCaches(); // discard caches (hold onto roots and pkg // fragments) } catch (RubyModelException e) { try { close(); // could not do better } catch (RubyModelException ex) { // ignore } } } } /** * Convenience method that returns the specific type of info for a Java * project. */ protected RubyProjectElementInfo getRubyProjectElementInfo() throws RubyModelException { return (RubyProjectElementInfo) getElementInfo(); } /** * Computes the collection of package fragment roots (local ones) and set it * on the given info. Need to check *all* package fragment roots in order to * reset NameLookup * * @param info * JavaProjectElementInfo * @throws JavaModelException */ public void computeChildren(RubyProjectElementInfo info) throws RubyModelException { ILoadpathEntry[] classpath = getResolvedLoadpath(true/* ignoreUnresolvedEntry */, false/* * don't * generateMarkerOnError */, false/* * don't * returnResolutionInProgress */); RubyProjectElementInfo.ProjectCache projectCache = info.projectCache; if (projectCache != null) { ISourceFolderRoot[] newRoots = computeSourceFolderRoots(classpath, true, null /* * no * reverse * map */); checkIdentical: { // compare all pkg fragment root lists ISourceFolderRoot[] oldRoots = projectCache.allPkgFragmentRootsCache; 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.setNonRubyResources(null); info.setChildren(computeSourceFolderRoots(classpath, false, null /* * no * reverse * map */)); } public ISourceFolderRoot[] getAllSourceFolderRoots(Map rootToResolvedEntries) throws RubyModelException { return computeSourceFolderRoots(getResolvedLoadpath(true/* ignoreUnresolvedEntry */, false/* * don't * generateMarkerOnError */, false/* * don't * returnResolutionInProgress */), true/* retrieveExportedRoots */, rootToResolvedEntries); } public boolean hasCycleMarker() { return this.getCycleMarker() != null; } /* * 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(IRubyModelMarker.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(IRubyModelMarker.CYCLE_DETECTED); if (cycleAttr != null && cycleAttr.equals("true")) { //$NON-NLS-1$ return marker; } } } } catch (CoreException e) { // could not get markers: return null } return null; } public boolean hasLoadpathCycle(ILoadpathEntry[] preferredClasspath) { HashSet cycleParticipants = new HashSet(); HashMap preferredClasspaths = new HashMap(1); preferredClasspaths.put(this, preferredClasspath); updateCycleParticipants(new ArrayList(2), cycleParticipants, ResourcesPlugin.getWorkspace().getRoot(), new HashSet(2), preferredClasspaths); return !cycleParticipants.isEmpty(); } /** * 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 preferredClasspaths * Map */ public void updateCycleParticipants(ArrayList prereqChain, HashSet cycleParticipants, IWorkspaceRoot workspaceRoot, HashSet traversed, Map preferredClasspaths) { IPath path = this.getPath(); prereqChain.add(path); traversed.add(path); try { ILoadpathEntry[] classpath = null; if (preferredClasspaths != null) classpath = (ILoadpathEntry[]) preferredClasspaths.get(this); if (classpath == null) classpath = getResolvedLoadpath(true/* ignoreUnresolvedEntry */, false/* * don't * generateMarkerOnError */, false/* * don't * returnResolutionInProgress */); for (int i = 0, length = classpath.length; i < length; i++) { ILoadpathEntry entry = classpath[i]; if (entry.getEntryKind() == ILoadpathEntry.CPE_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) { RubyProject javaProject = (RubyProject) RubyCore.create((IProject) member); javaProject.updateCycleParticipants(prereqChain, cycleParticipants, workspaceRoot, traversed, preferredClasspaths); } } } } } } catch (RubyModelException e) { // project doesn't exist: ignore } prereqChain.remove(path); } /** * Update cycle markers for all java projects * * @param preferredClasspaths * Map * @throws JavaModelException */ public static void updateAllCycleMarkers(Map preferredClasspaths) throws RubyModelException { // long start = System.currentTimeMillis(); IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); IProject[] rscProjects = workspaceRoot.getProjects(); int length = rscProjects.length; RubyProject[] projects = new RubyProject[length]; HashSet cycleParticipants = new HashSet(); HashSet traversed = new HashSet(); // compute cycle participants ArrayList prereqChain = new ArrayList(); for (int i = 0; i < length; i++) { if (hasRubyNature(rscProjects[i])) { RubyProject project = (projects[i] = (RubyProject) RubyCore.create(rscProjects[i])); if (!traversed.contains(project.getPath())) { prereqChain.clear(); project.updateCycleParticipants(prereqChain, cycleParticipants, workspaceRoot, traversed, preferredClasspaths); } } } // System.out.println("updateAllCycleMarkers: " + // (System.currentTimeMillis() - start) + " ms"); for (int i = 0; i < length; i++) { RubyProject project = projects[i]; if (project != null) { if (cycleParticipants.contains(project.getPath())) { IMarker cycleMarker = project.getCycleMarker(); String circularCPOption = project.getOption(RubyCore.CORE_CIRCULAR_CLASSPATH, true); int circularCPSeverity = RubyCore.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 RubyModelException(e); } } else { // create new marker project.createLoadpathProblemMarker(new RubyModelStatus(IRubyModelStatusConstants.CLASSPATH_CYCLE, project)); } } else { project.flushLoadpathProblemMarkers(true, false); } } } } public void setRawLoadpath(ILoadpathEntry[] newEntries, IPath newOutputLocation, IProgressMonitor monitor, boolean canChangeResource, ILoadpathEntry[] oldResolvedPath, boolean needValidation, boolean needSave) throws RubyModelException { RubyModelManager manager = RubyModelManager.getRubyModelManager(); try { ILoadpathEntry[] newRawPath = newEntries; if (newRawPath == null) { // are we already with the default // loadpath newRawPath = defaultLoadpath(); } SetLoadpathOperation op = new SetLoadpathOperation(this, oldResolvedPath, newRawPath, newOutputLocation, canChangeResource, needValidation, needSave); op.runOperation(monitor); } catch (RubyModelException e) { manager.getDeltaProcessor().flush(); throw e; } } public boolean saveLoadpath(ILoadpathEntry[] newLoadpath, IPath newOutputLocation) throws RubyModelException { if (!this.project.isAccessible()) return false; Map unknownElements = new HashMap(); ILoadpathEntry[] fileEntries = readLoadpathFile(false /* * don't create * markers */, false/* * don't * log * problems */, unknownElements); if (fileEntries != null && isLoadpathEqualsTo(newLoadpath, newOutputLocation, fileEntries)) { // no need to save it, it is the same return false; } // actual file saving try { setSharedProperty(LOADPATH_FILENAME, encodeLoadpath(newLoadpath, newOutputLocation, true, unknownElements)); return true; } catch (CoreException e) { throw new RubyModelException(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 JavaProject#getSharedProperty(String key) * @throws CoreException */ public void setSharedProperty(String key, String value) throws CoreException { IFile rscFile = this.project.getFile(key); byte[] bytes = null; try { bytes = value.getBytes(org.rubypeople.rdt.core.util.Util.UTF_8); // .loadpath // always // encoded // with // UTF-8 } catch (UnsupportedEncodingException e) { Util.log(e, "Could not write .loadpath with UTF-8 encoding "); //$NON-NLS-1$ // fallback to default bytes = value.getBytes(); } InputStream inputStream = new ByteArrayInputStream(bytes); // update the resource content if (rscFile.exists()) { if (rscFile.isReadOnly()) { // provide opportunity to checkout read-only .loadpath file // (23984) ResourcesPlugin.getWorkspace().validateEdit(new IFile[] { rscFile }, null); } rscFile.setContents(inputStream, IResource.FORCE, null); } else { rscFile.create(inputStream, IResource.FORCE, null); } } /** * Compare current classpath with given one to see if any different. Note * that the argument classpath contains its binary output. * * @param newClasspath * IClasspathEntry[] * @param newOutputLocation * IPath * @param otherClasspathWithOutput * IClasspathEntry[] * @return boolean */ public boolean isLoadpathEqualsTo(ILoadpathEntry[] newClasspath, IPath newOutputLocation, ILoadpathEntry[] otherClasspathWithOutput) { if (otherClasspathWithOutput == null || otherClasspathWithOutput.length == 0) return false; int length = otherClasspathWithOutput.length; if (length != newClasspath.length + 1) // output is amongst file entries (last one) return false; // compare classpath entries for (int i = 0; i < length - 1; i++) { if (!otherClasspathWithOutput[i].equals(newClasspath[i])) return false; } return true; } /** * Returns the XML String encoding of the class path. */ protected String encodeLoadpath(ILoadpathEntry[] classpath, IPath outputLocation, boolean indent, Map unknownElements) throws RubyModelException { try { ByteArrayOutputStream s = new ByteArrayOutputStream(); OutputStreamWriter writer = new OutputStreamWriter(s, "UTF8"); //$NON-NLS-1$ XMLWriter xmlWriter = new XMLWriter(writer, this, true/* * print XML * version */); xmlWriter.startTag(LoadpathEntry.TAG_LOADPATH, indent); for (int i = 0; i < classpath.length; ++i) { ((LoadpathEntry) classpath[i]).elementEncode(xmlWriter, this.project.getFullPath(), indent, true, unknownElements); } xmlWriter.endTag(LoadpathEntry.TAG_LOADPATH, indent, true/* * insert * new * line */); writer.flush(); writer.close(); return s.toString("UTF8");//$NON-NLS-1$ } catch (IOException e) { throw new RubyModelException(e, IRubyModelStatusConstants.IO_EXCEPTION); } } public void setRawLoadpath(ILoadpathEntry[] entries, boolean canModifyResources, IProgressMonitor monitor) throws RubyModelException { setRawLoadpath(entries, SetLoadpathOperation.DO_NOT_SET_OUTPUT, monitor, canModifyResources, getResolvedLoadpath(true/* ignoreUnresolvedEntry */, false/* * don't * generateMarkerOnError */, false/* * don't * returnResolutionInProgress */), true, // needValidation canModifyResources); // save only if modifying resources is // allowed } public void setRawLoadpath(ILoadpathEntry[] entries, IProgressMonitor monitor) throws RubyModelException { setRawLoadpath(entries, SetLoadpathOperation.DO_NOT_SET_OUTPUT, monitor, true, // canChangeResource // (as // per // API // contract) getResolvedLoadpath(true/* ignoreUnresolvedEntry */, false/* * don't * generateMarkerOnError */, false/* * don't * returnResolutionInProgress */), true, // needValidation true); // need to save } public void setRawLoadpath(ILoadpathEntry[] entries, IPath outputLocation, IProgressMonitor monitor) throws RubyModelException { setRawLoadpath(entries, outputLocation, monitor, true, // canChangeResource // (as per API // contract) getResolvedLoadpath(true/* ignoreUnresolvedEntry */, false/* * don't * generateMarkerOnError */, false/* * don't * returnResolutionInProgress */), true, // needValidation true); // need to save } public ISourceFolderRoot getSourceFolderRoot(String string) { return getPackageFragmentRoot0(RubyProject.canonicalizedPath(new Path(string))); } private ISourceFolderRoot getPackageFragmentRoot0(IPath path) { return new ExternalSourceFolderRoot(path, this); } /* * Force the project to reload its <code>.classpath</code> file from disk * and update the classpath accordingly. Usually, a change to the <code>.classpath</code> * file is automatically noticed and reconciled at the next resource change * notification event. If required to consider such a change prior to the * next automatic refresh, then this functionnality should be used to * trigger a refresh. In particular, if a change to the file is performed, * during an operation where this change needs to be reflected before the * operation ends, then an explicit refresh is necessary. Note that * classpath markers are NOT created. * * @param monitor a progress monitor for reporting operation progress * @exception JavaModelException if the classpath could not be updated. * Reasons include: <ul> <li> This Java element does not exist * (ELEMENT_DOES_NOT_EXIST)</li> <li> Two or more entries specify source * roots with the same or overlapping paths (NAME_COLLISION) <li> A entry of * kind <code>CPE_PROJECT</code> refers to this project (INVALID_PATH) * <li>This Java element does not exist (ELEMENT_DOES_NOT_EXIST)</li> <li>The * output location path refers to a location not contained in this project (<code>PATH_OUTSIDE_PROJECT</code>) * <li>The output location path is not an absolute path (<code>RELATIVE_PATH</code>) * <li>The output location path is nested inside a package fragment root of * this project (<code>INVALID_PATH</code>) <li> The classpath is being * modified during resource change event notification (CORE_EXCEPTION) </ul> */ protected void forceLoadpathReload(IProgressMonitor monitor) throws RubyModelException { if (monitor != null && monitor.isCanceled()) return; // check if any actual difference boolean wasSuccessful = false; // flag recording if .loadpath file // change got reflected try { // force to (re)read the property file ILoadpathEntry[] fileEntries = readLoadpathFile(false/* * don't * create * markers */, false/* * don't * log * problems */); if (fileEntries == null) { return; // could not read, ignore } RubyModelManager.PerProjectInfo info = getPerProjectInfo(); if (info.rawLoadpath != null) { // if there is an in-memory // classpath if (isLoadpathEqualsTo(info.rawLoadpath, info.outputLocation, fileEntries)) { wasSuccessful = true; return; } } ILoadpathEntry[] oldResolvedLoadpath = info.resolvedLoadpath; setRawLoadpath(fileEntries, SetLoadpathOperation.DO_NOT_SET_OUTPUT, monitor, !ResourcesPlugin.getWorkspace().isTreeLocked(), // canChangeResource oldResolvedLoadpath != null ? oldResolvedLoadpath : getResolvedLoadpath(true/* ignoreUnresolvedEntry */, false/* * don't * generateMarkerOnError */, false/* * don't * returnResolutionInProgress */), true, // needValidation false); // no need to save // if reach that far, the classpath file change got absorbed wasSuccessful = true; } catch (RuntimeException e) { // setRawClasspath might fire a delta, and a listener may throw an // exception if (this.project.isAccessible()) { Util.log(e, "Could not set loadpath for " + getPath()); //$NON-NLS-1$ } throw e; // rethrow } catch (RubyModelException e) { // CP failed validation if (!ResourcesPlugin.getWorkspace().isTreeLocked()) { if (this.project.isAccessible()) { if (e.getRubyModelStatus().getException() instanceof CoreException) { // happens if the .loadpath could not be written to disk createLoadpathProblemMarker(new RubyModelStatus(IRubyModelStatusConstants.INVALID_LOADPATH_FILE_FORMAT, Messages.bind(Messages.classpath_couldNotWriteClasspathFile, new String[] { getElementName(), e.getMessage() }))); } else { createLoadpathProblemMarker(new RubyModelStatus(IRubyModelStatusConstants.INVALID_LOADPATH_FILE_FORMAT, Messages.bind(Messages.classpath_invalidClasspathInClasspathFile, new String[] { getElementName(), e.getMessage() }))); } } } throw e; // rethrow } finally { if (!wasSuccessful) { try { this.getPerProjectInfo().updateLoadpathInformation(RubyProject.INVALID_LOADPATH); updateSourceFolderRoots(); } catch (RubyModelException e) { // ignore } } } } public void updateLoadpathMarkers(Map preferredClasspaths, Map preferredOutputs) { this.flushLoadpathProblemMarkers(false/* cycle */, true/* format */); this.flushLoadpathProblemMarkers(false/* cycle */, false/* format */); ILoadpathEntry[] classpath = this.readLoadpathFile(true/* marker */, false/* log */); // remember invalid path so as to avoid reupdating it again later on if (preferredClasspaths != null) { preferredClasspaths.put(this, classpath == null ? INVALID_LOADPATH : classpath); } if (preferredOutputs != null) { preferredOutputs.put(this, null); } // force classpath marker refresh if (classpath != null) { for (int i = 0; i < classpath.length; i++) { IRubyModelStatus status = LoadpathEntry.validateLoadpathEntry(this, classpath[i], false/* * src * attach */, true /* * recurse * in * container */); if (!status.isOK()) { if (status.getCode() == IRubyModelStatusConstants.INVALID_CLASSPATH && ((LoadpathEntry) classpath[i]).isOptional()) continue; // ignore this entry this.createLoadpathProblemMarker(status); } } IRubyModelStatus status = LoadpathEntry.validateLoadpath(this, classpath, null); if (!status.isOK()) this.createLoadpathProblemMarker(status); } } /** * Reads and decode an XML loadpath string */ public ILoadpathEntry[] decodeLoadpath(String xmlClasspath, boolean createMarker, boolean logProblems) { return decodeLoadpath(xmlClasspath, createMarker, logProblems, null/*not interested in unknown elements*/); } public ISourceFolderRoot findSourceFolderRoot(IPath path) throws RubyModelException { return findSourceFolderRoot0(RubyProject.canonicalizedPath(path)); } /* * no path canonicalization */ public ISourceFolderRoot findSourceFolderRoot0(IPath path) throws RubyModelException { ISourceFolderRoot[] allRoots = this.getAllSourceFolderRoots(); if (!path.isAbsolute()) { throw new IllegalArgumentException(Messages.path_mustBeAbsolute); } for (int i= 0; i < allRoots.length; i++) { ISourceFolderRoot classpathRoot= allRoots[i]; if (classpathRoot.getPath().equals(path)) { return classpathRoot; } } return null; } /** * @see IRubyProject */ public ISourceFolderRoot[] findSourceFolderRoots(ILoadpathEntry entry) { try { ILoadpathEntry[] classpath = this.getRawLoadpath(); for (int i = 0, length = classpath.length; i < length; i++) { if (classpath[i].equals(entry)) { // entry may need to be resolved return computeSourceFolderRoots( getResolvedLoadpath(new ILoadpathEntry[] {entry}, null, true, false, null/*no reverse map*/), false, // don't retrieve exported roots null); /*no reverse map*/ } } } catch (RubyModelException e) { // project doesn't exist: return an empty array } return new ISourceFolderRoot[] {}; } /** * @see IRubyProject */ public ISourceFolderRoot[] getAllSourceFolderRoots() throws RubyModelException { return getAllSourceFolderRoots(null /*no reverse map*/); } /* * @see RubyElement */ public IRubyElement getHandleFromMemento(String token, MementoTokenizer memento, WorkingCopyOwner owner) { switch (token.charAt(0)) { case JEM_SOURCEFOLDERROOT: String rootPath = ISourceFolderRoot.DEFAULT_PACKAGEROOT_PATH; token = null; while (memento.hasMoreTokens()) { token = memento.nextToken(); char firstChar = token.charAt(0); if (firstChar != JEM_SOURCE_FOLDER && firstChar != JEM_COUNT) { rootPath += token; } else { break; } } IPath path = new Path(rootPath); RubyElement root; if(path.isAbsolute()) { root = (RubyElement) getPackageFragmentRoot0(path); } else root = (RubyElement)getSourceFolderRoot(path); if (token != null && token.charAt(0) == JEM_SOURCE_FOLDER) { return root.getHandleFromMemento(token, memento, owner); } else { return root.getHandleFromMemento(memento, owner); } } return null; } /** * Returns the <code>char</code> that marks the start of this handles * contribution to a memento. */ protected char getHandleMementoDelimiter() { return JEM_RUBYPROJECT; } /** * @see IRubyProject */ public ITypeHierarchy newTypeHierarchy( IRegion region, IProgressMonitor monitor) throws RubyModelException { return newTypeHierarchy(region, DefaultWorkingCopyOwner.PRIMARY, monitor); } /** * @see IJavaProject */ public ITypeHierarchy newTypeHierarchy( IRegion region, WorkingCopyOwner owner, IProgressMonitor monitor) throws RubyModelException { if (region == null) { throw new IllegalArgumentException(Messages.hierarchy_nullRegion); } IRubyScript[] workingCopies = RubyModelManager.getRubyModelManager().getWorkingCopies(owner, true/*add primary working copies*/); CreateTypeHierarchyOperation op = new CreateTypeHierarchyOperation(region, workingCopies, null, true); op.runOperation(monitor); return op.getResult(); } /* * @see IRubyProject */ public boolean isOnLoadpath(IResource resource) { IPath exactPath = resource.getFullPath(); IPath path = exactPath; // ensure that folders are only excluded if all of their children are excluded boolean isFolderPath = resource.getType() == IResource.FOLDER; ILoadpathEntry[] classpath; try { classpath = this.getResolvedLoadpath(true/*ignoreUnresolvedEntry*/, false/*don't generateMarkerOnError*/, false/*don't returnResolutionInProgress*/); } catch(RubyModelException e){ return false; // not a Ruby project } for (int i = 0; i < classpath.length; i++) { ILoadpathEntry entry = classpath[i]; IPath entryPath = entry.getPath(); if (entryPath.equals(exactPath)) { // source folder roots must match exactly entry pathes (no exclusion there) return true; } if (entryPath.isPrefixOf(path) && !Util.isExcluded(path, ((LoadpathEntry)entry).fullInclusionPatternChars(), ((LoadpathEntry)entry).fullExclusionPatternChars(), isFolderPath)) { return true; } } return false; } }