extract various smart completion conversions into separate smaller classes from ReferenceExpressionCompletionContributor

This commit is contained in:
peter
2015-10-26 12:32:21 +01:00
parent a14ad4d037
commit 7156a9cd06
6 changed files with 566 additions and 370 deletions
@@ -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<LookupElement> 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;
}
}
@@ -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<LookupElement> 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;
}
}
@@ -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<LookupElement> 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);
}
}
@@ -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<PsiElement> 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<LookupElement> base =
JavaSmartCompletionContributor.completeReference(element, reference, filter, false, true, parameters.getParameters(), PrefixMatcher.ALWAYS_TRUE);
for (final LookupElement item : new LinkedHashSet<LookupElement>(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<LookupElement>() {
@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<LookupElement> completeFinalReference(final PsiElement element, PsiJavaCodeReferenceElement reference, ElementFilter filter,
static Set<LookupElement> completeFinalReference(final PsiElement element, PsiJavaCodeReferenceElement reference, ElementFilter filter,
final JavaSmartCompletionParameters parameters) {
final Set<PsiField> used = parameters.getParameters().getInvocationCount() < 2 ? findConstantsUsedInSwitch(element) : Collections.<PsiField>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<LookupElement> 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<LookupElement> 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<LookupElement> 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<LookupElement> 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<LookupElement> 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<LookupElement> 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 ? " " : "";
}
}
@@ -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<LookupElement> myBase;
private final PsiElement myElement;
private final PsiJavaCodeReferenceElement myReference;
private final JavaSmartCompletionParameters myParameters;
private final Consumer<LookupElement> myResult;
public SlowerTypeConversions(Set<LookupElement> base,
PsiElement element,
PsiJavaCodeReferenceElement reference,
JavaSmartCompletionParameters parameters, Consumer<LookupElement> 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<LookupElement>() {
@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<LookupElement> 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;
}
}
@@ -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<LookupElement> 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<LookupElement> 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;
}
}