1
+ /*
2
+ * Copyright 2023 the original author or authors.
3
+ * <p>
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ * <p>
8
+ * https://www.apache.org/licenses/LICENSE-2.0
9
+ * <p>
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ package org .openrewrite .java .migrate .guava ;
17
+
18
+ import org .openrewrite .ExecutionContext ;
19
+ import org .openrewrite .Preconditions ;
20
+ import org .openrewrite .Recipe ;
21
+ import org .openrewrite .TreeVisitor ;
22
+ import org .openrewrite .internal .lang .Nullable ;
23
+ import org .openrewrite .java .JavaTemplate ;
24
+ import org .openrewrite .java .JavaVisitor ;
25
+ import org .openrewrite .java .MethodMatcher ;
26
+ import org .openrewrite .java .search .UsesJavaVersion ;
27
+ import org .openrewrite .java .search .UsesType ;
28
+ import org .openrewrite .java .tree .*;
29
+
30
+ import java .time .Duration ;
31
+ import java .util .Objects ;
32
+ import java .util .stream .Collectors ;
33
+
34
+ abstract class AbstractNoGuavaImmutableOf extends Recipe {
35
+
36
+ private final String guavaType ;
37
+ private final String javaType ;
38
+
39
+ AbstractNoGuavaImmutableOf (String guavaType , String javaType ) {
40
+ this .guavaType = guavaType ;
41
+ this .javaType = javaType ;
42
+ }
43
+
44
+ private String getShortType (String fullyQualifiedType ) {
45
+ return fullyQualifiedType .substring (javaType .lastIndexOf ("." ) + 1 );
46
+ }
47
+
48
+ @ Override
49
+ public String getDisplayName () {
50
+ return "Prefer `" + getShortType (javaType ) + ".of(..)` in Java 9 or higher" ;
51
+ }
52
+
53
+ @ Override
54
+ public String getDescription () {
55
+ return "Replaces `" + getShortType (guavaType ) + ".of(..)` if the returned type is immediately down-cast." ;
56
+ }
57
+
58
+ @ Override
59
+ public Duration getEstimatedEffortPerOccurrence () {
60
+ return Duration .ofMinutes (10 );
61
+ }
62
+
63
+ @ Override
64
+ public TreeVisitor <?, ExecutionContext > getVisitor () {
65
+ TreeVisitor <?, ExecutionContext > check = Preconditions .and (new UsesJavaVersion <>(9 ),
66
+ new UsesType <>(guavaType , false ));
67
+ final MethodMatcher IMMUTABLE_MATCHER = new MethodMatcher (guavaType + " of(..)" );
68
+ return Preconditions .check (check , new JavaVisitor <ExecutionContext >() {
69
+ @ Override
70
+ public J visitMethodInvocation (J .MethodInvocation method , ExecutionContext ctx ) {
71
+ if (IMMUTABLE_MATCHER .matches (method ) && isParentTypeDownCast ()) {
72
+ maybeRemoveImport (guavaType );
73
+ maybeAddImport (javaType );
74
+
75
+ String template = method .getArguments ().stream ()
76
+ .map (arg -> {
77
+ if (arg .getType () instanceof JavaType .Primitive ) {
78
+ String type = "" ;
79
+ if (JavaType .Primitive .Boolean == arg .getType ()) {
80
+ type = "Boolean" ;
81
+ } else if (JavaType .Primitive .Byte == arg .getType ()) {
82
+ type = "Byte" ;
83
+ } else if (JavaType .Primitive .Char == arg .getType ()) {
84
+ type = "Character" ;
85
+ } else if (JavaType .Primitive .Double == arg .getType ()) {
86
+ type = "Double" ;
87
+ } else if (JavaType .Primitive .Float == arg .getType ()) {
88
+ type = "Float" ;
89
+ } else if (JavaType .Primitive .Int == arg .getType ()) {
90
+ type = "Integer" ;
91
+ } else if (JavaType .Primitive .Long == arg .getType ()) {
92
+ type = "Long" ;
93
+ } else if (JavaType .Primitive .Short == arg .getType ()) {
94
+ type = "Short" ;
95
+ } else if (JavaType .Primitive .String == arg .getType ()) {
96
+ type = "String" ;
97
+ }
98
+ return TypeUtils .asFullyQualified (JavaType .buildType ("java.lang." + type ));
99
+ } else {
100
+ return TypeUtils .asFullyQualified (arg .getType ());
101
+ }
102
+ })
103
+ .filter (Objects ::nonNull )
104
+ .map (type -> "#{any(" + type .getFullyQualifiedName () + ")}" )
105
+ .collect (Collectors .joining ("," , getShortType (javaType ) + ".of(" , ")" ));
106
+
107
+ return JavaTemplate .builder (template )
108
+ .contextSensitive ()
109
+ .imports (javaType )
110
+ .build ()
111
+ .apply (getCursor (),
112
+ method .getCoordinates ().replace (),
113
+ method .getArguments ().get (0 ) instanceof J .Empty ? new Object []{} : method .getArguments ().toArray ());
114
+ }
115
+ return super .visitMethodInvocation (method , ctx );
116
+ }
117
+
118
+ private boolean isParentTypeDownCast () {
119
+ J parent = getCursor ().dropParentUntil (J .class ::isInstance ).getValue ();
120
+ boolean isParentTypeDownCast = false ;
121
+ if (parent instanceof J .VariableDeclarations .NamedVariable ) {
122
+ isParentTypeDownCast = isParentTypeMatched (((J .VariableDeclarations .NamedVariable ) parent ).getType ());
123
+ } else if (parent instanceof J .Assignment ) {
124
+ J .Assignment a = (J .Assignment ) parent ;
125
+ if (a .getVariable () instanceof J .Identifier && ((J .Identifier ) a .getVariable ()).getFieldType () != null ) {
126
+ isParentTypeDownCast = isParentTypeMatched (((J .Identifier ) a .getVariable ()).getFieldType ().getType ());
127
+ } else if (a .getVariable () instanceof J .FieldAccess ) {
128
+ isParentTypeDownCast = isParentTypeMatched (a .getVariable ().getType ());
129
+ }
130
+ } else if (parent instanceof J .Return ) {
131
+ // Does not currently support returns in lambda expressions.
132
+ J j = getCursor ().dropParentUntil (is -> is instanceof J .MethodDeclaration || is instanceof J .CompilationUnit ).getValue ();
133
+ if (j instanceof J .MethodDeclaration ) {
134
+ TypeTree returnType = ((J .MethodDeclaration ) j ).getReturnTypeExpression ();
135
+ if (returnType != null ) {
136
+ isParentTypeDownCast = isParentTypeMatched (returnType .getType ());
137
+ }
138
+ }
139
+ } else if (parent instanceof J .MethodInvocation ) {
140
+ J .MethodInvocation m = (J .MethodInvocation ) parent ;
141
+ if (m .getMethodType () != null ) {
142
+ int index = 0 ;
143
+ for (Expression argument : m .getArguments ()) {
144
+ if (IMMUTABLE_MATCHER .matches (argument )) {
145
+ break ;
146
+ }
147
+ index ++;
148
+ }
149
+ isParentTypeDownCast = isParentTypeMatched (m .getMethodType ().getParameterTypes ().get (index ));
150
+ }
151
+ } else if (parent instanceof J .NewClass ) {
152
+ J .NewClass c = (J .NewClass ) parent ;
153
+ int index = 0 ;
154
+ if (c .getConstructorType () != null ) {
155
+ for (Expression argument : c .getArguments ()) {
156
+ if (IMMUTABLE_MATCHER .matches (argument )) {
157
+ break ;
158
+ }
159
+ index ++;
160
+ }
161
+ if (c .getConstructorType () != null ) {
162
+ isParentTypeDownCast = isParentTypeMatched (c .getConstructorType ().getParameterTypes ().get (index ));
163
+ }
164
+ }
165
+ } else if (parent instanceof J .NewArray ) {
166
+ J .NewArray a = (J .NewArray ) parent ;
167
+ JavaType arrayType = a .getType ();
168
+ while (arrayType instanceof JavaType .Array ) {
169
+ arrayType = ((JavaType .Array ) arrayType ).getElemType ();
170
+ }
171
+
172
+ isParentTypeDownCast = isParentTypeMatched (arrayType );
173
+ }
174
+ return isParentTypeDownCast ;
175
+ }
176
+
177
+ private boolean isParentTypeMatched (@ Nullable JavaType type ) {
178
+ JavaType .FullyQualified fq = TypeUtils .asFullyQualified (type );
179
+ return TypeUtils .isOfClassType (fq , javaType )
180
+ || TypeUtils .isOfClassType (fq , "java.lang.Object" );
181
+ }
182
+ });
183
+ }
184
+ }
0 commit comments