/*******************************************************************************
* Copyright (c) 2006, 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.codeassist;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.codeassist.complete.CompletionParser;
import org.eclipse.jdt.internal.codeassist.complete.CompletionScanner;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.Block;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Initializer;
import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.util.SimpleSetOfCharArray;
import org.eclipse.jdt.internal.compiler.util.Util;
public class UnresolvedReferenceNameFinder extends ASTVisitor {
private static final int MAX_LINE_COUNT= 100;
private static final int FAKE_BLOCKS_COUNT= 20;
public static interface UnresolvedReferenceNameRequestor {
public void acceptName(char[] name);
}
private UnresolvedReferenceNameRequestor requestor;
private CompletionEngine completionEngine;
private CompletionParser parser;
private CompletionScanner completionScanner;
private int parentsPtr;
private ASTNode[] parents;
private int potentialVariableNamesPtr;
private char[][] potentialVariableNames;
private int[] potentialVariableNameStarts;
private SimpleSetOfCharArray acceptedNames= new SimpleSetOfCharArray();
public UnresolvedReferenceNameFinder(CompletionEngine completionEngine) {
this.completionEngine= completionEngine;
this.parser= completionEngine.parser;
this.completionScanner= (CompletionScanner)this.parser.scanner;
}
private void acceptName(char[] name) {
// the null check is added to fix bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=166570
if (name == null)
return;
if (!CharOperation.prefixEquals(this.completionEngine.completionToken, name, false /* ignore case */)
&& !(this.completionEngine.options.camelCaseMatch && CharOperation.camelCaseMatch(this.completionEngine.completionToken, name)))
return;
if (this.acceptedNames.includes(name))
return;
this.acceptedNames.add(name);
// accept result
this.requestor.acceptName(name);
}
public void find(
char[] startWith,
Initializer initializer,
ClassScope scope,
int from,
char[][] discouragedNames,
UnresolvedReferenceNameRequestor nameRequestor) {
MethodDeclaration fakeMethod=
this.findAfter(startWith, scope, from, initializer.bodyEnd, MAX_LINE_COUNT, false, discouragedNames, nameRequestor);
if (fakeMethod != null)
fakeMethod.traverse(this, scope);
}
public void find(
char[] startWith,
AbstractMethodDeclaration methodDeclaration,
int from,
char[][] discouragedNames,
UnresolvedReferenceNameRequestor nameRequestor) {
MethodDeclaration fakeMethod=
this.findAfter(startWith, methodDeclaration.scope, from, methodDeclaration.bodyEnd, MAX_LINE_COUNT, false, discouragedNames, nameRequestor);
if (fakeMethod != null)
fakeMethod.traverse(this, methodDeclaration.scope.classScope());
}
public void findAfter(
char[] startWith,
Scope scope,
ClassScope classScope,
int from,
int to,
char[][] discouragedNames,
UnresolvedReferenceNameRequestor nameRequestor) {
MethodDeclaration fakeMethod=
this.findAfter(startWith, scope, from, to, MAX_LINE_COUNT / 2, true, discouragedNames, nameRequestor);
if (fakeMethod != null)
fakeMethod.traverse(this, classScope);
}
private MethodDeclaration findAfter(
char[] startWith,
Scope s,
int from,
int to,
int maxLineCount,
boolean outsideEnclosingBlock,
char[][] discouragedNames,
UnresolvedReferenceNameRequestor nameRequestor) {
this.requestor= nameRequestor;
// reinitialize completion scanner to be usable as a normal scanner
this.completionScanner.cursorLocation= 0;
if (!outsideEnclosingBlock) {
// compute location of the end of the current block
this.completionScanner.resetTo(from + 1, to);
this.completionScanner.jumpOverBlock();
to= this.completionScanner.startPosition - 1;
}
int maxEnd=
this.completionScanner.getLineEnd(
Util.getLineNumber(from, this.completionScanner.lineEnds, 0, this.completionScanner.linePtr) + maxLineCount);
int end;
if (maxEnd < 0) {
end= to;
} else {
end= maxEnd < to ? maxEnd : to;
}
this.parser.startRecordingIdentifiers(from, end);
MethodDeclaration fakeMethod= this.parser.parseSomeStatements(
from,
end,
outsideEnclosingBlock ? FAKE_BLOCKS_COUNT : 0,
s.compilationUnitScope().referenceContext);
this.parser.stopRecordingIdentifiers();
if (!initPotentialNamesTables(discouragedNames))
return null;
this.parentsPtr= -1;
this.parents= new ASTNode[10];
return fakeMethod;
}
public void findBefore(
char[] startWith,
Scope scope,
ClassScope classScope,
int from,
int recordTo,
int parseTo,
char[][] discouragedNames,
UnresolvedReferenceNameRequestor nameRequestor) {
MethodDeclaration fakeMethod=
this.findBefore(startWith, scope, from, recordTo, parseTo, MAX_LINE_COUNT / 2, discouragedNames, nameRequestor);
if (fakeMethod != null)
fakeMethod.traverse(this, classScope);
}
private MethodDeclaration findBefore(
char[] startWith,
Scope s,
int from,
int recordTo,
int parseTo,
int maxLineCount,
char[][] discouragedNames,
UnresolvedReferenceNameRequestor nameRequestor) {
this.requestor= nameRequestor;
// reinitialize completion scanner to be usable as a normal scanner
this.completionScanner.cursorLocation= 0;
int minStart=
this.completionScanner.getLineStart(
Util.getLineNumber(recordTo, this.completionScanner.lineEnds, 0, this.completionScanner.linePtr) - maxLineCount);
int start;
int fakeBlocksCount;
if (minStart <= from) {
start= from;
fakeBlocksCount= 0;
} else {
start= minStart;
fakeBlocksCount= FAKE_BLOCKS_COUNT;
}
this.parser.startRecordingIdentifiers(start, recordTo);
MethodDeclaration fakeMethod= this.parser.parseSomeStatements(
start,
parseTo,
fakeBlocksCount,
s.compilationUnitScope().referenceContext);
this.parser.stopRecordingIdentifiers();
if (!initPotentialNamesTables(discouragedNames))
return null;
this.parentsPtr= -1;
this.parents= new ASTNode[10];
return fakeMethod;
}
private boolean initPotentialNamesTables(char[][] discouragedNames) {
char[][] pvns= this.parser.potentialVariableNames;
int[] pvnss= this.parser.potentialVariableNameStarts;
int pvnsPtr= this.parser.potentialVariableNamesPtr;
if (pvnsPtr < 0)
return false; // there is no potential names
// remove null and discouragedNames
int discouragedNamesCount= discouragedNames == null ? 0 : discouragedNames.length;
int j= -1;
next: for (int i= 0; i <= pvnsPtr; i++) {
char[] temp= pvns[i];
if (temp == null)
continue next;
for (int k= 0; k < discouragedNamesCount; k++) {
if (CharOperation.equals(temp, discouragedNames[k], false)) {
continue next;
}
}
pvns[i]= null;
pvns[++j]= temp;
pvnss[j]= pvnss[i];
}
pvnsPtr= j;
if (pvnsPtr < 0)
return false; // there is no potential names
this.potentialVariableNames= pvns;
this.potentialVariableNameStarts= pvnss;
this.potentialVariableNamesPtr= pvnsPtr;
return true;
}
private void popParent() {
this.parentsPtr--;
}
private void pushParent(ASTNode parent) {
int length= this.parents.length;
if (this.parentsPtr >= length - 1) {
System.arraycopy(this.parents, 0, this.parents= new ASTNode[length * 2], 0, length);
}
this.parents[++this.parentsPtr]= parent;
}
private ASTNode getEnclosingDeclaration() {
int i= this.parentsPtr;
while (i > -1) {
ASTNode parent= this.parents[i];
if (parent instanceof AbstractMethodDeclaration) {
return parent;
} else if (parent instanceof Initializer) {
return parent;
} else if (parent instanceof FieldDeclaration) {
return parent;
} else if (parent instanceof TypeDeclaration) {
return parent;
}
i--;
}
return null;
}
public boolean visit(Block block, BlockScope blockScope) {
ASTNode enclosingDeclaration= getEnclosingDeclaration();
removeLocals(block.statements, enclosingDeclaration.sourceStart, block.sourceEnd);
pushParent(block);
return true;
}
public boolean visit(ConstructorDeclaration constructorDeclaration, ClassScope classScope) {
if (((constructorDeclaration.bits & ASTNode.IsDefaultConstructor) == 0) && !constructorDeclaration.isClinit()) {
removeLocals(
constructorDeclaration.arguments,
constructorDeclaration.declarationSourceStart,
constructorDeclaration.declarationSourceEnd);
removeLocals(
constructorDeclaration.statements,
constructorDeclaration.declarationSourceStart,
constructorDeclaration.declarationSourceEnd);
}
pushParent(constructorDeclaration);
return true;
}
public boolean visit(FieldDeclaration fieldDeclaration, MethodScope methodScope) {
pushParent(fieldDeclaration);
return true;
}
public boolean visit(Initializer initializer, MethodScope methodScope) {
pushParent(initializer);
return true;
}
public boolean visit(MethodDeclaration methodDeclaration, ClassScope classScope) {
removeLocals(
methodDeclaration.arguments,
methodDeclaration.declarationSourceStart,
methodDeclaration.declarationSourceEnd);
removeLocals(
methodDeclaration.statements,
methodDeclaration.declarationSourceStart,
methodDeclaration.declarationSourceEnd);
pushParent(methodDeclaration);
return true;
}
public boolean visit(TypeDeclaration localTypeDeclaration, BlockScope blockScope) {
removeFields(localTypeDeclaration);
pushParent(localTypeDeclaration);
return true;
}
public boolean visit(TypeDeclaration memberTypeDeclaration, ClassScope classScope) {
removeFields(memberTypeDeclaration);
pushParent(memberTypeDeclaration);
return true;
}
public void endVisit(Block block, BlockScope blockScope) {
popParent();
}
public void endVisit(Argument argument, BlockScope blockScope) {
endVisitRemoved(argument.declarationSourceStart, argument.sourceEnd);
}
public void endVisit(Argument argument, ClassScope classScope) {
endVisitRemoved(argument.declarationSourceStart, argument.sourceEnd);
}
public void endVisit(ConstructorDeclaration constructorDeclaration, ClassScope classScope) {
if (((constructorDeclaration.bits & ASTNode.IsDefaultConstructor) == 0) && !constructorDeclaration.isClinit()) {
endVisitPreserved(constructorDeclaration.bodyStart, constructorDeclaration.bodyEnd);
}
popParent();
}
public void endVisit(FieldDeclaration fieldDeclaration, MethodScope methodScope) {
endVisitRemoved(fieldDeclaration.declarationSourceStart, fieldDeclaration.sourceEnd);
endVisitPreserved(fieldDeclaration.sourceEnd, fieldDeclaration.declarationEnd);
popParent();
}
public void endVisit(Initializer initializer, MethodScope methodScope) {
endVisitPreserved(initializer.bodyStart, initializer.bodyEnd);
popParent();
}
public void endVisit(LocalDeclaration localDeclaration, BlockScope blockScope) {
endVisitRemoved(localDeclaration.declarationSourceStart, localDeclaration.sourceEnd);
}
public void endVisit(MethodDeclaration methodDeclaration, ClassScope classScope) {
endVisitPreserved(
methodDeclaration.bodyStart,
methodDeclaration.bodyEnd);
popParent();
}
public void endVisit(TypeDeclaration typeDeclaration, BlockScope blockScope) {
endVisitRemoved(typeDeclaration.sourceStart, typeDeclaration.declarationSourceEnd);
popParent();
}
public void endVisit(TypeDeclaration typeDeclaration, ClassScope classScope) {
endVisitRemoved(typeDeclaration.sourceStart, typeDeclaration.declarationSourceEnd);
popParent();
}
private int indexOfFisrtNameAfter(int position) {
int left= 0;
int right= this.potentialVariableNamesPtr;
next: while (true) {
if (right < left)
return -1;
int mid= left + (right - left) / 2;
int midPosition= this.potentialVariableNameStarts[mid];
if (midPosition < 0) {
int nextMid= indexOfNextName(mid);
if (nextMid < 0 || right < nextMid) { // no next index or next index is after 'right'
right= mid - 1;
continue next;
}
mid= nextMid;
midPosition= this.potentialVariableNameStarts[nextMid];
if (mid == right) { // mid and right are at the same index, we must move 'left'
int leftPosition= this.potentialVariableNameStarts[left];
if (leftPosition < 0 || leftPosition < position) { // 'left' is empty or 'left' is before the position
int nextLeft= indexOfNextName(left);
if (nextLeft < 0)
return -1;
left= nextLeft;
continue next;
}
return left;
}
}
if (left != right) {
if (midPosition < position) {
left= mid + 1;
} else {
right= mid;
}
} else {
if (midPosition < position) {
return -1;
}
return mid;
}
}
}
private int indexOfNextName(int index) {
int nextIndex= index + 1;
while (nextIndex <= this.potentialVariableNamesPtr &&
this.potentialVariableNames[nextIndex] == null) {
int jumpIndex= -this.potentialVariableNameStarts[nextIndex];
if (jumpIndex > 0) {
nextIndex= jumpIndex;
} else {
nextIndex++;
}
}
if (this.potentialVariableNamesPtr < nextIndex) {
if (index < this.potentialVariableNamesPtr) {
this.potentialVariableNamesPtr= index;
}
return -1;
}
if (index + 1 < nextIndex) {
this.potentialVariableNameStarts[index + 1]= -nextIndex;
}
return nextIndex;
}
private void removeNameAt(int index) {
this.potentialVariableNames[index]= null;
int nextIndex= indexOfNextName(index);
if (nextIndex != -1) {
this.potentialVariableNameStarts[index]= -nextIndex;
} else {
this.potentialVariableNamesPtr= index - 1;
}
}
private void endVisitPreserved(int start, int end) {
int i= indexOfFisrtNameAfter(start);
done: while (i != -1) {
int nameStart= this.potentialVariableNameStarts[i];
if (start < nameStart && nameStart < end) {
acceptName(this.potentialVariableNames[i]);
removeNameAt(i);
}
if (end < nameStart)
break done;
i= indexOfNextName(i);
}
}
private void endVisitRemoved(int start, int end) {
int i= indexOfFisrtNameAfter(start);
done: while (i != -1) {
int nameStart= this.potentialVariableNameStarts[i];
if (start < nameStart && nameStart < end) {
removeNameAt(i);
}
if (end < nameStart)
break done;
i= indexOfNextName(i);
}
}
private void removeLocals(Statement[] statements, int start, int end) {
if (statements != null) {
for (int i= 0; i < statements.length; i++) {
if (statements[i] instanceof LocalDeclaration) {
LocalDeclaration localDeclaration= (LocalDeclaration)statements[i];
int j= indexOfFisrtNameAfter(start);
done: while (j != -1) {
int nameStart= this.potentialVariableNameStarts[j];
if (start <= nameStart && nameStart <= end) {
if (CharOperation.equals(this.potentialVariableNames[j], localDeclaration.name, false)) {
removeNameAt(j);
}
}
if (end < nameStart)
break done;
j= indexOfNextName(j);
}
}
}
}
}
private void removeFields(TypeDeclaration typeDeclaration) {
int start= typeDeclaration.declarationSourceStart;
int end= typeDeclaration.declarationSourceEnd;
FieldDeclaration[] fieldDeclarations= typeDeclaration.fields;
if (fieldDeclarations != null) {
for (int i= 0; i < fieldDeclarations.length; i++) {
int j= indexOfFisrtNameAfter(start);
done: while (j != -1) {
int nameStart= this.potentialVariableNameStarts[j];
if (start <= nameStart && nameStart <= end) {
if (CharOperation.equals(this.potentialVariableNames[j], fieldDeclarations[i].name, false)) {
removeNameAt(j);
}
}
if (end < nameStart)
break done;
j= indexOfNextName(j);
}
}
}
}
}