/* * Copyright 2000-2009 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.*; import com.intellij.codeInsight.completion.impl.CamelHumpMatcher; import com.intellij.codeInsight.completion.scope.CompletionElement; import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor; import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler; import com.intellij.codeInsight.guess.GuessManager; import com.intellij.codeInsight.lookup.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.NullableLazyKey; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.patterns.PsiElementPattern; import com.intellij.patterns.PsiJavaPatterns; import com.intellij.psi.*; import com.intellij.psi.codeStyle.*; import com.intellij.psi.filters.AndFilter; import com.intellij.psi.filters.ClassFilter; import com.intellij.psi.filters.ElementFilter; import com.intellij.psi.filters.element.ExcludeDeclaredFilter; import com.intellij.psi.filters.element.ExcludeSillyAssignment; import com.intellij.psi.html.HtmlTag; import com.intellij.psi.impl.source.PsiImmediateClassType; import com.intellij.psi.infos.ClassCandidateInfo; import com.intellij.psi.javadoc.PsiDocToken; import com.intellij.psi.scope.BaseScopeProcessor; import com.intellij.psi.scope.ElementClassFilter; import com.intellij.psi.scope.ElementClassHint; import com.intellij.psi.scope.NameHint; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiFormatUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.psi.xml.XmlToken; import com.intellij.psi.xml.XmlTokenType; import com.intellij.util.IncorrectOperationException; import com.intellij.util.NullableFunction; import com.intellij.util.PairConsumer; import com.intellij.util.PairFunction; import com.intellij.util.containers.HashMap; import gnu.trove.THashSet; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; import static com.intellij.patterns.PlatformPatterns.psiElement; public class JavaCompletionUtil { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.JavaCompletionUtil"); public static final Key> DYNAMIC_TYPE_EVALUATOR = Key.create("DYNAMIC_TYPE_EVALUATOR"); static final Key QUALIFIER_TYPE_ATTR = Key.create("qualifierType"); // SmartPsiElementPointer to PsiType of "qualifier" @NonNls public static final String GET_PREFIX = "get"; @NonNls public static final String SET_PREFIX = "set"; @NonNls public static final String IS_PREFIX = "is"; public static final OffsetKey LPAREN_OFFSET = OffsetKey.create("lparen"); public static final OffsetKey RPAREN_OFFSET = OffsetKey.create("rparen"); public static final OffsetKey ARG_LIST_END_OFFSET = OffsetKey.create("argListEnd"); static final NullableLazyKey EXPECTED_TYPES = NullableLazyKey.create("expectedTypes", new NullableFunction() { @Nullable public ExpectedTypeInfo[] fun(final CompletionLocation location) { if (PsiJavaPatterns.psiElement().beforeLeaf(PsiJavaPatterns.psiElement().withText(".")) .accepts(location.getCompletionParameters().getPosition())) { return new ExpectedTypeInfo[0]; } return JavaSmartCompletionContributor.getExpectedTypes(location.getCompletionParameters()); } }); private static final PsiElementPattern.Capture LEFT_PAREN = psiElement(JavaTokenType.LPARENTH).andOr(psiElement().withParent( PsiExpressionList.class), psiElement().afterLeaf(".", PsiKeyword.NEW)); public static final Key SUPER_METHOD_PARAMETERS = Key.create("SUPER_METHOD_PARAMETERS"); @Nullable public static Set getExpectedTypes(final CompletionParameters parameters) { final PsiExpression expr = PsiTreeUtil.getContextOfType(parameters.getPosition(), PsiExpression.class, true); if (expr != null) { final ExpectedTypeInfo[] expectedInfos = JavaSmartCompletionContributor.getExpectedTypes(parameters); if(expectedInfos != null){ final Set set = new THashSet(); for (final ExpectedTypeInfo expectedInfo : expectedInfos) { set.add(expectedInfo.getType()); } return set; } } return null; } static final NullableLazyKey POSITION_METHOD = NullableLazyKey.create("positionMethod", new NullableFunction() { public PsiMethod fun(final CompletionLocation location) { return PsiTreeUtil.getParentOfType(location.getCompletionParameters().getPosition(), PsiMethod.class, false); } }); public static final Key> ALL_METHODS_ATTRIBUTE = Key.create("allMethods"); public static PsiType getQualifierType(LookupItem item) { return item.getUserData(QUALIFIER_TYPE_ATTR); } public static void completeVariableNameForRefactoring(Project project, Set set, String prefix, PsiType varType, VariableKind varKind) { JavaMemberNameCompletionContributor.completeVariableNameForRefactoring(project, set, new CamelHumpMatcher(prefix), varType, varKind); } public static String[] completeVariableNameForRefactoring(JavaCodeStyleManager codeStyleManager, final PsiType varType, final VariableKind varKind, SuggestedNameInfo suggestedNameInfo) { return JavaMemberNameCompletionContributor .completeVariableNameForRefactoring(codeStyleManager, new CamelHumpMatcher(""), varType, varKind, suggestedNameInfo); } public static PsiType eliminateWildcards(PsiType type) { return eliminateWildcardsInner(type, true); } static PsiType eliminateWildcardsInner(PsiType type, final boolean eliminateInTypeArguments) { if (eliminateInTypeArguments && type instanceof PsiClassType) { PsiClassType classType = ((PsiClassType)type); JavaResolveResult resolveResult = classType.resolveGenerics(); PsiClass aClass = (PsiClass)resolveResult.getElement(); if (aClass != null) { PsiManager manager = aClass.getManager(); PsiTypeParameter[] typeParams = aClass.getTypeParameters(); Map map = new HashMap(); for (PsiTypeParameter typeParam : typeParams) { PsiType substituted = resolveResult.getSubstitutor().substitute(typeParam); if (substituted instanceof PsiWildcardType) { substituted = ((PsiWildcardType)substituted).getBound(); if (substituted == null) substituted = PsiType.getJavaLangObject(manager, aClass.getResolveScope()); } map.put(typeParam, substituted); } PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory(); PsiSubstitutor substitutor = factory.createSubstitutor(map); type = factory.createType(aClass, substitutor); } } else if (type instanceof PsiArrayType) { return eliminateWildcardsInner(((PsiArrayType)type).getComponentType(), false).createArrayType(); } else if (type instanceof PsiWildcardType) { return ((PsiWildcardType)type).getExtendsBound(); } return type; } public static boolean isInExcludedPackage(@NotNull final PsiClass psiClass) { final String name = psiClass.getQualifiedName(); if (name == null) return false; CodeInsightSettings cis = CodeInsightSettings.getInstance(); for (String excluded : cis.EXCLUDED_PACKAGES) { if (name.equals(excluded) || name.startsWith(excluded + ".")) { return true; } } return false; } @NotNull public static PsiField getOriginalElement(@NotNull PsiField field) { final PsiClass cls = field.getContainingClass(); if (cls != null) { final PsiClass newParent = getOriginalElement(cls); if (newParent != cls) { final PsiField original = newParent.findFieldByName(field.getName(), false); if (original != null) { return original; } } } return field; } @NotNull public static PsiTypeParameter getOriginalElement(@NotNull PsiTypeParameter param) { final PsiClass parent = PsiTreeUtil.getParentOfType(param, PsiClass.class, true, PsiMethod.class); if (parent != null) { final PsiClass newParent = getOriginalElement(parent); if (newParent != parent) { for (PsiTypeParameter parameter : newParent.getTypeParameters()) { if (parameter.getName().equals(param.getName())) { return parameter; } } } } return param; } @NotNull public static PsiClass getOriginalElement(@NotNull PsiClass cls) { final PsiClass containingClass = cls.getContainingClass(); if (containingClass != null) { final PsiClass newParent = getOriginalElement(containingClass); if (newParent != containingClass) { return findClassByName(cls, newParent.getInnerClasses()); } } final PsiFile containingFile = cls.getContainingFile(); if (containingFile instanceof PsiClassOwner) { return findClassByName(cls, ((PsiClassOwner)containingFile.getOriginalFile()).getClasses()); } return cls; } private static PsiClass findClassByName(PsiClass defResult, PsiClass[] classes) { String name = defResult.getName(); if (name == null) return defResult; for (PsiClass candidate : classes) { if (name.equals(candidate.getName())) { return candidate; } } return defResult; } @SuppressWarnings({"unchecked"}) @NotNull public static T originalize(@NotNull T type) { if (!type.isValid()) { return type; } return (T)type.accept(new PsiTypeVisitor() { public PsiType visitArrayType(final PsiArrayType arrayType) { return new PsiArrayType(originalize(arrayType.getComponentType())); } public PsiType visitCapturedWildcardType(final PsiCapturedWildcardType capturedWildcardType) { return PsiCapturedWildcardType.create(originalize(capturedWildcardType.getWildcard()), capturedWildcardType.getContext()); } public PsiType visitClassType(final PsiClassType classType) { final PsiClassType.ClassResolveResult classResolveResult = classType.resolveGenerics(); final PsiClass psiClass = classResolveResult.getElement(); final PsiSubstitutor substitutor = classResolveResult.getSubstitutor(); if (psiClass == null) return classType; LOG.assertTrue(psiClass.isValid()); return new PsiImmediateClassType(getOriginalElement(psiClass), originalize(substitutor)); } public PsiType visitEllipsisType(final PsiEllipsisType ellipsisType) { return new PsiEllipsisType(originalize(ellipsisType.getComponentType())); } public PsiType visitPrimitiveType(final PsiPrimitiveType primitiveType) { return primitiveType; } public PsiType visitType(final PsiType type) { return type; } public PsiType visitWildcardType(final PsiWildcardType wildcardType) { final PsiType bound = wildcardType.getBound(); final PsiManager manager = wildcardType.getManager(); if (bound == null) return PsiWildcardType.createUnbounded(manager); return wildcardType.isExtends() ? PsiWildcardType.createExtends(manager, bound) : PsiWildcardType.createSuper(manager, bound); } }); } @Nullable private static PsiSubstitutor originalize(@Nullable final PsiSubstitutor substitutor) { if (substitutor == null) return null; PsiSubstitutor originalSubstitutor = PsiSubstitutor.EMPTY; for (final Map.Entry entry : substitutor.getSubstitutionMap().entrySet()) { final PsiType value = entry.getValue(); originalSubstitutor = originalSubstitutor.put(getOriginalElement(entry.getKey()), value == null ? null : originalize(value)); } return originalSubstitutor; } public static void initOffsets(final PsiFile file, final Project project, final OffsetMap offsetMap){ int selectionEndOffset = offsetMap.getOffset(CompletionInitializationContext.SELECTION_END_OFFSET); PsiElement element = file.findElementAt(selectionEndOffset); if (element == null) return; if (LEFT_PAREN.accepts(element)) { selectionEndOffset--; element = file.findElementAt(selectionEndOffset); if (element == null) return; } final PsiReference reference = file.findReferenceAt(selectionEndOffset); if(reference != null) { /* if(reference instanceof PsiJavaCodeReferenceElement){ offsetMap.addOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET, element.getParent().getTextRange().getEndOffset()); } else{ } */ offsetMap.addOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET, reference.getElement().getTextRange().getStartOffset() + reference.getRangeInElement().getEndOffset()); element = file.findElementAt(offsetMap.getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET)); } else if (isWord(element)){ /* if(element instanceof PsiIdentifier && element.getParent() instanceof PsiJavaCodeReferenceElement){ offsetMap.addOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET, element.getParent().getTextRange().getEndOffset()); } else{ } */ offsetMap.addOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET, element.getTextRange().getEndOffset()); element = file.findElementAt(offsetMap.getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET)); if (element == null) return; } if (element instanceof PsiWhiteSpace && ( !element.textContains('\n') || CodeStyleSettingsManager.getInstance(project).getCurrentSettings().METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE ) ){ element = file.findElementAt(element.getTextRange().getEndOffset()); } if (LEFT_PAREN.accepts(element)) { offsetMap.addOffset(LPAREN_OFFSET, element.getTextRange().getStartOffset()); PsiElement list = element.getParent(); PsiElement last = list.getLastChild(); if (last instanceof PsiJavaToken && ((PsiJavaToken)last).getTokenType() == JavaTokenType.RPARENTH) { offsetMap.addOffset(RPAREN_OFFSET, last.getTextRange().getStartOffset()); } offsetMap.addOffset(ARG_LIST_END_OFFSET, list.getTextRange().getEndOffset()); } } static boolean isWord(PsiElement element) { if (element instanceof PsiIdentifier || element instanceof PsiKeyword) { return true; } else if (element instanceof PsiJavaToken) { final String text = element.getText(); if (PsiKeyword.TRUE.equals(text)) return true; if (PsiKeyword.FALSE.equals(text)) return true; if (PsiKeyword.NULL.equals(text)) return true; return false; } else if (element instanceof PsiDocToken) { IElementType tokenType = ((PsiDocToken)element).getTokenType(); return tokenType == JavaDocTokenType.DOC_TAG_VALUE_TOKEN || tokenType == JavaDocTokenType.DOC_TAG_NAME; } else if (element instanceof XmlToken) { IElementType tokenType = ((XmlToken)element).getTokenType(); return tokenType == XmlTokenType.XML_TAG_NAME || tokenType == XmlTokenType.XML_NAME || tokenType == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN || // html data chars contains whitespaces (tokenType == XmlTokenType.XML_DATA_CHARACTERS && !(element.getParent() instanceof HtmlTag)); } else { return false; } } public static void resetParensInfo(final OffsetMap offsetMap) { offsetMap.removeOffset(LPAREN_OFFSET); offsetMap.removeOffset(RPAREN_OFFSET); offsetMap.removeOffset(ARG_LIST_END_OFFSET); offsetMap.removeOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET); } @Nullable public static List getAllPsiElements(final LookupElement item) { List allMethods = item.getUserData(ALL_METHODS_ATTRIBUTE); if (allMethods != null) return allMethods; if (item.getObject() instanceof PsiElement) return Arrays.asList((PsiElement)item.getObject()); return null; } @Nullable private static PsiType getPsiType(final Object o) { if (o instanceof PsiVariable) { return ((PsiVariable)o).getType(); } else if (o instanceof PsiMethod) { return ((PsiMethod)o).getReturnType(); } else if (o instanceof PsiClass) { final PsiClass psiClass = (PsiClass)o; return JavaPsiFacade.getInstance(psiClass.getProject()).getElementFactory().createType(psiClass); } else if (o instanceof PsiExpression) { return ((PsiExpression)o).getType(); } return null; } public static int getNameEndMatchingDegree(final String name, ExpectedTypeInfo[] expectedInfos, String prefix) { int res = 0; if (name != null && expectedInfos != null) { if (prefix.equals(name)) { res = Integer.MAX_VALUE; } else { final List words = NameUtil.nameToWordsLowerCase(name); final List wordsNoDigits = NameUtil.nameToWordsLowerCase(truncDigits(name)); int max1 = calcMatch(words, 0, expectedInfos); max1 = calcMatch(wordsNoDigits, max1, expectedInfos); res = max1; } } return res; } static String truncDigits(String name){ int count = name.length() - 1; while (count >= 0) { char c = name.charAt(count); if (!Character.isDigit(c)) break; count--; } return name.substring(0, count + 1); } private static int calcMatch(final List words, int max, ExpectedTypeInfo[] myExpectedInfos) { for (ExpectedTypeInfo myExpectedInfo : myExpectedInfos) { String expectedName = ((ExpectedTypeInfoImpl)myExpectedInfo).expectedName; if (expectedName == null) continue; max = calcMatch(expectedName, words, max); max = calcMatch(truncDigits(expectedName), words, max); } return max; } static int calcMatch(final String expectedName, final List words, int max) { if (expectedName == null) return max; String[] expectedWords = NameUtil.nameToWords(expectedName); int limit = Math.min(words.size(), expectedWords.length); for (int i = 0; i < limit; i++) { String word = words.get(words.size() - i - 1); String expectedWord = expectedWords[expectedWords.length - i - 1]; if (word.equalsIgnoreCase(expectedWord)) { max = Math.max(max, i + 1); } else { break; } } return max; } @Nullable static String getLookupObjectName(Object o) { if (o instanceof PsiVariable) { final PsiVariable variable = (PsiVariable)o; JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(variable.getProject()); VariableKind variableKind = codeStyleManager.getVariableKind(variable); return codeStyleManager.variableNameToPropertyName(variable.getName(), variableKind); } if (o instanceof PsiMethod) { return ((PsiMethod)o).getName(); } return null; } @Nullable public static PsiType getLookupElementType(final LookupElement element) { TypedLookupItem typed = element.as(TypedLookupItem.CLASS_CONDITION_KEY); if (typed != null) { return typed.getType(); } final PsiType qualifierType = getPsiType(element.getObject()); final LookupItem lookupItem = element.as(LookupItem.CLASS_CONDITION_KEY); if (lookupItem != null) { final Object o = lookupItem.getAttribute(LookupItem.TYPE); if (o instanceof PsiType) { return (PsiType)o; } final PsiSubstitutor substitutor = (PsiSubstitutor)lookupItem.getAttribute(LookupItem.SUBSTITUTOR); if (substitutor != null) { return substitutor.substitute(qualifierType); } } return qualifierType; } @Nullable public static PsiType getQualifiedMemberReferenceType(@Nullable PsiType qualifierType, @NotNull final PsiMember member) { final ClassCandidateInfo info = TypeConversionUtil.splitType(qualifierType, member); if (info == null) { return null; } final PsiClass element = info.getElement(); assert element != null; final Ref subst = Ref.create(PsiSubstitutor.EMPTY); class MyProcessor extends BaseScopeProcessor implements NameHint, ElementClassHint { public boolean execute(PsiElement element, ResolveState state) { if (element == member) { subst.set(state.get(PsiSubstitutor.KEY)); } return true; } public String getName(ResolveState state) { return member.getName(); } public boolean shouldProcess(DeclaractionKind kind) { return member instanceof PsiEnumConstant ? kind == DeclaractionKind.ENUM_CONST : member instanceof PsiField ? kind == DeclaractionKind.FIELD : kind == DeclaractionKind.METHOD; } @Override public T getHint(Key hintKey) { return hintKey == NameHint.KEY || hintKey == ElementClassHint.KEY ? (T)this : null; } } element.processDeclarations(new MyProcessor(), ResolveState.initial().put(PsiSubstitutor.KEY, info.getSubstitutor()), null, member); PsiType rawType = member instanceof PsiField ? ((PsiField) member).getType() : ((PsiMethod) member).getReturnType(); return subst.get().substitute(rawType); } public static Set processJavaReference(PsiElement element, PsiJavaReference javaReference, ElementFilter elementFilter, final boolean checkAccess, @Nullable final PrefixMatcher matcher, CompletionParameters parameters) { final THashSet set = new THashSet(); final Condition nameCondition = matcher == null ? null : new Condition() { public boolean value(String s) { return matcher.prefixMatches(s); } }; final JavaCompletionProcessor processor = new JavaCompletionProcessor(element, elementFilter, checkAccess, nameCondition); javaReference.processVariants(processor); final Collection plainResults = processor.getResults(); final PsiType qualifierType = processor.getQualifierType(); PsiType castedQualifierType = addQualifierCastingVariants(javaReference, processor, set, parameters); boolean mayHighlight = qualifierType != null && (castedQualifierType == null || !qualifierType.isAssignableFrom(castedQualifierType)); final boolean pkgContext = inSomePackage(element); final Set mentioned = new THashSet(); for (CompletionElement completionElement : plainResults) { LookupElement item = createLookupElement(completionElement, qualifierType); if (item != null) { final Object o = item.getObject(); if (o instanceof PsiClass && !isSourceLevelAccessible(element, (PsiClass)o, pkgContext)) { continue; } if (o instanceof PsiMember) { mentioned.add((PsiMember)o); } set.add(mayHighlight ? highlightIfNeeded(qualifierType, item) : item); } } if (javaReference instanceof PsiReferenceExpression && !((PsiReferenceExpression)javaReference).isQualified()) { final StaticMemberProcessor memberProcessor = JavaGlobalMemberNameCompletionContributor.completeStaticMembers(element); memberProcessor.processMembersOfRegisteredClasses(matcher, new PairConsumer() { @Override public void consume(PsiMember member, PsiClass psiClass) { if (!mentioned.contains(member) && processor.satisfies(member, ResolveState.initial())) { set.add(memberProcessor.createLookupElement(member, psiClass, true)); } } }); } return set; } @Nullable private static PsiType addQualifierCastingVariants(PsiJavaReference javaReference, JavaCompletionProcessor processor, THashSet set, CompletionParameters parameters) { if (javaReference instanceof PsiReferenceExpression) { final PsiReferenceExpression refExpr = (PsiReferenceExpression)javaReference; final PsiExpression qualifier = refExpr.getQualifierExpression(); if (qualifier != null) { final Project project = qualifier.getProject(); final PairFunction evaluator = refExpr.getContainingFile().getCopyableUserData(DYNAMIC_TYPE_EVALUATOR); PsiReferenceExpression context = refExpr; PsiType type = null; if (evaluator != null) { type = evaluator.fun(qualifier, parameters); context = null; } if (type == null) { type = GuessManager.getInstance(project).getControlFlowExpressionType(qualifier); context = refExpr; } if (type != null) { processor.clear(); return addQualifierCastingVariants(processor, refExpr, type, set, context); } } } return null; } private static PsiType addQualifierCastingVariants(JavaCompletionProcessor processor, PsiReferenceExpression refExpr, PsiType castTo, THashSet set, final PsiReferenceExpression context) { Project project = refExpr.getProject(); PsiExpression qualifier = refExpr.getQualifierExpression(); assert qualifier != null; final String newText = "((" + castTo.getCanonicalText() + ") " + qualifier.getText() + ")." + refExpr.getReferenceName(); final PsiExpression newRef = JavaPsiFacade.getElementFactory(project).createExpressionFromText(newText, context); ((PsiReferenceExpression)newRef).processVariants(processor); final LookupElement castItem = PsiTypeLookupItem.createLookupItem(castTo, refExpr); for (CompletionElement completionElement : processor.getResults()) { final LookupElement item = createLookupElement(completionElement, castTo); if (item != null) { set.add(highlightIfNeeded(castTo, castQualifier(project, item, castItem))); } } return castTo; } private static LookupElementDecorator castQualifier(final Project project, LookupElement item, final LookupElement to) { return LookupElementDecorator.withInsertHandler(item, new InsertHandlerDecorator() { public void handleInsert(InsertionContext context, LookupElementDecorator item) { final Document document = context.getEditor().getDocument(); PsiDocumentManager.getInstance(project).commitDocument(document); final PsiFile file = context.getFile(); final PsiJavaCodeReferenceElement ref = PsiTreeUtil.findElementOfClassAtOffset(file, context.getStartOffset(), PsiJavaCodeReferenceElement.class, false); if (ref != null) { final PsiElement qualifier = ref.getQualifier(); if (qualifier != null) { final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(project); final String parenSpace = settings.SPACE_WITHIN_PARENTHESES ? " " : ""; document.insertString(qualifier.getTextRange().getEndOffset(), parenSpace + ")"); final String spaceWithin = settings.SPACE_WITHIN_CAST_PARENTHESES ? " " : ""; final String prefix = "(" + parenSpace + "(" + spaceWithin; final String spaceAfter = settings.SPACE_AFTER_TYPE_CAST ? " " : ""; final int exprStart = qualifier.getTextRange().getStartOffset(); document.insertString(exprStart, prefix + spaceWithin + ")" + spaceAfter); CompletionUtil.emulateInsertion(context, exprStart + prefix.length(), to); context.getEditor().getCaretModel().moveToOffset(context.getTailOffset()); } } item.getDelegate().handleInsert(context); } }); } private static LookupElement highlightIfNeeded(@NotNull PsiType qualifierType, @NotNull LookupElement item) { Object o = item.getObject(); if (qualifierType instanceof PsiArrayType) { if (o instanceof PsiField || o instanceof PsiMethod) { //length and clone() PsiElement parent = ((PsiElement)o).getParent(); if (parent instanceof PsiClass && parent.getContainingFile().getVirtualFile() == null) { //yes, they're a bit dummy return highlight(item); } } } else if (qualifierType instanceof PsiClassType) { PsiClass qualifierClass = ((PsiClassType)qualifierType).resolve(); if (o instanceof PsiField || o instanceof PsiMethod || o instanceof PsiClass) { PsiElement parent = ((PsiElement)o).getParent(); if (parent != null && parent.equals(qualifierClass)) { return highlight(item); } } } return item; } private static LookupElement highlight(LookupElement decorator) { return PrioritizedLookupElement.withGrouping( LookupElementDecorator.withRenderer(decorator, new LookupElementRenderer>() { @Override public void renderElement(LookupElementDecorator element, LookupElementPresentation presentation) { element.getDelegate().renderElement(presentation); presentation.setItemTextBold(true); } }), 1); } private static LookupItem createLookupElement(CompletionElement completionElement, PsiType qualifierType) { Object completion = completionElement.getElement(); assert !(completion instanceof LookupElement); LookupElement _ret = LookupItemUtil.objectToLookupItem(completion); if (_ret == null || !(_ret instanceof LookupItem)) return null; LookupItem ret = (LookupItem)_ret; final PsiSubstitutor substitutor = completionElement.getSubstitutor(); if (substitutor != null) { ret.setAttribute(LookupItem.SUBSTITUTOR, substitutor); } ret.putUserData(QUALIFIER_TYPE_ATTR, qualifierType); return ret; } public static boolean hasAccessibleConstructor(PsiType type) { if (type instanceof PsiArrayType) return true; final PsiClass psiClass = PsiUtil.resolveClassInType(type); if (psiClass == null) return false; if (!(psiClass instanceof PsiCompiledElement)) return true; final PsiMethod[] methods = psiClass.getConstructors(); if (methods.length == 0) return true; for (final PsiMethod method : methods) { if (!method.hasModifierProperty(PsiModifier.PRIVATE)) return true; } return false; } public static LookupItem qualify(final LookupItem ret) { if (!(ret instanceof JavaMethodCallElement)) { final PsiMember completionElement = (PsiMember)ret.getObject(); String prefix = ""; PsiClass containingClass = completionElement.getContainingClass(); while (containingClass != null) { final String className = containingClass.getName(); if (className == null) { break; } prefix = className + "." + prefix; final PsiElement parent = containingClass.getParent(); if (!(parent instanceof PsiClass)) { break; } containingClass = (PsiClass)parent; } if (StringUtil.isNotEmpty(prefix)) { ret.setLookupString(prefix + ret.getLookupString()); } } return ret.forceQualify(); } public static LookupItem setShowFQN(final LookupItem ret) { final PsiClass psiClass = (PsiClass)ret.getObject(); @NonNls String packageName = PsiFormatUtil.getPackageDisplayName(psiClass); final String tailText = (String)ret.getAttribute(LookupItem.TAIL_TEXT_ATTR); ret.setAttribute(LookupItem.TAIL_TEXT_ATTR, StringUtil.notNullize(tailText) + " (" + packageName + ")"); ret.setAttribute(LookupItem.TAIL_TEXT_SMALL_ATTR, ""); return ret; } @Nullable static PsiElement getQualifier(final PsiElement element) { return element instanceof PsiJavaCodeReferenceElement ? ((PsiJavaCodeReferenceElement)element).getQualifier() : null; } public static boolean containsMethodCalls(@Nullable final PsiElement qualifier) { if (qualifier == null) return false; if (qualifier instanceof PsiMethodCallExpression) return true; if (qualifier instanceof PsiArrayAccessExpression) { return containsMethodCalls(((PsiArrayAccessExpression)qualifier).getArrayExpression()); } return containsMethodCalls(getQualifier(qualifier)); } @Nullable static ElementFilter recursionFilter(PsiElement element) { if (PsiJavaPatterns.psiElement().afterLeaf(PsiKeyword.RETURN).inside(PsiReturnStatement.class).accepts(element)) { return new ExcludeDeclaredFilter(ElementClassFilter.METHOD); } if (PsiJavaPatterns.psiElement().inside( PsiJavaPatterns.or( PsiJavaPatterns.psiElement(PsiAssignmentExpression.class), PsiJavaPatterns.psiElement(PsiVariable.class))). andNot(PsiJavaPatterns.psiElement().afterLeaf(".")).accepts(element)) { return new AndFilter(new ExcludeSillyAssignment(), new ExcludeDeclaredFilter(new ClassFilter(PsiVariable.class))); } return null; } public static int insertClassReference(@NotNull PsiClass psiClass, @NotNull PsiFile file, int offset) { return insertClassReference(psiClass, file, offset, offset); } public static int insertClassReference(PsiClass psiClass, PsiFile file, int startOffset, int endOffset) { final Project project = file.getProject(); PsiDocumentManager.getInstance(project).commitAllDocuments(); final PsiManager manager = file.getManager(); final Document document = FileDocumentManager.getInstance().getDocument(file.getViewProvider().getVirtualFile()); final PsiReference reference = file.findReferenceAt(startOffset); if (reference != null) { final PsiElement resolved = reference.resolve(); if (resolved instanceof PsiClass) { if (((PsiClass)resolved).getQualifiedName() == null || manager.areElementsEquivalent(psiClass, resolved)) { return startOffset; } } } String name = psiClass.getName(); document.replaceString(startOffset, endOffset, name); final RangeMarker toDelete = insertSpace(startOffset + name.length(), document); PsiDocumentManager.getInstance(project).commitAllDocuments(); int newStartOffset = startOffset; PsiElement element = file.findElementAt(startOffset); if (element instanceof PsiIdentifier) { PsiElement parent = element.getParent(); if (parent instanceof PsiJavaCodeReferenceElement && !((PsiJavaCodeReferenceElement)parent).isQualified() && !(parent.getParent() instanceof PsiPackageStatement)) { PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement)parent; if (!psiClass.getManager().areElementsEquivalent(psiClass, resolveReference(ref))) { final boolean staticImport = ref instanceof PsiImportStaticReferenceElement; PsiElement newElement = staticImport ? ((PsiImportStaticReferenceElement)ref).bindToTargetClass(psiClass) : ref.bindToElement(psiClass); RangeMarker marker = document.createRangeMarker(newElement.getTextRange()); newElement = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(newElement); newStartOffset = marker.getStartOffset(); if (!staticImport && newElement instanceof PsiJavaCodeReferenceElement && !psiClass.getManager().areElementsEquivalent(psiClass, resolveReference((PsiReference)newElement))) { final String qName = psiClass.getQualifiedName(); if (qName != null) { document.replaceString(newStartOffset, newElement.getTextRange().getEndOffset(), qName); } } } } } if (toDelete.isValid()) { document.deleteString(toDelete.getStartOffset(), toDelete.getEndOffset()); } return newStartOffset; } @Nullable static PsiElement resolveReference(final PsiReference psiReference) { if (psiReference instanceof PsiPolyVariantReference) { final ResolveResult[] results = ((PsiPolyVariantReference)psiReference).multiResolve(true); if (results.length == 1) return results[0].getElement(); } return psiReference.resolve(); } public static RangeMarker insertSpace(final int endOffset, final Document document) { final CharSequence chars = document.getCharsSequence(); final int length = chars.length(); final RangeMarker toDelete; if (endOffset < length && Character.isJavaIdentifierPart(chars.charAt(endOffset))){ document.insertString(endOffset, " "); toDelete = document.createRangeMarker(endOffset, endOffset + 1); } else if (endOffset >= length) { toDelete = document.createRangeMarker(length, length); } else { toDelete = document.createRangeMarker(endOffset, endOffset); } toDelete.setGreedyToLeft(true); toDelete.setGreedyToRight(true); return toDelete; } public static void insertParentheses(final InsertionContext context, final LookupElement item, boolean overloadsMatter, boolean hasParams) { insertParentheses(context, item, overloadsMatter, hasParams, false); } public static void insertParentheses(final InsertionContext context, final LookupElement item, boolean overloadsMatter, boolean hasParams, final boolean forceClosingParenthesis) { final Editor editor = context.getEditor(); final char completionChar = context.getCompletionChar(); final PsiFile file = context.getFile(); final TailType tailType = completionChar == '(' ? TailType.NONE : LookupItem.handleCompletionChar(context.getEditor(), item, completionChar); final boolean hasTail = tailType != TailType.NONE && tailType != TailType.UNKNOWN; final boolean smart = completionChar == Lookup.COMPLETE_STATEMENT_SELECT_CHAR; final boolean addCompletionChar = context.shouldAddCompletionChar(); context.setAddCompletionChar(false); final boolean needRightParenth = forceClosingParenthesis || !smart && (CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET || hasTail); if (hasTail) { hasParams = false; } final CodeStyleSettings styleSettings = CodeStyleSettingsManager.getSettings(context.getProject()); ParenthesesInsertHandler.getInstance(hasParams, styleSettings.SPACE_BEFORE_METHOD_CALL_PARENTHESES, styleSettings.SPACE_WITHIN_METHOD_CALL_PARENTHESES && hasParams, needRightParenth, styleSettings.METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE ).handleInsert(context, item); if (hasParams) { // Invoke parameters popup AutoPopupController.getInstance(file.getProject()).autoPopupParameterInfo(editor, overloadsMatter ? null : (PsiElement)item.getObject()); } if (smart || needRightParenth && addCompletionChar) { TailType toInsert = tailType; LookupItem lookupItem = item.as(LookupItem.CLASS_CONDITION_KEY); if (lookupItem == null || lookupItem.getAttribute(LookupItem.TAIL_TYPE_ATTR) != TailType.UNKNOWN) { if (!hasTail && item.getObject() instanceof PsiMethod && ((PsiMethod)item.getObject()).getReturnType() == PsiType.VOID) { PsiDocumentManager.getInstance(file.getProject()).commitAllDocuments(); if (psiElement().beforeLeaf(psiElement().withText(".")).accepts(file.findElementAt(context.getTailOffset() - 1))) { return; } toInsert = TailType.SEMICOLON; } } toInsert.processTail(editor, context.getTailOffset()); if (context.getCompletionChar() == '.') { AutoPopupController.getInstance(file.getProject()).autoPopupMemberLookup(context.getEditor(), null); } } } //need to shorten references in type argument list public static void shortenReference(final PsiFile file, final int offset) throws IncorrectOperationException { final PsiDocumentManager manager = PsiDocumentManager.getInstance(file.getProject()); final Document document = manager.getDocument(file); assert document != null; manager.commitDocument(document); final PsiReference ref = file.findReferenceAt(offset); if (ref instanceof PsiJavaCodeReferenceElement) { JavaCodeStyleManager.getInstance(file.getProject()).shortenClassReferences((PsiJavaCodeReferenceElement)ref); } } public static boolean hasAccessibleInnerClass(@NotNull PsiClass psiClass, @NotNull PsiElement position) { final PsiClass[] inners = psiClass.getInnerClasses(); if (inners.length > 0) { PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(position.getProject()).getResolveHelper(); for (PsiClass inner : inners) { if (inner.hasModifierProperty(PsiModifier.STATIC) && resolveHelper.isAccessible(inner, position, null)) { return true; } } } return false; } public static boolean inSomePackage(PsiElement context) { PsiFile contextFile = context.getContainingFile(); return contextFile instanceof PsiClassOwner && StringUtil.isNotEmpty(((PsiClassOwner)contextFile).getPackageName()); } public static boolean isSourceLevelAccessible(PsiElement context, PsiClass psiClass, final boolean pkgContext) { if (!JavaPsiFacade.getInstance(psiClass.getProject()).getResolveHelper().isAccessible(psiClass, context, null)) { return false; } if (pkgContext) { PsiFile classFile = psiClass.getContainingFile(); if (classFile instanceof PsiClassOwner && StringUtil.isEmpty(((PsiClassOwner)classFile).getPackageName())) { return false; } } return true; } public static boolean isDefinitelyExpected(PsiClass psiClass, Set expectedTypes, PsiElement position) { final PsiClassType classType = JavaPsiFacade.getElementFactory(psiClass.getProject()).createType(psiClass); for (PsiType expectedType : expectedTypes) { if (expectedType instanceof PsiArrayType) return false; } for (PsiType type : expectedTypes) { if (type instanceof PsiClassType && ((PsiClassType)type).rawType().isAssignableFrom(classType)) { return true; } } return !hasAccessibleInnerClass(psiClass, position); } }