|
| 1 | +/******************************************************************************* |
| 2 | + * Copyright (c) 2025 Broadcom, Inc. |
| 3 | + * All rights reserved. This program and the accompanying materials |
| 4 | + * are made available under the terms of the Eclipse Public License v1.0 |
| 5 | + * which accompanies this distribution, and is available at |
| 6 | + * https://www.eclipse.org/legal/epl-v10.html |
| 7 | + * |
| 8 | + * Contributors: |
| 9 | + * Broadcom, Inc. - initial API and implementation |
| 10 | + *******************************************************************************/ |
| 11 | +package org.springframework.ide.vscode.commons.rewrite.java; |
| 12 | + |
| 13 | +import java.util.ArrayList; |
| 14 | +import java.util.List; |
| 15 | + |
| 16 | +import org.openrewrite.Cursor; |
| 17 | +import org.openrewrite.ExecutionContext; |
| 18 | +import org.openrewrite.NlsRewrite.Description; |
| 19 | +import org.openrewrite.NlsRewrite.DisplayName; |
| 20 | +import org.openrewrite.Preconditions; |
| 21 | +import org.openrewrite.Recipe; |
| 22 | +import org.openrewrite.Tree; |
| 23 | +import org.openrewrite.TreeVisitor; |
| 24 | +import org.openrewrite.internal.ListUtils; |
| 25 | +import org.openrewrite.java.AnnotationMatcher; |
| 26 | +import org.openrewrite.java.JavaIsoVisitor; |
| 27 | +import org.openrewrite.java.search.DeclaresType; |
| 28 | +import org.openrewrite.java.tree.Expression; |
| 29 | +import org.openrewrite.java.tree.J; |
| 30 | +import org.openrewrite.java.tree.J.Annotation; |
| 31 | +import org.openrewrite.java.tree.J.ClassDeclaration; |
| 32 | +import org.openrewrite.java.tree.J.NewArray; |
| 33 | +import org.openrewrite.java.tree.JLeftPadded; |
| 34 | +import org.openrewrite.java.tree.JavaType; |
| 35 | +import org.openrewrite.java.tree.JavaType.FullyQualified; |
| 36 | +import org.openrewrite.java.tree.JavaType.ShallowClass; |
| 37 | +import org.openrewrite.java.tree.Space; |
| 38 | +import org.openrewrite.java.tree.TypeTree; |
| 39 | +import org.openrewrite.java.tree.TypeUtils; |
| 40 | +import org.openrewrite.marker.Markers; |
| 41 | + |
| 42 | +import com.fasterxml.jackson.annotation.JsonCreator; |
| 43 | +import com.fasterxml.jackson.annotation.JsonProperty; |
| 44 | + |
| 45 | +public class ImportBeanRegistrarInConfigRecipe extends Recipe { |
| 46 | + |
| 47 | + private static final String IMPORT_FQN = "org.springframework.context.annotation.Import"; |
| 48 | + |
| 49 | + private String configBeanFqn; |
| 50 | + |
| 51 | + private String beanRegFqn; |
| 52 | + |
| 53 | + @JsonCreator |
| 54 | + public ImportBeanRegistrarInConfigRecipe( |
| 55 | + @JsonProperty("configBeanFqn") String configBeanFqn, |
| 56 | + @JsonProperty("beanRegFqn") String beanRegFqn) { |
| 57 | + this.configBeanFqn = configBeanFqn; |
| 58 | + this.beanRegFqn = beanRegFqn; |
| 59 | + } |
| 60 | + |
| 61 | + @Override |
| 62 | + public @DisplayName String getDisplayName() { |
| 63 | + return "Add `BeanRegistrar` with `@Import` in Configuration bean"; |
| 64 | + } |
| 65 | + |
| 66 | + @Override |
| 67 | + public @Description String getDescription() { |
| 68 | + return "Add `BeanRegistrar` with `@Import` in Configuration bean."; |
| 69 | + } |
| 70 | + |
| 71 | + @Override |
| 72 | + public TreeVisitor<?, ExecutionContext> getVisitor() { |
| 73 | + final AnnotationMatcher importAnnotationMatcher = new AnnotationMatcher("@" + IMPORT_FQN); |
| 74 | + final ShallowClass beanRegistrarType = JavaType.ShallowClass.build(beanRegFqn); |
| 75 | + return Preconditions.check(new DeclaresType<>(configBeanFqn), new JavaIsoVisitor<>() { |
| 76 | + |
| 77 | + @Override |
| 78 | + public ClassDeclaration visitClassDeclaration(ClassDeclaration classDecl, ExecutionContext p) { |
| 79 | + ClassDeclaration cd = classDecl; |
| 80 | + FullyQualified type = TypeUtils.asFullyQualified(cd.getType()); |
| 81 | + |
| 82 | + if (type != null && configBeanFqn.equals(type.getFullyQualifiedName())) { |
| 83 | + J.Annotation importAnnotation = cd.getLeadingAnnotations().stream().filter(importAnnotationMatcher::matches).findFirst().orElse(null); |
| 84 | + if (importAnnotation == null) { |
| 85 | + ArrayList<J.Annotation> annotations = new ArrayList<>(cd.getLeadingAnnotations()); |
| 86 | + JavaType.ShallowClass annotationType = JavaType.ShallowClass.build(IMPORT_FQN); |
| 87 | + J.Identifier typeName = new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, List.of(), annotationType.getClassName(), annotationType, null); |
| 88 | + Space indent = Space.build("\n" + cd.getPrefix().getIndent(), List.of()); |
| 89 | + boolean noAnnotations = annotations.isEmpty(); |
| 90 | + importAnnotation = new J.Annotation( |
| 91 | + Tree.randomId(), |
| 92 | + noAnnotations ? Space.EMPTY : indent, |
| 93 | + Markers.EMPTY, |
| 94 | + typeName, |
| 95 | + null); |
| 96 | + annotations.add(importAnnotation); |
| 97 | + cd = cd.withLeadingAnnotations(annotations); |
| 98 | + if (noAnnotations) { |
| 99 | + cd = cd.getPadding().withKind(cd.getPadding().getKind().withPrefix(indent)); |
| 100 | + } |
| 101 | + maybeAddImport(IMPORT_FQN); |
| 102 | + } |
| 103 | + } |
| 104 | + return super.visitClassDeclaration(cd, p); |
| 105 | + } |
| 106 | + |
| 107 | + @Override |
| 108 | + public Annotation visitAnnotation(Annotation annotation, ExecutionContext p) { |
| 109 | + Annotation an = super.visitAnnotation(annotation, p); |
| 110 | + FullyQualified anType = TypeUtils.asFullyQualified(an.getType()); |
| 111 | + if (anType != null && IMPORT_FQN.equals(anType.getFullyQualifiedName())) { |
| 112 | + Cursor parentCursor = getCursor().getParentTreeCursor(); |
| 113 | + if (parentCursor != null && parentCursor.getValue() instanceof J.ClassDeclaration cd |
| 114 | + && cd.getType() != null && configBeanFqn.equals(cd.getType().getFullyQualifiedName())) { |
| 115 | + List<Expression> currentArgs = an.getArguments(); |
| 116 | + if (currentArgs == null || currentArgs.isEmpty() || currentArgs.get(0) instanceof J.Empty) { |
| 117 | + an = an.withArguments(List.of(createBeanRegistrarClassFieldAccess(beanRegistrarType))); |
| 118 | + maybeAddImport(beanRegistrarType); |
| 119 | + } else { |
| 120 | + List<Expression> newArgs = ListUtils.map(currentArgs, (i, arg) -> { |
| 121 | + if (arg instanceof J.FieldAccess fa) { |
| 122 | + if (!isBeanRegistrar(fa)) { |
| 123 | + // Turn into array |
| 124 | + NewArray array = new J.NewArray(Tree.randomId(), Space.EMPTY, Markers.EMPTY, null, List.of(), null, null); |
| 125 | + array = array.withInitializer(List.of( |
| 126 | + arg, |
| 127 | + createBeanRegistrarClassFieldAccess(beanRegistrarType) |
| 128 | + )); |
| 129 | + maybeAddImport(beanRegistrarType); |
| 130 | + return autoFormat(array, p, getCursor()); |
| 131 | + } |
| 132 | + } else if (arg instanceof J.NewArray na) { |
| 133 | + List<Expression> cna = new ArrayList<>(na.getInitializer()); |
| 134 | + boolean hasBeanRegistarar = cna.stream().filter(J.FieldAccess.class::isInstance) |
| 135 | + .map(J.FieldAccess.class::cast) |
| 136 | + .anyMatch(ImportBeanRegistrarInConfigRecipe.this::isBeanRegistrar); |
| 137 | + // Not found bean registrar - add it to the array |
| 138 | + if (!hasBeanRegistarar) { |
| 139 | + cna.add(createBeanRegistrarClassFieldAccess(beanRegistrarType)); |
| 140 | + maybeAddImport(beanRegistrarType); |
| 141 | + return autoFormat(na.withInitializer(cna), p, getCursor()); |
| 142 | + } |
| 143 | + } else if (arg instanceof J.Assignment assign && assign.getVariable() instanceof J.Identifier ident && "value".equals(ident.getSimpleName())) { |
| 144 | + if (assign.getAssignment() instanceof J.FieldAccess fa) { |
| 145 | + if (!isBeanRegistrar(fa)) { |
| 146 | + // Turn into array |
| 147 | + NewArray array = new J.NewArray(Tree.randomId(), Space.EMPTY, Markers.EMPTY, null, List.of(), null, null); |
| 148 | + array = array.withInitializer(List.of( |
| 149 | + fa, |
| 150 | + createBeanRegistrarClassFieldAccess(beanRegistrarType) |
| 151 | + )); |
| 152 | + maybeAddImport(beanRegistrarType); |
| 153 | + return autoFormat(assign.withAssignment(array), p, getCursor()); |
| 154 | + } |
| 155 | + } else if (assign.getAssignment() instanceof J.NewArray na) { |
| 156 | + List<Expression> cna = new ArrayList<>(na.getInitializer()); |
| 157 | + boolean hasBeanRegistarar = cna.stream().filter(J.FieldAccess.class::isInstance) |
| 158 | + .map(J.FieldAccess.class::cast) |
| 159 | + .anyMatch(ImportBeanRegistrarInConfigRecipe.this::isBeanRegistrar); |
| 160 | + // Not found bean registrar - add it to the array |
| 161 | + if (!hasBeanRegistarar) { |
| 162 | + cna.add(createBeanRegistrarClassFieldAccess(beanRegistrarType)); |
| 163 | + maybeAddImport(beanRegistrarType); |
| 164 | + return autoFormat(assign.withAssignment(na.withInitializer(cna)), p, getCursor()); |
| 165 | + } |
| 166 | + } |
| 167 | + } |
| 168 | + return arg; |
| 169 | + }); |
| 170 | + if (newArgs != currentArgs) { |
| 171 | + an = an.withArguments(newArgs); |
| 172 | + } |
| 173 | + } |
| 174 | + } |
| 175 | + } |
| 176 | + return an; |
| 177 | + } |
| 178 | + |
| 179 | + }); |
| 180 | + } |
| 181 | + |
| 182 | + private boolean isBeanRegistrar(J.FieldAccess fa) { |
| 183 | + if ("class".equals(fa.getSimpleName()) && fa.getTarget() instanceof TypeTree tt) { |
| 184 | + FullyQualified t = TypeUtils.asFullyQualified(tt.getType()); |
| 185 | + if (t != null && beanRegFqn.equals(t.getFullyQualifiedName())) { |
| 186 | + return true; |
| 187 | + } |
| 188 | + } |
| 189 | + return false; |
| 190 | + } |
| 191 | + |
| 192 | + private J.FieldAccess createBeanRegistrarClassFieldAccess(JavaType.FullyQualified t) { |
| 193 | + J.Identifier i = new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, List.of(), t.getClassName(), t, null); |
| 194 | + JavaType.Parameterized classType = new JavaType.Parameterized(0, JavaType.ShallowClass.build("java.lang.Class"), List.of(t)); |
| 195 | + J.Identifier c = new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, List.of(), "class", classType, null); |
| 196 | + return new J.FieldAccess(Tree.randomId(), Space.EMPTY, Markers.EMPTY, i, JLeftPadded.build(c), classType); |
| 197 | + } |
| 198 | + |
| 199 | +} |
0 commit comments