/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.intentions; import com.intellij.codeInsight.CodeInsightUtilBase; import com.intellij.codeInsight.ExpectedTypeInfo; import com.intellij.codeInsight.ExpectedTypeUtil; import com.intellij.codeInsight.ExpectedTypesProvider; import com.intellij.codeInsight.TailType; import com.intellij.codeInsight.completion.proc.VariablesProcessor; import com.intellij.codeInsight.daemon.QuickFixBundle; import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageBaseFix; import com.intellij.codeInsight.daemon.impl.quickfix.GuessTypeParameters; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupItemUtil; import com.intellij.codeInsight.template.Expression; import com.intellij.codeInsight.template.ExpressionContext; import com.intellij.codeInsight.template.ExpressionUtil; import com.intellij.codeInsight.template.Result; import com.intellij.codeInsight.template.TemplateBuilder; import com.intellij.codeInsight.template.TextResult; 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.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; 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.ModuleUtil; 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.Computable; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.CommonClassNames; import com.intellij.psi.JavaDirectoryService; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.JavaRecursiveElementWalkingVisitor; import com.intellij.psi.JavaResolveResult; import com.intellij.psi.PsiAnonymousClass; import com.intellij.psi.PsiCapturedWildcardType; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiClassType; import com.intellij.psi.PsiCodeBlock; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementFactory; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiExpressionList; import com.intellij.psi.PsiField; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiIdentifier; import com.intellij.psi.PsiJavaCodeReferenceElement; import com.intellij.psi.PsiJavaReference; import com.intellij.psi.PsiKeyword; import com.intellij.psi.PsiManager; import com.intellij.psi.PsiMember; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiMethodCallExpression; import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiPackage; import com.intellij.psi.PsiParameter; import com.intellij.psi.PsiParameterList; import com.intellij.psi.PsiReference; import com.intellij.psi.PsiReferenceExpression; import com.intellij.psi.PsiReferenceList; import com.intellij.psi.PsiSubstitutor; import com.intellij.psi.PsiType; import com.intellij.psi.PsiTypeParameter; import com.intellij.psi.PsiTypeVisitor; import com.intellij.psi.PsiVariable; import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.ResolveState; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.impl.source.PsiImmediateClassType; 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.PsiTreeUtil; import com.intellij.psi.util.PsiTypesUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import gw.lang.reflect.gs.GosuClassTypeLoader; import gw.plugin.ij.actions.GosuTemplatesFactory; import gw.plugin.ij.lang.psi.api.types.IGosuCodeReferenceElement; import gw.plugin.ij.lang.psi.impl.expressions.GosuTypeLiteralImpl; import gw.plugin.ij.lang.psi.impl.statements.typedef.GosuTypeDefinitionImpl; import gw.plugin.ij.lang.psi.util.GosuPsiParseUtil; import gw.plugin.ij.util.GosuModuleUtil; import gw.plugin.ij.util.JavaPsiFacadeUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Properties; import java.util.Set; /** * @author mike */ public class GosuCreateFromUsageUtils { private static final Logger LOG = Logger.getInstance( "#com.intellij.codeInsight.daemon.impl.actions.CreateFromUsageUtils"); private static final int MAX_GUESSED_MEMBERS_COUNT = 10; public static boolean isValidReference(@NotNull 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; } } return true; } public static boolean isValidMethodReference(@NotNull PsiReference reference, @NotNull 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(@Nullable PsiClass targetClass, @Nullable PsiExpressionList argList, @Nullable 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(@NotNull PsiMethod method) throws IncorrectOperationException { PsiClass aClass = method.getContainingClass(); setupMethodBody(method, aClass); } public static void setupMethodBody(@NotNull final PsiMethod method, @NotNull final PsiClass aClass) throws IncorrectOperationException { FileTemplate template = FileTemplateManager.getInstance().getCodeTemplate(JavaTemplateUtil.TEMPLATE_FROM_USAGE_METHOD_BODY); setupMethodBody(method, aClass, template); } public static void setupMethodBody(@NotNull final PsiMethod method, @NotNull final PsiClass aClass, @NotNull final FileTemplate template) throws IncorrectOperationException { PsiType returnType = method.getReturnType(); if (returnType == null) { returnType = PsiType.VOID; } PsiElementFactory factory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory(); LOG.assertTrue(!aClass.isInterface(), "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 (!"".equals(bodyText)) 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",e); } if (methodText != null) { PsiMethod m; try { m = factory.createMethodFromText(methodText, aClass); } catch (IncorrectOperationException e) { ApplicationManager.getApplication().invokeLater(new Runnable() { 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(@NotNull PsiMethod method, @NotNull 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(), end = r.getTextRange().getEndOffset(); newEditor.getCaretModel().moveToOffset(Math.max(start, end)); newEditor.getSelectionModel().setSelection(Math.min(start, end), Math.max(start, end)); newEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); } } } public static void setupMethodParameters(@NotNull PsiMethod method, @NotNull TemplateBuilder builder, @Nullable PsiExpressionList argumentList, PsiSubstitutor substitutor) throws IncorrectOperationException { if (argumentList == null) return; PsiExpression[] expressions = argumentList.getExpressions(); PsiType[] argTypes = new PsiType[expressions.length]; for (int i = 0; i < argTypes.length; i++) { argTypes[i] = expressions[i].getType(); } setupMethodParameters(method, builder, method, substitutor, argTypes); } public static void setupMethodParameters(@NotNull final PsiMethod method, @NotNull final TemplateBuilder builder, final PsiElement contextElement, final PsiSubstitutor substitutor, @NotNull PsiType[] arguments) throws IncorrectOperationException { PsiManager psiManager = method.getManager(); PsiElementFactory factory = JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory(); PsiParameterList parameterList = method.getParameterList(); GlobalSearchScope resolveScope = method.getResolveScope(); GuessTypeParameters guesser = new GuessTypeParameters(factory); final PsiClass containingClass = method.getContainingClass(); final boolean isInterface = containingClass != null && containingClass.isInterface(); for (int i = 0; i < arguments.length; i++) { PsiType argType = arguments[i]; //TODO-dp need name suggestions here // SuggestedNameInfo suggestedInfo = JavaCodeStyleManager.getInstance(psiManager.getProject()).suggestVariableName( // VariableKind.PARAMETER, null, exp, argType); // @NonNls String[] names = suggestedInfo.names; //TODO: callback about used name String[] names = new String[0]; if (names.length == 0) { names = new String[]{"p" + i}; } if (argType == null || PsiType.NULL.equals(argType)) { argType = PsiType.getJavaLangObject(psiManager, resolveScope); } PsiParameter parameter; if (parameterList.getParametersCount() <= i) { parameter = GosuPsiParseUtil.createParameter(names[0], argType, contextElement); 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); PsiIdentifier nameIdentifier = parameter.getNameIdentifier(); Expression expression = new ParameterNameExpression(names, nameIdentifier); builder.replaceElement(nameIdentifier, expression); } } @Nullable public static PsiClass createClass(@NotNull final GosuTypeLiteralImpl referenceElement, @NotNull final GosuCreateClassKind classKind, @Nullable final PsiType 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>() { @Nullable 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 = ModuleUtil.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())); GosuCreateClassDialog dialog = new GosuCreateClassDialog(project, title, name, aPackage.getQualifiedName(), classKind, false, module); 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, @NotNull PsiManager manager, @NotNull 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; } @Nullable public static PsiClass createClassInQualifier(@NotNull PsiClass psiClass, GosuCreateClassKind classKind, @NotNull String name, GosuTypeLiteralImpl referenceElement) { try { if (!CodeInsightUtilBase.preparePsiElementForWrite(psiClass)) return null; PsiManager manager = psiClass.getManager(); PsiElementFactory elementFactory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory(); PsiClass result = classKind == GosuCreateClassKind.JAVA_INTERFACE ? elementFactory.createInterface(name) : classKind == GosuCreateClassKind.JAVA_CLASS ? elementFactory.createClass(name) : elementFactory.createEnum(name); //TODO-dp // CreateFromUsageBaseFix.setupGenericParameters(result, referenceElement); result = (PsiClass)CodeStyleManager.getInstance(psiClass.getProject()).reformat(result); return (PsiClass) psiClass.add(result); } catch (IncorrectOperationException e) { LOG.error(e); return null; } } public static PsiClass createClass(@NotNull final GosuCreateClassKind classKind, @NotNull final PsiDirectory directory, @NotNull final String name, @NotNull final PsiManager manager, @NotNull final PsiElement contextElement, final PsiFile sourceFile, @Nullable final PsiType superType) { final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject()); final PsiElementFactory factory = facade.getElementFactory(); return ApplicationManager.getApplication().runWriteAction( new Computable<PsiClass>() { @Nullable public PsiClass compute() { try { PsiClass targetClass; // if (directory != null) { try { if (classKind.isGosu()) { targetClass = createGosuFile(directory, name, classKind, superType); } else if (classKind == GosuCreateClassKind.JAVA_INTERFACE) { targetClass = JavaDirectoryService.getInstance().createInterface(directory, name); } else if (classKind == GosuCreateClassKind.JAVA_CLASS) { targetClass = JavaDirectoryService.getInstance().createClass(directory, name); } else if (classKind == GosuCreateClassKind.JAVA_ENUM) { targetClass = JavaDirectoryService.getInstance().createEnum(directory, name); } else { LOG.error("Unknown kind of a class to create"); return null; } } catch (@NotNull final IncorrectOperationException e) { scheduleFileOrPackageCreationFailedMessageBox(e, name, directory, false); return null; } if (!facade.getResolveHelper().isAccessible(targetClass, contextElement, null)) { PsiUtil.setModifierProperty(targetClass, PsiKeyword.PUBLIC, true); } // } // else { //tests // PsiClass aClass; // if (classKind == GosuCreateClassKind.JAVA_INTERFACE) { // aClass = factory.createInterface(name); // } // else if (classKind == GosuCreateClassKind.JAVA_CLASS) { // aClass = factory.createClass(name); // } // else if (classKind == GosuCreateClassKind.JAVA_ENUM) { // aClass = factory.createEnum(name); // } // else { // LOG.error("Unknown kind of a class to create"); // return null; // } // targetClass = (PsiClass) sourceFile.add(aClass); // } if (superType != null && classKind.isJava()) { String superTypeName = getConcreteName(superType); final PsiClass superClass = facade.findClass(superTypeName, targetClass.getResolveScope()); final PsiJavaCodeReferenceElement superClassReference = factory.createReferenceElementByFQClassName(superTypeName, targetClass.getResolveScope()); final PsiReferenceList list = classKind == GosuCreateClassKind.JAVA_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; } } }); } @Nullable private static String getConcreteName(@NotNull PsiType type) { if (type instanceof PsiImmediateClassType) { return ((PsiImmediateClassType) type).resolve().getQualifiedName(); } return type.getCanonicalText(); } enum SuperType { Extends, Implements } @NotNull private static PsiClass createGosuFile(@NotNull PsiDirectory directory, String name, @NotNull GosuCreateClassKind kind, @Nullable PsiType superType) { if (kind.getTemplateName() == null) { throw new RuntimeException("No template found for " + kind); } PsiPackage pkg = JavaDirectoryService.getInstance().getPackage(directory); SuperType superKind = null; String superText = ""; PsiClass superClass = null; if (superType != null) { String superTypeName = getConcreteName(superType); superClass = JavaPsiFacadeUtil.findClass(directory.getProject(), superTypeName, pkg.getResolveScope()); if (superClass != null) { if ((kind == GosuCreateClassKind.JAVA_INTERFACE) || (kind == GosuCreateClassKind.GOSU_INTERFACE) || !superClass.isInterface()) { superKind = SuperType.Extends; superText = "extends " + superType.getPresentableText() + " "; } else { superKind = SuperType.Implements; superText = "implements " + superType.getPresentableText() + " "; } } } GosuTemplatesFactory.createFromTemplate(directory, name, name + GosuClassTypeLoader.GOSU_CLASS_FILE_EXT, kind.getTemplateName(), GosuTemplatesFactory.PROPERTY_EXTENDS_IMPLEMENTS, superText); Module module = GosuModuleUtil.getModule(GosuModuleUtil.findModuleForPsiElement(directory)); GosuTypeDefinitionImpl psiClass = (GosuTypeDefinitionImpl) JavaPsiFacadeUtil.findClass(directory.getProject(), pkg.getQualifiedName() + "." + name, GlobalSearchScope.moduleScope(module)); if (superClass != null) { PsiElement typeLiteral = superKind == SuperType.Extends ? psiClass.getExtendsClause().getChildren()[0] : psiClass.getImplementsClause().getChildren()[0]; ((IGosuCodeReferenceElement)typeLiteral).bindToElement(superClass); } return psiClass; } public static void scheduleFileOrPackageCreationFailedMessageBox(@NotNull final IncorrectOperationException e, final String name, @NotNull final PsiDirectory directory, final boolean isPackage) { ApplicationManager.getApplication().invokeLater(new Runnable() { 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(@NotNull final PsiExpression expression, Class<? extends PsiElement>... scopes) { PsiElement parent = PsiTreeUtil.getParentOfType(expression, scopes); final List<PsiReferenceExpression> result = new ArrayList<>(); JavaRecursiveElementWalkingVisitor visitor = new JavaRecursiveElementWalkingVisitor() { @NotNull public List getResult() { return result; } @Override public void visitReferenceExpression(@NotNull PsiReferenceExpression expr) { if (expression instanceof PsiReferenceExpression) { if (expr.textMatches(expression) && expr.resolve() == null) { result.add(expr); } } visitElement(expr); } @Override public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expr) { if (expression instanceof PsiMethodCallExpression) { PsiReferenceExpression methodExpression = expr.getMethodExpression(); if (methodExpression.textMatches(((PsiMethodCallExpression) expression).getMethodExpression())) { result.add(expr.getMethodExpression()); } } visitElement(expr); } }; parent.accept(visitor); return result.toArray(new PsiReferenceExpression[result.size()]); } public static PsiVariable[] guessMatchingVariables (@NotNull final PsiExpression expression) { List<ExpectedTypeInfo[]> typesList = new ArrayList<>(); List<String> expectedMethodNames = new ArrayList<>(); List<String> expectedFieldNames = new ArrayList<>(); getExpectedInformation(expression, typesList, expectedMethodNames, expectedFieldNames); final List<PsiVariable> list = new ArrayList<>(); VariablesProcessor varproc = new VariablesProcessor("", true, list){ public boolean execute(@NotNull PsiElement element, 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<>(); 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 (@NotNull PsiExpression expression, @NotNull List<ExpectedTypeInfo[]> types, @NotNull List<String> expectedMethodNames, @NotNull List<String> expectedFieldNames) { PsiExpression[] expressions = collectExpressions(expression, PsiMember.class, PsiFile.class); for (PsiExpression expr : expressions) { PsiElement parent = expr.getParent(); if (parent instanceof PsiReferenceExpression) { PsiElement pparent = parent.getParent(); if (pparent instanceof PsiMethodCallExpression) { String refName = ((PsiReferenceExpression)parent).getReferenceName(); if (refName != null) { expectedMethodNames.add(refName); } } else if (pparent instanceof PsiReferenceExpression || pparent instanceof PsiVariable || pparent instanceof PsiExpression) { String refName = ((PsiReferenceExpression)parent).getReferenceName(); if (refName != null) { expectedFieldNames.add(refName); } } } else { ExpectedTypeInfo[] someExpectedTypes = ExpectedTypesProvider.getExpectedTypes(expr, false); if (someExpectedTypes.length > 0) { types.add(someExpectedTypes); } } } } public static ExpectedTypeInfo[] guessExpectedTypes (@NotNull PsiExpression expression, boolean allowVoidType) { PsiManager manager = expression.getManager(); GlobalSearchScope resolveScope = expression.getResolveScope(); List<ExpectedTypeInfo[]> typesList = new ArrayList<>(); List<String> expectedMethodNames = new ArrayList<>(); List<String> expectedFieldNames = new ArrayList<>(); 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()) { PsiShortNamesCache shortNamesCache = PsiShortNamesCache.getInstance(manager.getProject()); final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject()); PsiElementFactory factory = facade.getElementFactory(); for (String fieldName : expectedFieldNames) { PsiField[] fields = shortNamesCache.getFieldsByName(fieldName, resolveScope); addMemberInfo(fields, expression, typesList, factory); } for (String methodName : expectedMethodNames) { PsiMethod[] methods = shortNamesCache.getMethodsByName(methodName, resolveScope); addMemberInfo(methods, expression, typesList, factory); } } ExpectedTypeInfo[] expectedTypes = ExpectedTypeUtil.intersect(typesList); if (expectedTypes.length == 0 && !typesList.isEmpty()) { List<ExpectedTypeInfo> union = new ArrayList<>(); for (ExpectedTypeInfo[] aTypesList : typesList) { ContainerUtil.addAll(union, (ExpectedTypeInfo[])aTypesList); } expectedTypes = union.toArray(new ExpectedTypeInfo[union.size()]); } if (expectedTypes == null || 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; } public static PsiType[] guessType(@NotNull PsiExpression expression, final boolean allowVoidType) { final PsiManager manager = expression.getManager(); final GlobalSearchScope resolveScope = expression.getResolveScope(); List<ExpectedTypeInfo[]> typesList = new ArrayList<>(); final List<String> expectedMethodNames = new ArrayList<>(); final List<String> expectedFieldNames = new ArrayList<>(); 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()) { PsiShortNamesCache shortNamesCache = PsiShortNamesCache.getInstance(manager.getProject()); final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject()); PsiElementFactory factory = facade.getElementFactory(); for (String fieldName : expectedFieldNames) { PsiField[] fields = shortNamesCache.getFieldsByName(fieldName, resolveScope); addMemberInfo(fields, expression, typesList, factory); } for (String methodName : expectedMethodNames) { PsiMethod[] methods = shortNamesCache.getMethodsByName(methodName, resolveScope); addMemberInfo(methods, expression, typesList, factory); } } ExpectedTypeInfo[] expectedTypes = ExpectedTypeUtil.intersect(typesList); if (expectedTypes.length == 0 && !typesList.isEmpty()) { List<ExpectedTypeInfo> union = new ArrayList<>(); for (ExpectedTypeInfo[] aTypesList : typesList) { ContainerUtil.addAll(union, (ExpectedTypeInfo[])aTypesList); } expectedTypes = union.toArray(new ExpectedTypeInfo[union.size()]); } if (expectedTypes == null || 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<>(); PsiTypeVisitor<PsiType> visitor = new PsiTypeVisitor<PsiType>() { @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; } public PsiType visitCapturedWildcardType(@NotNull 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(@NotNull PsiMember[] members, @NotNull PsiExpression expression, @NotNull List<ExpectedTypeInfo[]> types, @NotNull PsiElementFactory factory) { Arrays.sort(members, new Comparator<PsiMember>() { public int compare(@NotNull final PsiMember m1, @NotNull final PsiMember m2) { 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) return 0; return JavaStatisticsManager.createInfo(null, bClass).getUseCount() - JavaStatisticsManager.createInfo(null, aClass).getUseCount(); } }); List<ExpectedTypeInfo> l = new ArrayList<>(); PsiManager manager = expression.getManager(); JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject()); for (int i = 0; i < Math.min(MAX_GUESSED_MEMBERS_COUNT, members.length); i++) { ProgressManager.checkCanceled(); PsiMember member = members[i]; PsiClass aClass = member.getContainingClass(); if (aClass instanceof PsiAnonymousClass) continue; if (facade.getResolveHelper().isAccessible(aClass, 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.isEmpty()) { types.add(l.toArray(new ExpectedTypeInfo[l.size()])); } } public static boolean isAccessedForWriting(@NotNull final PsiExpression[] expressionOccurences) { for (PsiExpression expression : expressionOccurences) { if(PsiUtil.isAccessedForWriting(expression)) return true; } return false; } public static boolean shouldShowTag(int offset, @Nullable PsiElement namedElement, @NotNull 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(@NotNull final String memberName, @NotNull final PsiFile file, @NotNull final Set<String> possibleClassNames, final boolean method, final boolean staticAccess) { addClassesWithMember(memberName, file, possibleClassNames, method, staticAccess, true); } public static void addClassesWithMember(@NotNull final String memberName, @NotNull final PsiFile file, @NotNull final Set<String> possibleClassNames, final boolean method, final boolean staticAccess, final boolean addObjectInheritors) { final Project project = file.getProject(); final Module moduleForFile = ModuleUtil.findModuleForPsiElement(file); if (moduleForFile == null) return; final GlobalSearchScope searchScope = ApplicationManager.getApplication().runReadAction(new Computable<GlobalSearchScope>() { @NotNull public GlobalSearchScope compute() { return file.getResolveScope(); } }); GlobalSearchScope descendantsSearchScope = GlobalSearchScope.moduleWithDependenciesScope(moduleForFile); final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); if (handleObjectMethod(possibleClassNames, facade, searchScope, method, memberName, staticAccess, addObjectInheritors)) { return; } final PsiMember[] members = ApplicationManager.getApplication().runReadAction(new Computable<PsiMember[]>() { @NotNull public PsiMember[] compute() { PsiShortNamesCache shortNamesCache = PsiShortNamesCache.getInstance(project); return method ? shortNamesCache.getMethodsByName(memberName, searchScope) : shortNamesCache.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>() { public boolean process(@NotNull PsiClass psiClass) { ContainerUtil.addIfNotNull(getQualifiedName(psiClass), possibleClassNames); return true; } }); possibleClassNames.add(qName); } } members[i] = null; } } private static boolean handleObjectMethod(@NotNull Set<String> possibleClassNames, @NotNull final JavaPsiFacade facade, @NotNull final GlobalSearchScope searchScope, final boolean method, final String memberName, final boolean staticAccess, boolean addInheritors) { final PsiShortNamesCache shortNamesCache = PsiShortNamesCache.getInstance(facade.getProject()); final boolean[] allClasses = {false}; ApplicationManager.getApplication().runReadAction(new Runnable() { 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[]>() { @NotNull public String[] compute() { return shortNamesCache.getAllClassNames(); } }); for (final String className : strings) { final PsiClass[] classes = ApplicationManager.getApplication().runReadAction(new Computable<PsiClass[]>() { @NotNull public PsiClass[] compute() { return shortNamesCache.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(@NotNull final PsiClass aClass) { return ApplicationManager.getApplication().runReadAction(new Computable<String>() { @Nullable 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>() { @NotNull public Boolean compute() { return !member.hasModifierProperty(PsiModifier.PRIVATE) && member.hasModifierProperty(PsiModifier.STATIC) == staticAccess; } }); } // --- public static class ParameterNameExpression extends Expression { private final String[] myNames; private final PsiIdentifier nameIdentifier; public ParameterNameExpression(String[] names, PsiIdentifier nameIdentifier) { myNames = names; this.nameIdentifier = nameIdentifier; } public Result calculateResult(@NotNull ExpressionContext context) { LookupElement[] lookupItems = calculateLookupItems(context); if (lookupItems.length == 0) return new TextResult(""); return new TextResult(lookupItems[0].getLookupString()); } public Result calculateQuickResult(ExpressionContext context) { return null; } public LookupElement[] calculateLookupItems(@NotNull ExpressionContext context) { Project project = context.getProject(); int offset = context.getStartOffset(); PsiDocumentManager.getInstance(project).commitAllDocuments(); PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(context.getEditor().getDocument()); PsiElement elementAt = file.findElementAt(offset); PsiParameterList parameterList = PsiTreeUtil.getParentOfType(elementAt, PsiParameterList.class); if (parameterList == null) return LookupElement.EMPTY_ARRAY; PsiParameter parameter = PsiTreeUtil.getParentOfType(elementAt, PsiParameter.class); Set<String> parameterNames = new HashSet<>(); PsiParameter[] parameters = parameterList.getParameters(); for (PsiParameter psiParameter : parameters) { if (psiParameter == parameter) continue; parameterNames.add(psiParameter.getName()); } HashSet<String> names = new HashSet<>(); Set<LookupElement> set = new LinkedHashSet<>(); for (String name : myNames) { if (parameterNames.contains(name)) { int j = 1; while (parameterNames.contains(name + j)) j++; name += j; } names.add(name); LookupItemUtil.addLookupItem(set, 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; } if (!names.contains(name)) { LookupItemUtil.addLookupItem(set, name); } } } return set.toArray(new LookupElement[set.size()]); } } }