blob: 5cd222b34647d5c5c0f46482d136706c0728e0f8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2016 IBM Corporation and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* xored software, Inc. - initial API and Implementation
* xored software, Inc. - Search All occurences bugfix,
* hilight only class name when class is in search results ( Alex Panchenko <alex@xored.com>)
*******************************************************************************/
package org.eclipse.dltk.core.search.matching;
import java.text.Annotation;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.ast.declarations.FieldDeclaration;
import org.eclipse.dltk.ast.declarations.MethodDeclaration;
import org.eclipse.dltk.ast.declarations.TypeDeclaration;
import org.eclipse.dltk.ast.expressions.CallExpression;
import org.eclipse.dltk.ast.expressions.Expression;
import org.eclipse.dltk.ast.references.Reference;
import org.eclipse.dltk.ast.references.TypeReference;
import org.eclipse.dltk.compiler.CharOperation;
import org.eclipse.dltk.compiler.env.lookup.Scope;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.IDLTKLanguageToolkit;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.search.SearchMatch;
import org.eclipse.dltk.core.search.SearchPattern;
import org.eclipse.dltk.core.search.indexing.IIndexConstants;
import org.eclipse.dltk.internal.core.search.matching.DLTKSearchPattern;
import org.eclipse.dltk.internal.core.search.matching.FieldLocator;
import org.eclipse.dltk.internal.core.search.matching.FieldPattern;
import org.eclipse.dltk.internal.core.search.matching.InternalSearchPattern;
import org.eclipse.dltk.internal.core.search.matching.MatchingNodeSet;
import org.eclipse.dltk.internal.core.search.matching.MethodDeclarationLocator;
import org.eclipse.dltk.internal.core.search.matching.MethodDeclarationPattern;
import org.eclipse.dltk.internal.core.search.matching.MethodLocator;
import org.eclipse.dltk.internal.core.search.matching.MethodPattern;
import org.eclipse.dltk.internal.core.search.matching.OrLocator;
import org.eclipse.dltk.internal.core.search.matching.OrPattern;
import org.eclipse.dltk.internal.core.search.matching.TypeDeclarationLocator;
import org.eclipse.dltk.internal.core.search.matching.TypeDeclarationPattern;
import org.eclipse.dltk.internal.core.search.matching.TypeReferenceLocator;
import org.eclipse.dltk.internal.core.search.matching.TypeReferencePattern;
public abstract class PatternLocator implements IIndexConstants {
// store pattern info
protected int matchMode;
protected boolean isCaseSensitive;
protected boolean isCamelCase;
protected boolean isEquivalentMatch;
protected boolean isErasureMatch;
protected Pattern compiledPattern;
// match to report
SearchMatch match = null;
/* match levels */
public static final int IMPOSSIBLE_MATCH = 0;
public static final int INACCURATE_MATCH = 1;
public static final int POSSIBLE_MATCH = 2;
public static final int ACCURATE_MATCH = 3;
public static final int ERASURE_MATCH = 4;
// Possible rule match flavors
// see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=79866
protected static final int POSSIBLE_FULL_MATCH = POSSIBLE_MATCH
| (SearchPattern.R_FULL_MATCH << 16);
protected static final int POSSIBLE_PREFIX_MATCH = POSSIBLE_MATCH
| (SearchPattern.R_PREFIX_MATCH << 16);
protected static final int POSSIBLE_PATTERN_MATCH = POSSIBLE_MATCH
| (SearchPattern.R_PATTERN_MATCH << 16);
protected static final int POSSIBLE_REGEXP_MATCH = POSSIBLE_MATCH
| (SearchPattern.R_REGEXP_MATCH << 16);
protected static final int POSSIBLE_CAMELCASE_MATCH = POSSIBLE_MATCH
| (SearchPattern.R_CAMELCASE_MATCH << 16);
public static final int NODE_SET_MASK = 0xFF;
protected static final int POSSIBLE_MATCH_MASK = ~NODE_SET_MASK;
/* match container */
public static final int COMPILATION_UNIT_CONTAINER = 1;
public static final int CLASS_CONTAINER = 2;
public static final int METHOD_CONTAINER = 4;
public static final int FIELD_CONTAINER = 8;
public static final int ALL_CONTAINER = COMPILATION_UNIT_CONTAINER
| CLASS_CONTAINER | METHOD_CONTAINER | FIELD_CONTAINER;
/* match rule */
public static final int RAW_MASK = SearchPattern.R_EQUIVALENT_MATCH
| SearchPattern.R_ERASURE_MATCH;
public static final int RULE_MASK = RAW_MASK; // no other values for the
// while...
public static PatternLocator patternLocator(SearchPattern pattern,
IDLTKLanguageToolkit toolkit) {
switch (((InternalSearchPattern) pattern).kind) {
// case IIndexConstants.PKG_REF_PATTERN:
// return new PackageReferenceLocator((PackageReferencePattern)
// pattern);
// case IIndexConstants.PKG_DECL_PATTERN:
// return new PackageDeclarationLocator((PackageDeclarationPattern)
// pattern);
case IIndexConstants.TYPE_REF_PATTERN:
return new TypeReferenceLocator((TypeReferencePattern) pattern);
case IIndexConstants.TYPE_DECL_PATTERN:
return new TypeDeclarationLocator((TypeDeclarationPattern) pattern);
case IIndexConstants.METHOD_DECL_PATTERN:
return new MethodDeclarationLocator(
(MethodDeclarationPattern) pattern);
// case IIndexConstants.SUPER_REF_PATTERN:
// return new
// SuperTypeReferenceLocator((SuperTypeReferencePattern)
// pattern);
// case IIndexConstants.CONSTRUCTOR_PATTERN:
// return new ConstructorLocator((ConstructorPattern) pattern);
case IIndexConstants.FIELD_PATTERN:
return new FieldLocator((FieldPattern) pattern);
case IIndexConstants.METHOD_PATTERN:
return new MethodLocator((MethodPattern) pattern);
case IIndexConstants.OR_PATTERN:
return new OrLocator((OrPattern) pattern);
}
return null;
}
public static char[] qualifiedPattern(char[] simpleNamePattern,
char[] qualificationPattern) {
// NOTE: if case insensitive search then simpleNamePattern &
// qualificationPattern are assumed to be lowercase
if (simpleNamePattern == null) {
if (qualificationPattern == null)
return null;
return CharOperation.concat(qualificationPattern, ONE_STAR, '.');
} else {
return qualificationPattern == null ? CharOperation.concat(
ONE_STAR, simpleNamePattern) : CharOperation.concat(
qualificationPattern, simpleNamePattern, '.');
}
}
public PatternLocator(SearchPattern pattern) {
int matchRule = pattern.getMatchRule();
this.isCaseSensitive = (matchRule & SearchPattern.R_CASE_SENSITIVE) != 0;
this.isCamelCase = (matchRule & SearchPattern.R_CAMELCASE_MATCH) != 0;
this.isErasureMatch = (matchRule & SearchPattern.R_ERASURE_MATCH) != 0;
this.isEquivalentMatch = (matchRule & SearchPattern.R_EQUIVALENT_MATCH) != 0;
this.matchMode = matchRule & DLTKSearchPattern.MATCH_MODE_MASK;
}
/*
* Clear caches
*/
protected void clear() {
// nothing to clear by default
}
/*
* (non-Javadoc) Modify PatternLocator.qualifiedPattern behavior: do not add
* star before simple name pattern when qualification pattern is null. This
* avoid to match p.X when pattern is only X...
*/
protected char[] getQualifiedPattern(char[] simpleNamePattern,
char[] qualificationPattern) {
// NOTE: if case insensitive search then simpleNamePattern &
// qualificationPattern are assumed to be lowercase
if (simpleNamePattern == null) {
if (qualificationPattern == null)
return null;
return CharOperation.concat(qualificationPattern, ONE_STAR, '.');
} else if (qualificationPattern == null) {
return simpleNamePattern;
} else {
return CharOperation.concat(qualificationPattern,
simpleNamePattern, '.');
}
}
/**
* Initializes this search pattern so that polymorphic search can be
* performed.
*/
public void initializePolymorphicSearch(MatchLocator locator) {
// default is to do nothing
}
public int match(Annotation node, MatchingNodeSet nodeSet) {
// each subtype should override if needed
return IMPOSSIBLE_MATCH;
}
/**
* Check if the given ast node syntactically matches this pattern. If it
* does, add it to the match set. Returns the match level.
*/
public int match(ASTNode node, MatchingNodeSet nodeSet) { // needed for
// some generic
// nodes
// each subtype should override if needed
return IMPOSSIBLE_MATCH;
}
public int match(CallExpression node, MatchingNodeSet nodeSet) { // needed
// for
// each subtype should override if needed
return IMPOSSIBLE_MATCH;
}
public int match(Expression node, MatchingNodeSet nodeSet) {
// each subtype should override if needed
return IMPOSSIBLE_MATCH;
}
public int match(MethodDeclaration node, MatchingNodeSet nodeSet) {
// each subtype should override if needed
return IMPOSSIBLE_MATCH;
}
public int match(Reference node, MatchingNodeSet nodeSet) {
// each subtype should override if needed
return IMPOSSIBLE_MATCH;
}
public int match(TypeReference node, MatchingNodeSet nodeSet) {
// each subtype should override if needed
return IMPOSSIBLE_MATCH;
}
public int match(FieldDeclaration node, MatchingNodeSet nodeSet) {
// each subtype should override if needed
return IMPOSSIBLE_MATCH;
}
public int match(TypeDeclaration node, MatchingNodeSet nodeSet) {
// each subtype should override if needed
return IMPOSSIBLE_MATCH;
}
/**
* Returns the type(s) of container for this pattern. It is a bit
* combination of types, denoting compilation unit, class declarations,
* field declarations or method declarations.
*/
public int matchContainer() {
// override if the pattern can be more specific
return ALL_CONTAINER;
}
/**
* Returns whether the given name matches the given pattern.
*/
protected boolean matchesName(char[] pattern, char[] name) {
if (pattern == null)
return true; // null is as if it was "*"
if (name == null)
return false; // cannot match null name
return matchNameValue(pattern, name) != IMPOSSIBLE_MATCH;
}
/**
* Return how the given name matches the given pattern.
*
* @see "https://bugs.eclipse.org/bugs/show_bug.cgi?id=79866"
*
* @param pattern
* @param name
* @return Possible values are:
* <ul>
* <li>{@link #POSSIBLE_FULL_MATCH}: Given name is equals to pattern
* </li>
* <li>{@link #POSSIBLE_PREFIX_MATCH}: Given name prefix equals to
* pattern</li>
* <li>{@link #POSSIBLE_CAMELCASE_MATCH}: Given name matches pattern
* as Camel Case</li>
* <li>{@link #POSSIBLE_PATTERN_MATCH}: Given name matches pattern
* as Pattern (ie. using '*' and '?' characters)</li>
* </ul>
*/
protected int matchNameValue(char[] pattern, char[] name) {
if (pattern == null)
return ACCURATE_MATCH; // null is as if it was "*"
if (name == null)
return IMPOSSIBLE_MATCH; // cannot match null name
if (name.length == 0) { // empty name
if (pattern.length == 0) { // can only matches empty pattern
return ACCURATE_MATCH;
}
return IMPOSSIBLE_MATCH;
} else if (pattern.length == 0) {
return IMPOSSIBLE_MATCH; // need to have both name and pattern
// length==0 to be accurate
}
boolean matchFirstChar = !this.isCaseSensitive || pattern[0] == name[0];
boolean sameLength = pattern.length == name.length;
boolean canBePrefix = name.length >= pattern.length;
if (this.isCamelCase && matchFirstChar
&& CharOperation.camelCaseMatch(pattern, name)) {
return POSSIBLE_CAMELCASE_MATCH;
}
switch (this.matchMode) {
case SearchPattern.R_EXACT_MATCH:
if (!this.isCamelCase) {
if (sameLength
&& matchFirstChar
&& CharOperation.equals(pattern, name,
this.isCaseSensitive)) {
return POSSIBLE_FULL_MATCH;
}
break;
}
// fall through next case to match as prefix if camel case
// failed
case SearchPattern.R_PREFIX_MATCH:
if (canBePrefix
&& matchFirstChar
&& CharOperation.prefixEquals(pattern, name,
this.isCaseSensitive)) {
return POSSIBLE_PREFIX_MATCH;
}
break;
case SearchPattern.R_PATTERN_MATCH:
if (!this.isCaseSensitive) {
pattern = CharOperation.toLowerCase(pattern);
}
if (CharOperation.match(pattern, name, this.isCaseSensitive)) {
return POSSIBLE_MATCH;
}
break;
case SearchPattern.R_REGEXP_MATCH:
if (compiledPattern == null) {
compiledPattern = Pattern.compile(new String(pattern),
this.isCaseSensitive ? 0 : Pattern.CASE_INSENSITIVE);
}
if (compiledPattern.matcher(new String(name)).matches()) {
return POSSIBLE_REGEXP_MATCH;
}
break;
}
return IMPOSSIBLE_MATCH;
}
/**
* Reports the match of the given reference.
*/
protected void matchReportReference(ASTNode reference,
IModelElement element, int accuracy, MatchLocator locator)
throws CoreException {
match = null;
switch (referenceType()) {
case IModelElement.TYPE:
match = locator.newTypeReferenceMatch(element, accuracy, reference
.matchStart(), reference.matchLength(), reference);
break;
case IModelElement.FIELD:
match = locator.newFieldReferenceMatch(element, accuracy, reference
.matchStart(), reference.matchLength(), reference);
break;
case IModelElement.METHOD:
match = locator.newMethodReferenceMatch(element, accuracy,
reference.matchStart(), reference.matchLength(), false,
false, reference);
break;
}
if (match != null) {
locator.report(match);
}
}
/**
* Reports the match of the given reference. Also provide a local element to
* eventually report in match.
*/
protected void matchReportReference(ASTNode reference,
IModelElement element, IModelElement localElement,
IModelElement[] otherElements, int accuracy, MatchLocator locator)
throws CoreException {
matchReportReference(reference, element, accuracy, locator);
}
/**
* Reports the match of the given reference. Also provide a scope to look
* for potential other elements.
*/
public void matchReportReference(ASTNode reference, IModelElement element,
Scope scope, int accuracy, MatchLocator locator)
throws CoreException {
matchReportReference(reference, element, accuracy, locator);
}
/**
* @deprecated
*/
@Deprecated
public final SearchMatch newDeclarationMatch(ASTNode reference,
IModelElement element, int accuracy, int length,
MatchLocator locator) {
return newDeclarationMatch(reference, element, accuracy, locator);
}
public SearchMatch newDeclarationMatch(ASTNode reference,
IModelElement element, int accuracy, MatchLocator locator) {
return locator.newDeclarationMatch(element, accuracy, reference
.matchStart(), reference.matchLength());
}
protected int referenceType() {
return 0; // defaults to unknown (a generic JavaSearchMatch will be
// created)
}
/**
* Finds out whether the given ast node matches this search pattern. Returns
* IMPOSSIBLE_MATCH if it doesn't. Returns INACCURATE_MATCH if it
* potentially matches this search pattern (ie. it has already been resolved
* but resolving failed.) Returns ACCURATE_MATCH if it matches exactly this
* search pattern (ie. it doesn't need to be resolved or it has already been
* resolved.)
*/
public int resolveLevel(ASTNode possibleMatchingNode) {
// only called with nodes which were possible matches to the call to
// matchLevel
// need to do instance of checks to find out exact type of ASTNode
return IMPOSSIBLE_MATCH;
}
/*
* Update pattern locator match comparing type arguments with pattern ones.
* Try to resolve pattern and look for compatibility with type arguments to
* set match rule.
*/
protected void updateMatch(MatchLocator locator, char[][] patternArguments,
boolean hasTypeParameters) {
if (DLTKCore.DEBUG) {
System.err.println("TODO: updateMatch"); //$NON-NLS-1$
}
}
/**
* Returns whether the given type binding matches the given simple name
* pattern and qualification pattern. Note that this method resolve to
* accurate member or local types even if they are not fully qualified (ie.
* X.Member instead of p.X.Member). Returns ACCURATE_MATCH if it does.
* Returns INACCURATE_MATCH if resolve failed. Returns IMPOSSIBLE_MATCH if
* it doesn't.
*/
protected int resolveLevelForType(char[] simpleNamePattern,
char[] qualificationPattern) {
return INACCURATE_MATCH;
}
/**
* Returns whether the given type binding matches the given qualified
* pattern. Returns ACCURATE_MATCH if it does. Returns INACCURATE_MATCH if
* resolve failed. Returns IMPOSSIBLE_MATCH if it doesn't.
*/
protected int resolveLevelForType(char[] qualifiedPattern) {
return IMPOSSIBLE_MATCH;
}
@Override
public String toString() {
return "SearchPattern"; //$NON-NLS-1$
}
}