| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.codeInsight.completion; |
| |
| import com.intellij.codeInsight.TailType; |
| import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler; |
| import com.intellij.codeInsight.lookup.*; |
| import com.intellij.codeInsight.template.impl.TemplateManagerImpl; |
| import com.intellij.featureStatistics.FeatureUsageTracker; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.patterns.ElementPattern; |
| import com.intellij.patterns.PsiJavaPatterns; |
| import com.intellij.pom.java.LanguageLevel; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.JavaCodeStyleManager; |
| import com.intellij.psi.codeStyle.SuggestedNameInfo; |
| import com.intellij.psi.codeStyle.VariableKind; |
| import com.intellij.psi.util.PropertyUtil; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.psi.util.PsiUtilCore; |
| import com.intellij.refactoring.introduceField.InplaceIntroduceFieldPopup; |
| import com.intellij.refactoring.introduceVariable.IntroduceVariableBase; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.PlatformIcons; |
| import com.intellij.util.containers.ContainerUtil; |
| import gnu.trove.THashSet; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import static com.intellij.patterns.PlatformPatterns.psiElement; |
| import static com.intellij.patterns.PsiJavaPatterns.psiClass; |
| import static com.intellij.patterns.PsiJavaPatterns.psiField; |
| import static com.intellij.patterns.StandardPatterns.or; |
| |
| /** |
| * @author peter |
| */ |
| public class JavaMemberNameCompletionContributor extends CompletionContributor { |
| public static final ElementPattern<PsiElement> INSIDE_TYPE_PARAMS_PATTERN = psiElement(). |
| afterLeaf(psiElement().withText("?").andOr( |
| psiElement().afterLeaf("<", ","), |
| psiElement().afterSiblingSkipping(psiElement().whitespaceCommentEmptyOrError(), psiElement(PsiAnnotation.class)))); |
| |
| static final int MAX_SCOPE_SIZE_TO_SEARCH_UNRESOLVED = 50000; |
| |
| @Override |
| public void fillCompletionVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) { |
| if (parameters.getCompletionType() != CompletionType.BASIC && parameters.getCompletionType() != CompletionType.SMART) { |
| return; |
| } |
| |
| if (parameters.getInvocationCount() == 0 && TemplateManagerImpl.getTemplateState(parameters.getEditor()) != null) { |
| return; |
| } |
| |
| PsiElement position = parameters.getPosition(); |
| final Set<LookupElement> lookupSet = new THashSet<LookupElement>(); |
| if (psiElement(PsiIdentifier.class).andNot(INSIDE_TYPE_PARAMS_PATTERN).withParent( |
| or(psiElement(PsiLocalVariable.class), psiElement(PsiParameter.class))).accepts(position)) { |
| completeLocalVariableName(lookupSet, result.getPrefixMatcher(), (PsiVariable)parameters.getPosition().getParent(), |
| parameters.getInvocationCount() >= 1); |
| for (final LookupElement item : lookupSet) { |
| if (item instanceof LookupItem) { |
| ((LookupItem)item).setAutoCompletionPolicy(AutoCompletionPolicy.GIVE_CHANCE_TO_OVERWRITE); |
| } |
| } |
| } |
| |
| if (psiElement(PsiIdentifier.class).withParent(PsiField.class).andNot(INSIDE_TYPE_PARAMS_PATTERN).accepts(position)) { |
| final PsiField variable = (PsiField)parameters.getPosition().getParent(); |
| completeMethodName(lookupSet, variable, result.getPrefixMatcher()); |
| completeFieldName(lookupSet, variable, result.getPrefixMatcher(), parameters.getInvocationCount() >= 1); |
| } |
| |
| if (PsiJavaPatterns.psiElement().nameIdentifierOf(PsiJavaPatterns.psiMethod().withParent(PsiClass.class)).accepts(position)) { |
| completeMethodName(lookupSet, parameters.getPosition().getParent(), result.getPrefixMatcher()); |
| } |
| |
| for (final LookupElement item : lookupSet) { |
| result.addElement(item); |
| } |
| } |
| |
| private static void completeLocalVariableName(Set<LookupElement> set, PrefixMatcher matcher, PsiVariable var, boolean includeOverlapped) { |
| FeatureUsageTracker.getInstance().triggerFeatureUsed("editing.completion.variable.name"); |
| Project project = var.getProject(); |
| final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); |
| final VariableKind variableKind = codeStyleManager.getVariableKind(var); |
| |
| String propertyName = null; |
| if (variableKind == VariableKind.PARAMETER) { |
| final PsiMethod method = PsiTreeUtil.getParentOfType(var, PsiMethod.class); |
| if (method != null) { |
| propertyName = PropertyUtil.getPropertyName(method); |
| } |
| if (method != null && method.getName().startsWith("with")) { |
| propertyName = StringUtil.decapitalize(method.getName().substring(4)); |
| } |
| } |
| |
| final PsiType type = var.getType(); |
| SuggestedNameInfo suggestedNameInfo = codeStyleManager.suggestVariableName(variableKind, propertyName, null, type, StringUtil.isEmpty(matcher.getPrefix())); |
| suggestedNameInfo = codeStyleManager.suggestUniqueVariableName(suggestedNameInfo, var, false); |
| final String[] suggestedNames = suggestedNameInfo.names; |
| addLookupItems(set, suggestedNameInfo, matcher, project, suggestedNames); |
| if (!hasStartMatches(set, matcher)) { |
| if (type.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) && matcher.prefixMatches("object")) { |
| set.add(withInsertHandler(suggestedNameInfo, LookupElementBuilder.create("object"))); |
| } |
| if (type.equalsToText(CommonClassNames.JAVA_LANG_STRING) && matcher.prefixMatches("string")) { |
| set.add(withInsertHandler(suggestedNameInfo, LookupElementBuilder.create("string"))); |
| } |
| } |
| |
| if (!hasStartMatches(set, matcher) && includeOverlapped) { |
| addLookupItems(set, null, matcher, project, getOverlappedNameVersions(matcher.getPrefix(), suggestedNames, "")); |
| } |
| PsiElement parent = PsiTreeUtil.getParentOfType(var, PsiCodeBlock.class); |
| if(parent == null) parent = PsiTreeUtil.getParentOfType(var, PsiMethod.class, PsiLambdaExpression.class); |
| addLookupItems(set, suggestedNameInfo, matcher, project, getUnresolvedReferences(parent, false)); |
| if (var instanceof PsiParameter && parent instanceof PsiMethod) { |
| addSuggestionsInspiredByFieldNames(set, matcher, var, project, codeStyleManager); |
| } |
| |
| PsiExpression initializer = var.getInitializer(); |
| if (initializer != null) { |
| SuggestedNameInfo initializerSuggestions = IntroduceVariableBase.getSuggestedName(type, initializer); |
| addLookupItems(set, initializerSuggestions, matcher, project, initializerSuggestions.names); |
| } |
| } |
| |
| private static boolean hasStartMatches(PrefixMatcher matcher, Set<String> set) { |
| for (String s : set) { |
| if (matcher.isStartMatch(s)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| private static boolean hasStartMatches(Set<LookupElement> set, PrefixMatcher matcher) { |
| for (LookupElement lookupElement : set) { |
| if (matcher.isStartMatch(lookupElement)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static void addSuggestionsInspiredByFieldNames(Set<LookupElement> set, |
| PrefixMatcher matcher, |
| PsiVariable var, |
| Project project, |
| JavaCodeStyleManager codeStyleManager) { |
| PsiClass psiClass = PsiTreeUtil.getParentOfType(var, PsiClass.class); |
| if (psiClass == null) { |
| return; |
| } |
| |
| for (PsiField field : psiClass.getFields()) { |
| if (field.getType().isAssignableFrom(var.getType())) { |
| String prop = codeStyleManager.variableNameToPropertyName(field.getName(), VariableKind.FIELD); |
| addLookupItems(set, null, matcher, project, codeStyleManager.propertyNameToVariableName(prop, VariableKind.PARAMETER)); |
| } |
| } |
| } |
| |
| private static String[] getOverlappedNameVersions(final String prefix, final String[] suggestedNames, String suffix) { |
| final List<String> newSuggestions = new ArrayList<String>(); |
| int longestOverlap = 0; |
| |
| for (String suggestedName : suggestedNames) { |
| if (suggestedName.length() < 3) { |
| continue; |
| } |
| |
| if (suggestedName.toUpperCase().startsWith(prefix.toUpperCase())) { |
| newSuggestions.add(suggestedName); |
| longestOverlap = prefix.length(); |
| } |
| |
| suggestedName = String.valueOf(Character.toUpperCase(suggestedName.charAt(0))) + suggestedName.substring(1); |
| final int overlap = getOverlap(suggestedName, prefix); |
| |
| if (overlap < longestOverlap) continue; |
| |
| if (overlap > longestOverlap) { |
| newSuggestions.clear(); |
| longestOverlap = overlap; |
| } |
| |
| String suggestion = prefix.substring(0, prefix.length() - overlap) + suggestedName; |
| |
| final int lastIndexOfSuffix = suggestion.lastIndexOf(suffix); |
| if (lastIndexOfSuffix >= 0 && suffix.length() < suggestion.length() - lastIndexOfSuffix) { |
| suggestion = suggestion.substring(0, lastIndexOfSuffix) + suffix; |
| } |
| |
| if (!newSuggestions.contains(suggestion)) { |
| newSuggestions.add(suggestion); |
| } |
| } |
| return ArrayUtil.toStringArray(newSuggestions); |
| } |
| |
| private static int getOverlap(final String propertyName, final String prefix) { |
| int overlap = 0; |
| int propertyNameLen = propertyName.length(); |
| int prefixLen = prefix.length(); |
| for (int j = 1; j < prefixLen && j < propertyNameLen; j++) { |
| if (prefix.substring(prefixLen - j).equals(propertyName.substring(0, j))) { |
| overlap = j; |
| } |
| } |
| return overlap; |
| } |
| |
| private static String[] getUnresolvedReferences(final PsiElement parentOfType, final boolean referenceOnMethod) { |
| if (parentOfType != null && parentOfType.getTextLength() > MAX_SCOPE_SIZE_TO_SEARCH_UNRESOLVED) return ArrayUtil.EMPTY_STRING_ARRAY; |
| final Set<String> unresolvedRefs = new LinkedHashSet<String>(); |
| |
| if (parentOfType != null) { |
| parentOfType.accept(new JavaRecursiveElementWalkingVisitor() { |
| @Override public void visitReferenceExpression(PsiReferenceExpression reference) { |
| final PsiElement parent = reference.getParent(); |
| if (parent instanceof PsiReference) return; |
| if (referenceOnMethod && parent instanceof PsiMethodCallExpression && |
| reference == ((PsiMethodCallExpression)parent).getMethodExpression()) { |
| if (reference.resolve() == null) { |
| ContainerUtil.addIfNotNull(unresolvedRefs, reference.getReferenceName()); |
| } |
| } |
| else if (!referenceOnMethod && !(parent instanceof PsiMethodCallExpression) &&reference.resolve() == null) { |
| ContainerUtil.addIfNotNull(unresolvedRefs, reference.getReferenceName()); |
| } |
| } |
| }); |
| } |
| return ArrayUtil.toStringArray(unresolvedRefs); |
| } |
| |
| private static void completeFieldName(Set<LookupElement> set, PsiField var, final PrefixMatcher matcher, boolean includeOverlapped) { |
| FeatureUsageTracker.getInstance().triggerFeatureUsed("editing.completion.variable.name"); |
| |
| Project project = var.getProject(); |
| JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); |
| final VariableKind variableKind = JavaCodeStyleManager.getInstance(project).getVariableKind(var); |
| |
| final String prefix = matcher.getPrefix(); |
| if (PsiType.VOID.equals(var.getType()) || psiField().inClass(psiClass().isInterface().andNot(psiClass().isAnnotationType())).accepts(var)) { |
| completeVariableNameForRefactoring(project, set, matcher, var.getType(), variableKind, includeOverlapped, true); |
| return; |
| } |
| |
| SuggestedNameInfo suggestedNameInfo = codeStyleManager.suggestVariableName(variableKind, null, null, var.getType()); |
| final String[] suggestedNames = suggestedNameInfo.names; |
| addLookupItems(set, suggestedNameInfo, matcher, project, suggestedNames); |
| |
| if (!hasStartMatches(set, matcher) && includeOverlapped) { |
| // use suggested names as suffixes |
| final String requiredSuffix = codeStyleManager.getSuffixByVariableKind(variableKind); |
| if(variableKind != VariableKind.STATIC_FINAL_FIELD){ |
| for (int i = 0; i < suggestedNames.length; i++) |
| suggestedNames[i] = codeStyleManager.variableNameToPropertyName(suggestedNames[i], variableKind); |
| } |
| |
| |
| addLookupItems(set, null, matcher, project, getOverlappedNameVersions(prefix, suggestedNames, requiredSuffix)); |
| } |
| |
| addLookupItems(set, suggestedNameInfo, matcher, project, getUnresolvedReferences(var.getParent(), false)); |
| |
| PsiExpression initializer = var.getInitializer(); |
| PsiClass containingClass = var.getContainingClass(); |
| if (initializer != null && containingClass != null) { |
| SuggestedNameInfo initializerSuggestions = InplaceIntroduceFieldPopup. |
| suggestFieldName(var.getType(), null, initializer, var.hasModifierProperty(PsiModifier.STATIC), containingClass); |
| addLookupItems(set, initializerSuggestions, matcher, project, initializerSuggestions.names); |
| } |
| } |
| |
| public static void completeVariableNameForRefactoring(Project project, |
| Set<LookupElement> set, |
| PrefixMatcher matcher, |
| PsiType varType, |
| VariableKind varKind, final boolean includeOverlapped, final boolean methodPrefix) { |
| JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); |
| SuggestedNameInfo suggestedNameInfo = codeStyleManager.suggestVariableName(varKind, null, null, varType); |
| final String[] strings = completeVariableNameForRefactoring(codeStyleManager, matcher, varType, varKind, suggestedNameInfo, |
| includeOverlapped, methodPrefix); |
| addLookupItems(set, suggestedNameInfo, matcher, project, strings); |
| } |
| |
| public static String[] completeVariableNameForRefactoring(JavaCodeStyleManager codeStyleManager, |
| final PrefixMatcher matcher, |
| @Nullable final PsiType varType, |
| final VariableKind varKind, |
| SuggestedNameInfo suggestedNameInfo, |
| final boolean includeOverlapped, final boolean methodPrefix) { |
| Set<String> result = new LinkedHashSet<String>(); |
| final String[] suggestedNames = suggestedNameInfo.names; |
| for (final String suggestedName : suggestedNames) { |
| if (matcher.prefixMatches(suggestedName)) { |
| result.add(suggestedName); |
| } |
| } |
| |
| if (!hasStartMatches(matcher, result) && PsiType.VOID != varType && includeOverlapped) { |
| // use suggested names as suffixes |
| final String requiredSuffix = codeStyleManager.getSuffixByVariableKind(varKind); |
| final String prefix = matcher.getPrefix(); |
| if (varKind != VariableKind.STATIC_FINAL_FIELD || methodPrefix) { |
| for (int i = 0; i < suggestedNames.length; i++) { |
| suggestedNames[i] = codeStyleManager.variableNameToPropertyName(suggestedNames[i], varKind); |
| } |
| } |
| |
| ContainerUtil.addAll(result, getOverlappedNameVersions(prefix, suggestedNames, requiredSuffix)); |
| |
| |
| } |
| return ArrayUtil.toStringArray(result); |
| } |
| |
| private static void completeMethodName(Set<LookupElement> set, PsiElement element, final PrefixMatcher matcher){ |
| if(element instanceof PsiMethod) { |
| final PsiMethod method = (PsiMethod)element; |
| if (method.isConstructor()) { |
| final PsiClass containingClass = method.getContainingClass(); |
| if (containingClass != null) { |
| final String name = containingClass.getName(); |
| if (StringUtil.isNotEmpty(name)) { |
| addLookupItems(set, null, matcher, element.getProject(), name); |
| } |
| } |
| return; |
| } |
| } |
| |
| PsiClass ourClassParent = PsiTreeUtil.getParentOfType(element, PsiClass.class); |
| if (ourClassParent == null) return; |
| |
| if (ourClassParent.isAnnotationType() && matcher.prefixMatches(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME)) { |
| set.add(LookupElementBuilder.create(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME) |
| .withIcon(PlatformIcons.METHOD_ICON) |
| .withTailText("()") |
| .withInsertHandler(ParenthesesInsertHandler.NO_PARAMETERS)); |
| } |
| |
| addLookupItems(set, null, matcher, element.getProject(), getUnresolvedReferences(ourClassParent, true)); |
| |
| addLookupItems(set, null, matcher, element.getProject(), getPropertiesHandlersNames( |
| ourClassParent, |
| ((PsiModifierListOwner)element).hasModifierProperty(PsiModifier.STATIC), |
| PsiUtil.getTypeByPsiElement(element), element)); |
| } |
| |
| private static String[] getPropertiesHandlersNames(final PsiClass psiClass, |
| final boolean staticContext, |
| final PsiType varType, |
| final PsiElement element) { |
| final List<String> propertyHandlers = new ArrayList<String>(); |
| |
| for (final PsiField field : psiClass.getFields()) { |
| if (field == element) continue; |
| |
| PsiUtilCore.ensureValid(field); |
| PsiType fieldType = field.getType(); |
| PsiUtil.ensureValidType(fieldType); |
| |
| final PsiModifierList modifierList = field.getModifierList(); |
| if (staticContext && (modifierList != null && !modifierList.hasModifierProperty(PsiModifier.STATIC))) continue; |
| |
| if (fieldType.equals(varType)) { |
| final String getterName = PropertyUtil.suggestGetterName(field); |
| if ((psiClass.findMethodsByName(getterName, true).length == 0 || |
| psiClass.findMethodBySignature(PropertyUtil.generateGetterPrototype(field), true) == null)) { |
| propertyHandlers.add(getterName); |
| } |
| } |
| |
| if (PsiType.VOID.equals(varType)) { |
| final String setterName = PropertyUtil.suggestSetterName(field); |
| if ((psiClass.findMethodsByName(setterName, true).length == 0 || |
| psiClass.findMethodBySignature(PropertyUtil.generateSetterPrototype(field), true) == null)) { |
| propertyHandlers.add(setterName); |
| } |
| } |
| } |
| |
| return ArrayUtil.toStringArray(propertyHandlers); |
| } |
| |
| private static void addLookupItems(Set<LookupElement> lookupElements, @Nullable final SuggestedNameInfo callback, PrefixMatcher matcher, Project project, String... strings) { |
| outer: |
| for (int i = 0; i < strings.length; i++) { |
| String name = strings[i]; |
| if (!matcher.prefixMatches(name) || !PsiNameHelper.getInstance(project).isIdentifier(name, LanguageLevel.HIGHEST)) { |
| continue; |
| } |
| |
| for (LookupElement lookupElement : lookupElements) { |
| if (lookupElement.getAllLookupStrings().contains(name)) { |
| continue outer; |
| } |
| } |
| |
| LookupElement element = PrioritizedLookupElement.withPriority(LookupElementBuilder.create(name).withAutoCompletionPolicy(AutoCompletionPolicy.GIVE_CHANCE_TO_OVERWRITE), -i); |
| if (callback != null) { |
| element = withInsertHandler(callback, element); |
| } |
| lookupElements.add(element); |
| } |
| } |
| |
| private static LookupElementDecorator<LookupElement> withInsertHandler(final SuggestedNameInfo callback, LookupElement element) { |
| return LookupElementDecorator.withInsertHandler(element, new InsertHandler<LookupElementDecorator<LookupElement>>() { |
| @Override |
| public void handleInsert(InsertionContext context, LookupElementDecorator<LookupElement> item) { |
| TailType tailType = LookupItem.getDefaultTailType(context.getCompletionChar()); |
| if (tailType != null) { |
| context.setAddCompletionChar(false); |
| tailType.processTail(context.getEditor(), context.getTailOffset()); |
| } |
| callback.nameChosen(item.getLookupString()); |
| } |
| }); |
| } |
| } |