/******************************************************************************* * 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.Locale; import java.util.Map; import net.sourceforge.phpdt.core.compiler.IProblem; import net.sourceforge.phpdt.core.compiler.ITerminalSymbols; import net.sourceforge.phpdt.core.compiler.InvalidInputException; import net.sourceforge.phpdt.internal.codeassist.impl.AssistParser; import net.sourceforge.phpdt.internal.codeassist.impl.Engine; import net.sourceforge.phpdt.internal.codeassist.select.SelectionNodeFound; import net.sourceforge.phpdt.internal.codeassist.select.SelectionOnImportReference; import net.sourceforge.phpdt.internal.codeassist.select.SelectionOnPackageReference; import net.sourceforge.phpdt.internal.codeassist.select.SelectionOnQualifiedTypeReference; import net.sourceforge.phpdt.internal.codeassist.select.SelectionOnSingleTypeReference; import net.sourceforge.phpdt.internal.codeassist.select.SelectionParser; import net.sourceforge.phpdt.internal.compiler.CompilationResult; import net.sourceforge.phpdt.internal.compiler.DefaultErrorHandlingPolicies; import net.sourceforge.phpdt.internal.compiler.ast.AbstractMethodDeclaration; import net.sourceforge.phpdt.internal.compiler.ast.CompilationUnitDeclaration; import net.sourceforge.phpdt.internal.compiler.ast.FieldDeclaration; import net.sourceforge.phpdt.internal.compiler.ast.ImportReference; import net.sourceforge.phpdt.internal.compiler.ast.TypeDeclaration; import net.sourceforge.phpdt.internal.compiler.env.ICompilationUnit; import net.sourceforge.phpdt.internal.compiler.env.ISourceType; import net.sourceforge.phpdt.internal.compiler.impl.ReferenceContext; import net.sourceforge.phpdt.internal.compiler.lookup.ArrayBinding; import net.sourceforge.phpdt.internal.compiler.lookup.BaseTypeBinding; import net.sourceforge.phpdt.internal.compiler.lookup.Binding; import net.sourceforge.phpdt.internal.compiler.lookup.FieldBinding; import net.sourceforge.phpdt.internal.compiler.lookup.LocalVariableBinding; import net.sourceforge.phpdt.internal.compiler.lookup.LookupEnvironment; import net.sourceforge.phpdt.internal.compiler.lookup.MethodBinding; import net.sourceforge.phpdt.internal.compiler.lookup.PackageBinding; import net.sourceforge.phpdt.internal.compiler.lookup.ProblemReferenceBinding; import net.sourceforge.phpdt.internal.compiler.lookup.ReferenceBinding; import net.sourceforge.phpdt.internal.compiler.lookup.TypeBinding; import net.sourceforge.phpdt.internal.compiler.parser.Scanner; import net.sourceforge.phpdt.internal.compiler.parser.SourceTypeConverter; import net.sourceforge.phpdt.internal.compiler.problem.AbortCompilation; import net.sourceforge.phpdt.internal.compiler.problem.DefaultProblemFactory; import net.sourceforge.phpdt.internal.compiler.problem.ProblemReporter; import net.sourceforge.phpdt.internal.compiler.util.CharOperation; /** * 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)); } } } } } }