| /******************************************************************************* |
| * 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); |
| } |
| } |
| } |
| } |
| } |