blob: c5763cc2691e1f0aad5851111db93fbb06eec2bc [file] [log] [blame]
/*
* Copyright 2000-2014 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.daemon.impl.quickfix;
import com.intellij.codeInsight.*;
import com.intellij.codeInsight.completion.proc.VariablesProcessor;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.generation.OverrideImplementUtil;
import com.intellij.codeInsight.generation.PsiGenerationInfo;
import com.intellij.codeInsight.intention.impl.CreateClassDialog;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.codeInsight.template.*;
import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.ide.fileTemplates.FileTemplateUtil;
import com.intellij.ide.fileTemplates.JavaTemplateUtil;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorModificationUtil;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.SuggestedNameInfo;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.scope.util.PsiScopesUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.statistics.JavaStatisticsManager;
import com.intellij.psi.util.ProximityLocation;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.proximity.PsiProximityComparator;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author mike
*/
public class CreateFromUsageUtils {
private static final Logger LOG = Logger.getInstance(
"#com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageUtils");
private static final int MAX_GUESSED_MEMBERS_COUNT = 10;
public static boolean isValidReference(PsiReference reference, boolean unresolvedOnly) {
if (!(reference instanceof PsiJavaReference)) return false;
JavaResolveResult[] results = ((PsiJavaReference)reference).multiResolve(true);
if(results.length == 0) return false;
if (!unresolvedOnly) {
for (JavaResolveResult result : results) {
if (!result.isValidResult()) return false;
if (result.getElement() instanceof PsiPackage) return false;
}
}
return true;
}
public static boolean isValidMethodReference(PsiReference reference, PsiMethodCallExpression call) {
if (!(reference instanceof PsiJavaReference)) return false;
try {
JavaResolveResult candidate = ((PsiJavaReference) reference).advancedResolve(true);
PsiElement result = candidate.getElement();
return result instanceof PsiMethod && PsiUtil.isApplicable((PsiMethod)result, candidate.getSubstitutor(), call.getArgumentList());
}
catch (ClassCastException cce) {
// rear case
return false;
}
}
public static boolean shouldCreateConstructor(PsiClass targetClass, PsiExpressionList argList, PsiMethod candidate) {
if (argList == null) return false;
if (candidate == null) {
return targetClass != null && !targetClass.isInterface() && !(targetClass instanceof PsiTypeParameter) &&
!(argList.getExpressions().length == 0 && targetClass.getConstructors().length == 0);
}
else {
return !PsiUtil.isApplicable(candidate, PsiSubstitutor.EMPTY, argList);
}
}
public static void setupMethodBody(PsiMethod method) throws IncorrectOperationException {
PsiClass aClass = method.getContainingClass();
setupMethodBody(method, aClass);
}
public static void setupMethodBody(final PsiMethod method, final PsiClass aClass) throws IncorrectOperationException {
FileTemplate template = FileTemplateManager.getInstance().getCodeTemplate(JavaTemplateUtil.TEMPLATE_FROM_USAGE_METHOD_BODY);
setupMethodBody(method, aClass, template);
}
public static void setupMethodBody(final PsiMethod method, final PsiClass aClass, final FileTemplate template) throws
IncorrectOperationException {
PsiType returnType = method.getReturnType();
if (returnType == null) {
returnType = PsiType.VOID;
}
JVMElementFactory factory = JVMElementFactories.getFactory(aClass.getLanguage(), aClass.getProject());
LOG.assertTrue(!aClass.isInterface() ||
method.hasModifierProperty(PsiModifier.DEFAULT) ||
method.hasModifierProperty(PsiModifier.STATIC) ||
method.getLanguage() != JavaLanguage.INSTANCE, "Interface bodies should be already set up");
FileType fileType = FileTypeManager.getInstance().getFileTypeByExtension(template.getExtension());
Properties properties = new Properties();
properties.setProperty(FileTemplate.ATTRIBUTE_RETURN_TYPE, returnType.getPresentableText());
properties.setProperty(FileTemplate.ATTRIBUTE_DEFAULT_RETURN_VALUE,
PsiTypesUtil.getDefaultValueOfType(returnType));
JavaTemplateUtil.setClassAndMethodNameProperties(properties, aClass, method);
@NonNls String methodText;
CodeStyleManager csManager = CodeStyleManager.getInstance(method.getProject());
try {
String bodyText = template.getText(properties);
if (!bodyText.isEmpty()) bodyText += "\n";
methodText = returnType.getPresentableText() + " foo () {\n" + bodyText + "}";
methodText = FileTemplateUtil.indent(methodText, method.getProject(), fileType);
}
catch (ProcessCanceledException e) {
throw e;
}
catch (Exception e) {
throw new IncorrectOperationException("Failed to parse file template", (Throwable)e);
}
if (methodText != null) {
PsiMethod m;
try {
m = factory.createMethodFromText(methodText, aClass);
}
catch (IncorrectOperationException e) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
Messages.showErrorDialog(QuickFixBundle.message("new.method.body.template.error.text"),
QuickFixBundle.message("new.method.body.template.error.title"));
}
});
return;
}
PsiCodeBlock oldBody = method.getBody();
PsiCodeBlock newBody = m.getBody();
LOG.assertTrue(newBody != null);
if (oldBody != null) {
oldBody.replace(newBody);
} else {
method.addBefore(newBody, null);
}
csManager.reformat(method);
}
}
public static void setupEditor(PsiMethod method, final Editor newEditor) {
PsiCodeBlock body = method.getBody();
if (body != null) {
PsiElement l = PsiTreeUtil.skipSiblingsForward(body.getLBrace(), PsiWhiteSpace.class);
PsiElement r = PsiTreeUtil.skipSiblingsBackward(body.getRBrace(), PsiWhiteSpace.class);
if (l != null && r != null) {
int start = l.getTextRange().getStartOffset();
int end = r.getTextRange().getEndOffset();
newEditor.getCaretModel().moveToOffset(Math.max(start, end));
if (end < start) {
newEditor.getCaretModel().moveToOffset(end + 1);
CodeStyleManager styleManager = CodeStyleManager.getInstance(method.getProject());
PsiFile containingFile = method.getContainingFile();
final String lineIndent = styleManager.getLineIndent(containingFile, Math.min(start, end));
PsiDocumentManager manager = PsiDocumentManager.getInstance(method.getProject());
manager.doPostponedOperationsAndUnblockDocument(manager.getDocument(containingFile));
EditorModificationUtil.insertStringAtCaret(newEditor, lineIndent);
EditorModificationUtil.insertStringAtCaret(newEditor, "\n", false, false);
}
else {
//correct position caret for groovy and java methods
final PsiGenerationInfo<PsiMethod> info = OverrideImplementUtil.createGenerationInfo(method);
info.positionCaret(newEditor, true);
}
newEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
}
}
}
public static void setupMethodParameters(PsiMethod method, TemplateBuilder builder, PsiExpressionList argumentList,
PsiSubstitutor substitutor) throws IncorrectOperationException {
if (argumentList == null) return;
PsiExpression[] args = argumentList.getExpressions();
setupMethodParameters(method, builder, argumentList, substitutor, args);
}
public static void setupMethodParameters(final PsiMethod method, final TemplateBuilder builder, final PsiElement contextElement,
final PsiSubstitutor substitutor, final PsiExpression[] arguments) {
setupMethodParameters(method, builder, contextElement, substitutor, ContainerUtil.map2List(arguments, Pair.<PsiExpression, PsiType>createFunction(null)));
}
public static void setupMethodParameters(final PsiMethod method, final TemplateBuilder builder, final PsiElement contextElement,
final PsiSubstitutor substitutor, final List<Pair<PsiExpression, PsiType>> arguments)
throws IncorrectOperationException {
PsiManager psiManager = method.getManager();
JVMElementFactory factory = JVMElementFactories.getFactory(method.getLanguage(), method.getProject());
PsiParameterList parameterList = method.getParameterList();
GlobalSearchScope resolveScope = method.getResolveScope();
GuessTypeParameters guesser = new GuessTypeParameters(JavaPsiFacade.getElementFactory(method.getProject()));
final PsiClass containingClass = method.getContainingClass();
final boolean isInterface = containingClass != null && containingClass.isInterface();
for (int i = 0; i < arguments.size(); i++) {
Pair<PsiExpression, PsiType> arg = arguments.get(i);
PsiExpression exp = arg.first;
PsiType argType = exp == null ? arg.second : exp.getType();
SuggestedNameInfo suggestedInfo = JavaCodeStyleManager.getInstance(psiManager.getProject()).suggestVariableName(
VariableKind.PARAMETER, null, exp, argType);
@NonNls String[] names = suggestedInfo.names; //TODO: callback about used name
if (names.length == 0) {
names = new String[]{"p" + i};
}
if (argType == null || PsiType.NULL.equals(argType) ||
argType instanceof PsiLambdaExpressionType ||
argType instanceof PsiLambdaParameterType ||
argType instanceof PsiMethodReferenceType) {
argType = PsiType.getJavaLangObject(psiManager, resolveScope);
} else if (argType instanceof PsiDisjunctionType) {
argType = ((PsiDisjunctionType)argType).getLeastUpperBound();
} else if (argType instanceof PsiWildcardType) {
argType = ((PsiWildcardType)argType).isBounded() ? ((PsiWildcardType)argType).getBound() : PsiType.getJavaLangObject(psiManager, resolveScope);
}
PsiParameter parameter;
if (parameterList.getParametersCount() <= i) {
parameter = factory.createParameter(names[0], argType);
if (isInterface) {
PsiUtil.setModifierProperty(parameter, PsiModifier.FINAL, false);
}
parameter = (PsiParameter) parameterList.add(parameter);
} else {
parameter = parameterList.getParameters()[i];
}
ExpectedTypeInfo info = ExpectedTypesProvider.createInfo(argType, ExpectedTypeInfo.TYPE_OR_SUPERTYPE, argType, TailType.NONE);
PsiElement context = PsiTreeUtil.getParentOfType(contextElement, PsiClass.class, PsiMethod.class);
guesser.setupTypeElement(parameter.getTypeElement(), new ExpectedTypeInfo[]{info},
substitutor, builder, context, containingClass);
Expression expression = new ParameterNameExpression(names);
builder.replaceElement(parameter.getNameIdentifier(), expression);
}
}
@Nullable
public static PsiClass createClass(final PsiJavaCodeReferenceElement referenceElement,
final CreateClassKind classKind,
final String superClassName) {
assert !ApplicationManager.getApplication().isWriteAccessAllowed();
final String name = referenceElement.getReferenceName();
final PsiElement qualifierElement;
if (referenceElement.getQualifier() instanceof PsiJavaCodeReferenceElement) {
PsiJavaCodeReferenceElement qualifier = (PsiJavaCodeReferenceElement) referenceElement.getQualifier();
qualifierElement = qualifier == null? null : qualifier.resolve();
if (qualifierElement instanceof PsiClass) {
return ApplicationManager.getApplication().runWriteAction(
new Computable<PsiClass>() {
@Override
public PsiClass compute() {
return createClassInQualifier((PsiClass)qualifierElement, classKind, name, referenceElement);
}
});
}
}
else {
qualifierElement = null;
}
final PsiManager manager = referenceElement.getManager();
final PsiFile sourceFile = referenceElement.getContainingFile();
final Module module = ModuleUtilCore.findModuleForPsiElement(sourceFile);
PsiPackage aPackage = findTargetPackage(qualifierElement, manager, sourceFile);
if (aPackage == null) return null;
final PsiDirectory targetDirectory;
if (!ApplicationManager.getApplication().isUnitTestMode()) {
Project project = manager.getProject();
String title = QuickFixBundle.message("create.class.title", StringUtil.capitalize(classKind.getDescription()));
CreateClassDialog dialog = new CreateClassDialog(project, title, name, aPackage.getQualifiedName(), classKind, false, module){
@Override
protected boolean reportBaseInSourceSelectionInTest() {
return true;
}
};
dialog.show();
if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) return null;
targetDirectory = dialog.getTargetDirectory();
if (targetDirectory == null) return null;
}
else {
targetDirectory = null;
}
return createClass(classKind, targetDirectory, name, manager, referenceElement, sourceFile, superClassName);
}
@Nullable
public static PsiPackage findTargetPackage(PsiElement qualifierElement, PsiManager manager, PsiFile sourceFile) {
PsiPackage aPackage = null;
if (qualifierElement instanceof PsiPackage) {
aPackage = (PsiPackage)qualifierElement;
}
else {
final PsiDirectory directory = sourceFile.getContainingDirectory();
if (directory != null) {
aPackage = JavaDirectoryService.getInstance().getPackage(directory);
}
if (aPackage == null) {
aPackage = JavaPsiFacade.getInstance(manager.getProject()).findPackage("");
}
}
if (aPackage == null) return null;
return aPackage;
}
public static PsiClass createClassInQualifier(PsiClass psiClass,
CreateClassKind classKind,
String name,
PsiJavaCodeReferenceElement referenceElement) {
try {
if (!FileModificationService.getInstance().preparePsiElementForWrite(psiClass)) return null;
PsiManager manager = psiClass.getManager();
PsiElementFactory elementFactory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory();
PsiClass result = classKind == CreateClassKind.INTERFACE ? elementFactory.createInterface(name) :
classKind == CreateClassKind.CLASS ? elementFactory.createClass(name) :
classKind == CreateClassKind.ANNOTATION ? elementFactory.createAnnotationType(name) :
elementFactory.createEnum(name);
CreateFromUsageBaseFix.setupGenericParameters(result, referenceElement);
result = (PsiClass)CodeStyleManager.getInstance(manager.getProject()).reformat(result);
return (PsiClass) psiClass.add(result);
}
catch (IncorrectOperationException e) {
LOG.error(e);
return null;
}
}
public static PsiClass createClass(final CreateClassKind classKind,
final PsiDirectory directory,
final String name,
final PsiManager manager,
final PsiElement contextElement,
final PsiFile sourceFile,
final String superClassName) {
final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject());
final PsiElementFactory factory = facade.getElementFactory();
return ApplicationManager.getApplication().runWriteAction(
new Computable<PsiClass>() {
@Override
public PsiClass compute() {
try {
PsiClass targetClass;
if (directory != null) {
try {
if (classKind == CreateClassKind.INTERFACE) {
targetClass = JavaDirectoryService.getInstance().createInterface(directory, name);
}
else if (classKind == CreateClassKind.CLASS) {
targetClass = JavaDirectoryService.getInstance().createClass(directory, name);
}
else if (classKind == CreateClassKind.ENUM) {
targetClass = JavaDirectoryService.getInstance().createEnum(directory, name);
}
else if (classKind == CreateClassKind.ANNOTATION) {
targetClass = JavaDirectoryService.getInstance().createAnnotationType(directory, name);
}
else {
LOG.error("Unknown kind of a class to create");
return null;
}
}
catch (final IncorrectOperationException e) {
scheduleFileOrPackageCreationFailedMessageBox(e, name, directory, false);
return null;
}
if (!facade.getResolveHelper().isAccessible(targetClass, contextElement, null)) {
PsiUtil.setModifierProperty(targetClass, PsiModifier.PUBLIC, true);
}
}
else { //tests
PsiClass aClass;
if (classKind == CreateClassKind.INTERFACE) {
aClass = factory.createInterface(name);
}
else if (classKind == CreateClassKind.CLASS) {
aClass = factory.createClass(name);
}
else if (classKind == CreateClassKind.ENUM) {
aClass = factory.createEnum(name);
}
else if (classKind == CreateClassKind.ANNOTATION) {
aClass = factory.createAnnotationType(name);
}
else {
LOG.error("Unknown kind of a class to create");
return null;
}
targetClass = (PsiClass) sourceFile.add(aClass);
}
if (superClassName != null && (classKind != CreateClassKind.ENUM || !superClassName.equals(CommonClassNames.JAVA_LANG_ENUM))) {
final PsiClass superClass =
facade.findClass(superClassName, targetClass.getResolveScope());
final PsiJavaCodeReferenceElement superClassReference = factory.createReferenceElementByFQClassName(superClassName, targetClass.getResolveScope());
final PsiReferenceList list = classKind == CreateClassKind.INTERFACE || superClass == null || !superClass.isInterface() ?
targetClass.getExtendsList() : targetClass.getImplementsList();
list.add(superClassReference);
}
if (contextElement instanceof PsiJavaCodeReferenceElement) {
CreateFromUsageBaseFix.setupGenericParameters(targetClass, (PsiJavaCodeReferenceElement)contextElement);
}
return targetClass;
}
catch (IncorrectOperationException e) {
LOG.error(e);
return null;
}
}
});
}
public static void scheduleFileOrPackageCreationFailedMessageBox(final IncorrectOperationException e, final String name, final PsiDirectory directory,
final boolean isPackage) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
Messages.showErrorDialog(QuickFixBundle.message(
isPackage ? "cannot.create.java.package.error.text" : "cannot.create.java.file.error.text", name, directory.getVirtualFile().getName(), e.getLocalizedMessage()),
QuickFixBundle.message(
isPackage ? "cannot.create.java.package.error.title" : "cannot.create.java.file.error.title"));
}
});
}
public static PsiReferenceExpression[] collectExpressions(final PsiExpression expression, Class<? extends PsiElement>... scopes) {
PsiElement parent = PsiTreeUtil.getParentOfType(expression, scopes);
final List<PsiReferenceExpression> result = new ArrayList<PsiReferenceExpression>();
JavaRecursiveElementWalkingVisitor visitor = new JavaRecursiveElementWalkingVisitor() {
@Override public void visitReferenceExpression(PsiReferenceExpression expr) {
if (expression instanceof PsiReferenceExpression) {
if (expr.textMatches(expression) && !isValidReference(expr, false)) {
result.add(expr);
}
}
visitElement(expr);
}
@Override public void visitMethodCallExpression(PsiMethodCallExpression expr) {
if (expression instanceof PsiMethodCallExpression) {
PsiReferenceExpression methodExpression = expr.getMethodExpression();
if (methodExpression.textMatches(((PsiMethodCallExpression) expression).getMethodExpression())) {
result.add(expr.getMethodExpression());
}
}
visitElement(expr);
}
};
if (parent != null) {
parent.accept(visitor);
}
return result.toArray(new PsiReferenceExpression[result.size()]);
}
public static PsiVariable[] guessMatchingVariables (final PsiExpression expression) {
List<ExpectedTypeInfo[]> typesList = new ArrayList<ExpectedTypeInfo[]>();
List<String> expectedMethodNames = new ArrayList<String>();
List<String> expectedFieldNames = new ArrayList<String>();
getExpectedInformation(expression, typesList, expectedMethodNames, expectedFieldNames);
final List<PsiVariable> list = new ArrayList<PsiVariable>();
VariablesProcessor varproc = new VariablesProcessor("", true, list){
@Override
public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
if(!(element instanceof PsiField) ||
JavaPsiFacade.getInstance(element.getProject()).getResolveHelper().isAccessible((PsiField)element, expression, null)) {
return super.execute(element, state);
}
return true;
}
};
PsiScopesUtil.treeWalkUp(varproc, expression, null);
PsiVariable[] allVars = varproc.getResultsAsArray();
ExpectedTypeInfo[] infos = ExpectedTypeUtil.intersect(typesList);
List<PsiVariable> result = new ArrayList<PsiVariable>();
nextVar:
for (PsiVariable variable : allVars) {
PsiType varType = variable.getType();
boolean matched = infos.length == 0;
for (ExpectedTypeInfo info : infos) {
if (ExpectedTypeUtil.matches(varType, info)) {
matched = true;
break;
}
}
if (matched) {
if (!expectedFieldNames.isEmpty() && !expectedMethodNames.isEmpty()) {
if (!(varType instanceof PsiClassType)) continue;
PsiClass aClass = ((PsiClassType)varType).resolve();
if (aClass == null) continue;
for (String name : expectedFieldNames) {
if (aClass.findFieldByName(name, true) == null) continue nextVar;
}
for (String name : expectedMethodNames) {
PsiMethod[] methods = aClass.findMethodsByName(name, true);
if (methods.length == 0) continue nextVar;
}
}
result.add(variable);
}
}
return result.toArray(new PsiVariable[result.size()]);
}
private static void getExpectedInformation(final PsiExpression expression,
List<ExpectedTypeInfo[]> types,
List<String> expectedMethodNames,
List<String> expectedFieldNames) {
for (PsiExpression expr : collectExpressions(expression, PsiMember.class, PsiFile.class)) {
PsiElement parent = expr.getParent();
if (!(parent instanceof PsiReferenceExpression)) {
ExpectedTypeInfo[] someExpectedTypes = ExpectedTypesProvider.getExpectedTypes(expr, PsiUtil.skipParenthesizedExprUp(parent) instanceof PsiExpressionList);
if (someExpectedTypes.length > 0) {
Arrays.sort(someExpectedTypes, new Comparator<ExpectedTypeInfo>() {
@Override
public int compare(ExpectedTypeInfo o1, ExpectedTypeInfo o2) {
return compareExpectedTypes(o1, o2, expression);
}
});
types.add(someExpectedTypes);
}
continue;
}
String refName = ((PsiReferenceExpression)parent).getReferenceName();
if (refName == null) {
continue;
}
PsiElement pparent = parent.getParent();
if (pparent instanceof PsiMethodCallExpression) {
expectedMethodNames.add(refName);
if (refName.equals("equals")) {
ExpectedTypeInfo[] someExpectedTypes = equalsExpectedTypes((PsiMethodCallExpression)pparent);
if (someExpectedTypes.length > 0) {
Arrays.sort(someExpectedTypes, new Comparator<ExpectedTypeInfo>() {
@Override
public int compare(ExpectedTypeInfo o1, ExpectedTypeInfo o2) {
return compareExpectedTypes(o1, o2, expression);
}
});
types.add(someExpectedTypes);
}
}
continue;
}
if (pparent instanceof PsiReferenceExpression ||
pparent instanceof PsiVariable ||
pparent instanceof PsiExpression) {
expectedFieldNames.add(refName);
}
}
}
private static int compareExpectedTypes(ExpectedTypeInfo o1, ExpectedTypeInfo o2, PsiExpression expression) {
PsiClass c1 = PsiUtil.resolveClassInType(o1.getDefaultType());
PsiClass c2 = PsiUtil.resolveClassInType(o2.getDefaultType());
if (c1 == null && c2 == null) return 0;
if (c1 == null || c2 == null) return c1 == null ? -1 : 1;
return compareMembers(c1, c2, expression);
}
private static ExpectedTypeInfo[] equalsExpectedTypes(PsiMethodCallExpression methodCall) {
final PsiType[] argumentTypes = methodCall.getArgumentList().getExpressionTypes();
if (argumentTypes.length != 1) {
return ExpectedTypeInfo.EMPTY_ARRAY;
}
PsiType type = argumentTypes[0];
if (type instanceof PsiPrimitiveType) {
type = ((PsiPrimitiveType)type).getBoxedType(methodCall);
}
if (type == null) return ExpectedTypeInfo.EMPTY_ARRAY;
return new ExpectedTypeInfo[]{ExpectedTypesProvider.createInfo(type, ExpectedTypeInfo.TYPE_STRICTLY, type, TailType.NONE)};
}
public static ExpectedTypeInfo[] guessExpectedTypes (PsiExpression expression, boolean allowVoidType) {
PsiManager manager = expression.getManager();
GlobalSearchScope resolveScope = expression.getResolveScope();
List<ExpectedTypeInfo[]> typesList = new ArrayList<ExpectedTypeInfo[]>();
List<String> expectedMethodNames = new ArrayList<String>();
List<String> expectedFieldNames = new ArrayList<String>();
getExpectedInformation(expression, typesList, expectedMethodNames, expectedFieldNames);
if (typesList.size() == 1 && (!expectedFieldNames.isEmpty() || !expectedMethodNames.isEmpty())) {
ExpectedTypeInfo[] infos = typesList.get(0);
if (infos.length == 1 && infos[0].getKind() == ExpectedTypeInfo.TYPE_OR_SUBTYPE &&
infos[0].getType().equals(PsiType.getJavaLangObject(manager, resolveScope))) {
typesList.clear();
}
}
if (typesList.isEmpty()) {
final JavaPsiFacade facade = JavaPsiFacade.getInstance(expression.getProject());
final PsiShortNamesCache cache = PsiShortNamesCache.getInstance(expression.getProject());
PsiElementFactory factory = facade.getElementFactory();
for (String fieldName : expectedFieldNames) {
PsiField[] fields = cache.getFieldsByName(fieldName, resolveScope);
addMemberInfo(fields, expression, typesList, factory);
}
for (String methodName : expectedMethodNames) {
PsiMethod[] methods = cache.getMethodsByName(methodName, resolveScope);
addMemberInfo(methods, expression, typesList, factory);
}
}
ExpectedTypeInfo[] expectedTypes = ExpectedTypeUtil.intersect(typesList);
if (expectedTypes.length == 0 && !typesList.isEmpty()) {
List<ExpectedTypeInfo> union = new ArrayList<ExpectedTypeInfo>();
for (ExpectedTypeInfo[] aTypesList : typesList) {
ContainerUtil.addAll(union, (ExpectedTypeInfo[])aTypesList);
}
expectedTypes = union.toArray(new ExpectedTypeInfo[union.size()]);
}
if (expectedTypes.length == 0) {
PsiType t = allowVoidType ? PsiType.VOID : PsiType.getJavaLangObject(manager, resolveScope);
expectedTypes = new ExpectedTypeInfo[] {ExpectedTypesProvider.createInfo(t, ExpectedTypeInfo.TYPE_OR_SUBTYPE, t, TailType.NONE)};
}
return expectedTypes;
}
@Nullable
public static PsiType[] guessType(PsiExpression expression, final boolean allowVoidType) {
final PsiManager manager = expression.getManager();
final GlobalSearchScope resolveScope = expression.getResolveScope();
List<ExpectedTypeInfo[]> typesList = new ArrayList<ExpectedTypeInfo[]>();
final List<String> expectedMethodNames = new ArrayList<String>();
final List<String> expectedFieldNames = new ArrayList<String>();
getExpectedInformation(expression, typesList, expectedMethodNames, expectedFieldNames);
if (typesList.size() == 1 && (!expectedFieldNames.isEmpty() || !expectedMethodNames.isEmpty())) {
ExpectedTypeInfo[] infos = typesList.get(0);
if (infos.length == 1 && infos[0].getKind() == ExpectedTypeInfo.TYPE_OR_SUBTYPE &&
infos[0].getType().equals(PsiType.getJavaLangObject(manager, resolveScope))) {
typesList.clear();
}
}
if (typesList.isEmpty()) {
final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject());
final PsiShortNamesCache cache = PsiShortNamesCache.getInstance(expression.getProject());
PsiElementFactory factory = facade.getElementFactory();
for (String fieldName : expectedFieldNames) {
PsiField[] fields = cache.getFieldsByName(fieldName, resolveScope);
addMemberInfo(fields, expression, typesList, factory);
}
for (String methodName : expectedMethodNames) {
PsiMethod[] methods = cache.getMethodsByName(methodName, resolveScope);
addMemberInfo(methods, expression, typesList, factory);
}
}
ExpectedTypeInfo[] expectedTypes = ExpectedTypeUtil.intersect(typesList);
if (expectedTypes.length == 0 && !typesList.isEmpty()) {
List<ExpectedTypeInfo> union = new ArrayList<ExpectedTypeInfo>();
for (ExpectedTypeInfo[] aTypesList : typesList) {
ContainerUtil.addAll(union, (ExpectedTypeInfo[])aTypesList);
}
expectedTypes = union.toArray(new ExpectedTypeInfo[union.size()]);
}
if (expectedTypes.length == 0) {
return allowVoidType ? new PsiType[]{PsiType.VOID} : new PsiType[]{PsiType.getJavaLangObject(manager, resolveScope)};
}
else {
//Double check to avoid expensive operations on PsiClassTypes
final Set<PsiType> typesSet = new HashSet<PsiType>();
PsiTypeVisitor<PsiType> visitor = new PsiTypeVisitor<PsiType>() {
@Override
@Nullable
public PsiType visitType(PsiType type) {
if (PsiType.NULL.equals(type)) {
type = PsiType.getJavaLangObject(manager, resolveScope);
}
else if (PsiType.VOID.equals(type) && !allowVoidType) {
type = PsiType.getJavaLangObject(manager, resolveScope);
}
if (!typesSet.contains(type)) {
if (type instanceof PsiClassType && (!expectedFieldNames.isEmpty() || !expectedMethodNames.isEmpty())) {
PsiClass aClass = ((PsiClassType) type).resolve();
if (aClass != null) {
for (String fieldName : expectedFieldNames) {
if (aClass.findFieldByName(fieldName, true) == null) return null;
}
for (String methodName : expectedMethodNames) {
PsiMethod[] methods = aClass.findMethodsByName(methodName, true);
if (methods.length == 0) return null;
}
}
}
typesSet.add(type);
return type;
}
return null;
}
@Override
public PsiType visitCapturedWildcardType(PsiCapturedWildcardType capturedWildcardType) {
return capturedWildcardType.getUpperBound().accept(this);
}
};
PsiType[] types = ExpectedTypesProvider.processExpectedTypes(expectedTypes, visitor, manager.getProject());
if (types.length == 0) {
return allowVoidType
? new PsiType[]{PsiType.VOID}
: new PsiType[]{PsiType.getJavaLangObject(manager, resolveScope)};
}
return types;
}
}
private static void addMemberInfo(PsiMember[] members,
final PsiExpression expression,
List<ExpectedTypeInfo[]> types,
PsiElementFactory factory) {
Arrays.sort(members, new Comparator<PsiMember>() {
@Override
public int compare(final PsiMember m1, final PsiMember m2) {
return compareMembers(m1, m2, expression);
}
});
List<ExpectedTypeInfo> l = new ArrayList<ExpectedTypeInfo>();
PsiManager manager = expression.getManager();
JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject());
for (PsiMember member : members) {
ProgressManager.checkCanceled();
PsiClass aClass = member.getContainingClass();
if (aClass instanceof PsiAnonymousClass || aClass == null) continue;
if (facade.getResolveHelper().isAccessible(member, expression, null)) {
PsiClassType type;
final PsiElement pparent = expression.getParent().getParent();
if (pparent instanceof PsiMethodCallExpression && member instanceof PsiMethod) {
PsiSubstitutor substitutor = ExpectedTypeUtil.inferSubstitutor((PsiMethod)member, (PsiMethodCallExpression)pparent, false);
if (substitutor == null) {
type = factory.createType(aClass);
}
else {
type = factory.createType(aClass, substitutor);
}
}
else {
type = factory.createType(aClass);
}
l.add(ExpectedTypesProvider.createInfo(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.NONE));
if (l.size() == MAX_GUESSED_MEMBERS_COUNT) break;
}
}
if (!l.isEmpty()) {
types.add(l.toArray(new ExpectedTypeInfo[l.size()]));
}
}
private static int compareMembers(PsiMember m1, PsiMember m2, PsiExpression context) {
ProgressManager.checkCanceled();
int result = JavaStatisticsManager.createInfo(null, m2).getUseCount() - JavaStatisticsManager.createInfo(null, m1).getUseCount();
if (result != 0) return result;
final PsiClass aClass = m1.getContainingClass();
final PsiClass bClass = m2.getContainingClass();
if (aClass != null && bClass != null) {
result = JavaStatisticsManager.createInfo(null, bClass).getUseCount() - JavaStatisticsManager.createInfo(null, aClass).getUseCount();
if (result != 0) return result;
}
WeighingComparable<PsiElement,ProximityLocation> proximity1 = PsiProximityComparator.getProximity(m1, context);
WeighingComparable<PsiElement,ProximityLocation> proximity2 = PsiProximityComparator.getProximity(m2, context);
if (proximity1 != null && proximity2 != null) {
result = proximity2.compareTo(proximity1);
if (result != 0) return result;
}
String name1 = PsiUtil.getMemberQualifiedName(m1);
String name2 = PsiUtil.getMemberQualifiedName(m2);
return Comparing.compare(name1, name2);
}
public static boolean isAccessedForWriting(final PsiExpression[] expressionOccurences) {
for (PsiExpression expression : expressionOccurences) {
if(expression.isValid() && PsiUtil.isAccessedForWriting(expression)) return true;
}
return false;
}
public static boolean shouldShowTag(int offset, PsiElement namedElement, PsiElement element) {
if (namedElement == null) return false;
TextRange range = namedElement.getTextRange();
if (range.getLength() == 0) return false;
boolean isInNamedElement = range.contains(offset);
return isInNamedElement || element.getTextRange().contains(offset-1);
}
public static void addClassesWithMember(final String memberName, final PsiFile file, final Set<String> possibleClassNames, final boolean method,
final boolean staticAccess) {
addClassesWithMember(memberName, file, possibleClassNames, method, staticAccess, true);
}
public static void addClassesWithMember(final String memberName, final PsiFile file, final Set<String> possibleClassNames, final boolean method,
final boolean staticAccess,
final boolean addObjectInheritors) {
final Project project = file.getProject();
final Module moduleForFile = ModuleUtilCore.findModuleForPsiElement(file);
if (moduleForFile == null) return;
final GlobalSearchScope searchScope = ApplicationManager.getApplication().runReadAction(new Computable<GlobalSearchScope>() {
@Override
public GlobalSearchScope compute() {
return file.getResolveScope();
}
});
GlobalSearchScope descendantsSearchScope = GlobalSearchScope.moduleWithDependenciesScope(moduleForFile);
final JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
final PsiShortNamesCache cache = PsiShortNamesCache.getInstance(project);
if (handleObjectMethod(possibleClassNames, facade, searchScope, method, memberName, staticAccess, addObjectInheritors)) {
return;
}
final PsiMember[] members = ApplicationManager.getApplication().runReadAction(new Computable<PsiMember[]>() {
@Override
public PsiMember[] compute() {
return method ? cache.getMethodsByName(memberName, searchScope) : cache.getFieldsByName(memberName, searchScope);
}
});
for (int i = 0; i < members.length; ++i) {
final PsiMember member = members[i];
if (hasCorrectModifiers(member, staticAccess)) {
final PsiClass containingClass = member.getContainingClass();
if (containingClass != null) {
final String qName = getQualifiedName(containingClass);
if (qName == null) continue;
ClassInheritorsSearch.search(containingClass, descendantsSearchScope, true, true, false).forEach(new Processor<PsiClass>() {
@Override
public boolean process(PsiClass psiClass) {
ContainerUtil.addIfNotNull(getQualifiedName(psiClass), possibleClassNames);
return true;
}
});
possibleClassNames.add(qName);
}
}
members[i] = null;
}
}
private static boolean handleObjectMethod(Set<String> possibleClassNames, final JavaPsiFacade facade, final GlobalSearchScope searchScope, final boolean method, final String memberName, final boolean staticAccess, boolean addInheritors) {
final PsiShortNamesCache cache = PsiShortNamesCache.getInstance(facade.getProject());
final boolean[] allClasses = {false};
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
final PsiClass objectClass = facade.findClass(CommonClassNames.JAVA_LANG_OBJECT, searchScope);
if (objectClass != null) {
if (method && objectClass.findMethodsByName(memberName, false).length > 0) {
allClasses[0] = true;
}
else if (!method) {
final PsiField field = objectClass.findFieldByName(memberName, false);
if (hasCorrectModifiers(field, staticAccess)) {
allClasses[0] = true;
}
}
}
}
});
if (allClasses[0]) {
possibleClassNames.add(CommonClassNames.JAVA_LANG_OBJECT);
if (!addInheritors) {
return true;
}
final String[] strings = ApplicationManager.getApplication().runReadAction(new Computable<String[]>() {
@Override
public String[] compute() {
return cache.getAllClassNames();
}
});
for (final String className : strings) {
final PsiClass[] classes = ApplicationManager.getApplication().runReadAction(new Computable<PsiClass[]>() {
@Override
public PsiClass[] compute() {
return cache.getClassesByName(className, searchScope);
}
});
for (final PsiClass aClass : classes) {
final String qname = getQualifiedName(aClass);
ContainerUtil.addIfNotNull(qname, possibleClassNames);
}
}
return true;
}
return false;
}
@Nullable
private static String getQualifiedName(final PsiClass aClass) {
return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
@Nullable
@Override
public String compute() {
return aClass.getQualifiedName();
}
});
}
private static boolean hasCorrectModifiers(@Nullable final PsiMember member, final boolean staticAccess) {
if (member == null) {
return false;
}
return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
@Override
public Boolean compute() {
return !member.hasModifierProperty(PsiModifier.PRIVATE) && member.hasModifierProperty(PsiModifier.STATIC) == staticAccess;
}
}).booleanValue();
}
private static class ParameterNameExpression extends Expression {
private final String[] myNames;
public ParameterNameExpression(String[] names) {
myNames = names;
}
@Override
public Result calculateResult(ExpressionContext context) {
LookupElement[] lookupItems = calculateLookupItems(context);
if (lookupItems.length == 0) return new TextResult("");
return new TextResult(lookupItems[0].getLookupString());
}
@Override
public Result calculateQuickResult(ExpressionContext context) {
return null;
}
@Override
@NotNull
public LookupElement[] calculateLookupItems(ExpressionContext context) {
Project project = context.getProject();
int offset = context.getStartOffset();
PsiDocumentManager.getInstance(project).commitAllDocuments();
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(context.getEditor().getDocument());
assert file != null;
PsiElement elementAt = file.findElementAt(offset);
PsiParameterList parameterList = PsiTreeUtil.getParentOfType(elementAt, PsiParameterList.class);
if (parameterList == null) {
if (elementAt == null) return LookupElement.EMPTY_ARRAY;
final PsiElement parent = elementAt.getParent();
if (parent instanceof PsiMethod) {
parameterList = ((PsiMethod)parent).getParameterList();
}
else {
return LookupElement.EMPTY_ARRAY;
}
}
PsiParameter parameter = PsiTreeUtil.getParentOfType(elementAt, PsiParameter.class);
Set<String> parameterNames = new HashSet<String>();
for (PsiParameter psiParameter : parameterList.getParameters()) {
if (psiParameter == parameter) continue;
parameterNames.add(psiParameter.getName());
}
Set<LookupElement> set = new LinkedHashSet<LookupElement>();
for (String name : myNames) {
if (parameterNames.contains(name)) {
int j = 1;
while (parameterNames.contains(name + j)) j++;
name += j;
}
set.add(LookupElementBuilder.create(name));
}
String[] suggestedNames = ExpressionUtil.getNames(context);
if (suggestedNames != null) {
for (String name : suggestedNames) {
if (parameterNames.contains(name)) {
int j = 1;
while (parameterNames.contains(name + j)) j++;
name += j;
}
set.add(LookupElementBuilder.create(name));
}
}
return set.toArray(new LookupElement[set.size()]);
}
}
}