PsiElementVisitor (@NotNull ProblemsHolder holder, boolean isOnTheFly) { return new JavaElementVisitor() { @Override public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) { String methodName = call.getMethodExpression().getReferenceName(); if (!"get".equals(methodName) && !"with".equals(methodName) && !"plus".equals(methodName) && !"minus".equals(methodName)) { return; } if (!CAN_BE_SIMPLIFIED_MATCHERS.matches(call)) return; int fieldArgumentIndex = 1; if ("get".equals(methodName) || "with".equals(methodName)) { fieldArgumentIndex = 0; } PsiExpression[] expressions = call.getArgumentList().getExpressions(); if (expressions.length < fieldArgumentIndex + 1) { return; } @Nullable PsiExpression fieldExpression = expressions[fieldArgumentIndex]; String chronoEnumName = getNameOfChronoEnum(fieldExpression, methodName); if (chronoEnumName == null) return; String newMethodName = getNewMethodName(chronoEnumName, call); if (newMethodName == null) return; PsiElement identifier = getIdentifier(call.getMethodExpression()); if (identifier == null) return; holder.registerProblem(identifier, InspectionGadgetsBundle.message("inspection.explicit.chrono.field.problem.descriptor"), new InlineChronoEnumCallFix(newMethodName, fieldArgumentIndex)); } @Nullable private static String getNewMethodName(@NotNull String chronoEnumName, @NotNull PsiMethodCallExpression call) { PsiMethod method = call.resolveMethod(); if (method == null) { return null; } if (!isAvailableCall(method, chronoEnumName)) { return null; } //'with(ChronoField, long)' can be converted only to 'with...(int)' if ("with".equals(method.getName())) { PsiType[] types = call.getArgumentList().getExpressionTypes(); if (types.length != 2 || types[1] == null || TypeConversionUtil.getTypeRank(types[1]) > TypeConversionUtil.INT_RANK) { return null; } } String methodName = method.getName(); return findEquivalentMethod(chronoEnumName, methodName); } private static boolean isAvailableCall(@NotNull PsiMethod method, @NotNull String chronoEnumName) { return switch (method.getName()) { case "get" -> ChronoUtil.isAnyGetSupported(method, ChronoUtil.getChronoField(chronoEnumName)); case "with" -> ChronoUtil.isWithSupported(method, ChronoUtil.getChronoField(chronoEnumName)); case "plus", "minus" -> ChronoUtil.isPlusMinusSupported(method, ChronoUtil.getChronoUnit(chronoEnumName)); default -> false; }; } @Nullable private static String findEquivalentMethod(@NotNull String chronoEnumName, @NotNull String methodName) { return switch (methodName) { case "plus" -> switch (chronoEnumName) { case "NANOS" -> "plusNanos"; case "SECONDS" -> "plusSeconds"; case "MINUTES" -> "plusMinutes"; case "HOURS" -> "plusHours"; case "DAYS" -> "plusDays"; case "WEEKS" -> "plusWeeks"; case "MONTHS" -> "plusMonths"; case "YEARS" -> "plusYears"; default -> null; }; case "minus" -> switch (chronoEnumName) { case "NANOS" -> "minusNanos"; case "SECONDS" -> "minusSeconds"; case "MINUTES" -> "minusMinutes"; case "HOURS" -> "minusHours"; case "DAYS" -> "minusDays"; case "WEEKS" -> "minusWeeks"; case "MONTHS" -> "minusMonths"; case "YEARS" -> "minusYears"; default -> null; }; case "get" -> switch (chronoEnumName) { case "NANO_OF_SECOND" -> "getNano"; case "SECOND_OF_MINUTE" -> "getSecond"; case "MINUTE_OF_HOUR" -> "getMinute"; case "HOUR_OF_DAY" -> "getHour"; case "DAY_OF_MONTH" -> "getDayOfMonth"; case "DAY_OF_YEAR" -> "getDayOfYear"; case "MONTH_OF_YEAR" -> "getMonth"; case "YEAR" -> "getYear"; default -> null; }; case "with" -> switch (chronoEnumName) { case "NANO_OF_SECOND" -> "withNano"; case "SECOND_OF_MINUTE" -> "withSecond"; case "MINUTE_OF_HOUR" -> "withMinute"; case "HOUR_OF_DAY" -> "withHour"; case "DAY_OF_MONTH" -> "withDayOfMonth"; case "DAY_OF_YEAR" -> "withDayOfYear"; case "MONTH_OF_YEAR" -> "withMonth"; case "YEAR" -> "withYear"; default -> null; }; default -> null; }; } @Nullable private static PsiElement getIdentifier(@Nullable PsiReferenceExpression expression) { if (expression == null) return null; PsiIdentifier[] identifiers = PsiTreeUtil.getChildrenOfType(expression, PsiIdentifier.class); if (identifiers == null || identifiers.length != 1) { return null; } return identifiers[0]; } @Nullable private static String getNameOfChronoEnum(@Nullable PsiExpression expression, @Nullable String methodName) { if (expression == null || methodName == null) return null; if (!(expression instanceof PsiReferenceExpression referenceExpression)) { return null; } PsiElement resolvedElement = referenceExpression.resolve(); if (!(resolvedElement instanceof PsiEnumConstant enumConstant)) { return null; } PsiClass containingClass = enumConstant.getContainingClass(); if (containingClass == null || !containingClass.isEnum()) { return null; } String classQualifiedName = containingClass.getQualifiedName(); if (!(ChronoUtil.CHRONO_FIELD.equals(classQualifiedName) && (methodName.equals("get") || methodName.equals("with"))) && !(ChronoUtil.CHRONO_UNIT.equals(classQualifiedName)) && (methodName.equals("plus") || methodName.equals("minus"))) { return null; } return enumConstant.getName(); } }; }