X-Git-Url: http://git.phpeclipse.com diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/codeassist/SelectionEngine.java b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/codeassist/SelectionEngine.java new file mode 100644 index 0000000..6f5e306 --- /dev/null +++ b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/codeassist/SelectionEngine.java @@ -0,0 +1,739 @@ +/******************************************************************************* + * Copyright (c) 2000, 2001, 2002 International Business Machines Corp. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v0.5 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v05.html + * + * Contributors: + * IBM Corporation - initial API and implementation + ******************************************************************************/ +package net.sourceforge.phpdt.internal.codeassist; + +import java.util.*; + +import net.sourceforge.phpdt.core.compiler.*; +import net.sourceforge.phpdt.core.compiler.InvalidInputException; +import net.sourceforge.phpdt.core.compiler.IProblem; +import net.sourceforge.phpdt.internal.codeassist.impl.*; +import net.sourceforge.phpdt.internal.codeassist.select.*; +import net.sourceforge.phpdt.internal.compiler.*; +import net.sourceforge.phpdt.internal.compiler.env.*; +import net.sourceforge.phpdt.internal.compiler.ast.*; +import net.sourceforge.phpdt.internal.compiler.lookup.*; +import net.sourceforge.phpdt.internal.compiler.parser.*; +import net.sourceforge.phpdt.internal.compiler.problem.*; +import net.sourceforge.phpdt.internal.compiler.util.*; +import net.sourceforge.phpdt.internal.compiler.impl.*; + +/** + * The selection engine is intended to infer the nature of a selected name in some + * source code. This name can be qualified. + * + * Selection is resolving context using a name environment (no need to search), assuming + * the source where selection occurred is correct and will not perform any completion + * attempt. If this was the desired behavior, a call to the CompletionEngine should be + * performed instead. + */ +public final class SelectionEngine extends Engine implements ISearchRequestor { + + public static boolean DEBUG = false; + + SelectionParser parser; + ISelectionRequestor requestor; + + boolean acceptedAnswer; + + private int actualSelectionStart; + private int actualSelectionEnd; + private char[] qualifiedSelection; + private char[] selectedIdentifier; + + private char[][][] acceptedClasses; + private char[][][] acceptedInterfaces; + int acceptedClassesCount; + int acceptedInterfacesCount; + + /** + * The SelectionEngine is responsible for computing the selected object. + * + * It requires a searchable name environment, which supports some + * specific search APIs, and a requestor to feed back the results to a UI. + * + * @param nameEnvironment net.sourceforge.phpdt.internal.codeassist.ISearchableNameEnvironment + * used to resolve type/package references and search for types/packages + * based on partial names. + * + * @param requestor net.sourceforge.phpdt.internal.codeassist.ISelectionRequestor + * since the engine might produce answers of various forms, the engine + * is associated with a requestor able to accept all possible completions. + * + * @param settings java.util.Map + * set of options used to configure the code assist engine. + */ + public SelectionEngine( + ISearchableNameEnvironment nameEnvironment, + ISelectionRequestor requestor, + Map settings) { + + super(settings); + + this.requestor = requestor; + this.nameEnvironment = nameEnvironment; + + ProblemReporter problemReporter = + new ProblemReporter( + DefaultErrorHandlingPolicies.proceedWithAllProblems(), + this.compilerOptions, + new DefaultProblemFactory(Locale.getDefault())) { + public void record(IProblem problem, CompilationResult unitResult, ReferenceContext referenceContext) { + unitResult.record(problem, referenceContext); + SelectionEngine.this.requestor.acceptError(problem); + } + }; + this.parser = new SelectionParser(problemReporter, this.compilerOptions.assertMode); + this.lookupEnvironment = + new LookupEnvironment(this, this.compilerOptions, problemReporter, nameEnvironment); + } + + /** + * One result of the search consists of a new class. + * @param packageName char[] + * @param className char[] + * @param modifiers int + * + * NOTE - All package and type names are presented in their readable form: + * Package names are in the form "a.b.c". + * Nested type names are in the qualified form "A.M". + * The default package is represented by an empty array. + */ + public void acceptClass(char[] packageName, char[] className, int modifiers) { + if (CharOperation.equals(className, selectedIdentifier)) { + if (qualifiedSelection != null + && !CharOperation.equals( + qualifiedSelection, + CharOperation.concat(packageName, className, '.'))) { + return; + } + + if(mustQualifyType(packageName, className)) { + char[][] acceptedClass = new char[2][]; + acceptedClass[0] = packageName; + acceptedClass[1] = className; + + if(acceptedClasses == null) { + acceptedClasses = new char[10][][]; + acceptedClassesCount = 0; + } + int length = acceptedClasses.length; + if(length == acceptedClassesCount) { + System.arraycopy(acceptedClasses, 0, acceptedClasses = new char[(length + 1)* 2][][], 0, length); + } + acceptedClasses[acceptedClassesCount++] = acceptedClass; + + } else { + requestor.acceptClass( + packageName, + className, + false); + acceptedAnswer = true; + } + } + } + + /** + * One result of the search consists of a new interface. + * + * NOTE - All package and type names are presented in their readable form: + * Package names are in the form "a.b.c". + * Nested type names are in the qualified form "A.I". + * The default package is represented by an empty array. + */ + public void acceptInterface( + char[] packageName, + char[] interfaceName, + int modifiers) { + + if (CharOperation.equals(interfaceName, selectedIdentifier)) { + if (qualifiedSelection != null + && !CharOperation.equals( + qualifiedSelection, + CharOperation.concat(packageName, interfaceName, '.'))) { + return; + } + + if(mustQualifyType(packageName, interfaceName)) { + char[][] acceptedInterface= new char[2][]; + acceptedInterface[0] = packageName; + acceptedInterface[1] = interfaceName; + + if(acceptedInterfaces == null) { + acceptedInterfaces = new char[10][][]; + acceptedInterfacesCount = 0; + } + int length = acceptedInterfaces.length; + if(length == acceptedInterfacesCount) { + System.arraycopy(acceptedInterfaces, 0, acceptedInterfaces = new char[(length + 1) * 2][][], 0, length); + } + acceptedInterfaces[acceptedInterfacesCount++] = acceptedInterface; + + } else { + requestor.acceptInterface( + packageName, + interfaceName, + false); + acceptedAnswer = true; + } + } + } + + /** + * One result of the search consists of a new package. + * @param packageName char[] + * + * NOTE - All package names are presented in their readable form: + * Package names are in the form "a.b.c". + * The default package is represented by an empty array. + */ + public void acceptPackage(char[] packageName) { + } + + private void acceptQualifiedTypes() { + if(acceptedClasses != null){ + acceptedAnswer = true; + for (int i = 0; i < acceptedClassesCount; i++) { + requestor.acceptClass( + acceptedClasses[i][0], + acceptedClasses[i][1], + true); + } + acceptedClasses = null; + acceptedClassesCount = 0; + } + if(acceptedInterfaces != null){ + acceptedAnswer = true; + for (int i = 0; i < acceptedInterfacesCount; i++) { + requestor.acceptInterface( + acceptedInterfaces[i][0], + acceptedInterfaces[i][1], + true); + } + acceptedInterfaces = null; + acceptedInterfacesCount = 0; + } + } + + /** + * One result of the search consists of a new type. + * @param packageName char[] + * @param typeName char[] + * + * NOTE - All package and type names are presented in their readable form: + * Package names are in the form "a.b.c". + * Nested type names are in the qualified form "A.M". + * The default package is represented by an empty array. + */ + public void acceptType(char[] packageName, char[] typeName) { + acceptClass(packageName, typeName, 0); + } + + private boolean checkSelection( + char[] source, + int selectionStart, + int selectionEnd) { + + Scanner scanner = new Scanner(); + scanner.setSource(source); + + int lastIdentifierStart = -1; + int lastIdentifierEnd = -1; + char[] lastIdentifier = null; + int token, identCount = 0; + StringBuffer entireSelection = new StringBuffer(selectionEnd - selectionStart + 1); + + if(selectionStart > selectionEnd){ + + // compute start position of current line + int currentPosition = selectionStart - 1; + int nextCharacterPosition = selectionStart; + char currentCharacter = ' '; + try { + while(currentPosition > 0 || currentCharacter == '\r' || currentCharacter == '\n'){ + + if(source[currentPosition] == '\\' && source[currentPosition+1] == 'u') { + int pos = currentPosition + 2; + int c1 = 0, c2 = 0, c3 = 0, c4 = 0; + while (source[pos] == 'u') { + pos++; + } + if ((c1 = Character.getNumericValue(source[pos++])) > 15 + || c1 < 0 + || (c2 = Character.getNumericValue(source[pos++])) > 15 + || c2 < 0 + || (c3 = Character.getNumericValue(source[pos++])) > 15 + || c3 < 0 + || (c4 = Character.getNumericValue(source[pos++])) > 15 + || c4 < 0) { + return false; + } else { + currentCharacter = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4); + nextCharacterPosition = pos; + } + } else { + currentCharacter = source[currentPosition]; + nextCharacterPosition = currentPosition+1; + } + + if(currentCharacter == '\r' || currentCharacter == '\n') { + break; + } + currentPosition--; + } + } + catch (ArrayIndexOutOfBoundsException e) { + return false; + } + + // compute start and end of the last token + scanner.resetTo(nextCharacterPosition, selectionEnd); + do { + try { + token = scanner.getNextToken(); + } catch (InvalidInputException e) { + return false; + } + if(( + // token == ITerminalSymbols.TokenNamethis || + // token == ITerminalSymbols.TokenNamesuper || + token == ITerminalSymbols.TokenNameIdentifier) && + scanner.startPosition <= selectionStart && + selectionStart <= scanner.currentPosition) { + lastIdentifierStart = scanner.startPosition; + lastIdentifierEnd = scanner.currentPosition - 1; + lastIdentifier = scanner.getCurrentTokenSource(); + } + } while (token != ITerminalSymbols.TokenNameEOF); + } else { + scanner.resetTo(selectionStart, selectionEnd); + + boolean expectingIdentifier = true; + + do { + try { + token = scanner.getNextToken(); + } catch (InvalidInputException e) { + return false; + } + switch (token) { +// case ITerminalSymbols.TokenNamethis : +// case ITerminalSymbols.TokenNamesuper : + case ITerminalSymbols.TokenNameIdentifier : + if (!expectingIdentifier) + return false; + lastIdentifier = scanner.getCurrentTokenSource(); + lastIdentifierStart = scanner.startPosition; + lastIdentifierEnd = scanner.currentPosition - 1; + if(lastIdentifierEnd > selectionEnd) { + lastIdentifierEnd = selectionEnd; + lastIdentifier = CharOperation.subarray(lastIdentifier, 0,lastIdentifierEnd - lastIdentifierStart + 1); + } + entireSelection.append(lastIdentifier); + + identCount++; + expectingIdentifier = false; + break; + case ITerminalSymbols.TokenNameDOT : + if (expectingIdentifier) + return false; + entireSelection.append('.'); + expectingIdentifier = true; + break; + case ITerminalSymbols.TokenNameEOF : + if (expectingIdentifier) + return false; + break; + default : + return false; + } + } while (token != ITerminalSymbols.TokenNameEOF); + } + if (lastIdentifierStart > 0) { + actualSelectionStart = lastIdentifierStart; + actualSelectionEnd = lastIdentifierEnd; + selectedIdentifier = lastIdentifier; + if (identCount > 1) + qualifiedSelection = entireSelection.toString().toCharArray(); + return true; + } + return false; + } + + public AssistParser getParser() { + return parser; + } + + /** + * Ask the engine to compute the selection at the specified position + * of the given compilation unit. + + * @param sourceUnit net.sourceforge.phpdt.internal.compiler.env.ICompilationUnit + * the source of the current compilation unit. + * + * @param selectionSourceStart int + * @param selectionSourceEnd int + * a range in the source where the selection is. + */ + public void select( + ICompilationUnit sourceUnit, + int selectionSourceStart, + int selectionSourceEnd) { + + char[] source = sourceUnit.getContents(); + + if(DEBUG) { + System.out.print("SELECTION IN "); //$NON-NLS-1$ + System.out.print(sourceUnit.getFileName()); + System.out.print(" FROM "); //$NON-NLS-1$ + System.out.print(selectionSourceStart); + System.out.print(" TO "); //$NON-NLS-1$ + System.out.println(selectionSourceEnd); + System.out.println("SELECTION - Source :"); //$NON-NLS-1$ + System.out.println(source); + } + if (!checkSelection(source, selectionSourceStart, selectionSourceEnd)) + return; + try { + acceptedAnswer = false; + CompilationResult result = new CompilationResult(sourceUnit, 1, 1, this.compilerOptions.maxProblemsPerUnit); + CompilationUnitDeclaration parsedUnit = + parser.dietParse(sourceUnit, result, actualSelectionStart, actualSelectionEnd); + + if (parsedUnit != null) { + if(DEBUG) { + System.out.println("SELECTION - Diet AST :"); //$NON-NLS-1$ + System.out.println(parsedUnit.toString()); + } + + // scan the package & import statements first + if (parsedUnit.currentPackage instanceof SelectionOnPackageReference) { + char[][] tokens = + ((SelectionOnPackageReference) parsedUnit.currentPackage).tokens; + requestor.acceptPackage(CharOperation.concatWith(tokens, '.')); + return; + } + ImportReference[] imports = parsedUnit.imports; + if (imports != null) { + for (int i = 0, length = imports.length; i < length; i++) { + ImportReference importReference = imports[i]; + if (importReference instanceof SelectionOnImportReference) { + char[][] tokens = ((SelectionOnImportReference) importReference).tokens; + requestor.acceptPackage(CharOperation.concatWith(tokens, '.')); + nameEnvironment.findTypes(CharOperation.concatWith(tokens, '.'), this); + // accept qualified types only if no unqualified type was accepted + if(!acceptedAnswer) { + acceptQualifiedTypes(); + if (!acceptedAnswer) { + nameEnvironment.findTypes(selectedIdentifier, this); + // try with simple type name + if(!acceptedAnswer) { + acceptQualifiedTypes(); + } + } + } + return; + } + } + } + if (parsedUnit.types != null) { + lookupEnvironment.buildTypeBindings(parsedUnit); + if ((this.unitScope = parsedUnit.scope) != null) { + try { + lookupEnvironment.completeTypeBindings(parsedUnit, true); + parsedUnit.scope.faultInTypes(); + selectDeclaration(parsedUnit); + parseMethod(parsedUnit, selectionSourceStart); + if(DEBUG) { + System.out.println("SELECTION - AST :"); //$NON-NLS-1$ + System.out.println(parsedUnit.toString()); + } + parsedUnit.resolve(); + } catch (SelectionNodeFound e) { + if (e.binding != null) { + if(DEBUG) { + System.out.println("SELECTION - Selection binding:"); //$NON-NLS-1$ + System.out.println(e.binding.toString()); + } + // if null then we found a problem in the selection node + selectFrom(e.binding); + } + } + } + } + } + // only reaches here if no selection could be derived from the parsed tree + // thus use the selected source and perform a textual type search + if (!acceptedAnswer) { + nameEnvironment.findTypes(selectedIdentifier, this); + + // accept qualified types only if no unqualified type was accepted + if(!acceptedAnswer) { + acceptQualifiedTypes(); + } + } + } catch (IndexOutOfBoundsException e) { // work-around internal failure - 1GEMF6D + } catch (AbortCompilation e) { // ignore this exception for now since it typically means we cannot find java.lang.Object + } finally { + reset(); + } + } + + private void selectFrom(Binding binding) { + if (binding instanceof ReferenceBinding) { + ReferenceBinding typeBinding = (ReferenceBinding) binding; + if (qualifiedSelection != null + && !CharOperation.equals(qualifiedSelection, typeBinding.readableName())) { + return; + } + if (typeBinding.isInterface()) { + requestor.acceptInterface( + typeBinding.qualifiedPackageName(), + typeBinding.qualifiedSourceName(), + false); + } else if(typeBinding instanceof ProblemReferenceBinding){ + ProblemReferenceBinding problemBinding = (ProblemReferenceBinding)typeBinding; + if(problemBinding.original == null + || !(problemBinding.original instanceof ReferenceBinding)) { + return; + } + ReferenceBinding original = (ReferenceBinding) problemBinding.original; + + requestor.acceptClass( + original.qualifiedPackageName(), + original.qualifiedSourceName(), + false); + } else { + requestor.acceptClass( + typeBinding.qualifiedPackageName(), + typeBinding.qualifiedSourceName(), + false); + } + acceptedAnswer = true; + } else + if (binding instanceof MethodBinding) { + MethodBinding methodBinding = (MethodBinding) binding; + TypeBinding[] parameterTypes = methodBinding.parameters; + int length = parameterTypes.length; + char[][] parameterPackageNames = new char[length][]; + char[][] parameterTypeNames = new char[length][]; + for (int i = 0; i < length; i++) { + parameterPackageNames[i] = parameterTypes[i].qualifiedPackageName(); + parameterTypeNames[i] = parameterTypes[i].qualifiedSourceName(); + } + requestor.acceptMethod( + methodBinding.declaringClass.qualifiedPackageName(), + methodBinding.declaringClass.qualifiedSourceName(), + methodBinding.isConstructor() + ? methodBinding.declaringClass.sourceName() + : methodBinding.selector, + parameterPackageNames, + parameterTypeNames, + methodBinding.isConstructor()); + acceptedAnswer = true; + } else + if (binding instanceof FieldBinding) { + FieldBinding fieldBinding = (FieldBinding) binding; + if (fieldBinding.declaringClass != null) { // arraylength + requestor.acceptField( + fieldBinding.declaringClass.qualifiedPackageName(), + fieldBinding.declaringClass.qualifiedSourceName(), + fieldBinding.name); + acceptedAnswer = true; + } + } else + if (binding instanceof LocalVariableBinding) { + selectFrom(((LocalVariableBinding) binding).type); + // open on the type of the variable + } else + if (binding instanceof ArrayBinding) { + selectFrom(((ArrayBinding) binding).leafComponentType); + // open on the type of the array + } else + if (binding instanceof PackageBinding) { + PackageBinding packageBinding = (PackageBinding) binding; + requestor.acceptPackage(packageBinding.readableName()); + acceptedAnswer = true; + } else + if(binding instanceof BaseTypeBinding) { + acceptedAnswer = true; + } + } + + /** + * Asks the engine to compute the selection of the given type + * from the source type. + * + * @param sourceType net.sourceforge.phpdt.internal.compiler.env.ISourceType + * a source form of the current type in which code assist is invoked. + * + * @param typeName char[] + * a type name which is to be resolved in the context of a compilation unit. + * NOTE: the type name is supposed to be correctly reduced (no whitespaces, no unicodes left) + * + * @param searchInEnvironment + * if true and no selection could be found in context then search type in environment. + */ + public void selectType(ISourceType sourceType, char[] typeName, boolean searchInEnvironment) { + try { + acceptedAnswer = false; + + // find the outer most type + ISourceType outerType = sourceType; + ISourceType parent = sourceType.getEnclosingType(); + while (parent != null) { + outerType = parent; + parent = parent.getEnclosingType(); + } + // compute parse tree for this most outer type + CompilationResult result = new CompilationResult(outerType.getFileName(), 1, 1, this.compilerOptions.maxProblemsPerUnit); + CompilationUnitDeclaration parsedUnit = + SourceTypeConverter + .buildCompilationUnit( + new ISourceType[] { outerType }, + false, + // don't need field and methods + true, // by default get member types + this.parser.problemReporter(), result); + + if (parsedUnit != null && parsedUnit.types != null) { + if(DEBUG) { + System.out.println("SELECTION - Diet AST :"); //$NON-NLS-1$ + System.out.println(parsedUnit.toString()); + } + // find the type declaration that corresponds to the original source type + char[] packageName = sourceType.getPackageName(); + char[] sourceTypeName = sourceType.getQualifiedName(); + // the fully qualified name without the package name + if (packageName != null) { + // remove the package name if necessary + sourceTypeName = + CharOperation.subarray( + sourceType.getQualifiedName(), + packageName.length + 1, + sourceTypeName.length); + }; + TypeDeclaration typeDecl = + parsedUnit.declarationOfType(CharOperation.splitOn('.', sourceTypeName)); + if (typeDecl != null) { + + // add fake field with the type we're looking for + // note: since we didn't ask for fields above, there is no field defined yet + FieldDeclaration field = new FieldDeclaration(); + int dot; + if ((dot = CharOperation.lastIndexOf('.', typeName)) == -1) { + this.selectedIdentifier = typeName; + field.type = new SelectionOnSingleTypeReference(typeName, -1); + // position not used + } else { + qualifiedSelection = typeName; + char[][] previousIdentifiers = CharOperation.splitOn('.', typeName, 0, dot - 1); + char[] selectionIdentifier = + CharOperation.subarray(typeName, dot + 1, typeName.length); + this.selectedIdentifier = selectionIdentifier; + field.type = + new SelectionOnQualifiedTypeReference( + previousIdentifiers, + selectionIdentifier, + new long[previousIdentifiers.length + 1]); + } + field.name = "".toCharArray(); //$NON-NLS-1$ + typeDecl.fields = new FieldDeclaration[] { field }; + + // build bindings + lookupEnvironment.buildTypeBindings(parsedUnit); + if ((this.unitScope = parsedUnit.scope) != null) { + try { + // build fields + // note: this builds fields only in the parsed unit (the buildFieldsAndMethods flag is not passed along) + this.lookupEnvironment.completeTypeBindings(parsedUnit, true); + + // resolve + parsedUnit.scope.faultInTypes(); + parsedUnit.resolve(); + } catch (SelectionNodeFound e) { + if (e.binding != null) { + if(DEBUG) { + System.out.println("SELECTION - Selection binding :"); //$NON-NLS-1$ + System.out.println(e.binding.toString()); + } + // if null then we found a problem in the selection node + selectFrom(e.binding); + } + } + } + } + } + // only reaches here if no selection could be derived from the parsed tree + // thus use the selected source and perform a textual type search + if (!acceptedAnswer && searchInEnvironment) { + if (this.selectedIdentifier != null) { + nameEnvironment.findTypes(typeName, this); + + // accept qualified types only if no unqualified type was accepted + if(!acceptedAnswer) { + acceptQualifiedTypes(); + } + } + } + } catch (AbortCompilation e) { // ignore this exception for now since it typically means we cannot find java.lang.Object + } finally { + qualifiedSelection = null; + reset(); + } + } + + // Check if a declaration got selected in this unit + private void selectDeclaration(CompilationUnitDeclaration compilationUnit){ + + // the selected identifier is not identical to the parser one (equals but not identical), + // for traversing the parse tree, the parser assist identifier is necessary for identitiy checks + char[] assistIdentifier = this.getParser().assistIdentifier(); + if (assistIdentifier == null) return; + + // iterate over the types + TypeDeclaration[] types = compilationUnit.types; + for (int i = 0, length = types == null ? 0 : types.length; i < length; i++){ + selectDeclaration(types[i], assistIdentifier); + } + } + + // Check if a declaration got selected in this type + private void selectDeclaration(TypeDeclaration typeDeclaration, char[] assistIdentifier){ + + if (typeDeclaration.name == assistIdentifier){ + throw new SelectionNodeFound(typeDeclaration.binding); + } + TypeDeclaration[] memberTypes = typeDeclaration.memberTypes; + for (int i = 0, length = memberTypes == null ? 0 : memberTypes.length; i < length; i++){ + selectDeclaration(memberTypes[i], assistIdentifier); + } + FieldDeclaration[] fields = typeDeclaration.fields; + for (int i = 0, length = fields == null ? 0 : fields.length; i < length; i++){ + if (fields[i].name == assistIdentifier){ + throw new SelectionNodeFound(fields[i].binding); + } + } + AbstractMethodDeclaration[] methods = typeDeclaration.methods; + for (int i = 0, length = methods == null ? 0 : methods.length; i < length; i++){ + AbstractMethodDeclaration method = methods[i]; + if (method.selector == assistIdentifier){ + if(method.binding != null) { + throw new SelectionNodeFound(method.binding); + } else { + if(method.scope != null) { + throw new SelectionNodeFound(new MethodBinding(method.modifiers, method.selector, null, null, null, method.scope.referenceType().binding)); + } + } + } + } + } +} \ No newline at end of file