From 7156a9cd0664b32a5dc38655a1f7b7ace7dafebb Mon Sep 17 00:00:00 2001 From: peter Date: Sun, 25 Oct 2015 17:09:25 +0100 Subject: [PATCH] extract various smart completion conversions into separate smaller classes from ReferenceExpressionCompletionContributor --- .../completion/ArrayMemberAccess.java | 73 ++++ .../completion/ChainedCallCompletion.java | 137 +++++++ .../completion/FromArrayConversion.java | 94 +++++ ...erenceExpressionCompletionContributor.java | 377 +----------------- .../completion/SlowerTypeConversions.java | 124 ++++++ .../completion/ToArrayConversion.java | 131 ++++++ 6 files changed, 566 insertions(+), 370 deletions(-) create mode 100644 java/java-impl/src/com/intellij/codeInsight/completion/ArrayMemberAccess.java create mode 100644 java/java-impl/src/com/intellij/codeInsight/completion/ChainedCallCompletion.java create mode 100644 java/java-impl/src/com/intellij/codeInsight/completion/FromArrayConversion.java create mode 100644 java/java-impl/src/com/intellij/codeInsight/completion/SlowerTypeConversions.java create mode 100644 java/java-impl/src/com/intellij/codeInsight/completion/ToArrayConversion.java diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/ArrayMemberAccess.java b/java/java-impl/src/com/intellij/codeInsight/completion/ArrayMemberAccess.java new file mode 100644 index 000000000000..1aa48f78a47f --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/completion/ArrayMemberAccess.java @@ -0,0 +1,73 @@ +/* + * Copyright 2000-2015 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.lookup.ExpressionLookupItem; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.featureStatistics.FeatureUsageTracker; +import com.intellij.openapi.util.Iconable; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.util.Consumer; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.Nullable; + +import static com.intellij.codeInsight.completion.ReferenceExpressionCompletionContributor.createExpression; +import static com.intellij.codeInsight.completion.ReferenceExpressionCompletionContributor.getQualifierText; +import static com.intellij.codeInsight.completion.ReferenceExpressionCompletionContributor.getSpace; + +/** + * @author peter + */ +class ArrayMemberAccess { + static void addArrayMemberAccessors(final PsiElement element, final String prefix, final PsiType itemType, + final PsiElement qualifier, final Consumer result, PsiModifierListOwner object, + final PsiType expectedType) + throws IncorrectOperationException { + if (itemType instanceof PsiArrayType && expectedType.isAssignableFrom(((PsiArrayType)itemType).getComponentType())) { + final PsiExpression conversion = createExpression(getQualifierText(qualifier) + prefix + "[0]", element); + result.consume(new ExpressionLookupItem(conversion, object.getIcon(Iconable.ICON_FLAG_VISIBILITY), prefix + "[...]", prefix) { + @Override + public void handleInsert(InsertionContext context) { + FeatureUsageTracker.getInstance().triggerFeatureUsed(JavaCompletionFeatures.SECOND_SMART_COMPLETION_ARRAY_MEMBER); + + final int tailOffset = context.getTailOffset(); + final String callSpace = getSpace(CodeStyleSettingsManager.getSettings(element.getProject()).SPACE_WITHIN_BRACKETS); + context.getDocument().insertString(tailOffset, "[" + callSpace + callSpace + "]"); + context.getEditor().getCaretModel().moveToOffset(tailOffset + 1 + callSpace.length()); + } + }); + } + } + + @Nullable + static ExpressionLookupItem getSingleArrayElementAccess(PsiElement element, LookupElement item) { + if (item.getObject() instanceof PsiLocalVariable) { + final PsiLocalVariable variable = (PsiLocalVariable)item.getObject(); + final PsiType type = variable.getType(); + final PsiExpression expression = variable.getInitializer(); + if (type instanceof PsiArrayType && expression instanceof PsiNewExpression) { + final PsiNewExpression newExpression = (PsiNewExpression)expression; + final PsiExpression[] dimensions = newExpression.getArrayDimensions(); + if (dimensions.length == 1 && "1".equals(dimensions[0].getText()) && newExpression.getArrayInitializer() == null) { + final String text = variable.getName() + "[0]"; + return new ExpressionLookupItem(createExpression(text, element), variable.getIcon(Iconable.ICON_FLAG_VISIBILITY), text, text); + } + } + } + return null; + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/ChainedCallCompletion.java b/java/java-impl/src/com/intellij/codeInsight/completion/ChainedCallCompletion.java new file mode 100644 index 000000000000..ba69c9d9632c --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/completion/ChainedCallCompletion.java @@ -0,0 +1,137 @@ +/* + * Copyright 2000-2015 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.lookup.LookupElement; +import com.intellij.featureStatistics.FeatureUsageTracker; +import com.intellij.patterns.PsiMethodPattern; +import com.intellij.patterns.StandardPatterns; +import com.intellij.psi.*; +import com.intellij.psi.filters.ElementFilter; +import com.intellij.psi.util.InheritanceUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.util.Consumer; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NonNls; + +import static com.intellij.patterns.PsiJavaPatterns.psiMethod; + +/** + * @author peter + */ +class ChainedCallCompletion { + private static final PsiMethodPattern OBJECT_METHOD_PATTERN = psiMethod().withName( + StandardPatterns.string().oneOf("hashCode", "equals", "finalize", "wait", "notify", "notifyAll", "getClass", "clone", "toString")). + definedInClass(CommonClassNames.JAVA_LANG_OBJECT); + + static void addChainedCallVariants(final PsiElement place, LookupElement qualifierItem, + final Consumer result, + PsiType qualifierType, + final PsiType expectedType, JavaSmartCompletionParameters parameters) throws + IncorrectOperationException { + final Object object = qualifierItem.getObject(); + if (OBJECT_METHOD_PATTERN.accepts(object) && !allowGetClass(object, parameters)) { + return; + } + + if (parameters.getParameters().getInvocationCount() < 3 && qualifierType.equalsToText(CommonClassNames.JAVA_LANG_STRING)) { + return; + } + + if (object instanceof PsiMethod && ((PsiMethod)object).getParameterList().getParametersCount() > 0) { + return; + } + + final PsiReferenceExpression mockRef = ReferenceExpressionCompletionContributor.createMockReference(place, qualifierType, qualifierItem); + if (mockRef == null) { + return; + } + + final ElementFilter filter = ReferenceExpressionCompletionContributor.getReferenceFilter(place, true); + for (final LookupElement item : ReferenceExpressionCompletionContributor.completeFinalReference(place, mockRef, filter, parameters)) { + if (shouldChain(place, qualifierType, expectedType, item)) { + result.consume(new JavaChainLookupElement(qualifierItem, item) { + @Override + public void handleInsert(InsertionContext context) { + FeatureUsageTracker.getInstance().triggerFeatureUsed(JavaCompletionFeatures.SECOND_SMART_COMPLETION_CHAIN); + super.handleInsert(context); + } + }); + } + } + } + + private static boolean shouldChain(PsiElement element, PsiType qualifierType, PsiType expectedType, LookupElement item) { + Object object = item.getObject(); + if (object instanceof PsiModifierListOwner && ((PsiModifierListOwner)object).hasModifierProperty(PsiModifier.STATIC)) { + return false; + } + + if (object instanceof PsiMethod) { + final PsiMethod method = (PsiMethod)object; + if (psiMethod().withName("toArray").withParameterCount(1) + .definedInClass(CommonClassNames.JAVA_UTIL_COLLECTION).accepts(method)) { + return false; + } + final PsiMethod parentMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class); + if (isUselessObjectMethod(method, parentMethod, qualifierType)) { + return false; + } + + final PsiType type = method.getReturnType(); + if (type instanceof PsiClassType) { + final PsiClassType classType = (PsiClassType)type; + final PsiClass psiClass = classType.resolve(); + if (psiClass instanceof PsiTypeParameter && method.getTypeParameterList() == psiClass.getParent()) { + final PsiTypeParameter typeParameter = (PsiTypeParameter)psiClass; + if (typeParameter.getExtendsListTypes().length == 0) return false; + if (!expectedType.isAssignableFrom(TypeConversionUtil.typeParameterErasure(typeParameter))) return false; + } + } + } + return true; + } + + private static boolean isUselessObjectMethod(PsiMethod method, PsiMethod parentMethod, PsiType qualifierType) { + if (!OBJECT_METHOD_PATTERN.accepts(method)) { + return false; + } + + if (OBJECT_METHOD_PATTERN.accepts(parentMethod) && method.getName().equals(parentMethod.getName())) { + return false; + } + + if ("toString".equals(method.getName())) { + if (qualifierType.equalsToText(CommonClassNames.JAVA_LANG_STRING_BUFFER) || + InheritanceUtil.isInheritor(qualifierType, CommonClassNames.JAVA_LANG_ABSTRACT_STRING_BUILDER)) { + return false; + } + } + + return true; + } + + private static boolean allowGetClass(final Object object, final JavaSmartCompletionParameters parameters) { + if (!"getClass".equals(((PsiMethod)object).getName())) return false; + + final PsiType type = parameters.getDefaultType(); + @NonNls final String canonicalText = type.getCanonicalText(); + if ("java.lang.ClassLoader".equals(canonicalText)) return true; + if (canonicalText.startsWith("java.lang.reflect.")) return true; + return false; + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/FromArrayConversion.java b/java/java-impl/src/com/intellij/codeInsight/completion/FromArrayConversion.java new file mode 100644 index 000000000000..67115bf32933 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/completion/FromArrayConversion.java @@ -0,0 +1,94 @@ +/* + * Copyright 2000-2015 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.lookup.ExpressionLookupItem; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.featureStatistics.FeatureUsageTracker; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.Consumer; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.PlatformIcons; +import org.jetbrains.annotations.Nullable; + +import static com.intellij.codeInsight.completion.ReferenceExpressionCompletionContributor.createExpression; +import static com.intellij.codeInsight.completion.ReferenceExpressionCompletionContributor.getSpace; + +/** + * @author peter + */ +class FromArrayConversion { + static void addConversionsFromArray(final PsiElement element, + final String prefix, + final PsiType itemType, + final Consumer result, + @Nullable PsiElement qualifier, + final PsiType expectedType) throws IncorrectOperationException { + final String methodName = getArraysConversionMethod(itemType, expectedType); + if (methodName == null) return; + + final String qualifierText = ReferenceExpressionCompletionContributor.getQualifierText(qualifier); + final PsiExpression conversion = createExpression("java.util.Arrays." + methodName + "(" + qualifierText + prefix + ")", element); + final String presentable = "Arrays." + methodName + "(" + qualifierText + prefix + ")"; + String[] lookupStrings = {StringUtil.isEmpty(qualifierText) ? presentable : prefix, prefix, presentable, methodName + "(" + prefix + ")"}; + result.consume(new ExpressionLookupItem(conversion, PlatformIcons.METHOD_ICON, presentable, lookupStrings) { + @Override + public void handleInsert(InsertionContext context) { + FeatureUsageTracker.getInstance().triggerFeatureUsed(JavaCompletionFeatures.SECOND_SMART_COMPLETION_ASLIST); + + int startOffset = context.getStartOffset() - qualifierText.length(); + final Project project = element.getProject(); + final String callSpace = getSpace(CodeStyleSettingsManager.getSettings(project).SPACE_WITHIN_METHOD_CALL_PARENTHESES); + final String newText = "java.util.Arrays." + methodName + "(" + callSpace + qualifierText + prefix + callSpace + ")"; + context.getDocument().replaceString(startOffset, context.getTailOffset(), newText); + + context.commitDocument(); + JavaCodeStyleManager.getInstance(project).shortenClassReferences(context.getFile(), startOffset, startOffset + CommonClassNames.JAVA_UTIL_ARRAYS.length()); + } + }); + } + + @Nullable + private static String getArraysConversionMethod(PsiType itemType, PsiType expectedType) { + String methodName = "asList"; + PsiType componentType = PsiUtil.extractIterableTypeParameter(expectedType, true); + if (componentType == null) { + methodName = "stream"; + componentType = getStreamComponentType(expectedType); + PsiPrimitiveType unboxedType = PsiPrimitiveType.getUnboxedType(componentType); + if (unboxedType != null) { + componentType = unboxedType; + } + } + + if (componentType == null || + !(itemType instanceof PsiArrayType) || + !componentType.isAssignableFrom(((PsiArrayType)itemType).getComponentType())) { + return null; + + } + return methodName; + } + + private static PsiType getStreamComponentType(PsiType expectedType) { + return PsiUtil.substituteTypeParameter(expectedType, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM, 0, true); + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/ReferenceExpressionCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/ReferenceExpressionCompletionContributor.java index 7db3f1e6a1ab..4b359b951589 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/ReferenceExpressionCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/ReferenceExpressionCompletionContributor.java @@ -17,30 +17,18 @@ package com.intellij.codeInsight.completion; import com.intellij.codeInsight.lookup.ExpressionLookupItem; import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.featureStatistics.FeatureUsageTracker; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Iconable; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.patterns.ElementPattern; -import com.intellij.patterns.PsiMethodPattern; import com.intellij.patterns.StandardPatterns; import com.intellij.psi.*; -import com.intellij.psi.codeStyle.CodeStyleSettingsManager; -import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.filters.*; import com.intellij.psi.filters.element.ModifierFilter; import com.intellij.psi.filters.types.AssignableFromFilter; import com.intellij.psi.infos.CandidateInfo; -import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.Consumer; import com.intellij.util.IncorrectOperationException; -import com.intellij.util.PlatformIcons; import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -49,33 +37,17 @@ import java.util.LinkedHashSet; import java.util.Set; import static com.intellij.patterns.PsiJavaPatterns.psiElement; -import static com.intellij.patterns.PsiJavaPatterns.psiMethod; /** * @author peter */ public class ReferenceExpressionCompletionContributor { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.ReferenceExpressionCompletionContributor"); - private static final PsiMethodPattern OBJECT_METHOD_PATTERN = psiMethod().withName( - StandardPatterns.string().oneOf("hashCode", "equals", "finalize", "wait", "notify", "notifyAll", "getClass", "clone", "toString")). - definedInClass(CommonClassNames.JAVA_LANG_OBJECT); - private static final PrefixMatcher TRUE_MATCHER = new PrefixMatcher("") { - @Override - public boolean prefixMatches(@NotNull String name) { - return true; - } - - @NotNull - @Override - public PrefixMatcher cloneWithPrefix(@NotNull String prefix) { - return this; - } - }; public static final ElementPattern IN_SWITCH_LABEL = psiElement().withSuperParent(2, psiElement(PsiSwitchLabelStatement.class).withSuperParent(2, PsiSwitchStatement.class)); @NotNull - private static ElementFilter getReferenceFilter(PsiElement element, boolean allowRecursion) { + static ElementFilter getReferenceFilter(PsiElement element, boolean allowRecursion) { //throw foo if (psiElement().withParent(psiElement(PsiReferenceExpression.class).withParent(PsiThrowStatement.class)).accepts(element)) { return TrueFilter.INSTANCE; @@ -136,7 +108,7 @@ public class ReferenceExpressionCompletionContributor { final Set base = JavaSmartCompletionContributor.completeReference(element, reference, filter, false, true, parameters.getParameters(), PrefixMatcher.ALWAYS_TRUE); for (final LookupElement item : new LinkedHashSet(base)) { - ExpressionLookupItem access = getSingleArrayElementAccess(element, item); + ExpressionLookupItem access = ArrayMemberAccess.getSingleArrayElementAccess(element, item); if (access != null) { base.add(access); PsiType type = access.getType(); @@ -147,28 +119,13 @@ public class ReferenceExpressionCompletionContributor { } if (secondTime) { - return new Runnable() { - @Override - public void run() { - for (final LookupElement item : base) { - addSecondCompletionVariants(element, reference, item, parameters, result); - } - if (!psiElement().afterLeaf(".").accepts(element)) { - BasicExpressionCompletionContributor.processDataflowExpressionTypes(element, null, TRUE_MATCHER, new Consumer() { - @Override - public void consume(LookupElement baseItem) { - addSecondCompletionVariants(element, reference, baseItem, parameters, result); - } - }); - } - } - }; + return new SlowerTypeConversions(base, element, reference, parameters, result); } } return null; } - private static Set completeFinalReference(final PsiElement element, PsiJavaCodeReferenceElement reference, ElementFilter filter, + static Set completeFinalReference(final PsiElement element, PsiJavaCodeReferenceElement reference, ElementFilter filter, final JavaSmartCompletionParameters parameters) { final Set used = parameters.getParameters().getInvocationCount() < 2 ? findConstantsUsedInSwitch(element) : Collections.emptySet(); @@ -239,229 +196,14 @@ public class ReferenceExpressionCompletionContributor { return Collections.emptySet(); } - @Nullable - private static ExpressionLookupItem getSingleArrayElementAccess(PsiElement element, LookupElement item) { - if (item.getObject() instanceof PsiLocalVariable) { - final PsiLocalVariable variable = (PsiLocalVariable)item.getObject(); - final PsiType type = variable.getType(); - final PsiExpression expression = variable.getInitializer(); - if (type instanceof PsiArrayType && expression instanceof PsiNewExpression) { - final PsiNewExpression newExpression = (PsiNewExpression)expression; - final PsiExpression[] dimensions = newExpression.getArrayDimensions(); - if (dimensions.length == 1 && "1".equals(dimensions[0].getText()) && newExpression.getArrayInitializer() == null) { - final String text = variable.getName() + "[0]"; - return new ExpressionLookupItem(createExpression(text, element), variable.getIcon(Iconable.ICON_FLAG_VISIBILITY), text, text); - } - } - } - return null; - } - - private static PsiExpression createExpression(String text, PsiElement element) { + static PsiExpression createExpression(String text, PsiElement element) { return JavaPsiFacade.getInstance(element.getProject()).getElementFactory().createExpressionFromText(text, element); } - private static void addSecondCompletionVariants(PsiElement element, PsiReference reference, LookupElement baseItem, - JavaSmartCompletionParameters parameters, Consumer result) { - final Object object = baseItem.getObject(); - - try { - PsiType itemType = JavaCompletionUtil.getLookupElementType(baseItem); - if (itemType instanceof PsiWildcardType) { - itemType = ((PsiWildcardType)itemType).getExtendsBound(); - } - if (itemType == null) return; - assert itemType.isValid() : baseItem + "; " + baseItem.getClass(); - - final PsiElement element1 = reference.getElement(); - final PsiElement qualifier = - element1 instanceof PsiJavaCodeReferenceElement ? ((PsiJavaCodeReferenceElement)element1).getQualifier() : null; - final PsiType expectedType = parameters.getExpectedType(); - if (!OBJECT_METHOD_PATTERN.accepts(object) || allowGetClass(object, parameters)) { - if (parameters.getParameters().getInvocationCount() >= 3 || !itemType.equalsToText(CommonClassNames.JAVA_LANG_STRING)) { - if (!(object instanceof PsiMethod && ((PsiMethod)object).getParameterList().getParametersCount() > 0)) { - addChainedCallVariants(element, baseItem, result, itemType, expectedType, parameters); - } - } - } - - final String prefix = getItemText(object); - if (prefix == null) return; - - addConversionsToArray(element, prefix, itemType, result, qualifier, expectedType); - - addToArrayConversions(element, object, prefix, itemType, result, qualifier, expectedType); - - addArrayMemberAccessors(element, prefix, itemType, qualifier, result, (PsiModifierListOwner)object, expectedType); - } - catch (IncorrectOperationException ignored) { - } - } - - private static void addArrayMemberAccessors(final PsiElement element, final String prefix, final PsiType itemType, - final PsiElement qualifier, final Consumer result, PsiModifierListOwner object, - final PsiType expectedType) - throws IncorrectOperationException { - if (itemType instanceof PsiArrayType && expectedType.isAssignableFrom(((PsiArrayType)itemType).getComponentType())) { - final PsiExpression conversion = createExpression(getQualifierText(qualifier) + prefix + "[0]", element); - result.consume(new ExpressionLookupItem(conversion, object.getIcon(Iconable.ICON_FLAG_VISIBILITY), prefix + "[...]", prefix) { - @Override - public void handleInsert(InsertionContext context) { - FeatureUsageTracker.getInstance().triggerFeatureUsed(JavaCompletionFeatures.SECOND_SMART_COMPLETION_ARRAY_MEMBER); - - final int tailOffset = context.getTailOffset(); - final String callSpace = getSpace(CodeStyleSettingsManager.getSettings(element.getProject()).SPACE_WITHIN_BRACKETS); - context.getDocument().insertString(tailOffset, "[" + callSpace + callSpace + "]"); - context.getEditor().getCaretModel().moveToOffset(tailOffset + 1 + callSpace.length()); - } - }); - } - } - - private static boolean allowGetClass(final Object object, final JavaSmartCompletionParameters parameters) { - if (!"getClass".equals(((PsiMethod)object).getName())) return false; - - final PsiType type = parameters.getDefaultType(); - @NonNls final String canonicalText = type.getCanonicalText(); - if ("java.lang.ClassLoader".equals(canonicalText)) return true; - if (canonicalText.startsWith("java.lang.reflect.")) return true; - return false; - } - - private static void addConversionsToArray(final PsiElement element, - final String prefix, - final PsiType itemType, - final Consumer result, - @Nullable PsiElement qualifier, - final PsiType expectedType) throws IncorrectOperationException { - final String methodName = getArraysConversionMethod(itemType, expectedType); - if (methodName == null) return; - - final String qualifierText = getQualifierText(qualifier); - final PsiExpression conversion = createExpression("java.util.Arrays." + methodName + "(" + qualifierText + prefix + ")", element); - final String presentable = "Arrays." + methodName + "(" + qualifierText + prefix + ")"; - String[] lookupStrings = {StringUtil.isEmpty(qualifierText) ? presentable : prefix, prefix, presentable, methodName + "(" + prefix + ")"}; - result.consume(new ExpressionLookupItem(conversion, PlatformIcons.METHOD_ICON, presentable, lookupStrings) { - @Override - public void handleInsert(InsertionContext context) { - FeatureUsageTracker.getInstance().triggerFeatureUsed(JavaCompletionFeatures.SECOND_SMART_COMPLETION_ASLIST); - - int startOffset = context.getStartOffset() - qualifierText.length(); - final Project project = element.getProject(); - final String callSpace = getSpace(CodeStyleSettingsManager.getSettings(project).SPACE_WITHIN_METHOD_CALL_PARENTHESES); - final String newText = "java.util.Arrays." + methodName + "(" + callSpace + qualifierText + prefix + callSpace + ")"; - context.getDocument().replaceString(startOffset, context.getTailOffset(), newText); - - context.commitDocument(); - JavaCodeStyleManager.getInstance(project).shortenClassReferences(context.getFile(), startOffset, startOffset + CommonClassNames.JAVA_UTIL_ARRAYS.length()); - } - }); - } - - @Nullable - private static String getArraysConversionMethod(PsiType itemType, PsiType expectedType) { - String methodName = "asList"; - PsiType componentType = PsiUtil.extractIterableTypeParameter(expectedType, true); - if (componentType == null) { - methodName = "stream"; - componentType = getStreamComponentType(expectedType); - PsiPrimitiveType unboxedType = PsiPrimitiveType.getUnboxedType(componentType); - if (unboxedType != null) { - componentType = unboxedType; - } - } - - if (componentType == null || - !(itemType instanceof PsiArrayType) || - !componentType.isAssignableFrom(((PsiArrayType)itemType).getComponentType())) { - return null; - - } - return methodName; - } - - private static PsiType getStreamComponentType(PsiType expectedType) { - return PsiUtil.substituteTypeParameter(expectedType, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM, 0, true); - } - - private static void addToArrayConversions(final PsiElement element, final Object object, final String prefix, final PsiType itemType, - final Consumer result, @Nullable final PsiElement qualifier, - final PsiType expectedType) { - final String callSpace = getSpace(CodeStyleSettingsManager.getSettings(element.getProject()).SPACE_WITHIN_METHOD_CALL_PARENTHESES); - final PsiType componentType = PsiUtil.extractIterableTypeParameter(itemType, true); - if (componentType == null || !(expectedType instanceof PsiArrayType)) return; - - final PsiArrayType type = (PsiArrayType)expectedType; - if (!type.getComponentType().isAssignableFrom(componentType) || - componentType instanceof PsiClassType && ((PsiClassType) componentType).hasParameters()) { - return; - } - - final String bracketSpace = getSpace(CodeStyleSettingsManager.getSettings(element.getProject()).SPACE_WITHIN_BRACKETS); - if (object instanceof PsiVariable && !JavaCompletionUtil.mayHaveSideEffects(qualifier)) { - final PsiVariable variable = (PsiVariable)object; - addToArrayConversion(element, prefix, - "new " + componentType.getCanonicalText() + - "[" + bracketSpace + getQualifierText(qualifier) + variable.getName() + ".size(" + callSpace + ")" + bracketSpace + "]", - "new " + getQualifierText(qualifier) + componentType.getPresentableText() + "[" + variable.getName() + ".size()]", result, qualifier); - } else { - boolean hasEmptyArrayField = false; - final PsiClass psiClass = PsiUtil.resolveClassInType(type); - if (psiClass != null) { - for (final PsiField field : psiClass.getAllFields()) { - if (field.hasModifierProperty(PsiModifier.STATIC) && field.hasModifierProperty(PsiModifier.FINAL) && - JavaPsiFacade.getInstance(field.getProject()).getResolveHelper().isAccessible(field, element, null) && - type.isAssignableFrom(field.getType()) && isEmptyArrayInitializer(field.getInitializer())) { - boolean needQualify; - try { - needQualify = !field.isEquivalentTo(((PsiReferenceExpression)createExpression(field.getName(), element)).resolve()); - } - catch (IncorrectOperationException e) { - continue; - } - - addToArrayConversion(element, prefix, - (needQualify ? field.getContainingClass().getQualifiedName() + "." : "") + field.getName(), - (needQualify ? field.getContainingClass().getName() + "." : "") + field.getName(), result, qualifier); - hasEmptyArrayField = true; - } - } - } - if (!hasEmptyArrayField) { - addToArrayConversion(element, prefix, - "new " + componentType.getCanonicalText() + "[" + bracketSpace + "0" + bracketSpace + "]", - "new " + componentType.getPresentableText() + "[0]", result, qualifier); - } - } - } - - private static String getQualifierText(@Nullable final PsiElement qualifier) { + static String getQualifierText(@Nullable final PsiElement qualifier) { return qualifier == null ? "" : qualifier.getText() + "."; } - private static void addChainedCallVariants(final PsiElement place, LookupElement qualifierItem, - final Consumer result, - PsiType qualifierType, - final PsiType expectedType, JavaSmartCompletionParameters parameters) throws IncorrectOperationException { - final PsiReferenceExpression mockRef = createMockReference(place, qualifierType, qualifierItem); - if (mockRef == null) { - return; - } - - final ElementFilter filter = getReferenceFilter(place, true); - for (final LookupElement item : completeFinalReference(place, mockRef, filter, parameters)) { - if (shouldChain(place, qualifierType, expectedType, item)) { - result.consume(new JavaChainLookupElement(qualifierItem, item) { - @Override - public void handleInsert(InsertionContext context) { - FeatureUsageTracker.getInstance().triggerFeatureUsed(JavaCompletionFeatures.SECOND_SMART_COMPLETION_CHAIN); - super.handleInsert(context); - } - }); - } - } - } - @Nullable public static PsiReferenceExpression createMockReference(final PsiElement place, @NotNull PsiType qualifierType, LookupElement qualifierItem) { PsiElementFactory factory = JavaPsiFacade.getElementFactory(place.getProject()); @@ -487,112 +229,7 @@ public class ReferenceExpressionCompletionContributor { .createContextWithXxxVariable(place, qualifierType)); } - private static boolean shouldChain(PsiElement element, PsiType qualifierType, PsiType expectedType, LookupElement item) { - Object object = item.getObject(); - if (object instanceof PsiModifierListOwner && ((PsiModifierListOwner)object).hasModifierProperty(PsiModifier.STATIC)) { - return false; - } - - if (object instanceof PsiMethod) { - final PsiMethod method = (PsiMethod)object; - if (psiMethod().withName("toArray").withParameterCount(1) - .definedInClass(CommonClassNames.JAVA_UTIL_COLLECTION).accepts(method)) { - return false; - } - final PsiMethod parentMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class); - if (isUselessObjectMethod(method, parentMethod, qualifierType)) { - return false; - } - - final PsiType type = method.getReturnType(); - if (type instanceof PsiClassType) { - final PsiClassType classType = (PsiClassType)type; - final PsiClass psiClass = classType.resolve(); - if (psiClass instanceof PsiTypeParameter && method.getTypeParameterList() == psiClass.getParent()) { - final PsiTypeParameter typeParameter = (PsiTypeParameter)psiClass; - if (typeParameter.getExtendsListTypes().length == 0) return false; - if (!expectedType.isAssignableFrom(TypeConversionUtil.typeParameterErasure(typeParameter))) return false; - } - } - } - return true; - } - - private static boolean isUselessObjectMethod(PsiMethod method, PsiMethod parentMethod, PsiType qualifierType) { - if (!OBJECT_METHOD_PATTERN.accepts(method)) { - return false; - } - - if (OBJECT_METHOD_PATTERN.accepts(parentMethod) && method.getName().equals(parentMethod.getName())) { - return false; - } - - if ("toString".equals(method.getName())) { - if (qualifierType.equalsToText(CommonClassNames.JAVA_LANG_STRING_BUFFER) || - InheritanceUtil.isInheritor(qualifierType, CommonClassNames.JAVA_LANG_ABSTRACT_STRING_BUILDER)) { - return false; - } - } - - return true; - } - - private static void addToArrayConversion(final PsiElement element, final String prefix, @NonNls final String expressionString, @NonNls String presentableString, final Consumer result, PsiElement qualifier) { - final boolean callSpace = CodeStyleSettingsManager.getSettings(element.getProject()).SPACE_WITHIN_METHOD_CALL_PARENTHESES; - final PsiExpression conversion; - try { - conversion = createExpression( - getQualifierText(qualifier) + prefix + ".toArray(" + getSpace(callSpace) + expressionString + getSpace(callSpace) + ")", element); - } - catch (IncorrectOperationException e) { - return; - } - - String[] lookupStrings = {prefix + ".toArray(" + getSpace(callSpace) + expressionString + getSpace(callSpace) + ")", presentableString}; - result.consume(new ExpressionLookupItem(conversion, PlatformIcons.METHOD_ICON, prefix + ".toArray(" + presentableString + ")", lookupStrings) { - @Override - public void handleInsert(InsertionContext context) { - FeatureUsageTracker.getInstance().triggerFeatureUsed(JavaCompletionFeatures.SECOND_SMART_COMPLETION_TOAR); - - context.commitDocument(); - JavaCodeStyleManager.getInstance(context.getProject()).shortenClassReferences(context.getFile(), context.getStartOffset(), context.getTailOffset()); - } - }); - } - - private static boolean isEmptyArrayInitializer(@Nullable PsiElement element) { - if (element instanceof PsiNewExpression) { - final PsiNewExpression expression = (PsiNewExpression)element; - final PsiExpression[] dimensions = expression.getArrayDimensions(); - for (final PsiExpression dimension : dimensions) { - if (!(dimension instanceof PsiLiteralExpression) || !"0".equals(dimension.getText())) { - return false; - } - } - final PsiArrayInitializerExpression initializer = expression.getArrayInitializer(); - if (initializer != null && initializer.getInitializers().length > 0) return false; - - return true; - } - return false; - } - - @Nullable - private static String getItemText(Object o) { - if (o instanceof PsiMethod) { - final PsiMethod method = (PsiMethod)o; - final PsiType type = method.getReturnType(); - if (PsiType.VOID.equals(type) || PsiType.NULL.equals(type)) return null; - if (method.getParameterList().getParametersCount() > 0) return null; - return method.getName() + "(" + getSpace(CodeStyleSettingsManager.getSettings(method.getProject()).SPACE_WITHIN_METHOD_CALL_PARENTHESES) + ")"; } - else if (o instanceof PsiVariable) { - return ((PsiVariable)o).getName(); - } - return null; - } - - private static String getSpace(boolean needSpace) { + static String getSpace(boolean needSpace) { return needSpace ? " " : ""; } - } diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/SlowerTypeConversions.java b/java/java-impl/src/com/intellij/codeInsight/completion/SlowerTypeConversions.java new file mode 100644 index 000000000000..5140c9668703 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/completion/SlowerTypeConversions.java @@ -0,0 +1,124 @@ +/* + * Copyright 2000-2015 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.lookup.LookupElement; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.util.Consumer; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +import static com.intellij.codeInsight.completion.ReferenceExpressionCompletionContributor.getSpace; +import static com.intellij.patterns.PsiJavaPatterns.psiElement; + +/** + * @author peter + */ +class SlowerTypeConversions implements Runnable { + private static final PrefixMatcher TRUE_MATCHER = new PrefixMatcher("") { + @Override + public boolean prefixMatches(@NotNull String name) { + return true; + } + + @NotNull + @Override + public PrefixMatcher cloneWithPrefix(@NotNull String prefix) { + return this; + } + }; + private final Set myBase; + private final PsiElement myElement; + private final PsiJavaCodeReferenceElement myReference; + private final JavaSmartCompletionParameters myParameters; + private final Consumer myResult; + + public SlowerTypeConversions(Set base, + PsiElement element, + PsiJavaCodeReferenceElement reference, + JavaSmartCompletionParameters parameters, Consumer result) { + myBase = base; + myElement = element; + myReference = reference; + myParameters = parameters; + myResult = result; + } + + @Override + public void run() { + for (final LookupElement item : myBase) { + addSecondCompletionVariants(myElement, myReference, item, myParameters, myResult); + } + if (!psiElement().afterLeaf(".").accepts(myElement)) { + BasicExpressionCompletionContributor.processDataflowExpressionTypes(myElement, null, TRUE_MATCHER, new Consumer() { + @Override + public void consume(LookupElement baseItem) { + addSecondCompletionVariants(myElement, myReference, baseItem, myParameters, myResult); + } + }); + } + } + + private static void addSecondCompletionVariants(PsiElement element, PsiReference reference, LookupElement baseItem, + JavaSmartCompletionParameters parameters, Consumer result) { + final Object object = baseItem.getObject(); + + try { + PsiType itemType = JavaCompletionUtil.getLookupElementType(baseItem); + if (itemType instanceof PsiWildcardType) { + itemType = ((PsiWildcardType)itemType).getExtendsBound(); + } + if (itemType == null) return; + assert itemType.isValid() : baseItem + "; " + baseItem.getClass(); + + final PsiElement element1 = reference.getElement(); + final PsiElement qualifier = + element1 instanceof PsiJavaCodeReferenceElement ? ((PsiJavaCodeReferenceElement)element1).getQualifier() : null; + final PsiType expectedType = parameters.getExpectedType(); + ChainedCallCompletion.addChainedCallVariants(element, baseItem, result, itemType, expectedType, parameters); + + final String prefix = getItemText(object); + if (prefix == null) return; + + FromArrayConversion.addConversionsFromArray(element, prefix, itemType, result, qualifier, expectedType); + + ToArrayConversion.addToArrayConversions(element, object, prefix, itemType, result, qualifier, expectedType); + + ArrayMemberAccess.addArrayMemberAccessors(element, prefix, itemType, qualifier, result, (PsiModifierListOwner)object, expectedType); + } + catch (IncorrectOperationException ignored) { + } + } + + @Nullable + private static String getItemText(Object o) { + if (o instanceof PsiMethod) { + final PsiMethod method = (PsiMethod)o; + final PsiType type = method.getReturnType(); + if (PsiType.VOID.equals(type) || PsiType.NULL.equals(type)) return null; + if (method.getParameterList().getParametersCount() > 0) return null; + return method.getName() + "(" + + getSpace(CodeStyleSettingsManager.getSettings(method.getProject()).SPACE_WITHIN_METHOD_CALL_PARENTHESES) + ")"; } + else if (o instanceof PsiVariable) { + return ((PsiVariable)o).getName(); + } + return null; + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/ToArrayConversion.java b/java/java-impl/src/com/intellij/codeInsight/completion/ToArrayConversion.java new file mode 100644 index 000000000000..0365e8a7a820 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/completion/ToArrayConversion.java @@ -0,0 +1,131 @@ +/* + * Copyright 2000-2015 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.lookup.ExpressionLookupItem; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.featureStatistics.FeatureUsageTracker; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.Consumer; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.PlatformIcons; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +import static com.intellij.codeInsight.completion.ReferenceExpressionCompletionContributor.createExpression; +import static com.intellij.codeInsight.completion.ReferenceExpressionCompletionContributor.getQualifierText; +import static com.intellij.codeInsight.completion.ReferenceExpressionCompletionContributor.getSpace; + +/** + * @author peter + */ +public class ToArrayConversion { + static void addToArrayConversions(final PsiElement element, final Object object, final String prefix, final PsiType itemType, + final Consumer result, @Nullable final PsiElement qualifier, + final PsiType expectedType) { + final String callSpace = getSpace(CodeStyleSettingsManager.getSettings(element.getProject()).SPACE_WITHIN_METHOD_CALL_PARENTHESES); + final PsiType componentType = PsiUtil.extractIterableTypeParameter(itemType, true); + if (componentType == null || !(expectedType instanceof PsiArrayType)) return; + + final PsiArrayType type = (PsiArrayType)expectedType; + if (!type.getComponentType().isAssignableFrom(componentType) || + componentType instanceof PsiClassType && ((PsiClassType) componentType).hasParameters()) { + return; + } + + final String bracketSpace = getSpace(CodeStyleSettingsManager.getSettings(element.getProject()).SPACE_WITHIN_BRACKETS); + if (object instanceof PsiVariable && !JavaCompletionUtil.mayHaveSideEffects(qualifier)) { + final PsiVariable variable = (PsiVariable)object; + addToArrayConversion(element, prefix, + "new " + componentType.getCanonicalText() + + "[" + bracketSpace + getQualifierText(qualifier) + variable.getName() + ".size(" + callSpace + ")" + bracketSpace + "]", + "new " + getQualifierText(qualifier) + componentType.getPresentableText() + "[" + variable.getName() + ".size()]", result, qualifier); + } else { + boolean hasEmptyArrayField = false; + final PsiClass psiClass = PsiUtil.resolveClassInType(type); + if (psiClass != null) { + for (final PsiField field : psiClass.getAllFields()) { + if (field.hasModifierProperty(PsiModifier.STATIC) && field.hasModifierProperty(PsiModifier.FINAL) && + JavaPsiFacade.getInstance(field.getProject()).getResolveHelper().isAccessible(field, element, null) && + type.isAssignableFrom(field.getType()) && isEmptyArrayInitializer(field.getInitializer())) { + boolean needQualify; + try { + needQualify = !field.isEquivalentTo(((PsiReferenceExpression)createExpression(field.getName(), element)).resolve()); + } + catch (IncorrectOperationException e) { + continue; + } + + addToArrayConversion(element, prefix, + (needQualify ? field.getContainingClass().getQualifiedName() + "." : "") + field.getName(), + (needQualify ? field.getContainingClass().getName() + "." : "") + field.getName(), result, qualifier); + hasEmptyArrayField = true; + } + } + } + if (!hasEmptyArrayField) { + addToArrayConversion(element, prefix, + "new " + componentType.getCanonicalText() + "[" + bracketSpace + "0" + bracketSpace + "]", + "new " + componentType.getPresentableText() + "[0]", result, qualifier); + } + } + } + + private static void addToArrayConversion(final PsiElement element, final String prefix, @NonNls final String expressionString, @NonNls String presentableString, final Consumer result, PsiElement qualifier) { + final boolean callSpace = CodeStyleSettingsManager.getSettings(element.getProject()).SPACE_WITHIN_METHOD_CALL_PARENTHESES; + final PsiExpression conversion; + try { + conversion = createExpression( + getQualifierText(qualifier) + prefix + ".toArray(" + + getSpace(callSpace) + expressionString + getSpace(callSpace) + ")", element); + } + catch (IncorrectOperationException e) { + return; + } + + String[] lookupStrings = {prefix + ".toArray(" + getSpace(callSpace) + expressionString + + getSpace(callSpace) + ")", presentableString}; + result.consume(new ExpressionLookupItem(conversion, PlatformIcons.METHOD_ICON, prefix + ".toArray(" + presentableString + ")", lookupStrings) { + @Override + public void handleInsert(InsertionContext context) { + FeatureUsageTracker.getInstance().triggerFeatureUsed(JavaCompletionFeatures.SECOND_SMART_COMPLETION_TOAR); + + context.commitDocument(); + JavaCodeStyleManager.getInstance(context.getProject()).shortenClassReferences(context.getFile(), context.getStartOffset(), context.getTailOffset()); + } + }); + } + + private static boolean isEmptyArrayInitializer(@Nullable PsiElement element) { + if (element instanceof PsiNewExpression) { + final PsiNewExpression expression = (PsiNewExpression)element; + final PsiExpression[] dimensions = expression.getArrayDimensions(); + for (final PsiExpression dimension : dimensions) { + if (!(dimension instanceof PsiLiteralExpression) || !"0".equals(dimension.getText())) { + return false; + } + } + final PsiArrayInitializerExpression initializer = expression.getArrayInitializer(); + if (initializer != null && initializer.getInitializers().length > 0) return false; + + return true; + } + return false; + } +}