All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openrewrite.staticanalysis.UseDiamondOperator Maven / Gradle / Ivy

/*
 * Copyright 2020 the original author or authors.
 * 

* 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 *

* https://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 org.openrewrite.staticanalysis; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.internal.ListUtils; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.search.UsesJavaVersion; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; import org.openrewrite.staticanalysis.java.JavaFileChecker; import java.time.Duration; import java.util.*; import static java.util.Collections.singletonList; import static org.openrewrite.Tree.randomId; import static org.openrewrite.java.tree.TypeUtils.findDeclaredMethod; public class UseDiamondOperator extends Recipe { @Override public String getDisplayName() { return "Use the diamond operator"; } @Override public String getDescription() { return "The diamond operator (`<>`) should be used. Java 7 introduced the diamond operator (<>) to " + "reduce the verbosity of generics code. For instance, instead of having to declare a `List`'s " + "type in both its declaration and its constructor, you can now simplify the constructor declaration " + "with `<>`, and the compiler will infer the type."; } @Override public Set getTags() { return Collections.singleton("RSPEC-S2293"); } @Override public Duration getEstimatedEffortPerOccurrence() { return Duration.ofMinutes(1); } @Override public TreeVisitor getVisitor() { // don't try to do this for Groovy or Kotlin sources return Preconditions.check(new JavaFileChecker<>(), new UseDiamondOperatorVisitor()); } private static class UseDiamondOperatorVisitor extends JavaIsoVisitor { private boolean java9; @Override public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { java9 = new UsesJavaVersion(9).visit(cu, 0) != cu; return super.visitCompilationUnit(cu, ctx); } @Override public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { J.VariableDeclarations varDecls = super.visitVariableDeclarations(multiVariable, ctx); final TypedTree varDeclsTypeExpression = varDecls.getTypeExpression(); if (varDeclsTypeExpression != null && varDecls.getVariables().size() == 1 && varDecls.getVariables().get(0).getInitializer() != null && varDecls.getTypeExpression() instanceof J.ParameterizedType) { varDecls = varDecls.withVariables(ListUtils.map(varDecls.getVariables(), nv -> { if (nv.getInitializer() instanceof J.NewClass) { nv = nv.withInitializer(maybeRemoveParams(parameterizedTypes((J.ParameterizedType) varDeclsTypeExpression), (J.NewClass) nv.getInitializer())); } return nv; })); } return varDecls; } @Override public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ctx) { J.Assignment asgn = super.visitAssignment(assignment, ctx); if (asgn.getAssignment() instanceof J.NewClass) { JavaType.Parameterized assignmentType = TypeUtils.asParameterized(asgn.getType()); J.NewClass nc = (J.NewClass) asgn.getAssignment(); if (assignmentType != null && nc.getClazz() instanceof J.ParameterizedType) { asgn = asgn.withAssignment(maybeRemoveParams(assignmentType.getTypeParameters(), nc)); } } return asgn; } @Override public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { if (isAParameter()) { return method; } J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); JavaType.Method methodType = mi.getMethodType(); if (methodType != null && !mi.getArguments().isEmpty() && methodType.getParameterTypes().size() <= mi.getArguments().size()) { Optional declaredMethodType = findDeclaredMethod(methodType.getDeclaringType(), methodType.getName(), methodType.getParameterTypes()); if (!declaredMethodType.isPresent()) { // If we cannot find the method in the declaringType is because its parameter types doesn't match // due to generic type parameters being inferred on the invocation. We cannot safely apply the // diamond operator on an argument of this method, because we cannot guarantee type inference. return method; } mi = mi.withArguments(ListUtils.map(mi.getArguments(), (i, arg) -> { if (arg instanceof J.NewClass) { boolean isGenericType = false; boolean isVarArg = methodType.getParameterTypes().size() == 1 && methodType.getParameterTypes().get(0) instanceof JavaType.Array; if (isVarArg) { isGenericType = isGenericType(((JavaType.Array) methodType.getParameterTypes().get(0)).getElemType()); } else if (i < methodType.getParameterTypes().size()) { JavaType parameterType = methodType.getParameterTypes().get(i); isGenericType = isGenericType(parameterType); } if (isGenericType) { return arg; } J.NewClass nc = (J.NewClass) arg; if ((java9 || nc.getBody() == null) && !methodType.getParameterTypes().isEmpty()) { JavaType.Parameterized paramType = TypeUtils.asParameterized(getMethodParamType(methodType, i)); if (paramType != null && nc.getClazz() instanceof J.ParameterizedType) { return maybeRemoveParams(paramType.getTypeParameters(), nc); } } } return arg; })); } return mi; } private boolean isGenericType(@Nullable JavaType type) { if (type == null) { return false; } boolean isGeneric = false; JavaType.Parameterized parameterized = TypeUtils.asParameterized(type); if (parameterized != null) { List types = parameterized.getTypeParameters(); for (JavaType tp : types) { if (tp instanceof JavaType.GenericTypeVariable) { isGeneric = true; break; } } } return isGeneric; } private JavaType getMethodParamType(JavaType.Method methodType, int paramIndex) { if (methodType.hasFlags(Flag.Varargs) && paramIndex >= methodType.getParameterTypes().size() - 1) { return ((JavaType.Array) methodType.getParameterTypes().get(methodType.getParameterTypes().size() - 1)).getElemType(); } else { return methodType.getParameterTypes().get(paramIndex); } } @Override public J.Return visitReturn(J.Return _return, ExecutionContext ctx) { J.Return return_ = super.visitReturn(_return, ctx); J.NewClass nc = return_.getExpression() instanceof J.NewClass ? (J.NewClass) return_.getExpression() : null; if (nc != null && (java9 || nc.getBody() == null) && nc.getClazz() instanceof J.ParameterizedType) { J parentBlock = getCursor().dropParentUntil(v -> v instanceof J.MethodDeclaration || v instanceof J.Lambda).getValue(); if (parentBlock instanceof J.MethodDeclaration) { J.MethodDeclaration md = (J.MethodDeclaration) parentBlock; if (md.getReturnTypeExpression() instanceof J.ParameterizedType) { return_ = return_.withExpression( maybeRemoveParams(parameterizedTypes((J.ParameterizedType) md.getReturnTypeExpression()), nc)); } } } return return_; } private @Nullable List parameterizedTypes(J.ParameterizedType parameterizedType) { if (parameterizedType.getTypeParameters() == null) { return null; } List types = new ArrayList<>(parameterizedType.getTypeParameters().size()); for (Expression typeParameter : parameterizedType.getTypeParameters()) { types.add(typeParameter.getType()); } return types; } private J.NewClass maybeRemoveParams(@Nullable List paramTypes, J.NewClass newClass) { if (paramTypes != null && (java9 || newClass.getBody() == null) && newClass.getClazz() instanceof J.ParameterizedType) { J.ParameterizedType newClassType = (J.ParameterizedType) newClass.getClazz(); if (newClassType.getTypeParameters() != null) { if (paramTypes.size() != newClassType.getTypeParameters().size() || hasAnnotations(newClassType)) { return newClass; } else { for (int i = 0; i < paramTypes.size(); i++) { if (!TypeUtils.isAssignableTo(paramTypes.get(i), newClassType.getTypeParameters().get(i).getType())) { return newClass; } } } newClassType.getTypeParameters().stream() .map(e -> TypeUtils.asFullyQualified(e.getType())) .forEach(this::maybeRemoveImport); newClass = newClass.withClazz(newClassType.withTypeParameters(singletonList(new J.Empty(randomId(), Space.EMPTY, Markers.EMPTY)))); } } return newClass; } private static boolean hasAnnotations(J type) { if (type instanceof J.ParameterizedType) { J.ParameterizedType parameterizedType = (J.ParameterizedType) type; if (hasAnnotations(parameterizedType.getClazz())) { return true; } else if (parameterizedType.getTypeParameters() != null) { for (Expression typeParameter : parameterizedType.getTypeParameters()) { if (hasAnnotations(typeParameter)) { return true; } } } } else { return type instanceof J.AnnotatedType; } return false; } private boolean isAParameter() { return getCursor().dropParentUntil(p -> p instanceof J.MethodInvocation || p instanceof J.ClassDeclaration || p instanceof J.CompilationUnit || p instanceof J.Block || p == Cursor.ROOT_VALUE).getValue() instanceof J.MethodInvocation; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy