package org.rubypeople.rdt.internal.core.search.matching;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.jruby.ast.ClassVarAsgnNode;
import org.jruby.ast.GlobalAsgnNode;
import org.jruby.ast.InstAsgnNode;
import org.jruby.ast.Node;
import org.rubypeople.rdt.core.IRubyElement;
import org.rubypeople.rdt.core.IRubyProject;
import org.rubypeople.rdt.core.IRubyScript;
import org.rubypeople.rdt.core.RubyModelException;
import org.rubypeople.rdt.core.search.FieldDeclarationMatch;
import org.rubypeople.rdt.core.search.FieldReferenceMatch;
import org.rubypeople.rdt.core.search.IRubySearchScope;
import org.rubypeople.rdt.core.search.MethodDeclarationMatch;
import org.rubypeople.rdt.core.search.MethodReferenceMatch;
import org.rubypeople.rdt.core.search.SearchDocument;
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.core.search.SearchRequestor;
import org.rubypeople.rdt.core.search.TypeDeclarationMatch;
import org.rubypeople.rdt.core.search.TypeReferenceMatch;
import org.rubypeople.rdt.internal.compiler.util.SimpleLookupTable;
import org.rubypeople.rdt.internal.core.ExternalSourceFolderRoot;
import org.rubypeople.rdt.internal.core.Openable;
import org.rubypeople.rdt.internal.core.RubyElement;
import org.rubypeople.rdt.internal.core.RubyModelManager;
import org.rubypeople.rdt.internal.core.RubyProject;
import org.rubypeople.rdt.internal.core.RubyScript;
import org.rubypeople.rdt.internal.core.index.Index;
import org.rubypeople.rdt.internal.core.search.BasicSearchEngine;
import org.rubypeople.rdt.internal.core.search.HandleFactory;
import org.rubypeople.rdt.internal.core.search.IndexQueryRequestor;
import org.rubypeople.rdt.internal.core.search.IndexSelector;
import org.rubypeople.rdt.internal.core.search.RubySearchDocument;
import org.rubypeople.rdt.internal.core.util.Util;
public class MatchLocator {
public static final int MAX_AT_ONCE;
static {
long maxMemory = Runtime.getRuntime().maxMemory();
int ratio = (int) Math.round(((double) maxMemory) / (64 * 0x100000));
switch (ratio) {
case 0:
case 1:
MAX_AT_ONCE = 100;
break;
case 2:
MAX_AT_ONCE = 200;
break;
case 3:
MAX_AT_ONCE = 300;
break;
default:
MAX_AT_ONCE = 400;
break;
}
}
// permanent state
public SearchPattern pattern;
public PatternLocator patternLocator;
public int matchContainer;
public SearchRequestor requestor;
public IRubySearchScope scope;
public IProgressMonitor progressMonitor;
public IRubyScript[] workingCopies;
public HandleFactory handleFactory;
SimpleLookupTable bindings;
// Progress information
int progressStep;
int progressWorked;
private PossibleMatch currentPossibleMatch;
public MatchLocator(
SearchPattern pattern,
SearchRequestor requestor,
IRubySearchScope scope,
IProgressMonitor progressMonitor) {
this.pattern = pattern;
this.patternLocator = PatternLocator.patternLocator(this.pattern);
this.matchContainer = this.patternLocator.matchContainer();
this.requestor = requestor;
this.scope = scope;
this.progressMonitor = progressMonitor;
}
public static void findIndexMatches(InternalSearchPattern pattern, Index index,
IndexQueryRequestor requestor, SearchParticipant participant,
IRubySearchScope scope, IProgressMonitor monitor) throws IOException {
pattern.findIndexMatches(index, requestor, participant, scope, monitor);
}
public static IRubyElement getProjectOrJar(IRubyElement element) {
while (!(element instanceof IRubyProject)) {
element = element.getParent();
}
return element;
}
public static IRubyElement projectOrJarFocus(InternalSearchPattern pattern) {
return pattern == null || pattern.focus == null ? null : getProjectOrJar(pattern.focus);
}
public static SearchDocument[] addWorkingCopies(InternalSearchPattern pattern,
SearchDocument[] indexMatches, IRubyScript[] copies,
SearchParticipant participant) {
// working copies take precedence over corresponding compilation units
HashMap workingCopyDocuments = workingCopiesThatCanSeeFocus(copies, pattern.focus, pattern.isPolymorphicSearch(), participant);
SearchDocument[] matches = null;
int length = indexMatches.length;
for (int i = 0; i < length; i++) {
SearchDocument searchDocument = indexMatches[i];
if (searchDocument.getParticipant() == participant) {
SearchDocument workingCopyDocument = (SearchDocument) workingCopyDocuments.remove(searchDocument.getPath());
if (workingCopyDocument != null) {
if (matches == null) {
System.arraycopy(indexMatches, 0, matches = new SearchDocument[length], 0, length);
}
matches[i] = workingCopyDocument;
}
}
}
if (matches == null) { // no working copy
matches = indexMatches;
}
int remainingWorkingCopiesSize = workingCopyDocuments.size();
if (remainingWorkingCopiesSize != 0) {
System.arraycopy(matches, 0, matches = new SearchDocument[length+remainingWorkingCopiesSize], 0, length);
Iterator iterator = workingCopyDocuments.values().iterator();
int index = length;
while (iterator.hasNext()) {
matches[index++] = (SearchDocument) iterator.next();
}
}
return matches;
}
public static void setFocus(InternalSearchPattern pattern, IRubyElement focus) {
pattern.focus = focus;
}
/*
* Returns the working copies that can see the given focus.
*/
private static HashMap workingCopiesThatCanSeeFocus(IRubyScript[] copies, IRubyElement focus, boolean isPolymorphicSearch, SearchParticipant participant) {
if (copies == null) return new HashMap();
if (focus != null) {
while (!(focus instanceof IRubyProject) && !(focus instanceof ExternalSourceFolderRoot)) {
focus = focus.getParent();
}
}
HashMap result = new HashMap();
for (int i=0, length = copies.length; i<length; i++) {
IRubyScript workingCopy = copies[i];
IPath projectOrJar = MatchLocator.getProjectOrJar(workingCopy).getPath();
if (focus == null || IndexSelector.canSeeFocus(focus, isPolymorphicSearch, projectOrJar)) {
result.put(
workingCopy.getPath().toString(),
new WorkingCopyDocument(workingCopy, participant)
);
}
}
return result;
}
public static class WorkingCopyDocument extends RubySearchDocument {
public IRubyScript workingCopy;
WorkingCopyDocument(IRubyScript workingCopy, SearchParticipant participant) {
super(workingCopy.getPath().toString(), participant);
this.charContents = ((RubyScript)workingCopy).getContents();
this.workingCopy = workingCopy;
}
public String toString() {
return "WorkingCopyDocument for " + getPath(); //$NON-NLS-1$
}
}
/**
* Locate the matches in the given files and report them using the search
* requestor.
*/
public void locateMatches(SearchDocument[] searchDocuments)
throws CoreException {
int docsLength = searchDocuments.length;
if (BasicSearchEngine.VERBOSE) {
System.out.println("Locating matches in documents ["); //$NON-NLS-1$
for (int i = 0; i < docsLength; i++)
System.out.println("\t" + searchDocuments[i]); //$NON-NLS-1$
System.out.println("]"); //$NON-NLS-1$
}
// init infos for progress increasing
int n = docsLength < 1000 ? Math.min(Math.max(docsLength / 200 + 1, 2),
4) : 5 * (docsLength / 1000);
this.progressStep = docsLength < n ? 1 : docsLength / n; // step
// should
// not be 0
this.progressWorked = 0;
// extract working copies
ArrayList copies = new ArrayList();
for (int i = 0; i < docsLength; i++) {
SearchDocument document = searchDocuments[i];
if (document instanceof WorkingCopyDocument) {
copies.add(((WorkingCopyDocument) document).workingCopy);
}
}
int copiesLength = copies.size();
this.workingCopies = new IRubyScript[copiesLength];
copies.toArray(this.workingCopies);
RubyModelManager manager = RubyModelManager.getRubyModelManager();
this.bindings = new SimpleLookupTable();
try {
// optimize access to zip files during search operation
// manager.cacheZipFiles();
// initialize handle factory (used as a cache of handles so as to
// optimize space)
if (this.handleFactory == null)
this.handleFactory = new HandleFactory();
if (this.progressMonitor != null) {
this.progressMonitor.beginTask("", searchDocuments.length); //$NON-NLS-1$
}
// initialize pattern for polymorphic search (ie. method reference
// pattern)
// this.patternLocator.initializePolymorphicSearch(this);
RubyProject previousJavaProject = null;
PossibleMatchSet matchSet = new PossibleMatchSet();
Util.sort(searchDocuments, new Util.Comparer() {
public int compare(Object a, Object b) {
return ((SearchDocument) a).getPath().compareTo(
((SearchDocument) b).getPath());
}
});
int displayed = 0; // progress worked displayed
String previousPath = null;
for (int i = 0; i < docsLength; i++) {
if (this.progressMonitor != null
&& this.progressMonitor.isCanceled()) {
throw new OperationCanceledException();
}
// skip duplicate paths
SearchDocument searchDocument = searchDocuments[i];
searchDocuments[i] = null; // free current document
String pathString = searchDocument.getPath();
if (i > 0 && pathString.equals(previousPath)) {
if (this.progressMonitor != null) {
this.progressWorked++;
if ((this.progressWorked % this.progressStep) == 0)
this.progressMonitor.worked(this.progressStep);
}
displayed++;
continue;
}
previousPath = pathString;
Openable openable;
IRubyScript workingCopy = null;
if (searchDocument instanceof WorkingCopyDocument) {
workingCopy = ((WorkingCopyDocument) searchDocument).workingCopy;
openable = (Openable) workingCopy;
} else {
openable = this.handleFactory.createOpenable(pathString);
}
if (openable == null) {
if (this.progressMonitor != null) {
this.progressWorked++;
if ((this.progressWorked % this.progressStep) == 0)
this.progressMonitor.worked(this.progressStep);
}
displayed++;
continue; // match is outside classpath
}
// create new parser and lookup environment if this is a new
// project
IResource resource = null;
RubyProject javaProject = (RubyProject) openable
.getRubyProject();
resource = workingCopy != null ? workingCopy.getResource()
: openable.getResource();
if (resource == null)
resource = javaProject.getProject(); // case of a file in
// an external jar
if (!javaProject.equals(previousJavaProject)) {
// locate matches in previous project
if (previousJavaProject != null) {
try {
locateMatches(previousJavaProject, matchSet, i
- displayed);
displayed = i;
} catch (RubyModelException e) {
// problem with classpath in this project -> skip it
}
matchSet.reset();
}
previousJavaProject = javaProject;
}
matchSet.add(new PossibleMatch(this, resource, openable,
searchDocument,
((InternalSearchPattern) this.pattern).mustResolve));
}
// last project
if (previousJavaProject != null) {
try {
locateMatches(previousJavaProject, matchSet, docsLength
- displayed);
} catch (RubyModelException e) {
// problem with loadpath in last project -> ignore
}
}
} finally {
if (this.progressMonitor != null)
this.progressMonitor.done();
// if (this.nameEnvironment != null)
// this.nameEnvironment.cleanup();
// manager.flushZipFiles();
this.bindings = null;
}
}
protected boolean encloses(IRubyElement element) {
return element != null && this.scope.encloses(element);
}
protected void report(SearchMatch match) throws CoreException {
long start = -1;
if (BasicSearchEngine.VERBOSE) {
start = System.currentTimeMillis();
System.out.println("Reporting match"); //$NON-NLS-1$
System.out.println("\tResource: " + match.getResource());//$NON-NLS-1$
System.out.println("\tPositions: [offset=" + match.getOffset() + ", length=" + match.getLength() + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
// try {
// if (this.parser != null && match.getOffset() > 0 && match.getLength() > 0 && !(match.getElement() instanceof BinaryMember)) {
// String selection = new String(this.parser.scanner.source, match.getOffset(), match.getLength());
// System.out.println("\tSelection: -->" + selection + "<--"); //$NON-NLS-1$ //$NON-NLS-2$
// }
// } catch (Exception e) {
// // it's just for debug purposes... ignore all exceptions in this area
// }
try {
RubyElement javaElement = (RubyElement)match.getElement();
System.out.println("\tRuby element: "+ javaElement.toStringWithAncestors()); //$NON-NLS-1$
if (!javaElement.exists()) {
System.out.println("\t\tWARNING: this element does NOT exist!"); //$NON-NLS-1$
}
} catch (Exception e) {
// it's just for debug purposes... ignore all exceptions in this area
}
// if (match instanceof TypeReferenceMatch) {
// try {
// TypeReferenceMatch typeRefMatch = (TypeReferenceMatch) match;
// RubyElement local = (RubyElement) typeRefMatch.getLocalElement();
// if (local != null) {
// System.out.println("\tLocal element: "+ local.toStringWithAncestors()); //$NON-NLS-1$
// }
// IRubyElement[] others = typeRefMatch.getOtherElements();
// if (others != null) {
// int length = others.length;
// if (length > 0) {
// System.out.println("\tOther elements:"); //$NON-NLS-1$
// for (int i=0; i<length; i++) {
// RubyElement other = (RubyElement) others[i];
// System.out.println("\t\t- "+ other.toStringWithAncestors()); //$NON-NLS-1$
// }
// }
// }
// } catch (Exception e) {
// // it's just for debug purposes... ignore all exceptions in this area
// }
// }
System.out.println(match.getAccuracy() == SearchMatch.A_ACCURATE
? "\tAccuracy: EXACT_MATCH" //$NON-NLS-1$
: "\tAccuracy: POTENTIAL_MATCH"); //$NON-NLS-1$
System.out.print("\tRule: "); //$NON-NLS-1$
if (match.isExact()) {
System.out.print("EXACT"); //$NON-NLS-1$
} else if (match.isEquivalent()) {
System.out.print("EQUIVALENT"); //$NON-NLS-1$
} else if (match.isErasure()) {
System.out.print("ERASURE"); //$NON-NLS-1$
} else {
System.out.print("INVALID RULE"); //$NON-NLS-1$
}
// if (match instanceof MethodReferenceMatch) {
// MethodReferenceMatch methodReferenceMatch = (MethodReferenceMatch) match;
// if (methodReferenceMatch.isSuperInvocation()) {
// System.out.print("+SUPER INVOCATION"); //$NON-NLS-1$
// }
// if (methodReferenceMatch.isImplicit()) {
// System.out.print("+IMPLICIT"); //$NON-NLS-1$
// }
// if (methodReferenceMatch.isSynthetic()) {
// System.out.print("+SYNTHETIC"); //$NON-NLS-1$
// }
// }
System.out.println("\n\tRaw: "+match.isRaw()); //$NON-NLS-1$
}
this.requestor.acceptSearchMatch(match);
// if (BasicSearchEngine.VERBOSE)
// this.resultCollectorTime += System.currentTimeMillis()-start;
}
/**
* Locate the matches amongst the possible matches.
*/
protected void locateMatches(RubyProject javaProject, PossibleMatchSet matchSet, int expected) throws CoreException {
PossibleMatch[] possibleMatches = matchSet.getPossibleMatches(javaProject.getSourceFolderRoots());
int length = possibleMatches.length;
// increase progress from duplicate matches not stored in matchSet while adding...
if (this.progressMonitor != null && expected>length) {
this.progressWorked += expected-length;
this.progressMonitor.worked( expected-length);
}
// locate matches (processed matches are limited to avoid problem while using VM default memory heap size)
for (int index = 0; index < length;) {
int max = Math.min(MAX_AT_ONCE, length - index);
locateMatches(javaProject, possibleMatches, index, max);
index += max;
}
this.patternLocator.clear();
}
protected void locateMatches(RubyProject rubyProject, PossibleMatch[] possibleMatches, int start, int length) throws CoreException {
for (int i = start, maxUnits = start + length; i < maxUnits; i++) {
PossibleMatch possibleMatch = possibleMatches[i];
process(possibleMatch);
possibleMatch.cleanUp();
}
}
protected void process(PossibleMatch possibleMatch) {
this.currentPossibleMatch = possibleMatch;
RubyScript script = (RubyScript) possibleMatch.openable;
this.patternLocator.reportMatches(script, this);
this.currentPossibleMatch = null;
}
public SearchMatch newDeclarationMatch(
IRubyElement element,
int accuracy,
int offset,
int length,
SearchParticipant participant,
IResource resource) {
switch (element.getElementType()) {
// case IRubyElement.SOURCE_FOLDER:
// return new PackageDeclarationMatch(element, accuracy, offset, length, participant, resource);
case IRubyElement.TYPE:
return new TypeDeclarationMatch(element, accuracy, offset, length, participant, resource);
case IRubyElement.FIELD:
case IRubyElement.INSTANCE_VAR:
case IRubyElement.CLASS_VAR:
case IRubyElement.GLOBAL:
case IRubyElement.CONSTANT:
return new FieldDeclarationMatch(element, accuracy, offset, length, participant, resource);
case IRubyElement.METHOD:
return new MethodDeclarationMatch(element, accuracy, offset, length, participant, resource);
// case IRubyElement.LOCAL_VARIABLE:
// return new LocalVariableDeclarationMatch(element, accuracy, offset, length, participant, resource);
default:
return null;
}
}
public SearchMatch newDeclarationMatch(
IRubyElement element,
int accuracy,
int offset,
int length) {
SearchParticipant participant = getParticipant();
IResource resource = this.currentPossibleMatch.resource;
return newDeclarationMatch(element, accuracy, offset, length, participant, resource);
}
public SearchParticipant getParticipant() {
return this.currentPossibleMatch.document.getParticipant();
}
public TypeReferenceMatch newTypeReferenceMatch(
IRubyElement enclosingElement,
int accuracy,
int offset,
int length) {
SearchParticipant participant = getParticipant();
IResource resource = this.currentPossibleMatch.resource;
return new TypeReferenceMatch(enclosingElement, accuracy, offset, length, participant, resource);
}
public SearchMatch newFieldReferenceMatch(
IRubyElement enclosingElement,
IRubyElement binding, int accuracy,
int offset,
int length,
Node reference) {
boolean isReadAccess = false;
boolean isWriteAccess = false;
if ((reference instanceof GlobalAsgnNode) || (reference instanceof ClassVarAsgnNode) ||
(reference instanceof InstAsgnNode)) {
isWriteAccess = true;
} else {
isReadAccess = true;
}
SearchParticipant participant = getParticipant();
IResource resource = this.currentPossibleMatch.resource;
return new FieldReferenceMatch(enclosingElement, binding, accuracy, offset, length, isReadAccess, isWriteAccess, false, participant, resource);
}
public SearchMatch newMethodReferenceMatch(
IRubyElement enclosingElement,
IRubyElement binding,
List<String> arguments,
int accuracy,
int offset,
int length,
boolean isConstructor,
Node reference) {
SearchParticipant participant = getParticipant();
IResource resource = this.currentPossibleMatch.resource;
return new MethodReferenceMatch(enclosingElement, binding, arguments, accuracy, offset, length, isConstructor, false, participant, resource);
}
}