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

org.openrewrite.java.spring.boot2.MoveAutoConfigurationToImportsFile Maven / Gradle / Ivy

Go to download

Eliminate legacy Spring patterns and migrate between major Spring Boot versions. Automatically.

There is a newer version: 5.19.0
Show newest version
/*
 * Copyright 2021 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.java.spring.boot2; import lombok.EqualsAndHashCode; import lombok.Value; import org.openrewrite.*; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.RemoveAnnotation; import org.openrewrite.java.tree.J; import org.openrewrite.marker.Marker; import org.openrewrite.text.PlainText; import org.openrewrite.text.PlainTextParser; import java.nio.file.Path; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; public class MoveAutoConfigurationToImportsFile extends ScanningRecipe { private static final String AUTOCONFIGURATION_FILE = "org.springframework.boot.autoconfigure.AutoConfiguration.imports"; private static final String ENABLE_AUTO_CONFIG_KEY = "org.springframework.boot.autoconfigure.EnableAutoConfiguration"; @Override public String getDisplayName() { return "Use `AutoConfiguration#imports`"; } @Override public String getDescription() { return "Use `AutoConfiguration#imports` instead of the deprecated entry " + "`EnableAutoConfiguration` in `spring.factories` when defining " + "autoconfiguration classes."; } @Override public Accumulator getInitialValue(ExecutionContext ctx) { return new Accumulator(); } @Override public TreeVisitor getScanner(Accumulator acc) { // First pass will look for any spring.factories source files to collect any auto-config classes in those files // and remove them. We build a map to the path of the target import file (computed relative to the spring.factories // file) to a list of autoconfiguration classes from the spring.factories and any markers that may have been // on the factory class. If we end up creating a new file, we will copy the markers to this file as well. // We also look for any existing import files (because we may need to merge entries from the spring.factories into // an existing file). return new TreeVisitor() { @Override public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { if (tree instanceof PlainText) { PlainText source = ((PlainText) tree); Path sourcePath = source.getSourcePath(); if (sourcePath.endsWith("spring.factories")) { Set configs = new HashSet<>(); extractAutoConfigsFromSpringFactory(source, configs); if (!configs.isEmpty()) { acc.getExistingSpringFactories().add(sourcePath); acc.getTargetImports().put(sourcePath.getParent().resolve("spring/" + AUTOCONFIGURATION_FILE), new TargetImports(configs, source.getMarkers().getMarkers())); acc.getAllFoundConfigs().addAll(configs); } } else if (sourcePath.endsWith(AUTOCONFIGURATION_FILE)) { acc.getExistingImportFiles().add(sourcePath); } } return tree; } }; } @Override public Collection generate(Accumulator acc, ExecutionContext ctx) { List newImportFiles = new ArrayList<>(); for (Map.Entry entry : acc.getTargetImports().entrySet()) { if (entry.getValue().getAutoConfigurations().isEmpty() || acc.getExistingImportFiles().contains(entry.getKey())) { continue; } List finalList = new ArrayList<>(entry.getValue().getAutoConfigurations()); Collections.sort(finalList); PlainTextParser parser = new PlainTextParser(); PlainText brandNewFile = parser.parse(String.join("\n", finalList)) .map(PlainText.class::cast) .findFirst() .get(); newImportFiles.add(brandNewFile .withSourcePath(entry.getKey()) .withMarkers(brandNewFile.getMarkers().withMarkers(entry.getValue().getMarkers())) ); } if (!newImportFiles.isEmpty()) { return newImportFiles; } else { return Collections.emptyList(); } } @Override public TreeVisitor getVisitor(Accumulator acc) { Set mergeTargets = acc.getExistingImportFiles().stream().filter(acc.getTargetImports()::containsKey).collect(Collectors.toSet()); if (mergeTargets.isEmpty() && acc.getAllFoundConfigs().isEmpty() && acc.getExistingSpringFactories().isEmpty()) { return TreeVisitor.noop(); } return new TreeVisitor() { @Override public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { if (!(tree instanceof SourceFile)) { return tree; } SourceFile source = (SourceFile) tree; Path sourcePath = source.getSourcePath(); if (tree instanceof PlainText) { if (mergeTargets.contains(sourcePath)) { //If there is both a spring.factories and an existing imports file, merge the contents of both into the import tree = mergeEntries(source, acc.getTargetImports().get(sourcePath).getAutoConfigurations()); } else if (acc.getExistingSpringFactories().contains(sourcePath)) { tree = extractAutoConfigsFromSpringFactory((PlainText) source, new HashSet<>()); } } else if (tree instanceof J.CompilationUnit) { tree = new AddAutoConfigurationAnnotation(acc.getAllFoundConfigs()).visit(tree, ctx); } return tree; } }; } @Nullable private static PlainText extractAutoConfigsFromSpringFactory(PlainText springFactory, Set configs) { String contents = springFactory.getText(); int state = 0; int index = 0; StringBuilder currentKey = new StringBuilder(); StringBuilder currentValue = new StringBuilder(); int keyIndexStart = 0; int valueIndexEnd = 0; while (index < contents.length()) { if (contents.charAt(index) == '\\' && isLineBreakOrEof(contents, index + 1)) { //If this is a line continuous, advance to the next line and then chew up any white space. index = advanceToNextLine(contents, index); index = advancePastWhiteSpace(contents, index); } if (state == 0) { // Find New Key index = advancePastWhiteSpace(contents, index); if (index >= contents.length()) { break; } if (contents.charAt(index) == '#') { //Comment index = advanceToNextLine(contents, index); } else { state = 1; } continue; } else if (state == 1) { if (isLineBreakOrEof(contents, index)) { //Building a key and encountered a line ending, if there is a key, the value is null, reset //and continue; currentKey.setLength(0); currentValue.setLength(0); state = 0; } else if (contents.charAt(index) == '=' || contents.charAt(index) == ':' || Character.isWhitespace(contents.charAt(index))) { state = 2; } else { if (currentKey.length() == 0) { keyIndexStart = index; } currentKey.append(contents.charAt(index)); } } else { //State == 2 //Building Value if (isLineBreakOrEof(contents, index)) { //End of value! if (ENABLE_AUTO_CONFIG_KEY.contentEquals(currentKey)) { //Found the key, lets break now. index = advanceToNextLine(contents, index); valueIndexEnd = Math.min(index, contents.length()); break; } else { currentKey.setLength(0); currentValue.setLength(0); state = 0; } } else { currentValue.append(contents.charAt(index)); } } index++; } if (ENABLE_AUTO_CONFIG_KEY.contentEquals(currentKey)) { Stream.of(currentValue.toString().split(",")).map(String::trim).forEach(configs::add); String newContent = contents.substring(0, keyIndexStart) + contents.substring(valueIndexEnd == 0 ? contents.length() : valueIndexEnd); return newContent.isEmpty() ? null : springFactory.withText(newContent); } else { return springFactory; } } private static int advancePastWhiteSpace(String contents, int index) { while (index < contents.length() && contents.charAt(index) != '\r' && contents.charAt(index) != '\n' && Character.isWhitespace(contents.charAt(index))) { index++; } return index; } private static int advanceToNextLine(String contents, int index) { while (index < contents.length() && !isLineBreakOrEof(contents, index)) { index++; } if (index + 1 < contents.length() && contents.charAt(index) == '\r' && contents.charAt(index + 1) == '\n') { index = index + 2; } else { index++; } return index; } private static boolean isLineBreakOrEof(String contents, int index) { if (index == contents.length()) { return true; } char first = contents.charAt(index); Character second = index + 1 < contents.length() ? contents.charAt(index + 1) : null; return (second != null && first == '\r' && second == '\n') || first == '\r' || first == '\n'; } private static SourceFile mergeEntries(SourceFile before, Set configClasses) { PlainText plainText = (PlainText) before; Set original = new HashSet<>(Arrays.asList(plainText.getText().split("\n"))); Set merged = new TreeSet<>(configClasses); merged.addAll(original); if (merged.size() != original.size()) { return plainText.withText(String.join("\n", merged)); } else { return before; } } @Value @EqualsAndHashCode(callSuper = false) private static class AddAutoConfigurationAnnotation extends JavaIsoVisitor { Set fullyQualifiedConfigClasses; @Override public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { J.ClassDeclaration c = super.visitClassDeclaration(classDecl, ctx); if (c.getType() != null && fullyQualifiedConfigClasses.contains(c.getType().getFullyQualifiedName())) { JavaTemplate addAnnotationTemplate = JavaTemplate.builder("@AutoConfiguration") .javaParser(JavaParser.fromJavaVersion() .classpathFromResources(ctx, "spring-boot-autoconfigure-2.7.*")) .imports("org.springframework.boot.autoconfigure.AutoConfiguration") .build(); doAfterVisit(new RemoveAnnotation("@org.springframework.context.annotation.Configuration").getVisitor()); c = addAnnotationTemplate.apply(getCursor(), c.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName))); maybeAddImport("org.springframework.boot.autoconfigure.AutoConfiguration"); } return c; } } @Value static class Accumulator { Set existingSpringFactories = new HashSet<>(); Set existingImportFiles = new HashSet<>(); Set allFoundConfigs = new HashSet<>(); Map targetImports = new HashMap<>(); } /** * Used to track the auto configurations defined in `spring.factories` (along with any markers on that file) */ @Value static class TargetImports { Set autoConfigurations; List markers; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy