update copyright
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInsight / completion / JavaCompletionUtil.java
blob24c582f6457e31f769e96bdee1d8dac0f4e3e03a
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.CodeInsightSettings;
19 import com.intellij.codeInsight.ExpectedTypeInfo;
20 import com.intellij.codeInsight.ExpectedTypeInfoImpl;
21 import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
22 import com.intellij.codeInsight.completion.scope.CompletionElement;
23 import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
24 import com.intellij.codeInsight.generation.OverrideImplementUtil;
25 import com.intellij.codeInsight.guess.GuessManager;
26 import com.intellij.codeInsight.lookup.*;
27 import com.intellij.featureStatistics.FeatureUsageTracker;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.editor.Document;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.util.*;
32 import com.intellij.openapi.util.text.StringUtil;
33 import static com.intellij.patterns.PlatformPatterns.psiElement;
34 import com.intellij.patterns.PsiElementPattern;
35 import com.intellij.patterns.PsiJavaPatterns;
36 import com.intellij.psi.*;
37 import com.intellij.psi.codeStyle.*;
38 import com.intellij.psi.filters.ElementFilter;
39 import com.intellij.psi.filters.AndFilter;
40 import com.intellij.psi.filters.ClassFilter;
41 import com.intellij.psi.filters.element.ExcludeDeclaredFilter;
42 import com.intellij.psi.filters.element.ExcludeSillyAssignment;
43 import com.intellij.psi.html.HtmlTag;
44 import com.intellij.psi.impl.source.PsiImmediateClassType;
45 import com.intellij.psi.infos.CandidateInfo;
46 import com.intellij.psi.infos.ClassCandidateInfo;
47 import com.intellij.psi.javadoc.PsiDocToken;
48 import com.intellij.psi.scope.BaseScopeProcessor;
49 import com.intellij.psi.scope.ElementClassHint;
50 import com.intellij.psi.scope.NameHint;
51 import com.intellij.psi.scope.ElementClassFilter;
52 import com.intellij.psi.statistics.JavaStatisticsManager;
53 import com.intellij.psi.tree.IElementType;
54 import com.intellij.psi.util.*;
55 import com.intellij.psi.xml.XmlToken;
56 import com.intellij.psi.xml.XmlTokenType;
57 import com.intellij.util.ArrayUtil;
58 import com.intellij.util.NullableFunction;
59 import com.intellij.util.PairFunction;
60 import com.intellij.util.containers.HashMap;
61 import gnu.trove.THashSet;
62 import org.jetbrains.annotations.NonNls;
63 import org.jetbrains.annotations.NotNull;
64 import org.jetbrains.annotations.Nullable;
66 import java.util.*;
68 public class JavaCompletionUtil {
69 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.JavaCompletionUtil");
70 public static final Key<PairFunction<PsiExpression, CompletionParameters, PsiType>> DYNAMIC_TYPE_EVALUATOR = Key.create("DYNAMIC_TYPE_EVALUATOR");
72 static final Key<PsiType> QUALIFIER_TYPE_ATTR = Key.create("qualifierType"); // SmartPsiElementPointer to PsiType of "qualifier"
73 @NonNls
74 public static final String GET_PREFIX = "get";
75 @NonNls
76 public static final String SET_PREFIX = "set";
77 @NonNls
78 public static final String IS_PREFIX = "is";
79 private static final int MAX_SCOPE_SIZE_TO_SEARCH_UNRESOLVED = 50000;
80 public static final OffsetKey LPAREN_OFFSET = OffsetKey.create("lparen");
81 public static final OffsetKey RPAREN_OFFSET = OffsetKey.create("rparen");
82 public static final OffsetKey ARG_LIST_END_OFFSET = OffsetKey.create("argListEnd");
83 static final NullableLazyKey<ExpectedTypeInfo[], CompletionLocation> EXPECTED_TYPES = NullableLazyKey.create("expectedTypes", new NullableFunction<CompletionLocation, ExpectedTypeInfo[]>() {
84 @Nullable
85 public ExpectedTypeInfo[] fun(final CompletionLocation location) {
86 return JavaSmartCompletionContributor.getExpectedTypes(location.getCompletionParameters());
88 });
89 private static final PsiElementPattern.Capture<PsiElement> LEFT_PAREN = psiElement(JavaTokenType.LPARENTH).andOr(psiElement().withParent(
90 PsiExpressionList.class), psiElement().afterLeaf(".", PsiKeyword.NEW));
92 public static final Key<Boolean> SUPER_METHOD_PARAMETERS = Key.create("SUPER_METHOD_PARAMETERS");
94 @Nullable
95 public static Set<PsiType> getExpectedTypes(final CompletionParameters parameters) {
96 final PsiExpression expr = PsiTreeUtil.getContextOfType(parameters.getPosition(), PsiExpression.class, true);
97 if (expr != null) {
98 final ExpectedTypeInfo[] expectedInfos = JavaSmartCompletionContributor.getExpectedTypes(parameters);
99 if(expectedInfos != null){
100 final Set<PsiType> set = new THashSet<PsiType>();
101 for (final ExpectedTypeInfo expectedInfo : expectedInfos) {
102 set.add(expectedInfo.getType());
104 return set;
107 return null;
110 static final NullableLazyKey<PsiMethod, CompletionLocation> POSITION_METHOD = NullableLazyKey.create("positionMethod", new NullableFunction<CompletionLocation, PsiMethod>() {
111 public PsiMethod fun(final CompletionLocation location) {
112 return PsiTreeUtil.getParentOfType(location.getCompletionParameters().getPosition(), PsiMethod.class, false);
115 public static final Key<List<PsiMethod>> ALL_METHODS_ATTRIBUTE = Key.create("allMethods");
117 public static void completeLocalVariableName(Set<LookupElement> set, PrefixMatcher matcher, PsiVariable var){
118 FeatureUsageTracker.getInstance().triggerFeatureUsed("editing.completion.variable.name");
119 final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(var.getProject());
120 final VariableKind variableKind = codeStyleManager.getVariableKind(var);
122 String propertyName = null;
123 if (variableKind == VariableKind.PARAMETER) {
124 final PsiMethod method = PsiTreeUtil.getParentOfType(var, PsiMethod.class);
125 propertyName = PropertyUtil.getPropertyName(method);
128 final PsiType type = var.getType();
129 SuggestedNameInfo suggestedNameInfo = codeStyleManager.suggestVariableName(variableKind, propertyName, null, type);
130 final String[] suggestedNames = suggestedNameInfo.names;
131 tunePreferencePolicy(LookupItemUtil.addLookupItems(set, suggestedNames, matcher), suggestedNameInfo);
132 if (set.isEmpty()) {
133 if (type.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) && matcher.prefixMatches("object")) {
134 set.add(LookupElementBuilder.create("object"));
136 if (type.equalsToText(CommonClassNames.JAVA_LANG_STRING) && matcher.prefixMatches("string")) {
137 set.add(LookupElementBuilder.create("string"));
141 if (set.isEmpty()) {
142 suggestedNameInfo = new SuggestedNameInfo(getOverlappedNameVersions(matcher.getPrefix(), suggestedNames, "")) {
143 public void nameChoosen(String name) {
147 tunePreferencePolicy(LookupItemUtil.addLookupItems(set, suggestedNameInfo.names, matcher), suggestedNameInfo);
149 PsiElement parent = PsiTreeUtil.getParentOfType(var, PsiCodeBlock.class);
150 if(parent == null) parent = PsiTreeUtil.getParentOfType(var, PsiMethod.class);
151 tunePreferencePolicy(LookupItemUtil.addLookupItems(set, getUnresolvedReferences(parent, false), matcher), suggestedNameInfo);
152 final String[] nameSuggestions =
153 JavaStatisticsManager.getNameSuggestions(type, JavaStatisticsManager.getContext(var), matcher.getPrefix());
154 tunePreferencePolicy(LookupItemUtil.addLookupItems(set, nameSuggestions, matcher), suggestedNameInfo);
157 public static void completeFieldName(Set<LookupElement> set, PsiVariable var, final PrefixMatcher matcher){
158 FeatureUsageTracker.getInstance().triggerFeatureUsed("editing.completion.variable.name");
160 JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(var.getProject());
161 final VariableKind variableKind = JavaCodeStyleManager.getInstance(var.getProject()).getVariableKind(var);
163 final String prefix = matcher.getPrefix();
164 if (PsiType.VOID.equals(var.getType()) || prefix.startsWith(IS_PREFIX) ||
165 prefix.startsWith(GET_PREFIX) ||
166 prefix.startsWith(SET_PREFIX)) {
167 completeVariableNameForRefactoring(var.getProject(), set, matcher, var.getType(), variableKind);
168 return;
171 SuggestedNameInfo suggestedNameInfo = codeStyleManager.suggestVariableName(variableKind, null, null, var.getType());
172 final String[] suggestedNames = suggestedNameInfo.names;
173 tunePreferencePolicy(LookupItemUtil.addLookupItems(set, suggestedNames, matcher), suggestedNameInfo);
175 if (set.isEmpty()) {
176 // use suggested names as suffixes
177 final String requiredSuffix = codeStyleManager.getSuffixByVariableKind(variableKind);
178 if(variableKind != VariableKind.STATIC_FINAL_FIELD){
179 for (int i = 0; i < suggestedNames.length; i++)
180 suggestedNames[i] = codeStyleManager.variableNameToPropertyName(suggestedNames[i], variableKind);
184 suggestedNameInfo = new SuggestedNameInfo(getOverlappedNameVersions(prefix, suggestedNames, requiredSuffix)) {
185 public void nameChoosen(String name) {
189 tunePreferencePolicy(LookupItemUtil.addLookupItems(set, suggestedNameInfo.names, matcher), suggestedNameInfo);
192 tunePreferencePolicy(LookupItemUtil.addLookupItems(set, JavaStatisticsManager.getNameSuggestions(var.getType(), JavaStatisticsManager.getContext(var), matcher.getPrefix()), matcher), suggestedNameInfo);
193 tunePreferencePolicy(LookupItemUtil.addLookupItems(set, getUnresolvedReferences(var.getParent(), false),
194 matcher), suggestedNameInfo);
197 public static void completeMethodName(Set<LookupElement> set, PsiElement element, final PrefixMatcher matcher){
198 if(element instanceof PsiMethod) {
199 final PsiMethod method = (PsiMethod)element;
200 if (method.isConstructor()) {
201 final PsiClass containingClass = method.getContainingClass();
202 final String name = containingClass.getName();
203 if (StringUtil.isNotEmpty(name)) {
204 LookupItemUtil.addLookupItem(set, name, matcher);
206 return;
210 PsiClass ourClassParent = PsiTreeUtil.getParentOfType(element, PsiClass.class);
211 if (ourClassParent == null) return;
212 LookupItemUtil.addLookupItems(set, getUnresolvedReferences(ourClassParent, true), matcher);
214 if(!((PsiModifierListOwner)element).hasModifierProperty(PsiModifier.PRIVATE)){
215 LookupItemUtil.addLookupItems(set, getOverides(ourClassParent, PsiUtil.getTypeByPsiElement(element)),
216 matcher);
217 LookupItemUtil.addLookupItems(set, getImplements(ourClassParent, PsiUtil.getTypeByPsiElement(element)),
218 matcher);
220 LookupItemUtil.addLookupItems(set, getPropertiesHandlersNames(
221 ourClassParent,
222 ((PsiModifierListOwner)element).hasModifierProperty(PsiModifier.STATIC),
223 PsiUtil.getTypeByPsiElement(element), element), matcher);
226 public static PsiType getQualifierType(LookupItem item) {
227 return item.getUserData(QUALIFIER_TYPE_ATTR);
230 public static void completeVariableNameForRefactoring(Project project, Set<LookupElement> set, String prefix, PsiType varType, VariableKind varKind) {
231 completeVariableNameForRefactoring(project, set, new CamelHumpMatcher(prefix), varType, varKind);
234 public static void completeVariableNameForRefactoring(Project project, Set<LookupElement> set, PrefixMatcher matcher, PsiType varType, VariableKind varKind) {
235 JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project);
236 SuggestedNameInfo suggestedNameInfo = codeStyleManager.suggestVariableName(varKind, null, null, varType);
237 final String[] strings = completeVariableNameForRefactoring(codeStyleManager, matcher, varType, varKind, suggestedNameInfo);
238 tunePreferencePolicy(LookupItemUtil.addLookupItems(set, strings, matcher), suggestedNameInfo);
241 public static String[] completeVariableNameForRefactoring(JavaCodeStyleManager codeStyleManager, final PsiType varType,
242 final VariableKind varKind,
243 SuggestedNameInfo suggestedNameInfo) {
244 return completeVariableNameForRefactoring(codeStyleManager, new CamelHumpMatcher(""), varType, varKind, suggestedNameInfo);
247 public static String[] completeVariableNameForRefactoring(JavaCodeStyleManager codeStyleManager, final PrefixMatcher matcher, final PsiType varType,
248 final VariableKind varKind,
249 SuggestedNameInfo suggestedNameInfo) {
250 Set<String> result = new LinkedHashSet<String>();
251 final String[] suggestedNames = suggestedNameInfo.names;
252 for (final String suggestedName : suggestedNames) {
253 if (matcher.prefixMatches(suggestedName)) {
254 result.add(suggestedName);
258 if (result.isEmpty() && PsiType.VOID != varType) {
259 // use suggested names as suffixes
260 final String requiredSuffix = codeStyleManager.getSuffixByVariableKind(varKind);
261 final String prefix = matcher.getPrefix();
262 final boolean isMethodPrefix = prefix.startsWith(IS_PREFIX) || prefix.startsWith(GET_PREFIX) || prefix.startsWith(SET_PREFIX);
263 if (varKind != VariableKind.STATIC_FINAL_FIELD || isMethodPrefix) {
264 for (int i = 0; i < suggestedNames.length; i++) {
265 suggestedNames[i] = codeStyleManager.variableNameToPropertyName(suggestedNames[i], varKind);
269 result.addAll(Arrays.asList(getOverlappedNameVersions(prefix, suggestedNames, requiredSuffix)));
273 return result.toArray(new String[result.size()]);
276 private static void tunePreferencePolicy(final List<LookupElement> list, final SuggestedNameInfo suggestedNameInfo) {
277 final InsertHandler insertHandler = new InsertHandler() {
278 public void handleInsert(final InsertionContext context, final LookupElement item) {
279 suggestedNameInfo.nameChoosen(item.getLookupString());
283 for (int i = 0; i < list.size(); i++) {
284 LookupElement item = list.get(i);
285 if (item instanceof LookupItem) {
286 ((LookupItem)item).setPriority(list.size() - i).setInsertHandler(insertHandler);
291 public static String[] getOverlappedNameVersions(final String prefix, final String[] suggestedNames, String suffix) {
292 final List<String> newSuggestions = new ArrayList<String>();
293 int longestOverlap = 0;
295 for (String suggestedName : suggestedNames) {
296 if (suggestedName.toUpperCase().startsWith(prefix.toUpperCase())) {
297 newSuggestions.add(suggestedName);
298 longestOverlap = prefix.length();
301 suggestedName = String.valueOf(Character.toUpperCase(suggestedName.charAt(0))) + suggestedName.substring(1);
302 final int overlap = getOverlap(suggestedName, prefix);
304 if (overlap < longestOverlap) continue;
306 if (overlap > longestOverlap) {
307 newSuggestions.clear();
308 longestOverlap = overlap;
311 String suggestion = prefix.substring(0, prefix.length() - overlap) + suggestedName;
313 final int lastIndexOfSuffix = suggestion.lastIndexOf(suffix);
314 if (lastIndexOfSuffix >= 0 && suffix.length() < suggestion.length() - lastIndexOfSuffix) {
315 suggestion = suggestion.substring(0, lastIndexOfSuffix) + suffix;
318 if (!newSuggestions.contains(suggestion)) {
319 newSuggestions.add(suggestion);
322 return newSuggestions.toArray(new String[newSuggestions.size()]);
325 static int getOverlap(final String propertyName, final String prefix) {
326 int overlap = 0;
327 int propertyNameLen = propertyName.length();
328 int prefixLen = prefix.length();
329 for (int j = 1; j < prefixLen && j < propertyNameLen; j++) {
330 if (prefix.substring(prefixLen - j).equals(propertyName.substring(0, j))) {
331 overlap = j;
334 return overlap;
337 public static PsiType eliminateWildcards(PsiType type) {
338 return eliminateWildcardsInner(type, true);
341 static PsiType eliminateWildcardsInner(PsiType type, final boolean eliminateInTypeArguments) {
342 if (eliminateInTypeArguments && type instanceof PsiClassType) {
343 PsiClassType classType = ((PsiClassType)type);
344 JavaResolveResult resolveResult = classType.resolveGenerics();
345 PsiClass aClass = (PsiClass)resolveResult.getElement();
346 if (aClass != null) {
347 PsiManager manager = aClass.getManager();
348 PsiTypeParameter[] typeParams = aClass.getTypeParameters();
349 Map<PsiTypeParameter, PsiType> map = new HashMap<PsiTypeParameter, PsiType>();
350 for (PsiTypeParameter typeParam : typeParams) {
351 PsiType substituted = resolveResult.getSubstitutor().substitute(typeParam);
352 if (substituted instanceof PsiWildcardType) {
353 substituted = ((PsiWildcardType)substituted).getBound();
354 if (substituted == null) substituted = PsiType.getJavaLangObject(manager, aClass.getResolveScope());
356 map.put(typeParam, substituted);
359 PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory();
360 PsiSubstitutor substitutor = factory.createSubstitutor(map);
361 type = factory.createType(aClass, substitutor);
364 else if (type instanceof PsiArrayType) {
365 return eliminateWildcardsInner(((PsiArrayType)type).getComponentType(), false).createArrayType();
367 else if (type instanceof PsiWildcardType) {
368 return ((PsiWildcardType)type).getExtendsBound();
370 return type;
373 public static String[] getOverides(final PsiClass parent, final PsiType typeByPsiElement) {
374 final List<String> overides = new ArrayList<String>();
375 final Collection<CandidateInfo> methodsToOverrideImplement = OverrideImplementUtil.getMethodsToOverrideImplement(parent, true);
376 for (final CandidateInfo candidateInfo : methodsToOverrideImplement) {
377 final PsiElement element = candidateInfo.getElement();
378 if (Comparing.equal(typeByPsiElement, PsiUtil.getTypeByPsiElement(element)) && element instanceof PsiNamedElement) {
379 overides.add(((PsiNamedElement)element).getName());
382 return ArrayUtil.toStringArray(overides);
385 public static String[] getImplements(final PsiClass parent, final PsiType typeByPsiElement) {
386 final List<String> overides = new ArrayList<String>();
387 final Collection<CandidateInfo> methodsToOverrideImplement = OverrideImplementUtil.getMethodsToOverrideImplement(parent, false);
388 for (final CandidateInfo candidateInfo : methodsToOverrideImplement) {
389 final PsiElement element = candidateInfo.getElement();
390 if (Comparing.equal(typeByPsiElement,PsiUtil.getTypeByPsiElement(element)) && element instanceof PsiNamedElement) {
391 overides.add(((PsiNamedElement)element).getName());
394 return ArrayUtil.toStringArray(overides);
397 public static String[] getPropertiesHandlersNames(final PsiClass psiClass,
398 final boolean staticContext,
399 final PsiType varType,
400 final PsiElement element) {
401 class Change implements Runnable {
402 private String[] result;
404 public void run() {
405 final List<String> propertyHandlers = new ArrayList<String>();
406 final PsiField[] fields = psiClass.getFields();
408 for (final PsiField field : fields) {
409 if (field == element) continue;
410 final PsiModifierList modifierList = field.getModifierList();
411 if (staticContext && (modifierList != null && !modifierList.hasModifierProperty(PsiModifier.STATIC))) continue;
413 if (field.getType().equals(varType)) {
414 final String getterName = PropertyUtil.suggestGetterName(field.getProject(), field);
415 if ((psiClass.findMethodsByName(getterName, true).length == 0 ||
416 psiClass.findMethodBySignature(PropertyUtil.generateGetterPrototype(field), true) == null)) {
417 propertyHandlers.add(getterName);
421 if (PsiType.VOID.equals(varType)) {
422 final String setterName = PropertyUtil.suggestSetterName(field.getProject(), field);
423 if ((psiClass.findMethodsByName(setterName, true).length == 0 ||
424 psiClass.findMethodBySignature(PropertyUtil.generateSetterPrototype(field), true) == null)) {
425 propertyHandlers.add(setterName);
429 result = ArrayUtil.toStringArray(propertyHandlers);
432 final Change result = new Change();
433 element.getManager().performActionWithFormatterDisabled(result);
434 return result.result;
437 public static boolean isInExcludedPackage(@NotNull final PsiClass psiClass) {
438 final String name = psiClass.getQualifiedName();
439 if (name == null) return false;
440 CodeInsightSettings cis = CodeInsightSettings.getInstance();
441 for (String excluded : cis.EXCLUDED_PACKAGES) {
442 if (name.equals(excluded) || name.startsWith(excluded + ".")) {
443 return true;
446 return false;
449 @NotNull
450 public static PsiField getOriginalElement(@NotNull PsiField field) {
451 final PsiClass cls = field.getContainingClass();
452 if (cls != null) {
453 final PsiClass newParent = getOriginalElement(cls);
454 if (newParent != cls) {
455 final PsiField original = newParent.findFieldByName(field.getName(), false);
456 if (original != null) {
457 return original;
461 return field;
464 @NotNull
465 public static PsiTypeParameter getOriginalElement(@NotNull PsiTypeParameter param) {
466 final PsiClass parent = PsiTreeUtil.getParentOfType(param, PsiClass.class, true, PsiMethod.class);
467 if (parent != null) {
468 final PsiClass newParent = getOriginalElement(parent);
469 if (newParent != parent) {
470 for (PsiTypeParameter parameter : newParent.getTypeParameters()) {
471 if (parameter.getName().equals(param.getName())) {
472 return parameter;
477 return param;
480 @NotNull
481 public static PsiClass getOriginalElement(@NotNull PsiClass cls) {
482 final PsiClass containingClass = cls.getContainingClass();
483 if (containingClass != null) {
484 final PsiClass newParent = getOriginalElement(containingClass);
485 if (newParent != containingClass) {
486 return findClassByName(cls, newParent.getInnerClasses());
490 final PsiFile containingFile = cls.getContainingFile();
491 if (containingFile instanceof PsiClassOwner) {
492 return findClassByName(cls, ((PsiClassOwner)containingFile.getOriginalFile()).getClasses());
495 return cls;
498 private static PsiClass findClassByName(PsiClass defResult, PsiClass[] classes) {
499 String name = defResult.getName();
500 if (name == null) return defResult;
502 for (PsiClass candidate : classes) {
503 if (name.equals(candidate.getName())) {
504 return candidate;
507 return defResult;
510 @SuppressWarnings({"unchecked"})
511 @NotNull
512 public static <T extends PsiType> T originalize(@NotNull T type) {
513 if (!type.isValid()) {
514 return type;
517 return (T)type.accept(new PsiTypeVisitor<PsiType>() {
519 public PsiType visitArrayType(final PsiArrayType arrayType) {
520 return new PsiArrayType(originalize(arrayType.getComponentType()));
523 public PsiType visitCapturedWildcardType(final PsiCapturedWildcardType capturedWildcardType) {
524 return PsiCapturedWildcardType.create(originalize(capturedWildcardType.getWildcard()), capturedWildcardType.getContext());
527 public PsiType visitClassType(final PsiClassType classType) {
528 final PsiClassType.ClassResolveResult classResolveResult = classType.resolveGenerics();
529 final PsiClass psiClass = classResolveResult.getElement();
530 final PsiSubstitutor substitutor = classResolveResult.getSubstitutor();
531 if (psiClass == null) return classType;
533 LOG.assertTrue(psiClass.isValid());
535 return new PsiImmediateClassType(getOriginalElement(psiClass), originalize(substitutor));
538 public PsiType visitEllipsisType(final PsiEllipsisType ellipsisType) {
539 return new PsiEllipsisType(originalize(ellipsisType.getComponentType()));
542 public PsiType visitPrimitiveType(final PsiPrimitiveType primitiveType) {
543 return primitiveType;
546 public PsiType visitType(final PsiType type) {
547 return type;
550 public PsiType visitWildcardType(final PsiWildcardType wildcardType) {
551 final PsiType bound = wildcardType.getBound();
552 final PsiManager manager = wildcardType.getManager();
553 if (bound == null) return PsiWildcardType.createUnbounded(manager);
554 return wildcardType.isExtends() ? PsiWildcardType.createExtends(manager, bound) : PsiWildcardType.createSuper(manager, bound);
559 @Nullable
560 private static PsiSubstitutor originalize(@Nullable final PsiSubstitutor substitutor) {
561 if (substitutor == null) return null;
563 PsiSubstitutor originalSubstitutor = PsiSubstitutor.EMPTY;
564 for (final Map.Entry<PsiTypeParameter, PsiType> entry : substitutor.getSubstitutionMap().entrySet()) {
565 final PsiType value = entry.getValue();
566 originalSubstitutor = originalSubstitutor.put(getOriginalElement(entry.getKey()), value == null ? null : originalize(value));
568 return originalSubstitutor;
571 public static String[] getUnresolvedReferences(final PsiElement parentOfType, final boolean referenceOnMethod) {
572 if (parentOfType != null && parentOfType.getTextLength() > MAX_SCOPE_SIZE_TO_SEARCH_UNRESOLVED) return ArrayUtil.EMPTY_STRING_ARRAY;
573 final List<String> unresolvedRefs = new ArrayList<String>();
575 if (parentOfType != null) {
576 parentOfType.accept(new JavaRecursiveElementWalkingVisitor() {
577 @Override public void visitReferenceExpression(PsiReferenceExpression reference) {
578 final PsiElement parent = reference.getParent();
579 if (parent instanceof PsiReference) return;
580 if (referenceOnMethod && parent instanceof PsiMethodCallExpression &&
581 reference == ((PsiMethodCallExpression)parent).getMethodExpression()) {
582 if (reference.resolve() == null && reference.getReferenceName() != null) unresolvedRefs.add(reference.getReferenceName());
584 else if (!referenceOnMethod && !(parent instanceof PsiMethodCallExpression) &&reference.resolve() == null && reference.getReferenceName() != null) {
585 unresolvedRefs.add(reference.getReferenceName());
590 return ArrayUtil.toStringArray(unresolvedRefs);
593 public static void initOffsets(final PsiFile file, final Project project, final OffsetMap offsetMap){
594 int selectionEndOffset = offsetMap.getOffset(CompletionInitializationContext.SELECTION_END_OFFSET);
596 PsiElement element = file.findElementAt(selectionEndOffset);
597 if (element == null) return;
599 if (LEFT_PAREN.accepts(element)) {
600 selectionEndOffset--;
601 element = file.findElementAt(selectionEndOffset);
602 if (element == null) return;
605 final PsiReference reference = file.findReferenceAt(selectionEndOffset);
606 if(reference != null) {
607 if(reference instanceof PsiJavaCodeReferenceElement){
608 offsetMap.addOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET, element.getParent().getTextRange().getEndOffset());
610 else{
611 offsetMap.addOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET,
612 reference.getElement().getTextRange().getStartOffset() + reference.getRangeInElement().getEndOffset());
615 element = file.findElementAt(offsetMap.getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET));
617 else if (isWord(element)){
618 if(element instanceof PsiIdentifier && element.getParent() instanceof PsiJavaCodeReferenceElement){
619 offsetMap.addOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET, element.getParent().getTextRange().getEndOffset());
621 else{
622 offsetMap.addOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET, element.getTextRange().getEndOffset());
625 element = file.findElementAt(offsetMap.getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET));
626 if (element == null) return;
629 if (element instanceof PsiWhiteSpace &&
630 ( !element.textContains('\n') ||
631 CodeStyleSettingsManager.getInstance(project).getCurrentSettings().METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE
634 element = file.findElementAt(element.getTextRange().getEndOffset());
637 if (LEFT_PAREN.accepts(element)) {
638 offsetMap.addOffset(LPAREN_OFFSET, element.getTextRange().getStartOffset());
639 PsiElement list = element.getParent();
640 PsiElement last = list.getLastChild();
641 if (last instanceof PsiJavaToken && ((PsiJavaToken)last).getTokenType() == JavaTokenType.RPARENTH) {
642 offsetMap.addOffset(RPAREN_OFFSET, last.getTextRange().getStartOffset());
646 offsetMap.addOffset(ARG_LIST_END_OFFSET, list.getTextRange().getEndOffset());
651 static boolean isWord(PsiElement element) {
652 if (element instanceof PsiIdentifier){
653 return true;
655 else if (element instanceof PsiKeyword){
656 return true;
658 else if (element instanceof PsiJavaToken){
659 final String text = element.getText();
660 if(PsiKeyword.TRUE.equals(text)) return true;
661 if(PsiKeyword.FALSE.equals(text)) return true;
662 if(PsiKeyword.NULL.equals(text)) return true;
663 return false;
665 else if (element instanceof PsiDocToken) {
666 IElementType tokenType = ((PsiDocToken)element).getTokenType();
667 return tokenType == JavaDocTokenType.DOC_TAG_VALUE_TOKEN || tokenType == JavaDocTokenType.DOC_TAG_NAME;
669 else if (element instanceof XmlToken) {
670 IElementType tokenType = ((XmlToken)element).getTokenType();
671 return tokenType == XmlTokenType.XML_TAG_NAME ||
672 tokenType == XmlTokenType.XML_NAME ||
673 tokenType == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN ||
674 // html data chars contains whitespaces
675 (tokenType == XmlTokenType.XML_DATA_CHARACTERS && !(element.getParent() instanceof HtmlTag));
677 else{
678 return false;
682 public static void resetParensInfo(final OffsetMap offsetMap) {
683 offsetMap.removeOffset(LPAREN_OFFSET);
684 offsetMap.removeOffset(RPAREN_OFFSET);
685 offsetMap.removeOffset(ARG_LIST_END_OFFSET);
686 offsetMap.removeOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET);
689 @Nullable
690 public static List<? extends PsiElement> getAllPsiElements(final LookupElement item) {
691 List<PsiMethod> allMethods = item.getUserData(ALL_METHODS_ATTRIBUTE);
692 if (allMethods != null) return allMethods;
693 if (item.getObject() instanceof PsiElement) return Arrays.asList((PsiElement)item.getObject());
694 return null;
697 @Nullable
698 private static PsiType getPsiType(final Object o) {
699 if (o instanceof PsiVariable) {
700 return ((PsiVariable)o).getType();
702 else if (o instanceof PsiMethod) {
703 return ((PsiMethod)o).getReturnType();
705 else if (o instanceof PsiClass) {
706 final PsiClass psiClass = (PsiClass)o;
707 return JavaPsiFacade.getInstance(psiClass.getProject()).getElementFactory().createType(psiClass);
709 else if (o instanceof PsiExpression) {
710 return ((PsiExpression)o).getType();
712 return null;
715 public static int getNameEndMatchingDegree(final String name, ExpectedTypeInfo[] expectedInfos, String prefix) {
716 int res = 0;
717 if (name != null && expectedInfos != null) {
718 if (prefix.equals(name)) {
719 res = Integer.MAX_VALUE;
720 } else {
721 final List<String> words = NameUtil.nameToWordsLowerCase(name);
722 final List<String> wordsNoDigits = NameUtil.nameToWordsLowerCase(truncDigits(name));
723 int max1 = calcMatch(words, 0, expectedInfos);
724 max1 = calcMatch(wordsNoDigits, max1, expectedInfos);
725 res = max1;
729 return res;
732 static String truncDigits(String name){
733 int count = name.length() - 1;
734 while (count >= 0) {
735 char c = name.charAt(count);
736 if (!Character.isDigit(c)) break;
737 count--;
739 return name.substring(0, count + 1);
742 static int calcMatch(final List<String> words, int max, ExpectedTypeInfo[] myExpectedInfos) {
743 for (ExpectedTypeInfo myExpectedInfo : myExpectedInfos) {
744 String expectedName = ((ExpectedTypeInfoImpl)myExpectedInfo).expectedName;
745 if (expectedName == null) continue;
746 max = calcMatch(expectedName, words, max);
747 max = calcMatch(truncDigits(expectedName), words, max);
749 return max;
752 static int calcMatch(final String expectedName, final List<String> words, int max) {
753 if (expectedName == null) return max;
755 String[] expectedWords = NameUtil.nameToWords(expectedName);
756 int limit = Math.min(words.size(), expectedWords.length);
757 for (int i = 0; i < limit; i++) {
758 String word = words.get(words.size() - i - 1);
759 String expectedWord = expectedWords[expectedWords.length - i - 1];
760 if (word.equalsIgnoreCase(expectedWord)) {
761 max = Math.max(max, i + 1);
763 else {
764 break;
767 return max;
770 @Nullable
771 static String getLookupObjectName(Object o) {
772 if (o instanceof PsiVariable) {
773 final PsiVariable variable = (PsiVariable)o;
774 JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(variable.getProject());
775 VariableKind variableKind = codeStyleManager.getVariableKind(variable);
776 return codeStyleManager.variableNameToPropertyName(variable.getName(), variableKind);
778 if (o instanceof PsiMethod) {
779 return ((PsiMethod)o).getName();
781 return null;
784 @Nullable
785 public static PsiType getLookupElementType(final LookupElement element) {
786 final TypedLookupItem typed = element.as(TypedLookupItem.class);
787 if (typed != null) {
788 return typed.getType();
792 final PsiType qualifierType = getPsiType(element.getObject());
793 final LookupItem lookupItem = element.as(LookupItem.class);
794 if (lookupItem != null) {
795 final PsiSubstitutor substitutor = (PsiSubstitutor)lookupItem.getAttribute(LookupItem.SUBSTITUTOR);
796 if (substitutor != null) {
797 return substitutor.substitute(qualifierType);
800 return qualifierType;
803 @Nullable
804 public static PsiType getQualifiedMemberReferenceType(@Nullable PsiType qualifierType, @NotNull final PsiMember member) {
805 final ClassCandidateInfo info = TypeConversionUtil.splitType(qualifierType, member);
806 if (info == null) {
807 return null;
810 final PsiClass element = info.getElement();
811 assert element != null;
813 final Ref<PsiSubstitutor> subst = Ref.create(PsiSubstitutor.EMPTY);
814 class MyProcessor extends BaseScopeProcessor implements NameHint, ElementClassHint {
815 public boolean execute(PsiElement element, ResolveState state) {
816 if (element == member) {
817 subst.set(state.get(PsiSubstitutor.KEY));
819 return true;
822 public String getName(ResolveState state) {
823 return member.getName();
826 public boolean shouldProcess(DeclaractionKind kind) {
827 return member instanceof PsiEnumConstant ? kind == DeclaractionKind.ENUM_CONST :
828 member instanceof PsiField ? kind == DeclaractionKind.FIELD :
829 kind == DeclaractionKind.METHOD;
832 @Override
833 public <T> T getHint(Key<T> hintKey) {
834 return hintKey == NameHint.KEY || hintKey == ElementClassHint.KEY ? (T)this : null;
838 element.processDeclarations(new MyProcessor(), ResolveState.initial().put(PsiSubstitutor.KEY, info.getSubstitutor()), null, member);
840 PsiType rawType = member instanceof PsiField ? ((PsiField) member).getType() : ((PsiMethod) member).getReturnType();
841 return subst.get().substitute(rawType);
844 public static Set<LookupElement> processJavaReference(PsiElement element, PsiJavaReference javaReference, ElementFilter elementFilter,
845 final boolean checkAccess, @Nullable final PrefixMatcher matcher, CompletionParameters parameters) {
846 final THashSet<LookupElement> set = new THashSet<LookupElement>();
847 final Condition<String> nameCondition = matcher == null ? null : new Condition<String>() {
848 public boolean value(String s) {
849 return matcher.prefixMatches(s);
852 final JavaCompletionProcessor processor = new JavaCompletionProcessor(element, elementFilter, checkAccess, nameCondition);
853 javaReference.processVariants(processor);
854 final Collection<CompletionElement> plainResults = processor.getResults();
856 final PsiType qualifierType = processor.getQualifierType();
857 PsiType castedQualifierType = addQualifierCastingVariants(javaReference, processor, set, parameters);
859 boolean mayHighlight = qualifierType != null && (castedQualifierType == null || !qualifierType.isAssignableFrom(castedQualifierType));
861 for (CompletionElement completionElement : plainResults) {
862 LookupElement item = createLookupElement(completionElement, qualifierType);
863 if (item != null) {
864 set.add(mayHighlight ? highlightIfNeeded(qualifierType, item) : item);
868 return set;
871 @Nullable
872 private static PsiType addQualifierCastingVariants(PsiJavaReference javaReference, JavaCompletionProcessor processor, THashSet<LookupElement> set, CompletionParameters parameters) {
873 if (javaReference instanceof PsiReferenceExpression) {
874 final PsiReferenceExpression refExpr = (PsiReferenceExpression)javaReference;
875 final PsiExpression qualifier = refExpr.getQualifierExpression();
876 if (qualifier != null) {
877 final Project project = qualifier.getProject();
878 final PairFunction<PsiExpression, CompletionParameters, PsiType> evaluator = refExpr.getContainingFile().getCopyableUserData(DYNAMIC_TYPE_EVALUATOR);
879 PsiType type = null;
880 if (evaluator != null) {
881 type = evaluator.fun(qualifier, parameters);
883 if (type == null) {
884 type = GuessManager.getInstance(project).getControlFlowExpressionType(qualifier);
886 if (type != null) {
887 processor.clear();
889 return addQualifierCastingVariants(processor, refExpr, type, set);
893 return null;
896 private static PsiType addQualifierCastingVariants(JavaCompletionProcessor processor, PsiReferenceExpression refExpr,
897 PsiType castTo,
898 THashSet<LookupElement> set) {
899 Project project = refExpr.getProject();
901 PsiExpression qualifier = refExpr.getQualifierExpression();
902 assert qualifier != null;
903 final String newText = "((" + castTo.getCanonicalText() + ") " + qualifier.getText() + ")." + refExpr.getReferenceName();
904 final PsiExpression newRef = JavaPsiFacade.getElementFactory(project).createExpressionFromText(newText, refExpr);
905 ((PsiReferenceExpression)newRef).processVariants(processor);
907 final LookupElement castItem = PsiTypeLookupItem.createLookupItem(castTo);
909 for (CompletionElement completionElement : processor.getResults()) {
910 final LookupElement item = createLookupElement(completionElement, castTo);
911 if (item != null) {
912 set.add(highlightIfNeeded(castTo, castQualifier(project, item, castItem)));
915 return castTo;
918 private static LookupElementDecorator<LookupElement> castQualifier(final Project project, LookupElement item, final LookupElement to) {
919 return LookupElementDecorator.withInsertHandler(item, new InsertHandlerDecorator<LookupElement>() {
920 public void handleInsert(InsertionContext context, LookupElementDecorator<LookupElement> item) {
921 final Document document = context.getEditor().getDocument();
922 PsiDocumentManager.getInstance(project).commitDocument(document);
923 final PsiFile file = context.getFile();
924 final PsiReferenceExpression ref =
925 PsiTreeUtil.findElementOfClassAtOffset(file, context.getStartOffset(), PsiReferenceExpression.class, false);
926 if (ref != null) {
927 final PsiElement qualifier = ref.getQualifier();
928 if (qualifier != null) {
929 final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(project);
931 final String parenSpace = settings.SPACE_WITHIN_PARENTHESES ? " " : "";
932 document.insertString(qualifier.getTextRange().getEndOffset(), parenSpace + ")");
934 final String spaceWithin = settings.SPACE_WITHIN_CAST_PARENTHESES ? " " : "";
935 final String prefix = "(" + parenSpace + "(" + spaceWithin;
936 final String spaceAfter = settings.SPACE_AFTER_TYPE_CAST ? " " : "";
937 final int exprStart = qualifier.getTextRange().getStartOffset();
938 document.insertString(exprStart, prefix + spaceWithin + ")" + spaceAfter);
940 CompletionUtil.emulateInsertion(context, exprStart + prefix.length(), to, (char)0);
944 item.getDelegate().handleInsert(context);
949 private static LookupElement highlightIfNeeded(@NotNull PsiType qualifierType, @NotNull LookupElement item) {
950 Object o = item.getObject();
951 if (qualifierType instanceof PsiArrayType) {
952 if (o instanceof PsiField || o instanceof PsiMethod) { //length and clone()
953 PsiElement parent = ((PsiElement)o).getParent();
954 if (parent instanceof PsiClass && parent.getContainingFile().getVirtualFile() == null) { //yes, they're a bit dummy
955 return highlight(item);
959 else if (qualifierType instanceof PsiClassType) {
960 PsiClass qualifierClass = ((PsiClassType)qualifierType).resolve();
961 if (o instanceof PsiField || o instanceof PsiMethod || o instanceof PsiClass) {
962 PsiElement parent = ((PsiElement)o).getParent();
963 if (parent != null && parent.equals(qualifierClass)) {
964 return highlight(item);
968 return item;
971 private static LookupElementDecorator<LookupElement> highlight(LookupElement decorator) {
972 return LookupElementDecorator.withRenderer(decorator, new LookupElementRenderer<LookupElementDecorator<LookupElement>>() {
973 @Override
974 public void renderElement(LookupElementDecorator<LookupElement> element, LookupElementPresentation presentation) {
975 element.getDelegate().renderElement(presentation);
976 presentation.setItemTextBold(true);
981 private static LookupItem<?> createLookupElement(CompletionElement completionElement, PsiType qualifierType) {
982 Object completion = completionElement.getElement();
983 assert !(completion instanceof LookupElement);
986 LookupElement _ret = LookupItemUtil.objectToLookupItem(completion);
987 if (_ret == null || !(_ret instanceof LookupItem)) return null;
989 LookupItem<?> ret = (LookupItem<?>)_ret;
990 final PsiSubstitutor substitutor = completionElement.getSubstitutor();
991 if (substitutor != null) {
992 ret.setAttribute(LookupItem.SUBSTITUTOR, substitutor);
994 ret.putUserData(QUALIFIER_TYPE_ATTR, qualifierType);
996 return ret;
999 public static boolean hasAccessibleConstructor(PsiType type) {
1000 if (type instanceof PsiArrayType) return true;
1002 final PsiClass psiClass = PsiUtil.resolveClassInType(type);
1003 if (psiClass == null) return false;
1005 if (!(psiClass instanceof PsiCompiledElement)) return true;
1007 final PsiMethod[] methods = psiClass.getConstructors();
1008 if (methods.length == 0) return true;
1010 for (final PsiMethod method : methods) {
1011 if (!method.hasModifierProperty(PsiModifier.PRIVATE)) return true;
1013 return false;
1016 public static LookupItem qualify(final LookupItem ret) {
1017 final PsiMember completionElement = (PsiMember)ret.getObject();
1018 final PsiClass containingClass = completionElement.getContainingClass();
1019 if (containingClass != null) {
1020 final String className = containingClass.getName();
1021 ret.setLookupString(className + "." + ret.getLookupString());
1023 return ret.forceQualify();
1026 public static LookupItem setShowFQN(final LookupItem ret) {
1027 final PsiClass psiClass = (PsiClass)ret.getObject();
1028 @NonNls String packageName = PsiFormatUtil.getPackageDisplayName(psiClass);
1030 final String tailText = (String)ret.getAttribute(LookupItem.TAIL_TEXT_ATTR);
1031 ret.setAttribute(LookupItem.TAIL_TEXT_ATTR, StringUtil.notNullize(tailText) + " (" + packageName + ")");
1032 ret.setAttribute(LookupItem.TAIL_TEXT_SMALL_ATTR, "");
1033 return ret;
1036 @Nullable
1037 static PsiElement getQualifier(final PsiElement element) {
1038 return element instanceof PsiJavaCodeReferenceElement ? ((PsiJavaCodeReferenceElement)element).getQualifier() : null;
1041 public static boolean containsMethodCalls(@Nullable final PsiElement qualifier) {
1042 if (qualifier == null) return false;
1043 if (qualifier instanceof PsiMethodCallExpression) return true;
1044 return containsMethodCalls(getQualifier(qualifier));
1047 @Nullable
1048 static ElementFilter recursionFilter(PsiElement element) {
1049 if (com.intellij.patterns.PsiJavaPatterns.psiElement().afterLeaf(PsiKeyword.RETURN).inside(PsiReturnStatement.class).accepts(element)) {
1050 return new ExcludeDeclaredFilter(ElementClassFilter.METHOD);
1053 if (com.intellij.patterns.PsiJavaPatterns.psiElement().inside(
1054 PsiJavaPatterns.or(
1055 PsiJavaPatterns.psiElement(PsiAssignmentExpression.class),
1056 PsiJavaPatterns.psiElement(PsiVariable.class))).
1057 andNot(com.intellij.patterns.PsiJavaPatterns.psiElement().afterLeaf(".")).accepts(element)) {
1058 return new AndFilter(new ExcludeSillyAssignment(),
1059 new ExcludeDeclaredFilter(new ClassFilter(PsiVariable.class)));
1061 return null;