PsiElementVisitor (@NotNull final ProblemsHolder holder, boolean isOnTheFly) { if (!JavaFeature.STREAMS.isFeatureSupported(holder.getFile())) { return PsiElementVisitor.EMPTY_VISITOR; } return new JavaElementVisitor() { private final NotNullLazyValue<Map<String, PsiClass>> myGuavaClassConversions = NotNullLazyValue.atomicLazy(() -> { Map<String, PsiClass> map = new HashMap<>(); for (TypeConversionRule rule : TypeConversionRule.EP_NAME.getExtensions()) { if (rule instanceof BaseGuavaTypeConversionRule) { final String fromClass = ((BaseGuavaTypeConversionRule)rule).ruleFromClass(); final String toClass = ((BaseGuavaTypeConversionRule)rule).ruleToClass(); final Project project = holder.getProject(); final JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(project); final PsiClass targetClass = javaPsiFacade.findClass(toClass, GlobalSearchScope.allScope(project)); if (targetClass != null) { map.put(fromClass, targetClass); } } } return map; }); @Override public void visitVariable(@NotNull PsiVariable variable) { if (!checkVariables) return; final PsiIdentifier identifier = variable.getNameIdentifier(); if (identifier == null) return; final PsiType type = variable.getType(); PsiType targetType = getConversionClassType(type); if (targetType != null) { holder.registerProblem(identifier, TypeMigrationBundle.message("guava.functional.primitives.can.be.replaced.by.java.api.problem.description"), new MigrateGuavaTypeFix(variable, targetType)); } } @Override public void visitMethod(@NotNull PsiMethod method) { super.visitMethod(method); if (!checkReturnTypes) return; final PsiType targetType = getConversionClassType(method.getReturnType()); if (targetType != null) { final PsiTypeElement typeElement = method.getReturnTypeElement(); if (typeElement != null) { holder.registerProblem(typeElement, TypeMigrationBundle.message("guava.functional.primitives.can.be.replaced.by.java.api.problem.description"), new MigrateGuavaTypeFix(method, targetType)); } } } @Override public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) { checkFluentIterableGenerationMethod(expression); checkPredicatesUtilityMethod(expression); } private void checkPredicatesUtilityMethod(@NotNull PsiMethodCallExpression expression) { if (GuavaPredicateConversionRule.isPredicates(expression) && isPossibleToConvertPredicate(expression)) { final PsiClassType initialType = (PsiClassType)expression.getType(); if (initialType == null) return; PsiClassType targetType = createTargetType(initialType); if (targetType == null) return; final PsiElement anchor = expression.getMethodExpression().getReferenceNameElement(); assert anchor != null; holder.registerProblem(anchor, TypeMigrationBundle.message("guava.functional.primitives.can.be.replaced.by.java.api.problem.description"), new MigrateGuavaTypeFix(expression, targetType)); } } private static boolean isPossibleToConvertPredicate(@NotNull PsiMethodCallExpression expression) { PsiMethodCallExpression enclosingMethodCallExpression = PsiTreeUtil.getParentOfType(expression, PsiMethodCallExpression.class); return enclosingMethodCallExpression == null || isReceiverOfEnclosingCallAFluentIterable(enclosingMethodCallExpression) || GuavaPredicateConversionRule.isEnclosingCallAPredicate(enclosingMethodCallExpression) || GuavaPredicateConversionRule.isPredicateConvertibleInsideEnclosingMethod(expression, enclosingMethodCallExpression); } private void checkFluentIterableGenerationMethod(PsiMethodCallExpression expression) { if (!checkChains) return; if (!isFluentIterableFromCall(expression)) return; final PsiMethodCallExpression chain = findGuavaMethodChain(expression); if (chain == null) { return; } PsiClassType initialType = (PsiClassType)chain.getType(); LOG.assertTrue(initialType != null); PsiClassType targetType = createTargetType(initialType); if (targetType == null) return; PsiElement highlightedElement = chain; if (chain.getParent() instanceof PsiReferenceExpression && chain.getParent().getParent() instanceof PsiMethodCallExpression) { highlightedElement = chain.getParent().getParent(); } holder.registerProblem(highlightedElement, TypeMigrationBundle.message("guava.functional.primitives.can.be.replaced.by.java.api.problem.description"), new MigrateGuavaTypeFix(chain, targetType)); } @Nullable private PsiClassType createTargetType(PsiClassType initialType) { PsiClass resolvedClass = initialType.resolve(); PsiClass target; if (resolvedClass == null || (target = myGuavaClassConversions.getValue().get(resolvedClass.getQualifiedName())) == null) { return null; } return addTypeParameters(initialType, initialType.resolveGenerics(), target); } private PsiType getConversionClassType(PsiType initialType) { if (initialType == null) return null; final PsiType type = initialType.getDeepComponentType(); if (type instanceof PsiClassType) { final PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)type).resolveGenerics(); final PsiClass psiClass = resolveResult.getElement(); if (psiClass != null) { final String qName = psiClass.getQualifiedName(); final PsiClass targetClass = myGuavaClassConversions.getValue().get(qName); if (targetClass != null) { final PsiClassType createdType = addTypeParameters(type, resolveResult, targetClass); return initialType instanceof PsiArrayType ? wrapAsArray((PsiArrayType)initialType, createdType) : createdType; } } } return null; } private static PsiType wrapAsArray(PsiArrayType initial, PsiType created) { PsiArrayType result = new PsiArrayType(created); while (initial.getComponentType() instanceof PsiArrayType) { initial = (PsiArrayType)initial.getComponentType(); result = new PsiArrayType(result); } return result; } public static boolean isReceiverOfEnclosingCallAFluentIterable(@NotNull PsiMethodCallExpression enclosingMethodCallExpression) { PsiMethod method = enclosingMethodCallExpression.resolveMethod(); if (method == null) return false; return isFluentIterable(method.getContainingClass()); } private static boolean isFluentIterableFromCall(PsiMethodCallExpression expression) { PsiMethod method = expression.resolveMethod(); if (method == null || !GuavaFluentIterableConversionRule.CHAIN_HEAD_METHODS.contains(method.getName())) { return false; } PsiClass aClass = method.getContainingClass(); return isFluentIterable(aClass); } private static boolean isFluentIterable(PsiClass aClass) { return aClass != null && (GuavaOptionalConversionRule.GUAVA_OPTIONAL.equals(aClass.getQualifiedName()) || GuavaFluentIterableConversionRule.FLUENT_ITERABLE.equals(aClass.getQualifiedName())); } private static PsiMethodCallExpression findGuavaMethodChain(PsiMethodCallExpression expression) { PsiMethodCallExpression chain = expression; while (true) { final PsiMethodCallExpression current = PsiTreeUtil.getParentOfType(chain, PsiMethodCallExpression.class); if (current != null && current.getMethodExpression().getQualifierExpression() == chain) { final PsiMethod method = current.resolveMethod(); if (method == null) { return chain; } if (FLUENT_ITERABLE_STOP_METHODS.contains(method.getName())) { return null; } final PsiClass containingClass = method.getContainingClass(); if (containingClass == null || !(GuavaFluentIterableConversionRule.FLUENT_ITERABLE.equals(containingClass.getQualifiedName()) || GuavaOptionalConversionRule.GUAVA_OPTIONAL.equals(containingClass.getQualifiedName()))) { return chain; } final PsiType returnType = method.getReturnType(); final PsiClass returnClass = PsiTypesUtil.getPsiClass(returnType); if (returnClass == null || !(GuavaFluentIterableConversionRule.FLUENT_ITERABLE.equals(returnClass.getQualifiedName()) || GuavaOptionalConversionRule.GUAVA_OPTIONAL.equals(returnClass.getQualifiedName()))) { return chain; } if (GuavaTypeConversionDescriptor.isIterable(current)) { return chain; } } else { return chain; } chain = current; } } @NotNull private PsiClassType addTypeParameters(PsiType currentType, PsiClassType.ClassResolveResult currentTypeResolveResult, PsiClass targetClass) { final Map<PsiTypeParameter, PsiType> substitutionMap = currentTypeResolveResult.getSubstitutor().getSubstitutionMap(); final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(holder.getProject()); if (substitutionMap.size() == 1) { return elementFactory.createType(targetClass, ContainerUtil.getFirstItem(substitutionMap.values())); } else { LOG.assertTrue(substitutionMap.size() == 2); LOG.assertTrue(GuavaLambda.FUNCTION.getJavaAnalogueClassQName().equals(targetClass.getQualifiedName())); final PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(currentType); final List<PsiType> types = new ArrayList<>(substitutionMap.values()); types.remove(returnType); final PsiType parameterType = types.get(0); return elementFactory.createType(targetClass, parameterType, returnType); } } }; }