Merge pull request #8 from spring-projects/merge-module-parser

Merge ModuleParser into MavenModuleParser
This commit is contained in:
Fabian Krüger
2023-12-20 21:28:33 +00:00
committed by GitHub
4 changed files with 333 additions and 385 deletions

View File

@@ -1,275 +0,0 @@
/*
* Copyright 2021 - 2023 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.springframework.rewrite.parsers;
import org.jetbrains.annotations.NotNull;
import org.openrewrite.ExecutionContext;
import org.openrewrite.FileAttributes;
import org.openrewrite.Parser;
import org.openrewrite.SourceFile;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.internal.JavaTypeCache;
import org.openrewrite.java.marker.JavaSourceSet;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.marker.Generated;
import org.openrewrite.marker.Marker;
import org.openrewrite.marker.Markers;
import org.openrewrite.xml.tree.Xml;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.rewrite.utils.ResourceUtil;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.openrewrite.Tree.randomId;
/**
* @author Fabian Krüger
*/
public class ModuleParser {
private static final Logger LOGGER = LoggerFactory.getLogger(ModuleParser.class);
/**
* Add {@link Marker}s to {@link SourceFile}.
*/
public <T extends SourceFile> UnaryOperator<T> addProvenance(Path baseDir, List<Marker> provenance,
@Nullable Collection<Path> generatedSources) {
return s -> {
Markers markers = s.getMarkers();
for (Marker marker : provenance) {
markers = markers.addIfAbsent(marker);
}
if (generatedSources != null && generatedSources.contains(baseDir.resolve(s.getSourcePath()))) {
markers = markers.addIfAbsent(new Generated(randomId()));
}
return s.withMarkers(markers);
};
}
/**
* Parse Java sources and resources under {@code src/main} of current module.
*/
public SourceSetParsingResult processMainSources(Path baseDir, List<Resource> resources,
Xml.Document moduleBuildFile, JavaParser.Builder<? extends JavaParser, ?> javaParserBuilder,
RewriteResourceParser rp, List<Marker> provenanceMarkers, Set<Path> alreadyParsed,
ExecutionContext executionContext, MavenProject currentProject) {
LOGGER.info("Processing main sources in module '%s'".formatted(currentProject.getProjectId()));
// FIXME: 945
// Some annotation processors output generated sources to the /target directory.
// These are added for parsing but
// should be filtered out of the final SourceFile list.
List<Resource> mainJavaSources = new ArrayList<>();
List<Resource> javaSourcesInTarget = currentProject.getJavaSourcesInTarget(); // listJavaSources(resources,
// currentProject.getBasedir().resolve(currentProject.getBuildDirectory()));
List<Resource> javaSourcesInMain = currentProject.getMainJavaSources(); // listJavaSources(resources,
// currentProject.getBasedir().resolve(currentProject.getSourceDirectory()));
mainJavaSources.addAll(javaSourcesInTarget);
mainJavaSources.addAll(javaSourcesInMain);
LOGGER.info("[%s] Parsing source files".formatted(currentProject));
// FIXME 945 classpath
// - Resolve dependencies to non-reactor projects from Maven repository
// - Resolve dependencies to reactor projects by providing the sources
// javaParserBuilder.classpath(byte[])
// we're processing a module here. The classpath of the module consists of all
// declared dependencies and their transitive dependencies too.
// For dependencies to projects that belong to the current rector...
// They'd either need to be built with Maven before to guarantee that the jars are
// installed to local Maven repo.
// Or, the classpath must be created from the sources of the project.
List<Path> dependencies = currentProject.getCompileClasspathElements();
javaParserBuilder.classpath(dependencies);
JavaTypeCache typeCache = new JavaTypeCache();
javaParserBuilder.typeCache(typeCache);
Iterable<Parser.Input> inputs = mainJavaSources.stream().map(r -> {
FileAttributes fileAttributes = null;
Path path = ResourceUtil.getPath(r);
boolean isSynthetic = Files.exists(path);
Supplier<InputStream> inputStreamSupplier = () -> ResourceUtil.getInputStream(r);
Parser.Input input = new Parser.Input(path, fileAttributes, inputStreamSupplier, isSynthetic);
return input;
}).toList();
Set<JavaType.FullyQualified> localClassesCp = new HashSet<>();
JavaSourceSet javaSourceSet = sourceSet("main", dependencies, typeCache);
List<? extends SourceFile> cus = javaParserBuilder.build()
.parseInputs(inputs, baseDir, executionContext)
.peek(s -> {
((J.CompilationUnit) s).getClasses()
.stream()
.map(J.ClassDeclaration::getType)
.forEach(localClassesCp::add);
alreadyParsed.add(baseDir.resolve(s.getSourcePath()));
})
.toList();
// TODO: This is a hack:
// Parsed java sources are not themselves on the classpath (here).
// The actual parsing happens when the stream is terminated (toList),
// therefore the toList() must be called before the parsed compilation units can
// be added to the classpath
List<Marker> mainProjectProvenance = new ArrayList<>(provenanceMarkers);
javaSourceSet = appendToClasspath(localClassesCp, javaSourceSet);
mainProjectProvenance.add(javaSourceSet);
List<Path> parsedJavaPaths = javaSourcesInTarget.stream().map(ResourceUtil::getPath).toList();
Stream<SourceFile> parsedJava = cus.stream()
.map(addProvenance(baseDir, mainProjectProvenance, parsedJavaPaths));
LOGGER.debug(
"[%s] Scanned %d java source files in main scope.".formatted(currentProject, mainJavaSources.size()));
// Filter out any generated source files from the returned list, as we do not want
// to apply the recipe to the
// generated files.
Path buildDirectory = Paths.get(currentProject.getBuildDirectory());
List<SourceFile> sourceFiles = parsedJava.filter(s -> !s.getSourcePath().startsWith(buildDirectory))
.collect(Collectors.toCollection(ArrayList::new));
int sourcesParsedBefore = alreadyParsed.size();
alreadyParsed.addAll(parsedJavaPaths);
List<SourceFile> parsedResourceFiles = rp
.parse(currentProject.getModulePath().resolve("src/main/resources"), resources, alreadyParsed)
.map(addProvenance(baseDir, mainProjectProvenance, null))
.toList();
LOGGER.debug("[%s] Scanned %d resource files in main scope.".formatted(currentProject,
(alreadyParsed.size() - sourcesParsedBefore)));
// Any resources parsed from "main/resources" should also have the main source set
// added to them.
sourceFiles.addAll(parsedResourceFiles);
return new SourceSetParsingResult(sourceFiles, javaSourceSet.getClasspath());
}
/**
* Add entries that don't exist in the classpath of {@code javaSourceSet} from
* {@code appendingClasspath}.
*/
@NotNull
private static JavaSourceSet appendToClasspath(Set<JavaType.FullyQualified> appendingClasspath,
JavaSourceSet javaSourceSet) {
List<JavaType.FullyQualified> curCp = javaSourceSet.getClasspath();
appendingClasspath.forEach(f -> {
if (!curCp.contains(f)) {
curCp.add(f);
}
});
javaSourceSet = javaSourceSet.withClasspath(new ArrayList<>(curCp));
return javaSourceSet;
}
@NotNull
private static JavaSourceSet sourceSet(String name, List<Path> dependencies, JavaTypeCache typeCache) {
return JavaSourceSet.build(name, dependencies, typeCache, false);
}
/**
* Parse Java sources and resource files under {@code src/test}.
*/
public SourceSetParsingResult processTestSources(Path baseDir, Xml.Document moduleBuildFile,
JavaParser.Builder<? extends JavaParser, ?> javaParserBuilder, RewriteResourceParser rp,
List<Marker> provenanceMarkers, Set<Path> alreadyParsed, ExecutionContext executionContext,
MavenProject currentProject, List<Resource> resources, List<JavaType.FullyQualified> classpath) {
LOGGER.info("Processing test sources in module '%s'".formatted(currentProject.getProjectId()));
List<Path> testDependencies = currentProject.getTestClasspathElements();
javaParserBuilder.classpath(testDependencies);
JavaTypeCache typeCache = new JavaTypeCache();
javaParserBuilder.typeCache(typeCache);
List<Resource> testJavaSources = listJavaSources(resources,
currentProject.getBasedir().resolve(currentProject.getTestSourceDirectory()));
alreadyParsed.addAll(testJavaSources.stream().map(ResourceUtil::getPath).toList());
Iterable<Parser.Input> inputs = testJavaSources.stream()
.map(r -> new Parser.Input(ResourceUtil.getPath(r), () -> ResourceUtil.getInputStream(r)))
.toList();
final List<JavaType.FullyQualified> localClassesCp = new ArrayList<>();
List<? extends SourceFile> cus = javaParserBuilder.build()
.parseInputs(inputs, baseDir, executionContext)
.peek(s -> {
((J.CompilationUnit) s).getClasses()
.stream()
.map(J.ClassDeclaration::getType)
.forEach(localClassesCp::add);
alreadyParsed.add(baseDir.resolve(s.getSourcePath()));
})
.toList();
List<Marker> markers = new ArrayList<>(provenanceMarkers);
JavaSourceSet javaSourceSet = sourceSet("test", testDependencies, typeCache);
Set<JavaType.FullyQualified> curClasspath = Stream.concat(classpath.stream(), localClassesCp.stream())
.collect(Collectors.toSet());
javaSourceSet = appendToClasspath(curClasspath, javaSourceSet);
markers.add(javaSourceSet);
Stream<SourceFile> parsedJava = cus.stream().map(addProvenance(baseDir, markers, null));
LOGGER.debug(
"[%s] Scanned %d java source files in test scope.".formatted(currentProject, testJavaSources.size()));
Stream<SourceFile> sourceFiles = parsedJava;
// Any resources parsed from "test/resources" should also have the test source set
// added to them.
int sourcesParsedBefore = alreadyParsed.size();
Stream<SourceFile> parsedResourceFiles = rp
.parse(currentProject.getBasedir().resolve("src/test/resources"), resources, alreadyParsed)
.map(addProvenance(baseDir, markers, null));
LOGGER.debug("[%s] Scanned %d resource files in test scope.".formatted(currentProject,
(alreadyParsed.size() - sourcesParsedBefore)));
sourceFiles = Stream.concat(sourceFiles, parsedResourceFiles);
List<SourceFile> result = sourceFiles.toList();
return new SourceSetParsingResult(result, javaSourceSet.getClasspath());
}
// FIXME: 945 take Java sources from resources
private static List<Resource> listJavaSources(List<Resource> resources, Path sourceDirectory) {
return resources.stream().filter(whenIn(sourceDirectory)).filter(whenFileNameEndsWithJava()).toList();
}
@NotNull
private static Predicate<Resource> whenFileNameEndsWithJava() {
return p -> ResourceUtil.getPath(p).getFileName().toString().endsWith(".java");
}
@NotNull
private static Predicate<Resource> whenIn(Path sourceDirectory) {
return r -> ResourceUtil.getPath(r).toString().startsWith(sourceDirectory.toString());
}
}

View File

@@ -80,13 +80,8 @@ public class RewriteParserConfiguration {
}
@Bean
ModuleParser moduleParser() {
return new ModuleParser();
}
@Bean
MavenModuleParser mavenModuleParser(SpringRewriteProperties springRewriteProperties, ModuleParser moduleParser) {
return new MavenModuleParser(springRewriteProperties, moduleParser);
MavenModuleParser mavenModuleParser(SpringRewriteProperties springRewriteProperties) {
return new MavenModuleParser(springRewriteProperties);
}
@Bean

View File

@@ -15,11 +15,20 @@
*/
package org.springframework.rewrite.parsers.maven;
import org.jetbrains.annotations.NotNull;
import org.openrewrite.ExecutionContext;
import org.openrewrite.FileAttributes;
import org.openrewrite.Parser;
import org.openrewrite.SourceFile;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.internal.JavaTypeCache;
import org.openrewrite.java.marker.JavaSourceSet;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.marker.Generated;
import org.openrewrite.marker.Marker;
import org.openrewrite.marker.Markers;
import org.openrewrite.style.NamedStyles;
import org.openrewrite.tree.ParsingExecutionContextView;
import org.openrewrite.xml.tree.Xml;
@@ -27,134 +36,355 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.rewrite.parsers.*;
import org.springframework.rewrite.utils.ResourceUtil;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.openrewrite.Tree.randomId;
/**
* @author Fabian Krüger
*/
public class MavenModuleParser {
private static final Logger LOGGER = LoggerFactory.getLogger(MavenProvenanceMarkerFactory.class);
private static final Logger LOGGER = LoggerFactory.getLogger(MavenProvenanceMarkerFactory.class);
private final SpringRewriteProperties springRewriteProperties;
private final SpringRewriteProperties springRewriteProperties;
private final ModuleParser mavenMojoProjectParserPrivateMethods;
public MavenModuleParser(SpringRewriteProperties springRewriteProperties) {
this.springRewriteProperties = springRewriteProperties;
}
public MavenModuleParser(SpringRewriteProperties springRewriteProperties,
ModuleParser mavenMojoProjectParserPrivateMethods) {
this.springRewriteProperties = springRewriteProperties;
this.mavenMojoProjectParserPrivateMethods = mavenMojoProjectParserPrivateMethods;
}
public List<SourceFile> parseModuleSourceFiles(List<Resource> resources, MavenProject currentProject,
Xml.Document moduleBuildFile, List<Marker> provenanceMarkers, List<NamedStyles> styles,
ExecutionContext executionContext, Path baseDir) {
public List<SourceFile> parseModuleSourceFiles(List<Resource> resources, MavenProject currentProject,
Xml.Document moduleBuildFile, List<Marker> provenanceMarkers, List<NamedStyles> styles,
ExecutionContext executionContext, Path baseDir) {
List<SourceFile> sourceFiles = new ArrayList<>();
// 146:149: get source encoding from maven
// TDOD:
// String s =
// moduleBuildFile.getMarkers().findFirst(MavenResolutionResult.class).get().getPom().getProperties().get("project.build.sourceEncoding");
// if (mavenSourceEncoding != null) {
// ParsingExecutionContextView.view(ctx).setCharset(Charset.forName(mavenSourceEncoding.toString()));
// }
Object mavenSourceEncoding = currentProject.getProjectEncoding();
if (mavenSourceEncoding != null) {
ParsingExecutionContextView.view(executionContext)
.setCharset(Charset.forName(mavenSourceEncoding.toString()));
}
List<SourceFile> sourceFiles = new ArrayList<>();
// 146:149: get source encoding from maven
// TDOD:
// String s =
// moduleBuildFile.getMarkers().findFirst(MavenResolutionResult.class).get().getPom().getProperties().get("project.build.sourceEncoding");
// if (mavenSourceEncoding != null) {
// ParsingExecutionContextView.view(ctx).setCharset(Charset.forName(mavenSourceEncoding.toString()));
// }
Object mavenSourceEncoding = currentProject.getProjectEncoding();
if (mavenSourceEncoding != null) {
ParsingExecutionContextView.view(executionContext)
.setCharset(Charset.forName(mavenSourceEncoding.toString()));
}
JavaParser.Builder<? extends JavaParser, ?> javaParserBuilder = JavaParser.fromJavaVersion()
.styles(styles)
.logCompilationWarningsAndErrors(false);
JavaParser.Builder<? extends JavaParser, ?> javaParserBuilder = JavaParser.fromJavaVersion()
.styles(styles)
.logCompilationWarningsAndErrors(false);
Path buildFilePath = currentProject.getBasedir().resolve(moduleBuildFile.getSourcePath());
LOGGER.info("Parsing module " + buildFilePath);
// these paths will be ignored by ResourceParser
Set<Path> skipResourceScanDirs = pathsToOtherMavenProjects(currentProject, buildFilePath);
RewriteResourceParser rp = new RewriteResourceParser(baseDir, springRewriteProperties.getIgnoredPathPatterns(),
springRewriteProperties.getPlainTextMasks(), springRewriteProperties.getSizeThresholdMb(),
skipResourceScanDirs, javaParserBuilder.clone(), executionContext);
Path buildFilePath = currentProject.getBasedir().resolve(moduleBuildFile.getSourcePath());
LOGGER.info("Parsing module " + buildFilePath);
// these paths will be ignored by ResourceParser
Set<Path> skipResourceScanDirs = pathsToOtherMavenProjects(currentProject, buildFilePath);
RewriteResourceParser rp = new RewriteResourceParser(baseDir, springRewriteProperties.getIgnoredPathPatterns(),
springRewriteProperties.getPlainTextMasks(), springRewriteProperties.getSizeThresholdMb(),
skipResourceScanDirs, javaParserBuilder.clone(), executionContext);
Set<Path> alreadyParsed = new HashSet<>();
Path moduleBuildFilePath = baseDir.resolve(moduleBuildFile.getSourcePath());
alreadyParsed.add(moduleBuildFilePath);
alreadyParsed.addAll(skipResourceScanDirs);
SourceSetParsingResult mainSourcesParsingResult = parseMainSources(baseDir, currentProject, moduleBuildFile,
resources, javaParserBuilder.clone(), rp, provenanceMarkers, alreadyParsed, executionContext);
SourceSetParsingResult testSourcesParsingResult = parseTestSources(baseDir, currentProject, moduleBuildFile,
javaParserBuilder.clone(), rp, provenanceMarkers, alreadyParsed, executionContext, resources,
mainSourcesParsingResult.classpath());
// Collect the dirs of modules parsed in previous steps
Set<Path> alreadyParsed = new HashSet<>();
Path moduleBuildFilePath = baseDir.resolve(moduleBuildFile.getSourcePath());
alreadyParsed.add(moduleBuildFilePath);
alreadyParsed.addAll(skipResourceScanDirs);
SourceSetParsingResult mainSourcesParsingResult = parseMainSources(baseDir, currentProject, moduleBuildFile,
resources, javaParserBuilder.clone(), rp, provenanceMarkers, alreadyParsed, executionContext);
SourceSetParsingResult testSourcesParsingResult = parseTestSources(baseDir, currentProject, moduleBuildFile,
javaParserBuilder.clone(), rp, provenanceMarkers, alreadyParsed, executionContext, resources,
mainSourcesParsingResult.classpath());
// Collect the dirs of modules parsed in previous steps
// parse other project resources
Stream<SourceFile> parsedResourceFiles = rp.parse(moduleBuildFilePath.getParent(), resources, alreadyParsed)
// FIXME: handle generated sources
.map(addProvenance(baseDir, provenanceMarkers, null));
// parse other project resources
Stream<SourceFile> parsedResourceFiles = rp.parse(moduleBuildFilePath.getParent(), resources, alreadyParsed)
// FIXME: handle generated sources
.map(mavenMojoProjectParserPrivateMethods.addProvenance(baseDir, provenanceMarkers, null));
List<SourceFile> mainAndTestSources = mergeAndFilterExcluded(baseDir,
springRewriteProperties.getIgnoredPathPatterns(), mainSourcesParsingResult.sourceFiles(),
testSourcesParsingResult.sourceFiles());
List<SourceFile> resourceFilesList = parsedResourceFiles.toList();
sourceFiles.addAll(mainAndTestSources);
sourceFiles.addAll(resourceFilesList);
List<SourceFile> mainAndTestSources = mergeAndFilterExcluded(baseDir,
springRewriteProperties.getIgnoredPathPatterns(), mainSourcesParsingResult.sourceFiles(),
testSourcesParsingResult.sourceFiles());
List<SourceFile> resourceFilesList = parsedResourceFiles.toList();
sourceFiles.addAll(mainAndTestSources);
sourceFiles.addAll(resourceFilesList);
return sourceFiles;
}
return sourceFiles;
}
private List<SourceFile> mergeAndFilterExcluded(Path baseDir, Set<String> exclusions, List<SourceFile> mainSources,
List<SourceFile> testSources) {
List<PathMatcher> pathMatchers = exclusions.stream()
.map(pattern -> baseDir.getFileSystem().getPathMatcher("glob:" + pattern))
.toList();
if (pathMatchers.isEmpty()) {
return Stream.concat(mainSources.stream(), testSources.stream()).toList();
}
return new ArrayList<>(Stream.concat(mainSources.stream(), testSources.stream())
.filter(s -> isNotExcluded(baseDir, pathMatchers, s))
.toList());
}
/**
* Add {@link Marker}s to {@link SourceFile}.
*/
public <T extends SourceFile> UnaryOperator<T> addProvenance(Path baseDir, List<Marker> provenance,
@Nullable Collection<Path> generatedSources) {
return s -> {
Markers markers = s.getMarkers();
for (Marker marker : provenance) {
markers = markers.addIfAbsent(marker);
}
if (generatedSources != null && generatedSources.contains(baseDir.resolve(s.getSourcePath()))) {
markers = markers.addIfAbsent(new Generated(randomId()));
}
return s.withMarkers(markers);
};
}
private static boolean isNotExcluded(Path baseDir, List<PathMatcher> exclusions, SourceFile s) {
return exclusions.stream()
.noneMatch(pm -> pm.matches(baseDir.resolve(s.getSourcePath()).toAbsolutePath().normalize()));
}
private List<SourceFile> mergeAndFilterExcluded(Path baseDir, Set<String> exclusions, List<SourceFile> mainSources,
List<SourceFile> testSources) {
List<PathMatcher> pathMatchers = exclusions.stream()
.map(pattern -> baseDir.getFileSystem().getPathMatcher("glob:" + pattern))
.toList();
if (pathMatchers.isEmpty()) {
return Stream.concat(mainSources.stream(), testSources.stream()).toList();
}
return new ArrayList<>(Stream.concat(mainSources.stream(), testSources.stream())
.filter(s -> isNotExcluded(baseDir, pathMatchers, s))
.toList());
}
private SourceSetParsingResult parseTestSources(Path baseDir, MavenProject mavenProject,
Xml.Document moduleBuildFile, JavaParser.Builder<? extends JavaParser, ?> javaParserBuilder,
RewriteResourceParser rp, List<Marker> provenanceMarkers, Set<Path> alreadyParsed,
ExecutionContext executionContext, List<Resource> resources, List<JavaType.FullyQualified> classpath) {
return mavenMojoProjectParserPrivateMethods.processTestSources(baseDir, moduleBuildFile, javaParserBuilder, rp,
provenanceMarkers, alreadyParsed, executionContext, mavenProject, resources, classpath);
}
private static boolean isNotExcluded(Path baseDir, List<PathMatcher> exclusions, SourceFile s) {
return exclusions.stream()
.noneMatch(pm -> pm.matches(baseDir.resolve(s.getSourcePath()).toAbsolutePath().normalize()));
}
/**
*
*/
private SourceSetParsingResult parseMainSources(Path baseDir, MavenProject mavenProject,
Xml.Document moduleBuildFile, List<Resource> resources,
JavaParser.Builder<? extends JavaParser, ?> javaParserBuilder, RewriteResourceParser rp,
List<Marker> provenanceMarkers, Set<Path> alreadyParsed, ExecutionContext executionContext) {
private SourceSetParsingResult parseTestSources(Path baseDir, MavenProject mavenProject,
Xml.Document moduleBuildFile, JavaParser.Builder<? extends JavaParser, ?> javaParserBuilder,
RewriteResourceParser rp, List<Marker> provenanceMarkers, Set<Path> alreadyParsed,
ExecutionContext executionContext, List<Resource> resources, List<JavaType.FullyQualified> classpath) {
return processTestSources(baseDir, moduleBuildFile, javaParserBuilder, rp,
provenanceMarkers, alreadyParsed, executionContext, mavenProject, resources, classpath);
}
return mavenMojoProjectParserPrivateMethods.processMainSources(baseDir, resources, moduleBuildFile,
javaParserBuilder, rp, provenanceMarkers, alreadyParsed, executionContext, mavenProject);
}
/**
*
*/
private SourceSetParsingResult parseMainSources(Path baseDir, MavenProject mavenProject,
Xml.Document moduleBuildFile, List<Resource> resources,
JavaParser.Builder<? extends JavaParser, ?> javaParserBuilder, RewriteResourceParser rp,
List<Marker> provenanceMarkers, Set<Path> alreadyParsed, ExecutionContext executionContext) {
private Set<Path> pathsToOtherMavenProjects(MavenProject mavenProject, Path moduleBuildFile) {
return mavenProject.getCollectedProjects()
.stream()
.filter(p -> !p.getFile().toPath().toString().equals(moduleBuildFile.toString()))
.map(p -> p.getFile().toPath().getParent())
.collect(Collectors.toSet());
}
return processMainSources(baseDir, resources, moduleBuildFile,
javaParserBuilder, rp, provenanceMarkers, alreadyParsed, executionContext, mavenProject);
}
private Set<Path> pathsToOtherMavenProjects(MavenProject mavenProject, Path moduleBuildFile) {
return mavenProject.getCollectedProjects()
.stream()
.filter(p -> !p.getFile().toPath().toString().equals(moduleBuildFile.toString()))
.map(p -> p.getFile().toPath().getParent())
.collect(Collectors.toSet());
}
/**
* Parse Java sources and resources under {@code src/main} of current module.
*/
public SourceSetParsingResult processMainSources(Path baseDir, List<Resource> resources,
Xml.Document moduleBuildFile, JavaParser.Builder<? extends JavaParser, ?> javaParserBuilder,
RewriteResourceParser rp, List<Marker> provenanceMarkers, Set<Path> alreadyParsed,
ExecutionContext executionContext, MavenProject currentProject) {
LOGGER.info("Processing main sources in module '%s'".formatted(currentProject.getProjectId()));
// FIXME: 945
// Some annotation processors output generated sources to the /target directory.
// These are added for parsing but
// should be filtered out of the final SourceFile list.
List<Resource> mainJavaSources = new ArrayList<>();
List<Resource> javaSourcesInTarget = currentProject.getJavaSourcesInTarget(); // listJavaSources(resources,
// currentProject.getBasedir().resolve(currentProject.getBuildDirectory()));
List<Resource> javaSourcesInMain = currentProject.getMainJavaSources(); // listJavaSources(resources,
// currentProject.getBasedir().resolve(currentProject.getSourceDirectory()));
mainJavaSources.addAll(javaSourcesInTarget);
mainJavaSources.addAll(javaSourcesInMain);
LOGGER.info("[%s] Parsing source files".formatted(currentProject));
// FIXME 945 classpath
// - Resolve dependencies to non-reactor projects from Maven repository
// - Resolve dependencies to reactor projects by providing the sources
// javaParserBuilder.classpath(byte[])
// we're processing a module here. The classpath of the module consists of all
// declared dependencies and their transitive dependencies too.
// For dependencies to projects that belong to the current rector...
// They'd either need to be built with Maven before to guarantee that the jars are
// installed to local Maven repo.
// Or, the classpath must be created from the sources of the project.
List<Path> dependencies = currentProject.getCompileClasspathElements();
javaParserBuilder.classpath(dependencies);
JavaTypeCache typeCache = new JavaTypeCache();
javaParserBuilder.typeCache(typeCache);
Iterable<Parser.Input> inputs = mainJavaSources.stream().map(r -> {
FileAttributes fileAttributes = null;
Path path = ResourceUtil.getPath(r);
boolean isSynthetic = Files.exists(path);
Supplier<InputStream> inputStreamSupplier = () -> ResourceUtil.getInputStream(r);
Parser.Input input = new Parser.Input(path, fileAttributes, inputStreamSupplier, isSynthetic);
return input;
}).toList();
Set<JavaType.FullyQualified> localClassesCp = new HashSet<>();
JavaSourceSet javaSourceSet = sourceSet("main", dependencies, typeCache);
List<? extends SourceFile> cus = javaParserBuilder.build()
.parseInputs(inputs, baseDir, executionContext)
.peek(s -> {
((J.CompilationUnit) s).getClasses()
.stream()
.map(J.ClassDeclaration::getType)
.forEach(localClassesCp::add);
alreadyParsed.add(baseDir.resolve(s.getSourcePath()));
})
.toList();
// TODO: This is a hack:
// Parsed java sources are not themselves on the classpath (here).
// The actual parsing happens when the stream is terminated (toList),
// therefore the toList() must be called before the parsed compilation units can
// be added to the classpath
List<Marker> mainProjectProvenance = new ArrayList<>(provenanceMarkers);
javaSourceSet = appendToClasspath(localClassesCp, javaSourceSet);
mainProjectProvenance.add(javaSourceSet);
List<Path> parsedJavaPaths = javaSourcesInTarget.stream().map(ResourceUtil::getPath).toList();
Stream<SourceFile> parsedJava = cus.stream()
.map(addProvenance(baseDir, mainProjectProvenance, parsedJavaPaths));
LOGGER.debug(
"[%s] Scanned %d java source files in main scope.".formatted(currentProject, mainJavaSources.size()));
// Filter out any generated source files from the returned list, as we do not want
// to apply the recipe to the
// generated files.
Path buildDirectory = Paths.get(currentProject.getBuildDirectory());
List<SourceFile> sourceFiles = parsedJava.filter(s -> !s.getSourcePath().startsWith(buildDirectory))
.collect(Collectors.toCollection(ArrayList::new));
int sourcesParsedBefore = alreadyParsed.size();
alreadyParsed.addAll(parsedJavaPaths);
List<SourceFile> parsedResourceFiles = rp
.parse(currentProject.getModulePath().resolve("src/main/resources"), resources, alreadyParsed)
.map(addProvenance(baseDir, mainProjectProvenance, null))
.toList();
LOGGER.debug("[%s] Scanned %d resource files in main scope.".formatted(currentProject,
(alreadyParsed.size() - sourcesParsedBefore)));
// Any resources parsed from "main/resources" should also have the main source set
// added to them.
sourceFiles.addAll(parsedResourceFiles);
return new SourceSetParsingResult(sourceFiles, javaSourceSet.getClasspath());
}
/**
* Add entries that don't exist in the classpath of {@code javaSourceSet} from
* {@code appendingClasspath}.
*/
@NotNull
private static JavaSourceSet appendToClasspath(Set<JavaType.FullyQualified> appendingClasspath,
JavaSourceSet javaSourceSet) {
List<JavaType.FullyQualified> curCp = javaSourceSet.getClasspath();
appendingClasspath.forEach(f -> {
if (!curCp.contains(f)) {
curCp.add(f);
}
});
javaSourceSet = javaSourceSet.withClasspath(new ArrayList<>(curCp));
return javaSourceSet;
}
@NotNull
private static JavaSourceSet sourceSet(String name, List<Path> dependencies, JavaTypeCache typeCache) {
return JavaSourceSet.build(name, dependencies, typeCache, false);
}
/**
* Parse Java sources and resource files under {@code src/test}.
*/
public SourceSetParsingResult processTestSources(Path baseDir, Xml.Document moduleBuildFile,
JavaParser.Builder<? extends JavaParser, ?> javaParserBuilder, RewriteResourceParser rp,
List<Marker> provenanceMarkers, Set<Path> alreadyParsed, ExecutionContext executionContext,
MavenProject currentProject, List<Resource> resources, List<JavaType.FullyQualified> classpath) {
LOGGER.info("Processing test sources in module '%s'".formatted(currentProject.getProjectId()));
List<Path> testDependencies = currentProject.getTestClasspathElements();
javaParserBuilder.classpath(testDependencies);
JavaTypeCache typeCache = new JavaTypeCache();
javaParserBuilder.typeCache(typeCache);
List<Resource> testJavaSources = listJavaSources(resources,
currentProject.getBasedir().resolve(currentProject.getTestSourceDirectory()));
alreadyParsed.addAll(testJavaSources.stream().map(ResourceUtil::getPath).toList());
Iterable<Parser.Input> inputs = testJavaSources.stream()
.map(r -> new Parser.Input(ResourceUtil.getPath(r), () -> ResourceUtil.getInputStream(r)))
.toList();
final List<JavaType.FullyQualified> localClassesCp = new ArrayList<>();
List<? extends SourceFile> cus = javaParserBuilder.build()
.parseInputs(inputs, baseDir, executionContext)
.peek(s -> {
((J.CompilationUnit) s).getClasses()
.stream()
.map(J.ClassDeclaration::getType)
.forEach(localClassesCp::add);
alreadyParsed.add(baseDir.resolve(s.getSourcePath()));
})
.toList();
List<Marker> markers = new ArrayList<>(provenanceMarkers);
JavaSourceSet javaSourceSet = sourceSet("test", testDependencies, typeCache);
Set<JavaType.FullyQualified> curClasspath = Stream.concat(classpath.stream(), localClassesCp.stream())
.collect(Collectors.toSet());
javaSourceSet = appendToClasspath(curClasspath, javaSourceSet);
markers.add(javaSourceSet);
Stream<SourceFile> parsedJava = cus.stream().map(addProvenance(baseDir, markers, null));
LOGGER.debug(
"[%s] Scanned %d java source files in test scope.".formatted(currentProject, testJavaSources.size()));
Stream<SourceFile> sourceFiles = parsedJava;
// Any resources parsed from "test/resources" should also have the test source set
// added to them.
int sourcesParsedBefore = alreadyParsed.size();
Stream<SourceFile> parsedResourceFiles = rp
.parse(currentProject.getBasedir().resolve("src/test/resources"), resources, alreadyParsed)
.map(addProvenance(baseDir, markers, null));
LOGGER.debug("[%s] Scanned %d resource files in test scope.".formatted(currentProject,
(alreadyParsed.size() - sourcesParsedBefore)));
sourceFiles = Stream.concat(sourceFiles, parsedResourceFiles);
List<SourceFile> result = sourceFiles.toList();
return new SourceSetParsingResult(result, javaSourceSet.getClasspath());
}
// FIXME: 945 take Java sources from resources
private static List<Resource> listJavaSources(List<Resource> resources, Path sourceDirectory) {
return resources.stream().filter(whenIn(sourceDirectory)).filter(whenFileNameEndsWithJava()).toList();
}
@NotNull
private static Predicate<Resource> whenFileNameEndsWithJava() {
return p -> ResourceUtil.getPath(p).getFileName().toString().endsWith(".java");
}
@NotNull
private static Predicate<Resource> whenIn(Path sourceDirectory) {
return r -> ResourceUtil.getPath(r).toString().startsWith(sourceDirectory.toString());
}
}

View File

@@ -91,12 +91,10 @@ class RewriteProjectParserTest {
void parseSimpleMavenProject(@TempDir Path tempDir) throws PlexusCipherException {
Path basePath = tempDir;
SpringRewriteProperties springRewriteProperties = new SpringRewriteProperties();
ModuleParser mavenMojoParserPrivateMethods = new ModuleParser();
ExecutionContext executionContext = new InMemoryExecutionContext(t -> {
throw new RuntimeException(t);
});
MavenModuleParser mavenModuleParser = new MavenModuleParser(springRewriteProperties,
mavenMojoParserPrivateMethods);
MavenModuleParser mavenModuleParser = new MavenModuleParser(springRewriteProperties);
ProjectMetadata projectMetadata = new ProjectMetadata();
MavenSettingsInitializer mavenSettingsInitializer = new MavenSettingsInitializer(executionContext,
projectMetadata);