update copyright
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInsight / completion / JavaCompletionContributor.java
blob6c6c209a4d78596b925d3a051038eca13a64966f
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.codeInsight.completion;
18 import com.intellij.codeInsight.TailType;
19 import com.intellij.codeInsight.TailTypes;
20 import com.intellij.codeInsight.daemon.impl.quickfix.ImportClassFix;
21 import com.intellij.codeInsight.hint.ShowParameterInfoHandler;
22 import com.intellij.codeInsight.lookup.LookupElement;
23 import com.intellij.codeInsight.lookup.LookupItem;
24 import com.intellij.codeInsight.lookup.LookupItemUtil;
25 import com.intellij.codeInsight.lookup.TailTypeDecorator;
26 import com.intellij.lang.LangBundle;
27 import com.intellij.lang.StdLanguages;
28 import com.intellij.openapi.actionSystem.IdeActions;
29 import com.intellij.openapi.application.ApplicationManager;
30 import com.intellij.openapi.diagnostic.Logger;
31 import com.intellij.openapi.editor.Editor;
32 import com.intellij.openapi.editor.ex.EditorEx;
33 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
34 import com.intellij.openapi.project.Project;
35 import com.intellij.openapi.util.Comparing;
36 import com.intellij.openapi.util.Computable;
37 import com.intellij.openapi.util.text.StringUtil;
38 import com.intellij.patterns.ElementPattern;
39 import com.intellij.patterns.PatternCondition;
40 import static com.intellij.patterns.PsiJavaPatterns.*;
41 import com.intellij.patterns.PsiNameValuePairPattern;
42 import com.intellij.psi.*;
43 import com.intellij.psi.filters.*;
44 import com.intellij.psi.filters.classes.AssignableFromContextFilter;
45 import com.intellij.psi.filters.classes.ThisOrAnyInnerFilter;
46 import com.intellij.psi.filters.element.ExcludeDeclaredFilter;
47 import com.intellij.psi.filters.element.ModifierFilter;
48 import com.intellij.psi.filters.getters.ExpectedTypesGetter;
49 import com.intellij.psi.filters.types.AssignableFromFilter;
50 import com.intellij.psi.scope.ElementClassFilter;
51 import com.intellij.psi.util.PsiTreeUtil;
52 import com.intellij.psi.util.PsiUtil;
53 import com.intellij.util.PairConsumer;
54 import com.intellij.util.ProcessingContext;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
58 import java.util.Arrays;
59 import java.util.HashSet;
60 import java.util.LinkedHashSet;
61 import java.util.Set;
63 /**
64 * @author peter
66 public class JavaCompletionContributor extends CompletionContributor {
67 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.JavaCompletionContributor");
68 private static final Java15CompletionData ourJava15CompletionData = new Java15CompletionData();
69 private static final JavaCompletionData ourJavaCompletionData = new JavaCompletionData();
70 private static final PsiNameValuePairPattern NAME_VALUE_PAIR = psiNameValuePair().withSuperParent(
72 psiElement(PsiAnnotation.class));
73 private static final ElementPattern<PsiElement> ANNOTATION_ATTRIBUTE_NAME =
74 or(psiElement(PsiIdentifier.class).withParent(NAME_VALUE_PAIR),
75 psiElement().afterLeaf("(").withParent(psiReferenceExpression().withParent(NAME_VALUE_PAIR)));
76 public static final ElementPattern SWITCH_LABEL =
77 psiElement().withSuperParent(2, psiElement(PsiSwitchLabelStatement.class).withSuperParent(2,
78 psiElement(PsiSwitchStatement.class).with(new PatternCondition<PsiSwitchStatement>("enumExpressionType") {
79 @Override
80 public boolean accepts(@NotNull PsiSwitchStatement psiSwitchStatement, ProcessingContext context) {
81 final PsiExpression expression = psiSwitchStatement.getExpression();
82 if(expression == null) return false;
83 final PsiType type = expression.getType();
84 return type instanceof PsiClassType;
86 })));
88 @Nullable
89 private static ElementFilter getReferenceFilter(PsiElement position) {
90 // Completion after extends in interface, type parameter and implements in class
91 final PsiClass containingClass = PsiTreeUtil.getParentOfType(position, PsiClass.class, false, PsiCodeBlock.class, PsiMethod.class, PsiExpressionList.class);
92 if (containingClass != null && psiElement().afterLeaf(PsiKeyword.EXTENDS, PsiKeyword.IMPLEMENTS, ",", "&").accepts(position)) {
93 return new AndFilter(ElementClassFilter.CLASS, new NotFilter(new AssignableFromContextFilter()));
96 if (JavaCompletionData.DECLARATION_START.isAcceptable(position, position)) {
97 return new OrFilter(ElementClassFilter.CLASS, ElementClassFilter.PACKAGE_FILTER);
100 if (JavaCompletionData.INSIDE_PARAMETER_LIST.accepts(position)) {
101 return ElementClassFilter.CLASS;
104 // Completion for classes in method throws section
105 if (psiElement().afterLeaf(PsiKeyword.THROWS, ",").inside(psiElement(PsiReferenceList.class).withParent(PsiMethod.class)).accepts(position)) {
106 return new OrFilter(new ThisOrAnyInnerFilter(new AssignableFromFilter("java.lang.Throwable")), ElementClassFilter.PACKAGE_FILTER);
109 if (psiElement().afterLeaf(PsiKeyword.INSTANCEOF).accepts(position)) {
110 return new ElementExtractorFilter(ElementClassFilter.CLASS);
113 if (JavaCompletionData.AFTER_FINAL.accepts(position)) {
114 return ElementClassFilter.CLASS;
117 if (JavaCompletionData.AFTER_TRY_BLOCK.isAcceptable(position, position) ||
118 JavaCompletionData.START_SWITCH.isAcceptable(position, position) ||
119 JavaCompletionData.INSTANCEOF_PLACE.isAcceptable(position, position)) {
120 return null;
123 if (psiElement().afterLeaf(psiElement().withText("(").withParent(PsiTryStatement.class)).accepts(position)) {
124 return new OrFilter(new ThisOrAnyInnerFilter(new AssignableFromFilter(CommonClassNames.JAVA_LANG_THROWABLE)), ElementClassFilter.PACKAGE_FILTER);
127 if (JavaSmartCompletionContributor.AFTER_THROW_NEW.accepts(position)) {
128 return new ThisOrAnyInnerFilter(new AssignableFromFilter("java.lang.Throwable"));
131 if (JavaSmartCompletionContributor.AFTER_NEW.accepts(position)) {
132 return ElementClassFilter.CLASS;
135 if (psiElement().inside(PsiReferenceParameterList.class).accepts(position)) {
136 return ElementClassFilter.CLASS;
139 if (psiElement().inside(PsiAnnotationParameterList.class).accepts(position)) {
140 return new OrFilter(new ClassFilter(PsiAnnotationMethod.class), ElementClassFilter.CLASS, ElementClassFilter.PACKAGE_FILTER, new AndFilter(new ClassFilter(PsiField.class),
141 new ModifierFilter(PsiModifier.STATIC, PsiModifier.FINAL)));
144 if (psiElement().afterLeaf("=").inside(PsiVariable.class).accepts(position)) {
145 return new OrFilter(
146 new ClassFilter(PsiVariable.class, false),
147 new ExcludeDeclaredFilter(new ClassFilter(PsiVariable.class)));
150 if (SWITCH_LABEL.accepts(position)) {
151 return new ClassFilter(PsiField.class) {
152 @Override
153 public boolean isAcceptable(Object element, PsiElement context) {
154 return element instanceof PsiEnumConstant;
159 return TrueFilter.INSTANCE;
162 public void fillCompletionVariants(final CompletionParameters parameters, final CompletionResultSet _result) {
163 if (parameters.getCompletionType() != CompletionType.BASIC) {
164 return;
167 final PsiElement insertedElement = parameters.getPosition();
168 if (insertedElement.getContainingFile().getLanguage() != StdLanguages.JAVA) {
169 return;
172 final PsiFile file = parameters.getOriginalFile();
173 final int startOffset = parameters.getOffset();
174 final PsiElement lastElement = file.findElementAt(startOffset - 1);
175 final JavaAwareCompletionData completionData = ApplicationManager.getApplication().runReadAction(new Computable<JavaAwareCompletionData>() {
176 public JavaAwareCompletionData compute() {
177 return getCompletionDataByElementInner(lastElement);
181 if (ANNOTATION_ATTRIBUTE_NAME.accepts(insertedElement)) {
182 ApplicationManager.getApplication().runReadAction(new Runnable() {
183 public void run() {
184 completeAnnotationAttributeName(_result, file, insertedElement, parameters);
187 _result.stopHere();
188 return;
191 final boolean checkAccess = parameters.getInvocationCount() == 1;
193 LegacyCompletionContributor.processReferences(parameters, _result, completionData, new PairConsumer<PsiReference, CompletionResultSet>() {
194 public void consume(final PsiReference reference, final CompletionResultSet result) {
195 ApplicationManager.getApplication().runReadAction(new Runnable() {
196 public void run() {
197 if (reference instanceof PsiJavaReference) {
198 final ElementFilter filter = getReferenceFilter(insertedElement);
199 if (filter != null) {
200 final boolean isSwitchLabel = SWITCH_LABEL.accepts(insertedElement);
201 for (LookupElement element : JavaCompletionUtil.processJavaReference(insertedElement,
202 (PsiJavaReference) reference,
203 new ElementExtractorFilter(filter),
204 checkAccess,
205 result.getPrefixMatcher(), parameters)) {
206 if (isSwitchLabel) {
207 result.addElement(TailTypeDecorator.withTail(element, TailType.createSimpleTailType(':')));
208 } else {
209 final LookupItem item = element.as(LookupItem.class);
210 if (file instanceof PsiJavaCodeReferenceCodeFragment &&
211 !((PsiJavaCodeReferenceCodeFragment)file).isClassesAccepted() && item != null) {
212 item.setTailType(TailType.NONE);
215 result.addElement(element);
219 return;
223 final Object[] variants = reference.getVariants();
224 if (variants == null) {
225 LOG.assertTrue(false, "Reference=" + reference);
227 for (Object completion : variants) {
228 if (completion == null) {
229 LOG.assertTrue(false, "Position=" + insertedElement + "\n;Reference=" + reference + "\n;variants=" + Arrays.toString(
230 variants));
232 if (completion instanceof LookupElement) {
233 result.addElement((LookupElement)completion);
234 } else {
235 result.addElement(LookupItemUtil.objectToLookupItem(completion));
243 final Set<LookupElement> lookupSet = new LinkedHashSet<LookupElement>();
244 final Set<CompletionVariant> keywordVariants = new HashSet<CompletionVariant>();
245 completionData.addKeywordVariants(keywordVariants, insertedElement, parameters.getOriginalFile());
246 final CompletionResultSet result = _result.withPrefixMatcher(completionData.findPrefix(insertedElement, startOffset));
247 completionData.completeKeywordsBySet(lookupSet, keywordVariants, insertedElement, result.getPrefixMatcher(), parameters.getOriginalFile());
249 ApplicationManager.getApplication().runReadAction(new Runnable() {
250 public void run() {
251 completionData.fillCompletions(parameters, result);
255 for (final LookupElement item : lookupSet) {
256 if (item instanceof LookupItem && ((LookupItem)item).getInsertHandler() == null) {
257 ((LookupItem)item).setInsertHandler(new InsertHandler() {
258 public void handleInsert(final InsertionContext context, final LookupElement item) {
259 analyzeItem((LookupItem)item, item.getObject(), parameters.getPosition());
260 new DefaultInsertHandler().handleInsert(context, item);
265 result.addElement(item);
267 result.stopHere();
270 private static void completeAnnotationAttributeName(CompletionResultSet result, PsiFile file, PsiElement insertedElement,
271 CompletionParameters parameters) {
272 PsiNameValuePair pair = PsiTreeUtil.getParentOfType(insertedElement, PsiNameValuePair.class);
273 PsiAnnotationParameterList parameterList = (PsiAnnotationParameterList)pair.getParent();
274 PsiAnnotation anno = (PsiAnnotation)parameterList.getParent();
275 boolean showClasses = psiElement().afterLeaf("(").accepts(insertedElement);
276 PsiClass annoClass = null;
277 final PsiJavaCodeReferenceElement referenceElement = anno.getNameReferenceElement();
278 if (referenceElement != null) {
279 final PsiElement element = referenceElement.resolve();
280 if (element instanceof PsiClass) {
281 annoClass = (PsiClass)element;
282 if (annoClass.findMethodsByName("value", false).length == 0) {
283 showClasses = false;
288 if (showClasses && insertedElement.getParent() instanceof PsiReferenceExpression) {
289 final Set<LookupElement> set = JavaCompletionUtil.processJavaReference(insertedElement, (PsiJavaReference)insertedElement.getParent(), TrueFilter.INSTANCE, true, result.getPrefixMatcher(), parameters);
291 for (final LookupElement element : set) {
292 result.addElement(element);
297 if (annoClass != null) {
298 final PsiNameValuePair[] existingPairs = parameterList.getAttributes();
300 methods: for (PsiMethod method : annoClass.getMethods()) {
301 final String attrName = method.getName();
302 for (PsiNameValuePair apair : existingPairs) {
303 if (Comparing.equal(apair.getName(), attrName)) continue methods;
305 result.addElement(new LookupItem<PsiMethod>(method, attrName).setInsertHandler(new InsertHandler<LookupElement>() {
306 public void handleInsert(InsertionContext context, LookupElement item) {
307 final Editor editor = context.getEditor();
308 TailType.EQ.processTail(editor, editor.getCaretModel().getOffset());
309 context.setAddCompletionChar(false);
311 }));
316 private static JavaAwareCompletionData getCompletionDataByElementInner(PsiElement element) {
317 return element != null && PsiUtil.isLanguageLevel5OrHigher(element) ? ourJava15CompletionData : ourJavaCompletionData;
321 public String advertise(@NotNull final CompletionParameters parameters) {
322 if (!(parameters.getOriginalFile() instanceof PsiJavaFile)) return null;
324 if (parameters.getCompletionType() != CompletionType.SMART && shouldSuggestSmartCompletion(parameters.getPosition())) {
325 if (CompletionUtil.shouldShowFeature(parameters, CodeCompletionFeatures.EDITING_COMPLETION_SMARTTYPE_GENERAL)) {
326 final String shortcut = getActionShortcut(IdeActions.ACTION_SMART_TYPE_COMPLETION);
327 if (shortcut != null) {
328 return CompletionBundle.message("completion.smart.hint", shortcut);
333 if (parameters.getCompletionType() != CompletionType.CLASS_NAME && shouldSuggestClassNameCompletion(parameters.getPosition())) {
334 if (CompletionUtil.shouldShowFeature(parameters, CodeCompletionFeatures.EDITING_COMPLETION_CLASSNAME)) {
335 final String shortcut = getActionShortcut(IdeActions.ACTION_CLASS_NAME_COMPLETION);
336 if (shortcut != null) {
337 return CompletionBundle.message("completion.class.name.hint", shortcut);
342 if (parameters.getCompletionType() == CompletionType.SMART && parameters.getInvocationCount() == 1) {
343 final PsiType[] psiTypes = ExpectedTypesGetter.getExpectedTypes(parameters.getPosition(), true);
344 if (psiTypes.length > 0) {
345 if (CompletionUtil.shouldShowFeature(parameters, JavaCompletionFeatures.SECOND_SMART_COMPLETION_TOAR)) {
346 final String shortcut = getActionShortcut(IdeActions.ACTION_SMART_TYPE_COMPLETION);
347 if (shortcut != null) {
348 for (final PsiType psiType : psiTypes) {
349 final PsiType type = PsiUtil.extractIterableTypeParameter(psiType, false);
350 if (type != null) {
351 return CompletionBundle.message("completion.smart.aslist.hint", shortcut, type.getPresentableText());
356 if (CompletionUtil.shouldShowFeature(parameters, JavaCompletionFeatures.SECOND_SMART_COMPLETION_ASLIST)) {
357 final String shortcut = getActionShortcut(IdeActions.ACTION_SMART_TYPE_COMPLETION);
358 if (shortcut != null) {
359 for (final PsiType psiType : psiTypes) {
360 if (psiType instanceof PsiArrayType) {
361 final PsiType componentType = ((PsiArrayType)psiType).getComponentType();
362 if (!(componentType instanceof PsiPrimitiveType)) {
363 return CompletionBundle.message("completion.smart.toar.hint", shortcut, componentType.getPresentableText());
370 if (CompletionUtil.shouldShowFeature(parameters, JavaCompletionFeatures.SECOND_SMART_COMPLETION_CHAIN)) {
371 final String shortcut = getActionShortcut(IdeActions.ACTION_SMART_TYPE_COMPLETION);
372 if (shortcut != null) {
373 return CompletionBundle.message("completion.smart.chain.hint", shortcut);
378 return null;
381 public String handleEmptyLookup(@NotNull final CompletionParameters parameters, final Editor editor) {
382 if (!(parameters.getOriginalFile() instanceof PsiJavaFile)) return null;
384 final String ad = advertise(parameters);
385 final String suffix = ad == null ? "" : "; " + StringUtil.decapitalize(ad);
386 if (parameters.getCompletionType() == CompletionType.SMART) {
387 if (!ApplicationManager.getApplication().isUnitTestMode()) {
389 final Project project = parameters.getPosition().getProject();
390 final PsiFile file = parameters.getOriginalFile();
392 PsiExpression expression = PsiTreeUtil.getContextOfType(parameters.getPosition(), PsiExpression.class, true);
393 if (expression != null && expression.getParent() instanceof PsiExpressionList) {
394 int lbraceOffset = expression.getParent().getTextRange().getStartOffset();
395 new ShowParameterInfoHandler().invoke(project, editor, file, lbraceOffset, null);
398 if (expression instanceof PsiLiteralExpression) {
399 return LangBundle.message("completion.no.suggestions") + suffix;
402 if (expression instanceof PsiInstanceOfExpression) {
403 final PsiInstanceOfExpression instanceOfExpression = (PsiInstanceOfExpression)expression;
404 if (PsiTreeUtil.isAncestor(instanceOfExpression.getCheckType(), parameters.getPosition(), false)) {
405 return LangBundle.message("completion.no.suggestions") + suffix;
410 final Set<PsiType> expectedTypes = JavaCompletionUtil.getExpectedTypes(parameters);
411 if (expectedTypes != null) {
412 PsiType type = expectedTypes.size() == 1 ? expectedTypes.iterator().next() : null;
413 if (type != null) {
414 final PsiType deepComponentType = type.getDeepComponentType();
415 if (deepComponentType instanceof PsiClassType) {
416 if (((PsiClassType)deepComponentType).resolve() != null) {
417 return CompletionBundle.message("completion.no.suggestions.of.type", type.getPresentableText()) + suffix;
419 return CompletionBundle.message("completion.unknown.type", type.getPresentableText()) + suffix;
421 if (!PsiType.NULL.equals(type)) {
422 return CompletionBundle.message("completion.no.suggestions.of.type", type.getPresentableText()) + suffix;
427 return LangBundle.message("completion.no.suggestions") + suffix;
430 private static boolean shouldSuggestSmartCompletion(final PsiElement element) {
431 if (shouldSuggestClassNameCompletion(element)) return false;
433 final PsiElement parent = element.getParent();
434 if (parent instanceof PsiReferenceExpression && ((PsiReferenceExpression)parent).getQualifier() != null) return false;
435 if (parent instanceof PsiReferenceExpression && parent.getParent() instanceof PsiReferenceExpression) return true;
437 return new ExpectedTypesGetter().get(element, null).length > 0;
440 private static boolean shouldSuggestClassNameCompletion(final PsiElement element) {
441 if (element == null) return false;
442 final PsiElement parent = element.getParent();
443 if (parent == null) return false;
444 return parent.getParent() instanceof PsiTypeElement || parent.getParent() instanceof PsiExpressionStatement || parent.getParent() instanceof PsiReferenceList;
447 public static void analyzeItem(final LookupItem item, final Object completion, final PsiElement position) {
448 if(completion instanceof PsiKeyword){
449 if(PsiKeyword.BREAK.equals(((PsiKeyword)completion).getText())
450 || PsiKeyword.CONTINUE.equals(((PsiKeyword)completion).getText())){
451 PsiElement scope = position;
452 while(true){
453 if (scope instanceof PsiFile
454 || scope instanceof PsiMethod
455 || scope instanceof PsiClassInitializer){
456 item.setTailType(TailType.SEMICOLON);
457 break;
459 else if (scope instanceof PsiLabeledStatement){
460 item.setTailType(TailType.NONE);
461 break;
463 scope = scope.getParent();
466 if(PsiKeyword.RETURN.equals(((PsiKeyword)completion).getText())){
467 PsiElement scope = position;
468 while(true){
469 if (scope instanceof PsiFile
470 || scope instanceof PsiClassInitializer){
471 item.setTailType(TailType.NONE);
472 break;
474 else if (scope instanceof PsiMethod){
475 final PsiMethod method = (PsiMethod)scope;
476 if(method.isConstructor() || PsiType.VOID.equals(method.getReturnType())) {
477 item.setTailType(TailType.SEMICOLON);
479 else item.setTailType(TailType.SPACE);
481 break;
483 scope = scope.getParent();
486 if(PsiKeyword.SYNCHRONIZED.equals(((PsiKeyword)completion).getText())){
487 if (PsiTreeUtil.getParentOfType(position, PsiMember.class, PsiCodeBlock.class) instanceof PsiCodeBlock){
488 item.setTailType(TailTypes.SYNCHRONIZED_LPARENTH);
495 public void beforeCompletion(@NotNull final CompletionInitializationContext context) {
496 final PsiFile file = context.getFile();
497 final Project project = context.getProject();
499 JavaCompletionUtil.initOffsets(file, project, context.getOffsetMap());
501 if (file instanceof PsiJavaFile) {
502 autoImport(file, context.getStartOffset() - 1, context.getEditor());
505 if (context.getCompletionType() == CompletionType.BASIC && file instanceof PsiJavaFile) {
506 if (semicolonNeeded(context)) {
507 context.setFileCopyPatcher(new DummyIdentifierPatcher(CompletionInitializationContext.DUMMY_IDENTIFIER.trim() + ";"));
508 return;
511 final PsiElement element = file.findElementAt(context.getStartOffset());
513 if (psiElement().inside(PsiAnnotation.class).accepts(element)) {
514 return;
517 context.setFileCopyPatcher(new DummyIdentifierPatcher(CompletionInitializationContext.DUMMY_IDENTIFIER.trim()));
521 private static boolean semicolonNeeded(CompletionInitializationContext context) {
522 HighlighterIterator iterator = ((EditorEx) context.getEditor()).getHighlighter().createIterator(context.getStartOffset());
523 if (iterator.atEnd()) return false;
525 if (iterator.getTokenType() == JavaTokenType.IDENTIFIER) {
526 iterator.advance();
529 if (!iterator.atEnd() && iterator.getTokenType() == JavaTokenType.LPARENTH) {
530 return true;
533 while (!iterator.atEnd() && JavaTokenType.WHITE_SPACE_OR_COMMENT_BIT_SET.contains(iterator.getTokenType())) {
534 iterator.advance();
537 if (iterator.atEnd() || iterator.getTokenType() != JavaTokenType.IDENTIFIER) return false;
538 iterator.advance();
540 while (!iterator.atEnd() && JavaTokenType.WHITE_SPACE_OR_COMMENT_BIT_SET.contains(iterator.getTokenType())) {
541 iterator.advance();
543 if (iterator.atEnd()) return false;
545 return iterator.getTokenType() == JavaTokenType.EQ || iterator.getTokenType() == JavaTokenType.LPARENTH;
548 private static void autoImport(final PsiFile file, int offset, final Editor editor) {
549 final CharSequence text = editor.getDocument().getCharsSequence();
550 while (offset > 0 && Character.isJavaIdentifierPart(text.charAt(offset))) offset--;
551 if (offset <= 0) return;
553 while (offset > 0 && Character.isWhitespace(text.charAt(offset))) offset--;
554 if (offset <= 0 || text.charAt(offset) != '.') return;
556 offset--;
558 while (offset > 0 && Character.isWhitespace(text.charAt(offset))) offset--;
559 if (offset <= 0) return;
561 PsiJavaCodeReferenceElement element = extractReference(PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiExpression.class, false));
562 if (element == null) return;
564 while (true) {
565 final PsiJavaCodeReferenceElement qualifier = extractReference(element.getQualifier());
566 if (qualifier == null) break;
568 element = qualifier;
570 if (!(element.getParent() instanceof PsiMethodCallExpression) && element.multiResolve(true).length == 0) {
571 new ImportClassFix(element).doFix(editor, false, false);
575 @Nullable
576 private static PsiJavaCodeReferenceElement extractReference(@Nullable PsiElement expression) {
577 if (expression instanceof PsiJavaCodeReferenceElement) {
578 return (PsiJavaCodeReferenceElement)expression;
580 if (expression instanceof PsiMethodCallExpression) {
581 return ((PsiMethodCallExpression)expression).getMethodExpression();
583 return null;