/*******************************************************************************
* Copyright (c) 2007 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.seam.ui.search;
import java.io.File;
import java.util.List;
import java.util.Set;
import java.util.zip.CRC32;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.core.JavaModelManager;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.search.core.text.TextSearchEngine;
import org.jboss.tools.common.el.core.model.ELInvocationExpression;
import org.jboss.tools.common.el.core.resolver.ElVarSearcher;
import org.jboss.tools.common.el.core.resolver.Var;
import org.jboss.tools.seam.core.ISeamContextVariable;
import org.jboss.tools.seam.core.ISeamProject;
import org.jboss.tools.seam.core.SeamCoreMessages;
import org.jboss.tools.seam.core.SeamCorePlugin;
import org.jboss.tools.seam.internal.core.el.SeamELCompletionEngine;
import org.jboss.tools.seam.ui.SeamGuiPlugin;
/**
* A helper class used for search operations
*
* @author Jeremy
*/
public abstract class SeamSearchEngine {
private static SeamSearchEngine fInstance = null;
private final IProgressMonitor fMonitor = new NullProgressMonitor();
private SearchParticipant fParticipant = null;
private IPath fSeamUIPluginLocation = null;
private final CRC32 fChecksumCalculator = new CRC32();
/**
* Returns an instance. If the instance isn't initialized creates an instance
* of the search engine.
* @return the created {@link SeamSearchEngine}.
*/
public static SeamSearchEngine getInstance() {
if (fInstance == null) {
fInstance = createDefault();
}
return fInstance;
}
/**
* Creates the default, built-in, text search engine that implements a brute-force search, not using
* any search index.
* Note that clients should always use the search engine provided by {@link #create()}.
* @return an instance of the default text search engine {@link TextSearchEngine}.
*/
private static SeamSearchEngine createDefault() {
return new SeamSearchEngine() {
@Override
public IStatus search(SeamSearchScope javaScope,
SeamSearchRequestor requestor,
IFile sourceFile,
ELInvocationExpression tokens,
IProgressMonitor monitor) {
if (tokens == null /*|| tokens.size() == 0*/) {
return Status.OK_STATUS;
}
IProject project = (sourceFile == null ? null : sourceFile.getProject());
ISeamProject seamProject = SeamCorePlugin.getSeamProject(project, true);
if (seamProject == null)
return Status.OK_STATUS;
SeamELCompletionEngine engine = new SeamELCompletionEngine();
//Find Seam variable names
// - if the tokens are the variable name only - search for variable declaration in Seam project
String variableName = tokens.getText(); //SeamSearchVisitor.tokensToString(tokens);
Set<ISeamContextVariable> variables = seamProject.getVariablesByName(variableName);
if (variables != null && !variables.isEmpty()) {
return search(javaScope, requestor, sourceFile, variables.toArray(new ISeamContextVariable[0]), monitor);
}
// - else try to find Java Elements and declarations/references for them
List<IJavaElement> elements = null;
try {
elements = engine.getJavaElementsForELOperandTokens(seamProject, sourceFile, tokens);
} catch (StringIndexOutOfBoundsException e) {
SeamGuiPlugin.getPluginLog().logError(e);
return Status.OK_STATUS;
} catch (BadLocationException e) {
SeamGuiPlugin.getPluginLog().logError(e);
return Status.OK_STATUS;
}
if (elements != null && !elements.isEmpty()) {
return search(javaScope, requestor, sourceFile, elements.toArray(new IJavaElement[0]), monitor);
}
// Try to find a local Var (a pair of variable-value attributes)
ElVarSearcher varSearcher = new ElVarSearcher(sourceFile, engine);
// Find a Var in the EL
int start = tokens.getStartPosition();
int end = tokens.getEndPosition();
StringBuffer elText = new StringBuffer();
elText.append(tokens.toString());
if (elText == null || elText.length() == 0)
return Status.OK_STATUS;
List<Var> allVars= varSearcher.findAllVars(sourceFile, tokens.getStartPosition());
Var var = varSearcher.findVarForEl(elText.toString(), null, allVars, true);
if (var == null) {
// Find a Var in the current offset assuming that it's a node with var/value attribute pair
var = varSearcher.findVar(sourceFile, tokens.getStartPosition());
}
if (var == null)
return Status.OK_STATUS;
if (tokens.getLeft() == null) {
// The only Var is selected to search for
if (isSearchForDeclarations(javaScope.getLimitTo())) {
boolean res= SeamSearchVisitor.acceptPaternMatch(
requestor, sourceFile, var.getDeclarationOffset(), var.getDeclarationLength());
if (!res) {
return Status.OK_STATUS; // no further reporting requested
}
} else {
return search(javaScope, requestor, sourceFile, new Var[] {var}, monitor);
}
return Status.OK_STATUS;
}
// Need to extract the var value and search for the real elements
return Status.OK_STATUS;
}
@Override
public IStatus search(SeamSearchScope scope,
SeamSearchRequestor requestor,
IFile sourceFile,
IJavaElement[] elements,
IProgressMonitor monitor) {
IProject project = (sourceFile == null ? null : sourceFile.getProject());
return new SeamSearchVisitor(requestor, elements, project).search(scope, monitor);
}
@Override
public IStatus search(SeamSearchScope scope,
SeamSearchRequestor requestor,
IFile sourceFile,
Var[] vars,
IProgressMonitor monitor) {
IProject project = (sourceFile == null ? null : sourceFile.getProject());
return new SeamSearchVisitor(requestor, vars, sourceFile).search(scope, monitor);
}
@Override
public IStatus search(String searchText, SeamSearchScope scope,
int searchFor, int limitTo, int matchMode, boolean isCaseSensitive, SearchRequestor requestor) {
SeamJavaSearchJob job = new SeamJavaSearchJob(searchText, scope, searchFor, limitTo, matchMode, isCaseSensitive, requestor);
setCanceled(false);
job.setUser(true);
job.schedule();
return Status.OK_STATUS;
}
@Override
public IStatus search(SeamSearchScope scope,
SeamSearchRequestor requestor, IFile sourceFile,
ISeamContextVariable[] variables, IProgressMonitor monitor) {
IProject project = (sourceFile == null ? null : sourceFile.getProject());
return new SeamSearchVisitor(requestor, variables, project).search(scope, monitor);
}
};
}
/**
* Uses a given tokens to find matches in the content of
* workspace file resources. If a file is open in an editor, the
* editor buffer is searched.
*
* @param scope
* @param requestor
* @param sourceFile
* @param tokens
* @param monitor
* @return the status containing information about problems in resources searched.
*/
public abstract IStatus search(SeamSearchScope scope, SeamSearchRequestor requestor,
IFile sourceFile, ELInvocationExpression tokens, IProgressMonitor monitor);
/**
* Uses a given IJavaElement-s to find matches in the content of
* workspace file resources. If a file is open in an editor, the
* editor buffer is searched.
*
* @param scope
* @param requestor
* @param sourceFile
* @param elements
* @param monitor
* @return
*/
public abstract IStatus search(SeamSearchScope scope, SeamSearchRequestor requestor,
IFile sourceFile, IJavaElement[] elements, IProgressMonitor monitor);
/**
* Uses a given {@link ISeamContextVariable} objects to find matches in the content of
* workspace file resources. If a file is open in an editor, the
* editor buffer is searched.
*
* @param scope
* @param requestor
* @param sourceFile
* @param variables
* @param monitor
* @return
*/
public abstract IStatus search(SeamSearchScope scope, SeamSearchRequestor requestor,
IFile sourceFile, ISeamContextVariable[] variables, IProgressMonitor monitor);
/**
* Uses a given {@link Var} objects to find matches in the content of
* workspace file resources. If a file is open in an editor, the
* editor buffer is searched.
*
* @param scope
* @param requestor
* @param sourceFile
* @param vars
* @param monitor
* @return
*/
public abstract IStatus search(SeamSearchScope scope, SeamSearchRequestor requestor,
IFile sourceFile, Var[] vars, IProgressMonitor monitor);
/**
* Perform a java search w/ the given parameters. Runs in a background Job
* (results may still come in after this method call)
*
* @param searchText
* the string of text to search on
* @param searchFor
* IJavaSearchConstants.TYPE, METHOD, FIELD, PACKAGE, etc...
* @param limitTo
* IJavaSearchConstants.DECLARATIONS,
* IJavaSearchConstants.REFERENCES,
* IJavaSearchConstants.IMPLEMENTORS, or
* IJavaSearchConstants.ALL_OCCURRENCES
* @param matchMode
* allow * wildcards or not
* @param isCaseSensitive
* @param requestor
* passed in to accept search matches (and do "something" with
* them)
*/
public abstract IStatus search(String searchText, SeamSearchScope scope, int searchFor, int limitTo, int matchMode, boolean isCaseSensitive, SearchRequestor requestor);
/**
* Seam Indexing and Search jobs check this
*
* @return
*/
public synchronized final void setCanceled(boolean cancel) {
fMonitor.setCanceled(cancel);
}
/**
* Seam Indexing and Search jobs check this
*
* @return
*/
public synchronized final boolean isCanceled() {
return fMonitor.isCanceled();
}
// This is called from SeamPathIndexer
public final IPath computeIndexLocation(IPath containerPath) {
IPath indexLocation = null;
String pathString = containerPath.toOSString();
this.fChecksumCalculator.reset();
this.fChecksumCalculator.update(pathString.getBytes());
String fileName = Long.toString(this.fChecksumCalculator.getValue()) + ".index"; //$NON-NLS-1$
indexLocation = getSeamUIPluginWorkingLocation().append(fileName);
JavaModelManager.getJavaModelManager().getIndexManager().indexLocations.put(containerPath, indexLocation);
return indexLocation;
}
// copied from JDT IndexManager
public IPath getSeamUIPluginWorkingLocation() {
if (this.fSeamUIPluginLocation != null)
return this.fSeamUIPluginLocation;
// Append the folder name "seamsearch" to keep the state location area cleaner
IPath stateLocation = SeamGuiPlugin.getDefault().getStateLocation().append("seamsearch");
String device = stateLocation.getDevice();
if (device != null && device.charAt(0) == '/')
stateLocation = stateLocation.setDevice(device.substring(1));
// ensure that it exists on disk
File folder = new File(stateLocation.toOSString());
if (!folder.isDirectory()) {
try {
folder.mkdir();
}
catch (SecurityException e) {
}
}
return this.fSeamUIPluginLocation = stateLocation;
}
/**
* This operation ensures that the live resource's search markers show up in
* the open editor. It also allows the ability to pass in a ProgressMonitor
*/
private class SeamJavaSearchJob extends Job implements IJavaSearchConstants {
String fSearchText = ""; //$NON-NLS-1$
IJavaSearchScope fScope = null;
int fSearchFor = FIELD;
int fLimitTo = ALL_OCCURRENCES;
int fMatchMode = SearchPattern.R_PATTERN_MATCH;
boolean fIsCaseSensitive = false;
SearchRequestor fRequestor = null;
IJavaElement fElement = null;
// constructor w/ java element
public SeamJavaSearchJob(IJavaElement element, IJavaSearchScope scope, SearchRequestor requestor) {
super(SeamCoreMessages.SeamSearch + element.getElementName());
this.fElement = element;
this.fScope = scope;
this.fRequestor = requestor;
}
// constructor w/ search text
public SeamJavaSearchJob(String searchText, IJavaSearchScope scope, int searchFor, int limitTo, int matchMode, boolean isCaseSensitive, SearchRequestor requestor) {
super(SeamCoreMessages.SeamSearch + searchText);
this.fSearchText = searchText;
this.fScope = scope;
this.fSearchFor = searchFor;
this.fLimitTo = limitTo;
this.fMatchMode = matchMode;
this.fIsCaseSensitive = isCaseSensitive;
this.fRequestor = requestor;
}
public IStatus run(IProgressMonitor jobMonitor) {
if (jobMonitor != null && jobMonitor.isCanceled())
return Status.CANCEL_STATUS;
if (SeamSearchEngine.getInstance().isCanceled())
return Status.CANCEL_STATUS;
SearchPattern javaSearchPattern = null;
// if an element is available, use that to create search pattern
// (eg. LocalVariable)
// otherwise use the text and other paramters
boolean searchForDeclarations = isSearchForDeclarations(this.fLimitTo);
boolean searchForReferences = isSearchForDeclarations(this.fLimitTo);
if (this.fElement != null) {
javaSearchPattern = SearchPattern.createPattern(this.fElement, this.fLimitTo);
} else {
javaSearchPattern = SearchPattern.createPattern(this.fSearchText, this.fSearchFor, this.fLimitTo, this.fMatchMode);
}
// if is searching for the declarations:
// if (fElement != null):
// - No components can be found for the element (the declaration for the element itself will be found by java search)
// - No properties/methods can be found for the Variable/Method (the declaration for the element itself will be found by java search)
// if (fSearchText != null):
// try to find the Seam Variable or the Seam variable's Method/Property and search for its declaration.
if (isSearchForDeclarations(this.fLimitTo)) {
if (this.fElement != null) {
javaSearchPattern = SearchPattern.createPattern(this.fElement, this.fLimitTo);
} else {
javaSearchPattern = SearchPattern.createPattern(this.fSearchText, this.fSearchFor, this.fLimitTo, this.fMatchMode);
}
}
// if is searching for the references:
// if (fElement != null):
// ? Search for references to the components which are declared by this element's class
// - Search for properties/methods can be found for the Variable/Method
// if (fSearchText != null):
// try to find the Seam Variable or the Seam variable's Method/Property and search for its declaration.
if (isSearchForReferences(this.fLimitTo)) {
if (this.fElement != null) {
javaSearchPattern = SearchPattern.createPattern(this.fElement, this.fLimitTo);
} else {
javaSearchPattern = SearchPattern.createPattern(this.fSearchText, this.fSearchFor, this.fLimitTo, this.fMatchMode);
}
}
if (javaSearchPattern != null) {
}
return Status.OK_STATUS;
}
}
/**
* Checks if the given limitTo flag is limited to declarations
*
* @param limitTo
* @return
*/
public static boolean isSearchForDeclarations(int limitTo) {
int maskedLimitTo = limitTo & ~(IJavaSearchConstants.IGNORE_DECLARING_TYPE+IJavaSearchConstants.IGNORE_RETURN_TYPE);
if (maskedLimitTo == IJavaSearchConstants.DECLARATIONS || maskedLimitTo == IJavaSearchConstants.ALL_OCCURRENCES) {
return true;
}
return false;
}
/**
* Checks if the given limitTo flag is limited to references
*
* @param limitTo
* @return
*/
public static boolean isSearchForReferences(int limitTo) {
int maskedLimitTo = limitTo & ~(IJavaSearchConstants.IGNORE_DECLARING_TYPE+IJavaSearchConstants.IGNORE_RETURN_TYPE);
if (maskedLimitTo == IJavaSearchConstants.REFERENCES || maskedLimitTo == IJavaSearchConstants.ALL_OCCURRENCES) {
return true;
}
return false;
}
/**
* Checks if the given element is IField
*
* @param element
* @return
*/
public static boolean isField(IJavaElement element) {
if (element == null)
return false;
return (element.getElementType() == IJavaElement.FIELD);
}
/**
* Checks if the given element is IMethod
*
* @param element
* @return
*/
public static boolean isMethod(IJavaElement element) {
if (element == null)
return false;
return (element.getElementType() == IJavaElement.METHOD);
}
/**
* Checks if the given element is IType
*
* @param element
* @return
*/
public static boolean isType(IJavaElement element) {
if (element == null)
return false;
return (element.getElementType() == IJavaElement.TYPE);
}
}