|
From: <caw...@us...> - 2007-03-28 15:38:55
|
Revision: 2236
http://svn.sourceforge.net/rubyeclipse/?rev=2236&view=rev
Author: cawilliams
Date: 2007-03-28 08:37:53 -0700 (Wed, 28 Mar 2007)
Log Message:
-----------
modify the existing Expermintenal Index to be a bit msarter. Before it ate up memeory like crazy saving every type constant and global (and probably all their children). Now it actually is more like a real search engine and create string index keys with very basic info. Then we retrieve the names and elements using those keys.
Modified Paths:
--------------
trunk/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/codeassist/CompletionEngine.java
trunk/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/core/search/ExperimentalIndex.java
Added Paths:
-----------
trunk/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/core/search/HandleFactory.java
Modified: trunk/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/codeassist/CompletionEngine.java
===================================================================
--- trunk/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/codeassist/CompletionEngine.java 2007-03-27 18:35:40 UTC (rev 2235)
+++ trunk/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/codeassist/CompletionEngine.java 2007-03-28 15:37:53 UTC (rev 2236)
@@ -133,7 +133,7 @@
}
private void suggestGlobals() {
- Set<String> globals = ExperimentalIndex.getGlobalNames();
+ Set<String> globals = ExperimentalIndex.getGlobalNames(fContext.getScript());
for (String name : globals) {
if (!fContext.prefixStartsWith(name))
continue;
@@ -143,7 +143,7 @@
}
private void suggestTypeNames() {
- Set<String> types = ExperimentalIndex.getTypeNames();
+ Set<String> types = ExperimentalIndex.getTypeNames(fContext.getScript());
for (String name : types) {
if (!fContext.prefixStartsWith(name))
continue;
@@ -160,7 +160,7 @@
}
private void suggestConstantNames() {
- Set<String> types = ExperimentalIndex.getConstantNames();
+ Set<String> types = ExperimentalIndex.getConstantNames(fContext.getScript());
for (String name : types) {
if (!fContext.prefixStartsWith(name))
continue;
Modified: trunk/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/core/search/ExperimentalIndex.java
===================================================================
--- trunk/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/core/search/ExperimentalIndex.java 2007-03-27 18:35:40 UTC (rev 2235)
+++ trunk/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/core/search/ExperimentalIndex.java 2007-03-28 15:37:53 UTC (rev 2236)
@@ -1,77 +1,98 @@
package org.rubypeople.rdt.internal.core.search;
-import java.util.Collections;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
+import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.rubypeople.rdt.core.ElementChangedEvent;
import org.rubypeople.rdt.core.IElementChangedListener;
-import org.rubypeople.rdt.core.IField;
import org.rubypeople.rdt.core.IParent;
import org.rubypeople.rdt.core.IRubyElement;
import org.rubypeople.rdt.core.IRubyElementDelta;
import org.rubypeople.rdt.core.IRubyModel;
+import org.rubypeople.rdt.core.IRubyProject;
+import org.rubypeople.rdt.core.IRubyScript;
+import org.rubypeople.rdt.core.ISourceFolderRoot;
import org.rubypeople.rdt.core.IType;
import org.rubypeople.rdt.core.RubyCore;
import org.rubypeople.rdt.core.RubyModelException;
+import org.rubypeople.rdt.internal.core.Openable;
import org.rubypeople.rdt.internal.core.RubyModelManager;
public class ExperimentalIndex implements IElementChangedListener {
-
+
private static ExperimentalIndex fgInstance;
- private static HashSet<IField> fgConstants;
- private static HashSet<IType> fgTypes;
- private static HashSet<IField> fgGlobals;
-
+ private static Map<IPath, SearchDocument> documents;
+ private static HandleFactory factory = new HandleFactory();
+
private ExperimentalIndex() {
- fgTypes = new HashSet<IType>();
- fgConstants = new HashSet<IField>();
- fgGlobals = new HashSet<IField>();
+ documents = new HashMap<IPath, SearchDocument>();
}
-
+
public void elementChanged(ElementChangedEvent event) {
processDelta(event.getDelta());
}
+
+ // FIXME We're ding poor man's scoping by passing in the script. We should actually create scope classes which could tell if a document fell in our out of it...
+ public static Set<String> getTypeNames(IRubyScript script) {
+ return getElementNames(IRubyElement.TYPE, script);
+ }
- public static Set<String> getTypeNames() {
- Set<IType> types = Collections.unmodifiableSet((HashSet<IType>)fgTypes.clone()); // clone to avoid concurrent modification when iterating
- Set<String> names = new HashSet<String>();
- for (IType type : types) {
- names.add(type.getElementName());
+ public static Set<String> getConstantNames(IRubyScript script) {
+ return getElementNames(IRubyElement.CONSTANT, script);
+ }
+
+ private static Set<String> getElementNames(int type, IRubyScript script) {
+ Set<String> names = new HashSet<String>();
+ Collection<SearchDocument> documents = getDocumentsInScope(script);
+ for (SearchDocument doc : documents) {
+ Set<String> elements = doc.getElementNamesOfType(type);
+ for (String element : elements) {
+ names.add(element);
+ }
}
return names;
}
-
- public static Set<String> getConstantNames() {
- Set<IField> types = Collections.unmodifiableSet((HashSet<IField>)fgConstants.clone()); // clone to avoid concurrent modification when iterating
- Set<String> names = new HashSet<String>();
- for (IField type : types) {
- names.add(type.getElementName());
+
+ private static Collection<SearchDocument> getDocumentsInScope(IRubyScript script) {
+ try {
+ Set<SearchDocument> matches = new HashSet<SearchDocument>();
+ IRubyProject project = script.getRubyProject();
+ ISourceFolderRoot[] roots = project.getSourceFolderRoots();
+ for (IPath path : documents.keySet()) {
+ // If path is in loadpath of script's project, add it
+ for (int i = 0; i < roots.length; i++) {
+ if (roots[i].getPath().isPrefixOf(path)) matches.add(documents.get(path));
+ }
+ }
+ return matches;
+ } catch (RubyModelException e) {
+ // ignore?
+ return documents.values();
}
- return names;
}
-
+
public static Set<IType> findType(String name) {
- Set<IType> types = Collections.unmodifiableSet((HashSet<IType>)fgTypes.clone()); // clone to avoid concurrent modification when iterating
- Set<IType> matches = new HashSet<IType>();
- for (IType type : types) {
- if (type.getElementName().equals(name))
- matches.add(type);
+ Set<IType> types = new HashSet<IType>();
+ for (SearchDocument doc : documents.values()) {
+ IType type = doc.findType(name);
+ if (type != null)
+ types.add(type);
}
- return matches;
+ return types;
}
-
- public static Set<String> getGlobalNames() {
- Set<IField> types = Collections.unmodifiableSet((HashSet<IField>)fgGlobals.clone()); // clone to avoid concurrent modification when iterating
- Set<String> names = new HashSet<String>();
- for (IField type : types) {
- names.add(type.getElementName());
- }
- return names;
+
+ public static Set<String> getGlobalNames(IRubyScript script) {
+ return getElementNames(IRubyElement.GLOBAL, script);
}
private void processDelta(IRubyElementDelta delta) {
@@ -84,7 +105,7 @@
this.processDelta(child);
}
break;
- case IRubyElementDelta.REMOVED:
+ case IRubyElementDelta.REMOVED:
removeElement(element);
break;
case IRubyElementDelta.ADDED:
@@ -94,31 +115,31 @@
}
void removeElement(IRubyElement element) {
- switch (element.getElementType()) {
- case IRubyElement.TYPE:
- fgTypes.remove(element);
- break;
- case IRubyElement.CONSTANT:
- fgConstants.remove(element);
- break;
- case IRubyElement.GLOBAL:
- fgGlobals.remove(element);
- break;
- }
+ if ((element.isType(IRubyElement.RUBY_MODEL)) ||
+ (element.isType(IRubyElement.RUBY_PROJECT)) ||
+ (element.isType(IRubyElement.SCRIPT)) ||
+ (element.isType(IRubyElement.SOURCE_FOLDER_ROOT)) ||
+ (element.isType(IRubyElement.SOURCE_FOLDER))) return;
+ SearchDocument doc = documents.get(element.getPath());
+ if (doc == null)
+ return;
+ doc.removeElement(element);
+ if (doc.isEmpty())
+ documents.remove(element.getPath());
}
void addElement(IRubyElement element) {
- switch (element.getElementType()) {
- case IRubyElement.TYPE:
- fgTypes.add((IType)element);
- break;
- case IRubyElement.CONSTANT:
- fgConstants.add((IField)element);
- break;
- case IRubyElement.GLOBAL:
- fgGlobals.add((IField)element);
- break;
+ if ((element.isType(IRubyElement.RUBY_MODEL)) ||
+ (element.isType(IRubyElement.RUBY_PROJECT)) ||
+ (element.isType(IRubyElement.SCRIPT)) ||
+ (element.isType(IRubyElement.SOURCE_FOLDER_ROOT)) ||
+ (element.isType(IRubyElement.SOURCE_FOLDER))) return;
+ SearchDocument doc = documents.get(element.getPath());
+ if (doc == null) {
+ doc = new SearchDocument(element.getPath());
+ documents.put(element.getPath(), doc);
}
+ doc.addElement(element);
}
public static ExperimentalIndex instance() {
@@ -130,9 +151,9 @@
public static void start() {
Job job = new ExperimentalIndexJob(instance());
- job.schedule();
+ job.schedule();
}
-
+
private static class ExperimentalIndexJob extends Job {
private ExperimentalIndex index;
@@ -143,11 +164,12 @@
@Override
protected IStatus run(IProgressMonitor monitor) {
- // TODO Load up saved data if there is any, rather than starting over
+ // TODO Load up saved data if there is any, rather than starting
+ // over
// TODO Clear saved state if user cleans a project
// TODO Save state after a run
IRubyModel model = RubyModelManager.getRubyModelManager().getRubyModel();
- addChildren(model);
+ addChildren(model);
return Status.OK_STATUS;
}
@@ -163,8 +185,110 @@
}
} catch (RubyModelException e) {
RubyCore.log(e);
- }
+ }
}
-
+
}
+
+ private class SearchDocument {
+ private static final String SEPARATOR = "/";
+ private List<String> indices = new ArrayList<String>();
+ private IPath path;
+ private IRubyScript script;
+
+ SearchDocument(IPath path) {
+ this.path = path;
+ }
+
+ public Set<String> getElementNamesOfType(int type) {
+ Set<String> names = new HashSet<String>();
+ for (String indexKey : indices) {
+ if (getTypeFromKey(indexKey) != type) continue;
+ names.add(getNameFromKey(indexKey));
+ }
+ return names;
+ }
+
+ public List<IRubyElement> getElementsOfType(int type) {
+ IRubyScript script = getScript();
+ return getChildrenOfType(script, type);
+ }
+
+ private IRubyScript getScript() {
+ if (this.script == null) {
+ Openable openable = factory.createOpenable(path.toString());
+ this.script = (IRubyScript) openable;
+ }
+ return this.script;
+ }
+
+ private List<IRubyElement> getChildrenOfType(IParent parent, int type) {
+ List<IRubyElement> elements = new ArrayList<IRubyElement>();
+ if (parent == null) return elements;
+ try {
+ IRubyElement[] children = parent.getChildren();
+ if (children == null)
+ return elements;
+ for (int i = 0; i < children.length; i++) {
+ if (children[i].isType(type))
+ elements.add(children[i]);
+ if (children[i] instanceof IParent) {
+ IParent childParent = (IParent) children[i];
+ elements.addAll(getChildrenOfType(childParent, type));
+ }
+ }
+ } catch (RubyModelException e) {
+ // ignore
+ }
+ return elements;
+ }
+
+ public boolean isEmpty() {
+ return indices.isEmpty();
+ }
+
+ public void removeElement(IRubyElement element) {
+ indices.remove(createKey(element));
+ }
+
+ private String createKey(IRubyElement element) {
+ return createKey(element.getElementType(), element.getElementName());
+ }
+
+ private String createKey(int type, String name) {
+ return type + SEPARATOR + name;
+ }
+
+ public void addElement(IRubyElement element) {
+ indices.add(createKey(element));
+ }
+
+ public IType findType(String name) {
+ return (IType) findElement(createKey(IRubyElement.TYPE, name));
+ }
+
+ private IRubyElement findElement(String key) {
+ for (String indexKey : indices) {
+ if (!indexKey.equals(key))
+ continue;
+ IRubyScript script = getScript();
+ List<IRubyElement> children = getChildrenOfType(script, getTypeFromKey(key));
+ for (IRubyElement element : children) {
+ if (element.getElementName().equals(getNameFromKey(key)))
+ return element;
+ }
+ }
+ return null;
+ }
+
+ private String getNameFromKey(String key) {
+ String[] parts = key.split(SEPARATOR);
+ return parts[1];
+ }
+
+ private int getTypeFromKey(String key) {
+ String[] parts = key.split(SEPARATOR);
+ return Integer.parseInt(parts[0]);
+ }
+ }
}
Added: trunk/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/core/search/HandleFactory.java
===================================================================
--- trunk/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/core/search/HandleFactory.java (rev 0)
+++ trunk/org.rubypeople.rdt.core/src/org/rubypeople/rdt/internal/core/search/HandleFactory.java 2007-03-28 15:37:53 UTC (rev 2236)
@@ -0,0 +1,109 @@
+package org.rubypeople.rdt.internal.core.search;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+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.RubyCore;
+import org.rubypeople.rdt.internal.core.Openable;
+import org.rubypeople.rdt.internal.core.RubyModel;
+import org.rubypeople.rdt.internal.core.RubyModelManager;
+import org.rubypeople.rdt.internal.core.SourceFolderRoot;
+import org.rubypeople.rdt.internal.core.util.CharOperation;
+import org.rubypeople.rdt.internal.core.util.HashtableOfArrayToObject;
+import org.rubypeople.rdt.internal.core.util.Util;
+
+public class HandleFactory {
+
+ /**
+ * Cache package fragment root information to optimize speed performance.
+ */
+ private String lastSrcFolderRootPath;
+ private ISourceFolderRoot lastSrcFolderRoot;
+
+ /**
+ * Cache package handles to optimize memory.
+ */
+ private HashtableOfArrayToObject folderHandles;
+
+ private RubyModel rubyModel;
+
+ public HandleFactory() {
+ this.rubyModel = RubyModelManager.getRubyModelManager().getRubyModel();
+ }
+
+ /**
+ * Returns the source folder root that contains the given resource path.
+ */
+ private ISourceFolderRoot getSourceFolderRoot(String pathString) {
+
+ IPath path = new Path(pathString);
+ IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
+ for (int i = 0, max = projects.length; i < max; i++) {
+ try {
+ IProject project = projects[i];
+ if (!project.isAccessible() || !project.hasNature(RubyCore.NATURE_ID))
+ continue;
+ IRubyProject rubyProject = this.rubyModel.getRubyProject(project);
+ ISourceFolderRoot[] roots = rubyProject.getSourceFolderRoots();
+ for (int j = 0, rootCount = roots.length; j < rootCount; j++) {
+ SourceFolderRoot root = (SourceFolderRoot) roots[j];
+ if (root.getPath().isPrefixOf(path) && !Util.isExcluded(path, root.fullInclusionPatternChars(), root.fullExclusionPatternChars(), false)) {
+ return root;
+ }
+ }
+ } catch (CoreException e) {
+ // CoreException from hasNature - should not happen since we
+ // check that the project is accessible
+ // RubyModelException from getPackageFragmentRoots - a problem
+ // occured while accessing project: nothing we can do, ignore
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Creates an Openable handle from the given resource path. The resource
+ * path can be a path to a file in the workbench.
+ */
+ public Openable createOpenable(String resourcePath) {
+ // path to a file in a directory
+ // Optimization: cache source folder root handle and package handles
+ int rootPathLength = -1;
+ if (this.lastSrcFolderRootPath == null || !(resourcePath.startsWith(this.lastSrcFolderRootPath) && (rootPathLength = this.lastSrcFolderRootPath.length()) > 0 && resourcePath.charAt(rootPathLength) == '/')) {
+ ISourceFolderRoot root = this.getSourceFolderRoot(resourcePath);
+ if (root == null)
+ return null; // match is outside loadpath
+ this.lastSrcFolderRoot = root;
+ this.lastSrcFolderRootPath = this.lastSrcFolderRoot.getPath().toString();
+ this.folderHandles = new HashtableOfArrayToObject(5);
+ }
+ // create handle
+ resourcePath = resourcePath.substring(this.lastSrcFolderRootPath.length() + 1);
+ String[] simpleNames = new Path(resourcePath).segments();
+ String[] pkgName;
+ int length = simpleNames.length - 1;
+ if (length > 0) {
+ pkgName = new String[length];
+ System.arraycopy(simpleNames, 0, pkgName, 0, length);
+ } else {
+ pkgName = CharOperation.NO_STRINGS;
+ }
+ ISourceFolder pkgFragment = (ISourceFolder) this.folderHandles.get(pkgName);
+ if (pkgFragment == null) {
+ pkgFragment = ((SourceFolderRoot) this.lastSrcFolderRoot).getSourceFolder(pkgName);
+ this.folderHandles.put(pkgName, pkgFragment);
+ }
+ String simpleName = simpleNames[length];
+ if (org.rubypeople.rdt.internal.core.util.Util.isRubyLikeFileName(simpleName)) {
+ IRubyScript unit = pkgFragment.getRubyScript(simpleName);
+ return (Openable) unit;
+ }
+ return null;
+ }
+}
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|