1 /*******************************************************************************
2 * Copyright (c) 2000, 2001, 2002 International Business Machines Corp. and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v0.5
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v05.html
9 * IBM Corporation - initial API and implementation
10 ******************************************************************************/
11 package net.sourceforge.phpdt.internal.codeassist;
15 import net.sourceforge.phpdt.core.compiler.*;
16 import net.sourceforge.phpdt.core.compiler.InvalidInputException;
17 import net.sourceforge.phpdt.core.compiler.IProblem;
18 import net.sourceforge.phpdt.internal.codeassist.impl.*;
19 import net.sourceforge.phpdt.internal.codeassist.select.*;
20 import net.sourceforge.phpdt.internal.compiler.*;
21 import net.sourceforge.phpdt.internal.compiler.env.*;
22 import net.sourceforge.phpdt.internal.compiler.ast.*;
23 import net.sourceforge.phpdt.internal.compiler.lookup.*;
24 import net.sourceforge.phpdt.internal.compiler.parser.*;
25 import net.sourceforge.phpdt.internal.compiler.problem.*;
26 import net.sourceforge.phpdt.internal.compiler.util.*;
27 import net.sourceforge.phpdt.internal.compiler.impl.*;
30 * The selection engine is intended to infer the nature of a selected name in some
31 * source code. This name can be qualified.
33 * Selection is resolving context using a name environment (no need to search), assuming
34 * the source where selection occurred is correct and will not perform any completion
35 * attempt. If this was the desired behavior, a call to the CompletionEngine should be
38 public final class SelectionEngine extends Engine implements ISearchRequestor {
40 public static boolean DEBUG = false;
42 SelectionParser parser;
43 ISelectionRequestor requestor;
45 boolean acceptedAnswer;
47 private int actualSelectionStart;
48 private int actualSelectionEnd;
49 private char[] qualifiedSelection;
50 private char[] selectedIdentifier;
52 private char[][][] acceptedClasses;
53 private char[][][] acceptedInterfaces;
54 int acceptedClassesCount;
55 int acceptedInterfacesCount;
58 * The SelectionEngine is responsible for computing the selected object.
60 * It requires a searchable name environment, which supports some
61 * specific search APIs, and a requestor to feed back the results to a UI.
63 * @param nameEnvironment net.sourceforge.phpdt.internal.codeassist.ISearchableNameEnvironment
64 * used to resolve type/package references and search for types/packages
65 * based on partial names.
67 * @param requestor net.sourceforge.phpdt.internal.codeassist.ISelectionRequestor
68 * since the engine might produce answers of various forms, the engine
69 * is associated with a requestor able to accept all possible completions.
71 * @param settings java.util.Map
72 * set of options used to configure the code assist engine.
74 public SelectionEngine(
75 ISearchableNameEnvironment nameEnvironment,
76 ISelectionRequestor requestor,
81 this.requestor = requestor;
82 this.nameEnvironment = nameEnvironment;
84 ProblemReporter problemReporter =
86 DefaultErrorHandlingPolicies.proceedWithAllProblems(),
88 new DefaultProblemFactory(Locale.getDefault())) {
89 public void record(IProblem problem, CompilationResult unitResult, ReferenceContext referenceContext) {
90 unitResult.record(problem, referenceContext);
91 SelectionEngine.this.requestor.acceptError(problem);
94 this.parser = new SelectionParser(problemReporter, this.compilerOptions.assertMode);
95 this.lookupEnvironment =
96 new LookupEnvironment(this, this.compilerOptions, problemReporter, nameEnvironment);
100 * One result of the search consists of a new class.
101 * @param packageName char[]
102 * @param className char[]
103 * @param modifiers int
105 * NOTE - All package and type names are presented in their readable form:
106 * Package names are in the form "a.b.c".
107 * Nested type names are in the qualified form "A.M".
108 * The default package is represented by an empty array.
110 public void acceptClass(char[] packageName, char[] className, int modifiers) {
111 if (CharOperation.equals(className, selectedIdentifier)) {
112 if (qualifiedSelection != null
113 && !CharOperation.equals(
115 CharOperation.concat(packageName, className, '.'))) {
119 if(mustQualifyType(packageName, className)) {
120 char[][] acceptedClass = new char[2][];
121 acceptedClass[0] = packageName;
122 acceptedClass[1] = className;
124 if(acceptedClasses == null) {
125 acceptedClasses = new char[10][][];
126 acceptedClassesCount = 0;
128 int length = acceptedClasses.length;
129 if(length == acceptedClassesCount) {
130 System.arraycopy(acceptedClasses, 0, acceptedClasses = new char[(length + 1)* 2][][], 0, length);
132 acceptedClasses[acceptedClassesCount++] = acceptedClass;
135 requestor.acceptClass(
139 acceptedAnswer = true;
145 * One result of the search consists of a new interface.
147 * NOTE - All package and type names are presented in their readable form:
148 * Package names are in the form "a.b.c".
149 * Nested type names are in the qualified form "A.I".
150 * The default package is represented by an empty array.
152 public void acceptInterface(
154 char[] interfaceName,
157 if (CharOperation.equals(interfaceName, selectedIdentifier)) {
158 if (qualifiedSelection != null
159 && !CharOperation.equals(
161 CharOperation.concat(packageName, interfaceName, '.'))) {
165 if(mustQualifyType(packageName, interfaceName)) {
166 char[][] acceptedInterface= new char[2][];
167 acceptedInterface[0] = packageName;
168 acceptedInterface[1] = interfaceName;
170 if(acceptedInterfaces == null) {
171 acceptedInterfaces = new char[10][][];
172 acceptedInterfacesCount = 0;
174 int length = acceptedInterfaces.length;
175 if(length == acceptedInterfacesCount) {
176 System.arraycopy(acceptedInterfaces, 0, acceptedInterfaces = new char[(length + 1) * 2][][], 0, length);
178 acceptedInterfaces[acceptedInterfacesCount++] = acceptedInterface;
181 requestor.acceptInterface(
185 acceptedAnswer = true;
191 * One result of the search consists of a new package.
192 * @param packageName char[]
194 * NOTE - All package names are presented in their readable form:
195 * Package names are in the form "a.b.c".
196 * The default package is represented by an empty array.
198 public void acceptPackage(char[] packageName) {
201 private void acceptQualifiedTypes() {
202 if(acceptedClasses != null){
203 acceptedAnswer = true;
204 for (int i = 0; i < acceptedClassesCount; i++) {
205 requestor.acceptClass(
206 acceptedClasses[i][0],
207 acceptedClasses[i][1],
210 acceptedClasses = null;
211 acceptedClassesCount = 0;
213 if(acceptedInterfaces != null){
214 acceptedAnswer = true;
215 for (int i = 0; i < acceptedInterfacesCount; i++) {
216 requestor.acceptInterface(
217 acceptedInterfaces[i][0],
218 acceptedInterfaces[i][1],
221 acceptedInterfaces = null;
222 acceptedInterfacesCount = 0;
227 * One result of the search consists of a new type.
228 * @param packageName char[]
229 * @param typeName char[]
231 * NOTE - All package and type names are presented in their readable form:
232 * Package names are in the form "a.b.c".
233 * Nested type names are in the qualified form "A.M".
234 * The default package is represented by an empty array.
236 public void acceptType(char[] packageName, char[] typeName) {
237 acceptClass(packageName, typeName, 0);
240 private boolean checkSelection(
245 Scanner scanner = new Scanner();
246 scanner.setSource(source);
248 int lastIdentifierStart = -1;
249 int lastIdentifierEnd = -1;
250 char[] lastIdentifier = null;
251 int token, identCount = 0;
252 StringBuffer entireSelection = new StringBuffer(selectionEnd - selectionStart + 1);
254 if(selectionStart > selectionEnd){
256 // compute start position of current line
257 int currentPosition = selectionStart - 1;
258 int nextCharacterPosition = selectionStart;
259 char currentCharacter = ' ';
261 while(currentPosition > 0 || currentCharacter == '\r' || currentCharacter == '\n'){
263 if(source[currentPosition] == '\\' && source[currentPosition+1] == 'u') {
264 int pos = currentPosition + 2;
265 int c1 = 0, c2 = 0, c3 = 0, c4 = 0;
266 while (source[pos] == 'u') {
269 if ((c1 = Character.getNumericValue(source[pos++])) > 15
271 || (c2 = Character.getNumericValue(source[pos++])) > 15
273 || (c3 = Character.getNumericValue(source[pos++])) > 15
275 || (c4 = Character.getNumericValue(source[pos++])) > 15
279 currentCharacter = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
280 nextCharacterPosition = pos;
283 currentCharacter = source[currentPosition];
284 nextCharacterPosition = currentPosition+1;
287 if(currentCharacter == '\r' || currentCharacter == '\n') {
293 catch (ArrayIndexOutOfBoundsException e) {
297 // compute start and end of the last token
298 scanner.resetTo(nextCharacterPosition, selectionEnd);
301 token = scanner.getNextToken();
302 } catch (InvalidInputException e) {
306 // token == ITerminalSymbols.TokenNamethis ||
307 // token == ITerminalSymbols.TokenNamesuper ||
308 token == ITerminalSymbols.TokenNameIdentifier) &&
309 scanner.startPosition <= selectionStart &&
310 selectionStart <= scanner.currentPosition) {
311 lastIdentifierStart = scanner.startPosition;
312 lastIdentifierEnd = scanner.currentPosition - 1;
313 lastIdentifier = scanner.getCurrentTokenSource();
315 } while (token != ITerminalSymbols.TokenNameEOF);
317 scanner.resetTo(selectionStart, selectionEnd);
319 boolean expectingIdentifier = true;
323 token = scanner.getNextToken();
324 } catch (InvalidInputException e) {
328 // case ITerminalSymbols.TokenNamethis :
329 // case ITerminalSymbols.TokenNamesuper :
330 case ITerminalSymbols.TokenNameIdentifier :
331 if (!expectingIdentifier)
333 lastIdentifier = scanner.getCurrentTokenSource();
334 lastIdentifierStart = scanner.startPosition;
335 lastIdentifierEnd = scanner.currentPosition - 1;
336 if(lastIdentifierEnd > selectionEnd) {
337 lastIdentifierEnd = selectionEnd;
338 lastIdentifier = CharOperation.subarray(lastIdentifier, 0,lastIdentifierEnd - lastIdentifierStart + 1);
340 entireSelection.append(lastIdentifier);
343 expectingIdentifier = false;
345 case ITerminalSymbols.TokenNameDOT :
346 if (expectingIdentifier)
348 entireSelection.append('.');
349 expectingIdentifier = true;
351 case ITerminalSymbols.TokenNameEOF :
352 if (expectingIdentifier)
358 } while (token != ITerminalSymbols.TokenNameEOF);
360 if (lastIdentifierStart > 0) {
361 actualSelectionStart = lastIdentifierStart;
362 actualSelectionEnd = lastIdentifierEnd;
363 selectedIdentifier = lastIdentifier;
365 qualifiedSelection = entireSelection.toString().toCharArray();
371 public AssistParser getParser() {
376 * Ask the engine to compute the selection at the specified position
377 * of the given compilation unit.
379 * @param sourceUnit net.sourceforge.phpdt.internal.compiler.env.ICompilationUnit
380 * the source of the current compilation unit.
382 * @param selectionSourceStart int
383 * @param selectionSourceEnd int
384 * a range in the source where the selection is.
387 ICompilationUnit sourceUnit,
388 int selectionSourceStart,
389 int selectionSourceEnd) {
391 char[] source = sourceUnit.getContents();
394 System.out.print("SELECTION IN "); //$NON-NLS-1$
395 System.out.print(sourceUnit.getFileName());
396 System.out.print(" FROM "); //$NON-NLS-1$
397 System.out.print(selectionSourceStart);
398 System.out.print(" TO "); //$NON-NLS-1$
399 System.out.println(selectionSourceEnd);
400 System.out.println("SELECTION - Source :"); //$NON-NLS-1$
401 System.out.println(source);
403 if (!checkSelection(source, selectionSourceStart, selectionSourceEnd))
406 acceptedAnswer = false;
407 CompilationResult result = new CompilationResult(sourceUnit, 1, 1, this.compilerOptions.maxProblemsPerUnit);
408 CompilationUnitDeclaration parsedUnit =
409 parser.dietParse(sourceUnit, result, actualSelectionStart, actualSelectionEnd);
411 if (parsedUnit != null) {
413 System.out.println("SELECTION - Diet AST :"); //$NON-NLS-1$
414 System.out.println(parsedUnit.toString());
417 // scan the package & import statements first
418 if (parsedUnit.currentPackage instanceof SelectionOnPackageReference) {
420 ((SelectionOnPackageReference) parsedUnit.currentPackage).tokens;
421 requestor.acceptPackage(CharOperation.concatWith(tokens, '.'));
424 ImportReference[] imports = parsedUnit.imports;
425 if (imports != null) {
426 for (int i = 0, length = imports.length; i < length; i++) {
427 ImportReference importReference = imports[i];
428 if (importReference instanceof SelectionOnImportReference) {
429 char[][] tokens = ((SelectionOnImportReference) importReference).tokens;
430 requestor.acceptPackage(CharOperation.concatWith(tokens, '.'));
431 nameEnvironment.findTypes(CharOperation.concatWith(tokens, '.'), this);
432 // accept qualified types only if no unqualified type was accepted
433 if(!acceptedAnswer) {
434 acceptQualifiedTypes();
435 if (!acceptedAnswer) {
436 nameEnvironment.findTypes(selectedIdentifier, this);
437 // try with simple type name
438 if(!acceptedAnswer) {
439 acceptQualifiedTypes();
447 if (parsedUnit.types != null) {
448 lookupEnvironment.buildTypeBindings(parsedUnit);
449 if ((this.unitScope = parsedUnit.scope) != null) {
451 lookupEnvironment.completeTypeBindings(parsedUnit, true);
452 parsedUnit.scope.faultInTypes();
453 selectDeclaration(parsedUnit);
454 parseMethod(parsedUnit, selectionSourceStart);
456 System.out.println("SELECTION - AST :"); //$NON-NLS-1$
457 System.out.println(parsedUnit.toString());
459 parsedUnit.resolve();
460 } catch (SelectionNodeFound e) {
461 if (e.binding != null) {
463 System.out.println("SELECTION - Selection binding:"); //$NON-NLS-1$
464 System.out.println(e.binding.toString());
466 // if null then we found a problem in the selection node
467 selectFrom(e.binding);
473 // only reaches here if no selection could be derived from the parsed tree
474 // thus use the selected source and perform a textual type search
475 if (!acceptedAnswer) {
476 nameEnvironment.findTypes(selectedIdentifier, this);
478 // accept qualified types only if no unqualified type was accepted
479 if(!acceptedAnswer) {
480 acceptQualifiedTypes();
483 } catch (IndexOutOfBoundsException e) { // work-around internal failure - 1GEMF6D
484 } catch (AbortCompilation e) { // ignore this exception for now since it typically means we cannot find java.lang.Object
490 private void selectFrom(Binding binding) {
491 if (binding instanceof ReferenceBinding) {
492 ReferenceBinding typeBinding = (ReferenceBinding) binding;
493 if (qualifiedSelection != null
494 && !CharOperation.equals(qualifiedSelection, typeBinding.readableName())) {
497 if (typeBinding.isInterface()) {
498 requestor.acceptInterface(
499 typeBinding.qualifiedPackageName(),
500 typeBinding.qualifiedSourceName(),
502 } else if(typeBinding instanceof ProblemReferenceBinding){
503 ProblemReferenceBinding problemBinding = (ProblemReferenceBinding)typeBinding;
504 if(problemBinding.original == null
505 || !(problemBinding.original instanceof ReferenceBinding)) {
508 ReferenceBinding original = (ReferenceBinding) problemBinding.original;
510 requestor.acceptClass(
511 original.qualifiedPackageName(),
512 original.qualifiedSourceName(),
515 requestor.acceptClass(
516 typeBinding.qualifiedPackageName(),
517 typeBinding.qualifiedSourceName(),
520 acceptedAnswer = true;
522 if (binding instanceof MethodBinding) {
523 MethodBinding methodBinding = (MethodBinding) binding;
524 TypeBinding[] parameterTypes = methodBinding.parameters;
525 int length = parameterTypes.length;
526 char[][] parameterPackageNames = new char[length][];
527 char[][] parameterTypeNames = new char[length][];
528 for (int i = 0; i < length; i++) {
529 parameterPackageNames[i] = parameterTypes[i].qualifiedPackageName();
530 parameterTypeNames[i] = parameterTypes[i].qualifiedSourceName();
532 requestor.acceptMethod(
533 methodBinding.declaringClass.qualifiedPackageName(),
534 methodBinding.declaringClass.qualifiedSourceName(),
535 methodBinding.isConstructor()
536 ? methodBinding.declaringClass.sourceName()
537 : methodBinding.selector,
538 parameterPackageNames,
540 methodBinding.isConstructor());
541 acceptedAnswer = true;
543 if (binding instanceof FieldBinding) {
544 FieldBinding fieldBinding = (FieldBinding) binding;
545 if (fieldBinding.declaringClass != null) { // arraylength
546 requestor.acceptField(
547 fieldBinding.declaringClass.qualifiedPackageName(),
548 fieldBinding.declaringClass.qualifiedSourceName(),
550 acceptedAnswer = true;
553 if (binding instanceof LocalVariableBinding) {
554 selectFrom(((LocalVariableBinding) binding).type);
555 // open on the type of the variable
557 if (binding instanceof ArrayBinding) {
558 selectFrom(((ArrayBinding) binding).leafComponentType);
559 // open on the type of the array
561 if (binding instanceof PackageBinding) {
562 PackageBinding packageBinding = (PackageBinding) binding;
563 requestor.acceptPackage(packageBinding.readableName());
564 acceptedAnswer = true;
566 if(binding instanceof BaseTypeBinding) {
567 acceptedAnswer = true;
572 * Asks the engine to compute the selection of the given type
573 * from the source type.
575 * @param sourceType net.sourceforge.phpdt.internal.compiler.env.ISourceType
576 * a source form of the current type in which code assist is invoked.
578 * @param typeName char[]
579 * a type name which is to be resolved in the context of a compilation unit.
580 * NOTE: the type name is supposed to be correctly reduced (no whitespaces, no unicodes left)
582 * @param searchInEnvironment
583 * if <code>true</code> and no selection could be found in context then search type in environment.
585 public void selectType(ISourceType sourceType, char[] typeName, boolean searchInEnvironment) {
587 acceptedAnswer = false;
589 // find the outer most type
590 ISourceType outerType = sourceType;
591 ISourceType parent = sourceType.getEnclosingType();
592 while (parent != null) {
594 parent = parent.getEnclosingType();
596 // compute parse tree for this most outer type
597 CompilationResult result = new CompilationResult(outerType.getFileName(), 1, 1, this.compilerOptions.maxProblemsPerUnit);
598 CompilationUnitDeclaration parsedUnit =
600 .buildCompilationUnit(
601 new ISourceType[] { outerType },
603 // don't need field and methods
604 true, // by default get member types
605 this.parser.problemReporter(), result);
607 if (parsedUnit != null && parsedUnit.types != null) {
609 System.out.println("SELECTION - Diet AST :"); //$NON-NLS-1$
610 System.out.println(parsedUnit.toString());
612 // find the type declaration that corresponds to the original source type
613 char[] packageName = sourceType.getPackageName();
614 char[] sourceTypeName = sourceType.getQualifiedName();
615 // the fully qualified name without the package name
616 if (packageName != null) {
617 // remove the package name if necessary
619 CharOperation.subarray(
620 sourceType.getQualifiedName(),
621 packageName.length + 1,
622 sourceTypeName.length);
624 TypeDeclaration typeDecl =
625 parsedUnit.declarationOfType(CharOperation.splitOn('.', sourceTypeName));
626 if (typeDecl != null) {
628 // add fake field with the type we're looking for
629 // note: since we didn't ask for fields above, there is no field defined yet
630 FieldDeclaration field = new FieldDeclaration();
632 if ((dot = CharOperation.lastIndexOf('.', typeName)) == -1) {
633 this.selectedIdentifier = typeName;
634 field.type = new SelectionOnSingleTypeReference(typeName, -1);
637 qualifiedSelection = typeName;
638 char[][] previousIdentifiers = CharOperation.splitOn('.', typeName, 0, dot - 1);
639 char[] selectionIdentifier =
640 CharOperation.subarray(typeName, dot + 1, typeName.length);
641 this.selectedIdentifier = selectionIdentifier;
643 new SelectionOnQualifiedTypeReference(
646 new long[previousIdentifiers.length + 1]);
648 field.name = "<fakeField>".toCharArray(); //$NON-NLS-1$
649 typeDecl.fields = new FieldDeclaration[] { field };
652 lookupEnvironment.buildTypeBindings(parsedUnit);
653 if ((this.unitScope = parsedUnit.scope) != null) {
656 // note: this builds fields only in the parsed unit (the buildFieldsAndMethods flag is not passed along)
657 this.lookupEnvironment.completeTypeBindings(parsedUnit, true);
660 parsedUnit.scope.faultInTypes();
661 parsedUnit.resolve();
662 } catch (SelectionNodeFound e) {
663 if (e.binding != null) {
665 System.out.println("SELECTION - Selection binding :"); //$NON-NLS-1$
666 System.out.println(e.binding.toString());
668 // if null then we found a problem in the selection node
669 selectFrom(e.binding);
675 // only reaches here if no selection could be derived from the parsed tree
676 // thus use the selected source and perform a textual type search
677 if (!acceptedAnswer && searchInEnvironment) {
678 if (this.selectedIdentifier != null) {
679 nameEnvironment.findTypes(typeName, this);
681 // accept qualified types only if no unqualified type was accepted
682 if(!acceptedAnswer) {
683 acceptQualifiedTypes();
687 } catch (AbortCompilation e) { // ignore this exception for now since it typically means we cannot find java.lang.Object
689 qualifiedSelection = null;
694 // Check if a declaration got selected in this unit
695 private void selectDeclaration(CompilationUnitDeclaration compilationUnit){
697 // the selected identifier is not identical to the parser one (equals but not identical),
698 // for traversing the parse tree, the parser assist identifier is necessary for identitiy checks
699 char[] assistIdentifier = this.getParser().assistIdentifier();
700 if (assistIdentifier == null) return;
702 // iterate over the types
703 TypeDeclaration[] types = compilationUnit.types;
704 for (int i = 0, length = types == null ? 0 : types.length; i < length; i++){
705 selectDeclaration(types[i], assistIdentifier);
709 // Check if a declaration got selected in this type
710 private void selectDeclaration(TypeDeclaration typeDeclaration, char[] assistIdentifier){
712 if (typeDeclaration.name == assistIdentifier){
713 throw new SelectionNodeFound(typeDeclaration.binding);
715 TypeDeclaration[] memberTypes = typeDeclaration.memberTypes;
716 for (int i = 0, length = memberTypes == null ? 0 : memberTypes.length; i < length; i++){
717 selectDeclaration(memberTypes[i], assistIdentifier);
719 FieldDeclaration[] fields = typeDeclaration.fields;
720 for (int i = 0, length = fields == null ? 0 : fields.length; i < length; i++){
721 if (fields[i].name == assistIdentifier){
722 throw new SelectionNodeFound(fields[i].binding);
725 AbstractMethodDeclaration[] methods = typeDeclaration.methods;
726 for (int i = 0, length = methods == null ? 0 : methods.length; i < length; i++){
727 AbstractMethodDeclaration method = methods[i];
728 if (method.selector == assistIdentifier){
729 if(method.binding != null) {
730 throw new SelectionNodeFound(method.binding);
732 if(method.scope != null) {
733 throw new SelectionNodeFound(new MethodBinding(method.modifiers, method.selector, null, null, null, method.scope.referenceType().binding));