package org.rubypeople.rdt.internal.core.search.matching;
import org.rubypeople.rdt.core.search.SearchPattern;
import org.rubypeople.rdt.internal.core.RubyScript;
import org.rubypeople.rdt.internal.core.search.indexing.IIndexConstants;
import org.rubypeople.rdt.internal.core.util.CharOperation;
public class PatternLocator implements IIndexConstants {
// store pattern info
protected int matchMode;
protected boolean isCaseSensitive;
protected boolean isCamelCase;
protected boolean isEquivalentMatch;
protected boolean isErasureMatch;
protected boolean mustResolve;
protected boolean mayBeGeneric;
/* 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
public static final int EXACT_FLAVOR = 0x0010;
public static final int PREFIX_FLAVOR = 0x0020;
public static final int PATTERN_FLAVOR = 0x0040;
public static final int REGEXP_FLAVOR = 0x0080;
public static final int CAMELCASE_FLAVOR = 0x0100;
public static final int SUPER_INVOCATION_FLAVOR = 0x0200;
public static final int SUB_INVOCATION_FLAVOR = 0x0400;
public static final int OVERRIDDEN_METHOD_FLAVOR = 0x0800;
public static final int MATCH_LEVEL_MASK = 0x0F;
public static final int FLAVORS_MASK = ~MATCH_LEVEL_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;
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 & RubySearchPattern.MATCH_MODE_MASK;
this.mustResolve = ((InternalSearchPattern)pattern).mustResolve;
}
/**
* 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.
*/
protected int matchContainer() {
// override if the pattern can be more specific
return ALL_CONTAINER;
}
public static PatternLocator patternLocator(SearchPattern pattern) {
switch (((InternalSearchPattern)pattern).kind) { // XXX Implement all pattern types (not just methods, fields, and type declarations)
case IIndexConstants.TYPE_REF_PATTERN :
return new TypeReferenceLocator((TypeReferencePattern) pattern);
case IIndexConstants.TYPE_DECL_PATTERN :
return new TypeDeclarationLocator((TypeDeclarationPattern) pattern);
// case IIndexConstants.SUPER_REF_PATTERN : FIXME Implement these commented out pattern locators!
// 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);
case IIndexConstants.LOCAL_VAR_PATTERN :
return new LocalVariableLocator((LocalVariablePattern) pattern);
}
return null;
}
/*
* Clear caches
*/
protected void clear() {
// nothing to clear by default
}
public void reportMatches(RubyScript script, MatchLocator locator) {
// TODO Auto-generated method stub
// override in specific locators!
}
/**
* 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 #ACCURATE_MATCH}</li>
* <li> {@link #IMPOSSIBLE_MATCH}</li>
* <li> {@link #POSSIBLE_MATCH} which may be flavored with following values:
* <ul>
* <li>{@link #EXACT_FLAVOR}: Given name is equals to pattern</li>
* <li>{@link #PREFIX_FLAVOR}: Given name prefix equals to pattern</li>
* <li>{@link #CAMELCASE_FLAVOR}: Given name matches pattern as Camel Case</li>
* <li>{@link #PATTERN_FLAVOR}: Given name matches pattern as Pattern (ie. using '*' and '?' characters)</li>
* </ul>
* </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_MATCH;
}
switch (this.matchMode) {
case SearchPattern.R_EXACT_MATCH:
if (!this.isCamelCase) {
if (sameLength && matchFirstChar && CharOperation.equals(pattern, name, this.isCaseSensitive)) {
return POSSIBLE_MATCH | EXACT_FLAVOR;
}
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_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 :
// TODO (frederic) implement regular expression match
break;
}
return IMPOSSIBLE_MATCH;
}
}