blob: 240bd3b4dc553ed6c6441863ce4cae651b841e61 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.core.search.matching;
import java.io.IOException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.search.IJavaSearchResultCollector;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.internal.compiler.ast.Assignment;
import org.eclipse.jdt.internal.compiler.ast.AstNode;
import org.eclipse.jdt.internal.compiler.ast.CompoundAssignment;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldReference;
import org.eclipse.jdt.internal.compiler.ast.NameReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.core.index.IEntryResult;
import org.eclipse.jdt.internal.core.index.impl.IndexInput;
import org.eclipse.jdt.internal.core.index.impl.IndexedFile;
import org.eclipse.jdt.internal.core.search.IIndexSearchRequestor;
import org.eclipse.jdt.internal.core.search.indexing.AbstractIndexer;
public class FieldReferencePattern extends MultipleSearchPattern {
// selector
protected char[] name;
// declaring type
protected char[] declaringQualification;
protected char[] declaringSimpleName;
// type
protected char[] typeQualification;
protected char[] typeSimpleName;
// read/write access
protected boolean readAccess = true;
protected boolean writeAccess = true;
protected char[] decodedName;
private static char[][] REF_TAGS = { FIELD_REF, REF };
private static char[][] REF_AND_DECL_TAGS = { FIELD_REF, REF, FIELD_DECL };
public FieldReferencePattern(
char[] name,
int matchMode,
boolean isCaseSensitive,
char[] declaringQualification,
char[] declaringSimpleName,
char[] typeQualification,
char[] typeSimpleName,
boolean readAccess,
boolean writeAccess) {
super(matchMode, isCaseSensitive);
this.name = isCaseSensitive ? name : CharOperation.toLowerCase(name);
this.declaringQualification = isCaseSensitive ? declaringQualification : CharOperation.toLowerCase(declaringQualification);
this.declaringSimpleName = isCaseSensitive ? declaringSimpleName : CharOperation.toLowerCase(declaringSimpleName);
this.typeQualification = isCaseSensitive ? typeQualification : CharOperation.toLowerCase(typeQualification);
this.typeSimpleName = isCaseSensitive ? typeSimpleName : CharOperation.toLowerCase(typeSimpleName);
this.readAccess = readAccess;
this.writeAccess = writeAccess;
this.needsResolve = true; // always resolve (in case of a simple name reference being a potential match)
}
/**
* Either decode ref/name, fieldRef/name
*/
public void decodeIndexEntry(IEntryResult entryResult){
char[] word = entryResult.getWord();
int size = word.length;
int tagLength = currentTag.length;
int nameLength = CharOperation.indexOf(SEPARATOR, word, tagLength);
if (nameLength < 0) nameLength = size;
decodedName = CharOperation.subarray(word, tagLength, nameLength);}
/**
* see SearchPattern.feedIndexRequestor
*/
public void feedIndexRequestor(IIndexSearchRequestor requestor, int detailLevel, int[] references, IndexInput input, IJavaSearchScope scope) throws IOException {
if (currentTag == REF) {
foundAmbiguousIndexMatches = true;
}
for (int i = 0, max = references.length; i < max; i++) {
int reference = references[i];
if (reference != -1) { // if the reference has not been eliminated
IndexedFile file = input.getIndexedFile(reference);
String path;
if (file != null && scope.encloses(path = IndexedFile.convertPath(file.getPath()))) {
requestor.acceptFieldReference(path, decodedName);
}
}
}
}
protected char[][] getPossibleTags() {
if (this.writeAccess && !this.readAccess) {
return REF_AND_DECL_TAGS;
} else {
return REF_TAGS;
}
}
/**
* @see AndPattern#hasNextQuery
*/
protected boolean hasNextQuery() {
return false;
}
/**
* see SearchPattern.indexEntryPrefix()
*/
public char[] indexEntryPrefix(){
return AbstractIndexer.bestReferencePrefix(
currentTag,
name,
matchMode,
isCaseSensitive);
}
/**
* @see SearchPattern#matchCheck(AstNode, MatchSet)
*/
protected void matchCheck(AstNode node, MatchSet set) {
if (this.readAccess) {
super.matchCheck(node, set);
}
if (node instanceof Assignment) {
AstNode lhs = ((Assignment)node).lhs;
if (this.writeAccess) {
super.matchCheck(lhs, set);
} else if (!(node instanceof CompoundAssignment)){
// the lhs may have been added when checking if it was a read access
set.removePossibleMatch(lhs);
set.removeTrustedMatch(lhs);
}
} else if (node instanceof FieldDeclaration) {
super.matchCheck(node, set);
}
}
/**
* @see SearchPattern#matchContainer()
*/
protected int matchContainer() {
int matchContainer = METHOD | FIELD;
if (this.writeAccess && !this.readAccess) {
matchContainer |= CLASS;
}
return matchContainer;
}
/**
* @see SearchPattern#matchIndexEntry
*/
protected boolean matchIndexEntry() {
/* check name matches */
if (name != null){
switch(matchMode){
case EXACT_MATCH :
if (!CharOperation.equals(name, decodedName, isCaseSensitive)){
return false;
}
break;
case PREFIX_MATCH :
if (!CharOperation.prefixEquals(name, decodedName, isCaseSensitive)){
return false;
}
break;
case PATTERN_MATCH :
if (!CharOperation.match(name, decodedName, isCaseSensitive)){
return false;
}
}
}
return true;
}
/**
* @see SearchPattern#matchReportReference
*/
protected void matchReportReference(AstNode reference, IJavaElement element, int accuracy, MatchLocator locator) throws CoreException {
if (reference instanceof QualifiedNameReference) {
QualifiedNameReference qNameRef = (QualifiedNameReference)reference;
int length = qNameRef.tokens.length;
int[] accuracies = new int[length];
Binding binding = qNameRef.binding;
int indexOfFirstFieldBinding = qNameRef.indexOfFirstFieldBinding > 0 ? qNameRef.indexOfFirstFieldBinding-1 : 0;
for (int i = 0; i < indexOfFirstFieldBinding; i++) {
accuracies[i] = -1;
}
// first token
if (this.matchesName(this.name, qNameRef.tokens[indexOfFirstFieldBinding])
&& !(binding instanceof LocalVariableBinding)) {
FieldBinding fieldBinding =
binding instanceof FieldBinding ?
(FieldBinding)binding :
null;
if (fieldBinding == null) {
accuracies[indexOfFirstFieldBinding] = accuracy;
} else {
int level = this.matchLevel(fieldBinding);
switch (level) {
case ACCURATE_MATCH:
accuracies[indexOfFirstFieldBinding] = IJavaSearchResultCollector.EXACT_MATCH;
break;
case INACCURATE_MATCH:
accuracies[indexOfFirstFieldBinding] = IJavaSearchResultCollector.POTENTIAL_MATCH;
break;
default:
accuracies[indexOfFirstFieldBinding] = -1;
}
}
} else {
accuracies[indexOfFirstFieldBinding] = -1;
}
// other tokens
for (int i = indexOfFirstFieldBinding+1; i < length; i++){
char[] token = qNameRef.tokens[i];
if (this.matchesName(this.name, token)) {
FieldBinding otherBinding = qNameRef.otherBindings == null ? null : qNameRef.otherBindings[i-(indexOfFirstFieldBinding+1)];
if (otherBinding == null) {
accuracies[i] = accuracy;
} else {
int level = this.matchLevel(otherBinding);
switch (level) {
case ACCURATE_MATCH:
accuracies[i] = IJavaSearchResultCollector.EXACT_MATCH;
break;
case INACCURATE_MATCH:
accuracies[i] = IJavaSearchResultCollector.POTENTIAL_MATCH;
break;
default:
accuracies[i] = -1;
}
}
} else {
accuracies[i] = -1;
}
}
locator.reportAccurateReference(
reference.sourceStart,
reference.sourceEnd,
qNameRef.tokens,
element,
accuracies);
} else {
locator.reportAccurateReference(
reference.sourceStart,
reference.sourceEnd,
new char[][] {this.name},
element,
accuracy);
}
}
/**
* @see AndPattern#resetQuery
*/
protected void resetQuery() {
}
public String toString(){
StringBuffer buffer = new StringBuffer(20);
buffer.append("FieldReferencePattern: "); //$NON-NLS-1$
if (declaringQualification != null) buffer.append(declaringQualification).append('.');
if (declaringSimpleName != null)
buffer.append(declaringSimpleName).append('.');
else if (declaringQualification != null) buffer.append("*."); //$NON-NLS-1$
if (name != null) {
buffer.append(name);
} else {
buffer.append("*"); //$NON-NLS-1$
}
if (typeQualification != null)
buffer.append(" --> ").append(typeQualification).append('.'); //$NON-NLS-1$
else if (typeSimpleName != null) buffer.append(" --> "); //$NON-NLS-1$
if (typeSimpleName != null)
buffer.append(typeSimpleName);
else if (typeQualification != null) buffer.append("*"); //$NON-NLS-1$
buffer.append(", "); //$NON-NLS-1$
switch(matchMode){
case EXACT_MATCH :
buffer.append("exact match, "); //$NON-NLS-1$
break;
case PREFIX_MATCH :
buffer.append("prefix match, "); //$NON-NLS-1$
break;
case PATTERN_MATCH :
buffer.append("pattern match, "); //$NON-NLS-1$
break;
}
if (isCaseSensitive)
buffer.append("case sensitive"); //$NON-NLS-1$
else
buffer.append("case insensitive"); //$NON-NLS-1$
return buffer.toString();
}
/**
* @see SearchPattern#matchLevel(AstNode, boolean)
*/
public int matchLevel(AstNode node, boolean resolve) {
if (node instanceof FieldReference) {
return this.matchLevel((FieldReference)node, resolve);
} else if (node instanceof NameReference) {
return this.matchLevel((NameReference)node, resolve);
} else if (node instanceof FieldDeclaration) {
return this.matchLevel((FieldDeclaration)node, resolve);
}
return IMPOSSIBLE_MATCH;
}
/**
* Returns whether this field reference pattern matches the given field reference.
* Look at resolved information only if specified.
*/
private int matchLevel(FieldReference fieldRef, boolean resolve) {
// field name
if (!this.matchesName(this.name, fieldRef.token))
return IMPOSSIBLE_MATCH;
if (resolve) {
// receiver type and field type
return this.matchLevel(fieldRef.binding);
} else {
return this.needsResolve ? POSSIBLE_MATCH : ACCURATE_MATCH;
}
}
/**
* Returns whether this field reference pattern matches the given field declaration in
* write access.
* Look at resolved information only if specified.
*/
private int matchLevel(FieldDeclaration fieldDecl, boolean resolve) {
// nedd to be a write only access
if (!this.writeAccess || this.readAccess) return IMPOSSIBLE_MATCH;
// need have an initialization
if (fieldDecl.initialization == null) return IMPOSSIBLE_MATCH;
// field name
if (!this.matchesName(this.name, fieldDecl.name))
return IMPOSSIBLE_MATCH;
if (resolve) {
// receiver type and field type
return this.matchLevel(fieldDecl.binding);
} else {
return this.needsResolve ? POSSIBLE_MATCH : ACCURATE_MATCH;
}
}
/**
* Returns whether this field reference pattern matches the given name reference.
* Look at resolved information only if specified.
*/
private int matchLevel(NameReference nameRef, boolean resolve) {
if (!resolve) {
// field name
if (this.name == null) {
return this.needsResolve ? POSSIBLE_MATCH : ACCURATE_MATCH;
} else {
if (nameRef instanceof SingleNameReference) {
if (this.matchesName(this.name, ((SingleNameReference)nameRef).token)) {
// can only be a possible match since resolution is needed
// to find out if it is a field ref
return POSSIBLE_MATCH;
} else {
return IMPOSSIBLE_MATCH;
}
} else { // QualifiedNameReference
QualifiedNameReference qNameRef = (QualifiedNameReference)nameRef;
char[][] tokens = qNameRef.tokens;
if (this.writeAccess && !this.readAccess) {
// in the case of the assigment of a qualified name reference, the match must be on the last token
if (this.matchesName(this.name, tokens[tokens.length-1])) {
// can only be a possible match since resolution is needed
// to find out if it is a field ref
return POSSIBLE_MATCH;
}
} else {
for (int i = 0, max = tokens.length; i < max; i++){
if (this.matchesName(this.name, tokens[i])) {
// can only be a possible match since resolution is needed
// to find out if it is a field ref
return POSSIBLE_MATCH;
}
}
}
return IMPOSSIBLE_MATCH;
}
}
} else {
Binding binding = nameRef.binding;
if (binding == null) {
return INACCURATE_MATCH;
} else {
if (nameRef instanceof SingleNameReference){
if (binding instanceof FieldBinding){
return this.matchLevel((FieldBinding) binding);
} else {
return IMPOSSIBLE_MATCH; // must be a field binding
}
} else { // QualifiedNameReference
QualifiedNameReference qNameRef = (QualifiedNameReference)nameRef;
FieldBinding fieldBinding = null;
if (binding instanceof FieldBinding) {
fieldBinding = (FieldBinding)binding;
char[] name = fieldBinding.name;
int lastDot = CharOperation.lastIndexOf('.', name);
if (lastDot > -1) {
name = CharOperation.subarray(name, lastDot+1, name.length);
}
if (this.matchesName(this.name, name)) {
return this.matchLevel(fieldBinding);
}
}
int otherLevel = IMPOSSIBLE_MATCH;
int otherMax = qNameRef.otherBindings == null ? 0 : qNameRef.otherBindings.length;
for (int i = 0; i < otherMax && (otherLevel == IMPOSSIBLE_MATCH); i++){
char[] token = qNameRef.tokens[i + qNameRef.indexOfFirstFieldBinding];
if (this.matchesName(this.name, token)) {
FieldBinding otherBinding = qNameRef.otherBindings[i];
otherLevel = this.matchLevel(otherBinding);
}
}
return otherLevel;
}
}
}
}
/**
* Returns whether this field reference pattern matches the given field binding.
*/
private int matchLevel(FieldBinding binding) {
if (binding == null) return INACCURATE_MATCH;
int level;
// receiver type
ReferenceBinding receiverBinding = binding.declaringClass;
if (receiverBinding == null) {
if (binding == ArrayBinding.LengthField) {
// optimized case for length field of an array
if (this.declaringQualification == null && this.declaringSimpleName == null) {
return ACCURATE_MATCH;
} else {
return IMPOSSIBLE_MATCH;
}
} else {
return INACCURATE_MATCH;
}
} else {
// Note there is no dynamic lookup for field access
level = this.matchLevelForType(this.declaringSimpleName, this.declaringQualification, receiverBinding);
if (level == IMPOSSIBLE_MATCH) {
return IMPOSSIBLE_MATCH;
}
}
// look at field type only if declaring type is not specified
if (this.declaringSimpleName == null) {
int newLevel = this.matchLevelForType(this.typeSimpleName, this.typeQualification, binding.type);
switch (newLevel) {
case IMPOSSIBLE_MATCH:
return IMPOSSIBLE_MATCH;
case ACCURATE_MATCH: // keep previous level
break;
default: // ie. INACCURATE_MATCH
level = newLevel;
break;
}
}
return level;
}
}