diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ClasspathExtractor.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ClasspathExtractor.java new file mode 100644 index 000000000..955949ad8 --- /dev/null +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ClasspathExtractor.java @@ -0,0 +1,52 @@ +/* + * 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.sbm.parsers; + +import lombok.RequiredArgsConstructor; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.maven.tree.ResolvedDependency; +import org.openrewrite.maven.tree.Scope; +import org.openrewrite.maven.utilities.MavenArtifactDownloader; + +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; + + +/** + * @author Fabian Krüger + */ +@RequiredArgsConstructor +public class ClasspathExtractor { + private final MavenArtifactDownloader rewriteMavenArtifactDownloader; + + public Set extractClasspath(MavenResolutionResult pom, Scope scope) { + List resolvedDependencies = pom.getDependencies().get(scope); + if (resolvedDependencies != null) { + return resolvedDependencies + // FIXME: 945 - deal with dependencies to projects in reactor + // + .stream() + .filter(rd -> rd.getRepository() != null) + .map(rd -> rewriteMavenArtifactDownloader.downloadArtifact(rd)) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toSet()); + } else { + return new HashSet<>(); + } + } +} diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ClasspathRegistry.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ClasspathRegistry.java new file mode 100644 index 000000000..059039706 --- /dev/null +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ClasspathRegistry.java @@ -0,0 +1,59 @@ +/* + * 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.sbm.parsers; + +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.maven.tree.Scope; +import org.springframework.sbm.scopes.ProjectMetadata; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author Fabian Krüger + */ +public class ClasspathRegistry { + private final ClasspathExtractor classpathExtractor; + private final Map map; + + public ClasspathRegistry(ClasspathExtractor classpathExtractor) { + this.map = new HashMap<>(); + this.classpathExtractor = classpathExtractor; + } + + public void registerClasspaths(Map map) { + this.map.putAll(map); + } + + public void registerClasspath(Path buildFilePath, MavenResolutionResult mavenResolutionResult) { + this.map.put(buildFilePath, mavenResolutionResult); + } + + public void clear() { + this.map.clear(); + } + + public Set getClasspath(Path buildFileProjectPath, Scope scope) { + if(!map.containsKey(buildFileProjectPath)) { + throw new IllegalArgumentException("The given pom path '%s' does not define a classpath.".formatted(buildFileProjectPath)); + } + MavenResolutionResult mavenResolutionResult = map.get(buildFileProjectPath); + return classpathExtractor.extractClasspath(mavenResolutionResult, scope); + } +} diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ClasspathRegistryFactory.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ClasspathRegistryFactory.java new file mode 100644 index 000000000..f555218fb --- /dev/null +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ClasspathRegistryFactory.java @@ -0,0 +1,46 @@ +/* + * 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.sbm.parsers; + +import lombok.RequiredArgsConstructor; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.xml.tree.Xml; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Fabian Krüger + */ +@RequiredArgsConstructor +public class ClasspathRegistryFactory { + + private final ClasspathExtractor classpathExtractor; + + public ClasspathRegistry create(Path baseDir, List parsedBuildFiles) { + Map map = new HashMap<>(); + parsedBuildFiles.stream() + .forEach(buildFile -> { + Path pomPath = baseDir.resolve(buildFile.getSourcePath()).toAbsolutePath().normalize(); + MavenResolutionResult marker = buildFile.getMarkers().findFirst(MavenResolutionResult.class).get(); + map.put(pomPath, marker); + }); + + return new ClasspathRegistry(classpathExtractor); + } +} diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/JavaParserMarker.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/JavaParserMarker.java new file mode 100644 index 000000000..0ccc396dc --- /dev/null +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/JavaParserMarker.java @@ -0,0 +1,39 @@ +/* + * 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.sbm.parsers; + +import lombok.Value; +import lombok.With; +import org.openrewrite.java.JavaParser; +import org.openrewrite.marker.Marker; + +import java.util.UUID; + + +/** + * Used to keep the stateful {@link JavaParser} for later parsing. + * + * This is required when (re-)parsing a submodule somewhere (non-leaf) in a reactor build tree. + * Then this module requires the JavaParser with types from the last parse where types from lower modules are cached. + * + * @author Fabian Krüger + */ +@Value +@With +public class JavaParserMarker implements Marker { + private final UUID id; + private final JavaParser javaParser; +} diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/MavenProject.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/MavenProject.java index 725560384..f0fe98ee9 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/MavenProject.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/MavenProject.java @@ -20,9 +20,7 @@ import org.apache.maven.model.Model; import org.apache.maven.model.Plugin; import org.jetbrains.annotations.NotNull; -import org.openrewrite.SourceFile; import org.openrewrite.maven.tree.*; -import org.openrewrite.maven.utilities.MavenArtifactDownloader; import org.openrewrite.xml.tree.Xml; import org.springframework.core.io.Resource; import org.springframework.sbm.parsers.maven.MavenRuntimeInformation; @@ -43,20 +41,19 @@ public class MavenProject { private final Path projectRoot; private final Resource pomFile; - // FIXME: 945 temporary method, model should nopt come from Maven private final Model pomModel; + private final List resources; + private final ClasspathRegistry classpathRegistry; private List collectedProjects = new ArrayList<>(); private Xml.Document sourceFile; - private final MavenArtifactDownloader rewriteMavenArtifactDownloader; - private final List resources; private ProjectId projectId; - public MavenProject(Path projectRoot, Resource pomFile, Model pomModel, MavenArtifactDownloader rewriteMavenArtifactDownloader, List resources) { + public MavenProject(Path projectRoot, Resource pomFile, Model pomModel, List resources, ClasspathRegistry classpathRegistry) { this.projectRoot = projectRoot; this.pomFile = pomFile; this.pomModel = pomModel; - this.rewriteMavenArtifactDownloader = rewriteMavenArtifactDownloader; this.resources = resources; + this.classpathRegistry = classpathRegistry; projectId = new ProjectId(getGroupId(), getArtifactId()); } @@ -145,32 +142,22 @@ public String getSourceDirectory() { return s == null ? ResourceUtil.getPath(pomFile).getParent().resolve("src/main/java").toAbsolutePath().normalize().toString() : s; } - public List getCompileClasspathElements() { + + /** + * Must be called after {@link ParserContext#setParsedBuildFiles(List)}. + */ + public Set getCompileClasspathElements() { Scope scope = Scope.Compile; return getClasspathElements(scope); } - public List getTestClasspathElements() { + public Set getTestClasspathElements() { return getClasspathElements(Scope.Test); } @NotNull - private List getClasspathElements(Scope scope) { - MavenResolutionResult pom = getSourceFile().getMarkers().findFirst(MavenResolutionResult.class).get(); - List resolvedDependencies = pom.getDependencies().get(scope); - if(resolvedDependencies != null) { - return resolvedDependencies - // FIXME: 945 - deal with dependencies to projects in reactor - // - .stream() - .filter(rd -> rd.getRepository() != null) - .map(rd -> rewriteMavenArtifactDownloader.downloadArtifact(rd)) - .filter(Objects::nonNull) - .distinct() - .toList(); - } else { - return new ArrayList<>(); - } + private Set getClasspathElements(Scope scope) { + return classpathRegistry.getClasspath(getPomFilePath(), scope); } public String getTestSourceDirectory() { @@ -199,7 +186,6 @@ private static Predicate whenIn(Path sourceDirectory) { return r -> ResourceUtil.getPath(r).toString().startsWith(sourceDirectory.toString()); } - public List getJavaSourcesInTarget() { return listJavaSources(getResources(), getBasedir().resolve(getBuildDirectory())); } diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ModuleParser.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ModuleParser.java index 61d8c8121..8b9f7f523 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ModuleParser.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ModuleParser.java @@ -73,7 +73,6 @@ public UnaryOperator addProvenance( public List processMainSources( Path baseDir, List resources, - Xml.Document moduleBuildFile, JavaParser.Builder javaParserBuilder, RewriteResourceParser rp, List provenanceMarkers, @@ -105,9 +104,9 @@ public List processMainSources( // Or, the classpath must be created from the sources of the project. - List dependencies = currentProject.getCompileClasspathElements(); + Set jarDependencies = currentProject.getCompileClasspathElements(); - javaParserBuilder.classpath(dependencies); + javaParserBuilder.classpath(jarDependencies); JavaTypeCache typeCache = new JavaTypeCache(); javaParserBuilder.typeCache(typeCache); @@ -116,13 +115,14 @@ public List processMainSources( .map(r -> new Parser.Input(ResourceUtil.getPath(r), () -> ResourceUtil.getInputStream(r))) .toList(); - Stream cus = Stream.of(javaParserBuilder) - .map(JavaParser.Builder::build) - .flatMap(parser -> parser.parseInputs(inputs, baseDir, executionContext)) + JavaParser javaParser = javaParserBuilder.build(); + Stream cus = javaParser.parseInputs(inputs, baseDir, executionContext) .peek(s -> alreadyParsed.add(baseDir.resolve(s.getSourcePath()))); + List mainProjectProvenance = new ArrayList<>(provenanceMarkers); - mainProjectProvenance.add(sourceSet("main", dependencies, typeCache)); + mainProjectProvenance.add(sourceSet("main", jarDependencies, typeCache)); + mainProjectProvenance.add(new JavaParserMarker(UUID.randomUUID(), javaParser)); // FIXME: 945 Why target and not all main? List parsedJavaPaths = mainJavaSources.stream().map(ResourceUtil::getPath).toList(); @@ -149,7 +149,7 @@ public List processMainSources( } @NotNull - private static JavaSourceSet sourceSet(String name, List dependencies, JavaTypeCache typeCache) { + private static JavaSourceSet sourceSet(String name, Set dependencies, JavaTypeCache typeCache) { return JavaSourceSet.build(name, dependencies, typeCache, false); } @@ -159,7 +159,6 @@ private static JavaSourceSet sourceSet(String name, List dependencies, Jav */ public List processTestSources( Path baseDir, - Xml.Document moduleBuildFile, JavaParser.Builder javaParserBuilder, RewriteResourceParser rp, List provenanceMarkers, @@ -170,7 +169,7 @@ public List processTestSources( ) { log.info("Processing test sources in module '%s'".formatted(currentProject.getProjectId())); - List testDependencies = currentProject.getTestClasspathElements(); + Set testDependencies = currentProject.getTestClasspathElements(); javaParserBuilder.classpath(testDependencies); JavaTypeCache typeCache = new JavaTypeCache(); diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ModuleParsingResult.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ModuleParsingResult.java new file mode 100644 index 000000000..2de23c423 --- /dev/null +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ModuleParsingResult.java @@ -0,0 +1,27 @@ +/* + * 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.sbm.parsers; + +import org.openrewrite.SourceFile; + +import java.util.List; + +/** + * @author Fabian Krüger + */ +public record ModuleParsingResult(List sourceFiles) { + +} diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ParserContext.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ParserContext.java index 81d5f55a9..e5b5eae0f 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ParserContext.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ParserContext.java @@ -40,7 +40,7 @@ public class ParserContext { private final List sortedProjects; @Getter private Map pathDocumentMap; - + private ClasspathRegistry classpathRegistry; public List getActiveProfiles() { @@ -80,4 +80,8 @@ private void addSourceFileToModel(Path baseDir, List sortedProject .filter(p -> ResourceUtil.getPath(p.getPomFile()).toString().equals(baseDir.resolve(s.getSourcePath()).toString())) .forEach(p -> p.setSourceFile(s)); } + + public void setClasspathRegistry(ClasspathRegistry classpathRegistry) { + this.classpathRegistry = classpathRegistry; + } } diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteParserConfiguration.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteParserConfiguration.java index bc2c8e1e0..095587ed8 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteParserConfiguration.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteParserConfiguration.java @@ -40,6 +40,7 @@ import java.io.StringWriter; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashMap; import java.util.function.Consumer; @@ -102,6 +103,11 @@ Consumer artifactDownloaderErrorConsumer() { return (t) -> {throw new RuntimeException(t);}; } + @Bean + ClasspathExtractor classpathExtractor(MavenArtifactDownloader rewriteMavenArtifactDownloader) { + return new ClasspathExtractor(rewriteMavenArtifactDownloader); + } + @Bean RewriteMavenArtifactDownloader artifactDownloader(MavenArtifactCache mavenArtifactCache, ProjectMetadata projectMetadata, Consumer artifactDownloaderErrorConsumer) { return new RewriteMavenArtifactDownloader(mavenArtifactCache, projectMetadata.getMavenSettings(), artifactDownloaderErrorConsumer); @@ -113,8 +119,8 @@ ModuleParser helperWithoutAGoodName() { } @Bean - MavenModuleParser mavenModuleParser(ParserProperties parserPropeties, ModuleParser moduleParser) { - return new MavenModuleParser(parserPropeties, moduleParser); + MavenModuleParser mavenModuleParser(ParserProperties parserProperties, ModuleParser moduleParser, ClasspathExtractor classpathExtractor, ExecutionContext executionContext) { + return new MavenModuleParser(parserProperties, moduleParser, classpathExtractor, executionContext); } @Bean @@ -147,8 +153,13 @@ ParsingEventListener parsingEventListener(ApplicationEventPublisher eventPublish // } @Bean - MavenProjectAnalyzer mavenProjectAnalyzer(MavenArtifactDownloader artifactDownloader) { - return new MavenProjectAnalyzer(artifactDownloader); + ClasspathRegistry classpathRegistry(ClasspathExtractor classpathExtractor) { + return new ClasspathRegistry(classpathExtractor); + } + + @Bean + MavenProjectAnalyzer mavenProjectAnalyzer(ClasspathRegistry classpathRegistry) { + return new MavenProjectAnalyzer(classpathRegistry); } @Bean @@ -164,7 +175,8 @@ RewriteProjectParser rewriteProjectParser( ConfigurableListableBeanFactory beanFactory, ProjectScanner projectScanner, ExecutionContext executionContext, - MavenProjectAnalyzer mavenProjectAnalyzer) { + MavenProjectAnalyzer mavenProjectAnalyzer, + ClasspathRegistry classpathRegistry) { return new RewriteProjectParser( provenanceMarkerFactory, buildFileParser, @@ -177,7 +189,9 @@ RewriteProjectParser rewriteProjectParser( beanFactory, projectScanner, executionContext, - mavenProjectAnalyzer); + mavenProjectAnalyzer, + classpathRegistry + ); } @Bean diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteProjectParser.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteProjectParser.java index a343179aa..10eeb939d 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteProjectParser.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteProjectParser.java @@ -21,6 +21,7 @@ import org.openrewrite.ExecutionContext; import org.openrewrite.SourceFile; import org.openrewrite.marker.Marker; +import org.openrewrite.maven.tree.MavenResolutionResult; import org.openrewrite.style.NamedStyles; import org.openrewrite.tree.ParsingEventListener; import org.openrewrite.tree.ParsingExecutionContextView; @@ -33,6 +34,7 @@ import org.springframework.sbm.parsers.maven.BuildFileParser; import org.springframework.sbm.parsers.maven.MavenProjectAnalyzer; import org.springframework.sbm.parsers.maven.ProvenanceMarkerFactory; +import org.springframework.sbm.project.resource.ProjectResourceSetFactory; import org.springframework.sbm.scopes.ScanScope; import java.nio.file.Path; @@ -77,6 +79,7 @@ public class RewriteProjectParser { private final ProjectScanner scanner; private final ExecutionContext executionContext; private final MavenProjectAnalyzer mavenProjectAnalyzer; + private final ClasspathRegistry classpathRegistry; /** @@ -114,6 +117,7 @@ public RewriteProjectParsingResult parse(Path givenBaseDir, List resou List parsedBuildFiles = buildFileParser.parseBuildFiles(baseDir, parserContext.getBuildFileResources(), parserContext.getActiveProfiles(), executionContext, parserProperties.isSkipMavenParsing(), provenanceMarkers); parserContext.setParsedBuildFiles(parsedBuildFiles); + registerClasspaths(baseDir, parsedBuildFiles); log.trace("Start to parse %d source files in %d modules".formatted(resources.size() + parsedBuildFiles.size(), parsedBuildFiles.size())); List otherSourceFiles = sourceFileParser.parseOtherSourceFiles(baseDir, parserContext, resources, provenanceMarkers, styles, executionContext); @@ -130,6 +134,19 @@ public RewriteProjectParsingResult parse(Path givenBaseDir, List resou return new RewriteProjectParsingResult(sourceFiles, executionContext); } + private void registerClasspaths(Path baseDir, List parsedBuildFiles) { + // The classpath must be passed down to the module parsing + // The ClasspathInfo must provide the classpath for all source sets for all modules + // E.g.: + // List mainModuleCp = classpathInfo.getClasspath(moduleName, "main"); + parsedBuildFiles.stream() + .forEach(buildFile -> { + Path pomPath = baseDir.resolve(buildFile.getSourcePath()).toAbsolutePath().normalize(); + MavenResolutionResult mavenResolutionResult = buildFile.getMarkers().findFirst(MavenResolutionResult.class).get(); + classpathRegistry.registerClasspath(pomPath, mavenResolutionResult); + }); + } + @NotNull private static Path normalizePath(Path givenBaseDir) { if (!givenBaseDir.isAbsolute()) { diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/SourceFileParser.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/SourceFileParser.java index dfe725d8d..4c42ae987 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/SourceFileParser.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/SourceFileParser.java @@ -19,19 +19,14 @@ import lombok.extern.slf4j.Slf4j; import org.openrewrite.ExecutionContext; import org.openrewrite.SourceFile; -import org.openrewrite.java.JavaParser; import org.openrewrite.marker.Marker; import org.openrewrite.style.NamedStyles; import org.openrewrite.xml.tree.Xml; import org.springframework.core.io.Resource; import org.springframework.sbm.parsers.maven.MavenModuleParser; -import org.springframework.sbm.utils.ResourceUtil; import java.nio.file.Path; -import java.nio.file.PathMatcher; import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; /** * @author Fabian Krüger @@ -49,7 +44,8 @@ public List parseOtherSourceFiles( List resources, Map> provenanceMarkers, List styles, - ExecutionContext executionContext) { + ExecutionContext executionContext + ) { Set parsedSourceFiles = new LinkedHashSet<>(); diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenModuleParser.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenModuleParser.java index 82d2cac65..298470f77 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenModuleParser.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenModuleParser.java @@ -18,23 +18,22 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.openrewrite.ExecutionContext; +import org.openrewrite.Parser; import org.openrewrite.SourceFile; import org.openrewrite.java.JavaParser; import org.openrewrite.marker.Marker; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.maven.tree.Scope; import org.openrewrite.style.NamedStyles; import org.openrewrite.xml.tree.Xml; import org.springframework.core.io.Resource; -import org.springframework.sbm.parsers.MavenProject; -import org.springframework.sbm.parsers.ModuleParser; -import org.springframework.sbm.parsers.ParserProperties; -import org.springframework.sbm.parsers.RewriteResourceParser; +import org.springframework.sbm.parsers.*; +import org.springframework.sbm.utils.ResourceUtil; +import java.nio.file.FileSystems; 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.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -47,6 +46,8 @@ public class MavenModuleParser { private final ParserProperties parserProperties; private final ModuleParser mavenMojoProjectParserPrivateMethods; + private final ClasspathExtractor classpathExtractor; + private final ExecutionContext executionContext; public List parseModuleSourceFiles( List resources, @@ -90,8 +91,8 @@ public List parseModuleSourceFiles( Path moduleBuildFilePath = baseDir.resolve(moduleBuildFile.getSourcePath()); alreadyParsed.add(moduleBuildFilePath); alreadyParsed.addAll(skipResourceScanDirs); - List mainSources = parseMainSources(baseDir, currentProject, moduleBuildFile, resources, javaParserBuilder.clone(), rp, provenanceMarkers, alreadyParsed, executionContext); - List testSources = parseTestSources(baseDir, currentProject, moduleBuildFile, javaParserBuilder.clone(), rp, provenanceMarkers, alreadyParsed, executionContext, resources); + List mainSources = parseMainSources(baseDir, currentProject, resources, javaParserBuilder.clone(), rp, provenanceMarkers, alreadyParsed, executionContext); + List testSources = parseTestSources(baseDir, currentProject, javaParserBuilder.clone(), rp, provenanceMarkers, alreadyParsed, executionContext, resources); // Collect the dirs of modules parsed in previous steps // parse other project resources @@ -124,20 +125,20 @@ private static boolean isNotExcluded(Path baseDir, List exclusions, .noneMatch(pm -> pm.matches(baseDir.resolve(s.getSourcePath()).toAbsolutePath().normalize())); } - private List parseTestSources(Path baseDir, MavenProject mavenProject, Xml.Document moduleBuildFile, JavaParser.Builder javaParserBuilder, RewriteResourceParser rp, List provenanceMarkers, Set alreadyParsed, ExecutionContext executionContext, List resources) { - return mavenMojoProjectParserPrivateMethods.processTestSources(baseDir, moduleBuildFile, javaParserBuilder, rp, provenanceMarkers, alreadyParsed, executionContext, mavenProject, resources); + private List parseTestSources(Path baseDir, MavenProject mavenProject, JavaParser.Builder javaParserBuilder, RewriteResourceParser rp, List provenanceMarkers, Set alreadyParsed, ExecutionContext executionContext, List resources) { + return mavenMojoProjectParserPrivateMethods.processTestSources(baseDir, javaParserBuilder, rp, provenanceMarkers, alreadyParsed, executionContext, mavenProject, resources); } /** */ - private List parseMainSources(Path baseDir, MavenProject mavenProject, Xml.Document moduleBuildFile, List resources, JavaParser.Builder javaParserBuilder, RewriteResourceParser rp, List provenanceMarkers, Set alreadyParsed, ExecutionContext executionContext) { + private List parseMainSources(Path baseDir, MavenProject mavenProject, List resources, JavaParser.Builder javaParserBuilder, RewriteResourceParser rp, List provenanceMarkers, Set alreadyParsed, ExecutionContext executionContext) { // MavenMojoProjectParser#processMainSources(..) takes MavenProject // it reads from it: // - mavenProject.getBuild().getDirectory() // - mavenProject.getBuild().getSourceDirectory() // - mavenProject.getCompileClasspathElements() --> The classpath of the given project/module // - mavenProject.getBasedir().toPath() - return mavenMojoProjectParserPrivateMethods.processMainSources(baseDir, resources, moduleBuildFile, javaParserBuilder, rp, provenanceMarkers, alreadyParsed, executionContext, mavenProject); + return mavenMojoProjectParserPrivateMethods.processMainSources(baseDir, resources, javaParserBuilder, rp, provenanceMarkers, alreadyParsed, executionContext, mavenProject); // return invokeProcessMethod(baseDir, moduleBuildFile, javaParserBuilder, rp, provenanceMarkers, alreadyParsed, executionContext, "processMainSources"); } @@ -148,4 +149,78 @@ private Set pathsToOtherMavenProjects(MavenProject mavenProject, Path modu .collect(Collectors.toSet()); } + //------------- + + public ModuleParsingResult parseModule(Path baseDir, String modulePathSegment, Collection classpath, Collection classBytesClasspath, List resources) { + resources = filterModuleResources(baseDir, modulePathSegment, resources); + List parsedSources = new ArrayList<>(); + List mainSources = parseMainSources(baseDir, classpath, classBytesClasspath, resources); + parsedSources.addAll(mainSources); + List testSources = parseTestSources(resources, classpath, classBytesClasspath, resources); + parsedSources.addAll(testSources); + List otherResources = parseOtherResources(resources); + parsedSources.addAll(otherResources); + ModuleParsingResult moduleParsingResult = new ModuleParsingResult(parsedSources); + return moduleParsingResult; + } + + private List parseOtherResources(List resources) { + return new ArrayList<>(); + } + + private List parseTestSources(List resources, Collection classpath, Collection classBytesClasspath, List resources1) { + return new ArrayList<>(); + } + + private List parseMainSources(Path baseDir, Collection classpath, Collection classBytesClasspath, List resources) { + return new ArrayList<>(); + } + + private List filterModuleResources(Path baseDir, String modulePathSegment, List resources) { + String pattern = "glob:" + baseDir.resolve(modulePathSegment).normalize().toString() + "/**"; + PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher(pattern); + return resources.stream() + .filter(r -> pathMatcher.matches(ResourceUtil.getPath(r))) + .toList(); + } + + public ModuleParsingResult parseLeafModule(Path baseDir, List resources, MavenResolutionResult result) { + resources = filterModuleResources(baseDir, "", resources); + List parsedSources = new ArrayList<>(); + Set compileCP = classpathExtractor.extractClasspath(result, Scope.Compile); + List mainSources = parseMainSources(baseDir, compileCP, resources); + parsedSources.addAll(mainSources); +// List testCP = classpathExtractor.extractClasspath(result, Scope.Test); +// List testSources = parseTestSources(resources, testCP, resources); +// parsedSources.addAll(testSources); + List otherResources = parseOtherResources(resources); + parsedSources.addAll(otherResources); + ModuleParsingResult moduleParsingResult = new ModuleParsingResult(parsedSources); + return moduleParsingResult; + } + +// private List parseTestSources(List resources, List testCP, List moduleResources) { +// JavaParser javaParser = JavaParser.fromJavaVersion().classpath(testCP).build(); +// List mainJavaSourcesParserInputs = moduleResources.stream() +// .filter(this::isMainJavaSource) +// .map(r -> new Parser.Input(ResourceUtil.getPath(r), () -> ResourceUtil.getInputStream(r))) +// .toList(); +// return javaParser.parseInputs(mainJavaSourcesParserInputs, null, executionContext).toList(); +// } + + private boolean isMainJavaSource(Resource resource) { + Path path = ResourceUtil.getPath(resource); + return FileSystems.getDefault().getPathMatcher("glob:**/src/main/java/**").matches(path); + } + + private List parseMainSources(Path baseDir, Set compileCP, List moduleResources) { + JavaParser javaParser = JavaParser.fromJavaVersion().classpath(compileCP).build(); + List mainJavaSourcesParserInputs = moduleResources.stream() + .filter(this::isMainJavaSource) + .map(r -> new Parser.Input(ResourceUtil.getPath(r), () -> ResourceUtil.getInputStream(r))) + .toList(); + return javaParser.parseInputs(mainJavaSourcesParserInputs, null, executionContext) + .map(js -> (SourceFile)js.withMarkers(js.getMarkers().addIfAbsent(new JavaParserMarker(UUID.randomUUID(), javaParser)))) + .toList(); + } } diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenProjectAnalyzer.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenProjectAnalyzer.java index 30de7eb4e..3dc220f21 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenProjectAnalyzer.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenProjectAnalyzer.java @@ -18,8 +18,8 @@ import org.apache.maven.model.*; import org.apache.maven.model.io.xpp3.MavenXpp3Reader; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; -import org.openrewrite.maven.utilities.MavenArtifactDownloader; import org.springframework.core.io.Resource; +import org.springframework.sbm.parsers.ClasspathRegistry; import org.springframework.sbm.parsers.MavenProject; import org.springframework.sbm.parsers.ParserContext; import org.springframework.sbm.utils.ResourceUtil; @@ -41,10 +41,10 @@ public class MavenProjectAnalyzer { private static final String POM_XML = "pom.xml"; private static final MavenXpp3Reader XPP_3_READER = new MavenXpp3Reader(); - private final MavenArtifactDownloader rewriteMavenArtifactDownloader; + private final ClasspathRegistry classpathRegistry; - public MavenProjectAnalyzer(MavenArtifactDownloader rewriteMavenArtifactDownloader) { - this.rewriteMavenArtifactDownloader = rewriteMavenArtifactDownloader; + public MavenProjectAnalyzer(ClasspathRegistry classpathRegistry) { + this.classpathRegistry = classpathRegistry; } public List getSortedProjects(Path baseDir, List resources) { @@ -55,11 +55,17 @@ public List getSortedProjects(Path baseDir, List resourc throw new IllegalArgumentException("The provided resources did not contain any 'pom.xml' file."); } - Resource rootPom = allPomFiles.stream().filter(r -> ResourceUtil.getPath(r).toString().equals(baseDir.resolve(POM_XML).normalize().toString())).findFirst().orElseThrow(() -> new IllegalArgumentException("The provided resources do not contain a root 'pom.xml' file.")); + Resource rootPom = allPomFiles.stream() + .filter(r -> { + String string = baseDir.resolve(POM_XML).normalize().toString(); + return ResourceUtil.getPath(r).toString().equals(string); + }) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("The provided resources do not contain a root 'pom.xml' file.")); Model rootPomModel = new Model(rootPom); if (isSingleModuleProject(rootPomModel)) { - return List.of(new MavenProject(baseDir, rootPom, rootPomModel, rewriteMavenArtifactDownloader, resources)); + return List.of(new MavenProject(baseDir, rootPom, rootPomModel, resources, classpathRegistry)); } List reactorModels = new ArrayList<>(); recursivelyFindReactorModules(baseDir, null, reactorModels, allPomFiles, rootPomModel); @@ -75,7 +81,7 @@ private List map(Path baseDir, List resources, List { String projectDir = baseDir.resolve(m.getProjectDirectory().toString()).normalize().toString(); List filteredResources = resources.stream().filter(r -> ResourceUtil.getPath(r).toString().startsWith(projectDir)).toList(); - mavenProjects.add(new MavenProject(baseDir, m.getResource(), m, rewriteMavenArtifactDownloader, filteredResources)); + mavenProjects.add(new MavenProject(baseDir, m.getResource(), m, filteredResources, classpathRegistry)); }); // set all non parent poms as collected projects for root parent pom List collected = new ArrayList<>(mavenProjects); diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenProvenanceMarkerFactory.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenProvenanceMarkerFactory.java index bc23eb3b1..37c7e0d01 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenProvenanceMarkerFactory.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenProvenanceMarkerFactory.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.maven.model.Plugin; import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.jetbrains.annotations.NotNull; import org.openrewrite.Tree; import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; @@ -31,6 +32,7 @@ import java.nio.file.Path; import java.util.List; import java.util.Objects; +import java.util.Properties; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -43,16 +45,62 @@ public class MavenProvenanceMarkerFactory { public List generateProvenance(Path baseDir, MavenProject mavenProject) { + // extract information from mavenProject MavenRuntimeInformation runtime = mavenProject.getMavenRuntimeInformation(); + Plugin compilerPlugin = mavenProject.getPlugin("org.apache.maven.plugins:maven-compiler-plugin"); + Properties properties = mavenProject.getProperties(); + String projectName = mavenProject.getName(); + String groupId = mavenProject.getGroupId(); + String artifactId = mavenProject.getArtifactId(); + String version = mavenProject.getVersion(); + + return generateProvenanceMarkers(baseDir, runtime, compilerPlugin, properties, projectName, groupId, artifactId, version); + } + + @NotNull + public List generateProvenanceMarkers(Path baseDir, MavenRuntimeInformation runtime, Plugin compilerPlugin, Properties properties, String projectName, String groupId, String artifactId, String version) { + BuildEnvironment buildEnvironment = BuildEnvironment.build(System::getenv); + GitProvenance gitProvenance = this.gitProvenance(baseDir, buildEnvironment); + OperatingSystemProvenance operatingSystemProvenance = OperatingSystemProvenance.current(); BuildTool buildTool = new BuildTool(Tree.randomId(), BuildTool.Type.Maven, runtime.getMavenVersion()); + JavaVersion javaVersion = getJavaVersion(compilerPlugin, properties); + JavaProject javaProject = getJavaProject(projectName, groupId, artifactId, version); + + List provenanceMarkers = Stream.of( + buildEnvironment, + gitProvenance, + operatingSystemProvenance, + buildTool, + javaVersion, + javaProject + ) + .filter(Objects::nonNull) + .toList(); + return provenanceMarkers; + } + + @NotNull + private static JavaProject getJavaProject(String projectName, String groupId, String artifactId, String version) { + return new JavaProject( + Tree.randomId(), + projectName, + new JavaProject.Publication( + groupId, + artifactId, + version + ) + ); + } + + @NotNull + private static JavaVersion getJavaVersion(Plugin compilerPlugin, Properties properties) { String javaRuntimeVersion = System.getProperty("java.specification.version"); String javaVendor = System.getProperty("java.vm.vendor"); String sourceCompatibility = null; String targetCompatibility = null; - Plugin compilerPlugin = mavenProject.getPlugin("org.apache.maven.plugins:maven-compiler-plugin"); if (compilerPlugin != null && compilerPlugin.getConfiguration() instanceof Xpp3Dom) { - Xpp3Dom dom = (Xpp3Dom)compilerPlugin.getConfiguration(); + Xpp3Dom dom = (Xpp3Dom) compilerPlugin.getConfiguration(); Xpp3Dom release = dom.getChild("release"); if (release != null && StringUtils.isNotEmpty(release.getValue()) && !release.getValue().contains("${")) { sourceCompatibility = release.getValue(); @@ -71,17 +119,18 @@ public List generateProvenance(Path baseDir, MavenProject mavenProject) } if (sourceCompatibility == null || targetCompatibility == null) { - String propertiesReleaseCompatibility = (String)mavenProject.getProperties().get("maven.compiler.release"); + + String propertiesReleaseCompatibility = (String) properties.get("maven.compiler.release"); if (propertiesReleaseCompatibility != null) { sourceCompatibility = propertiesReleaseCompatibility; targetCompatibility = propertiesReleaseCompatibility; } else { - String propertiesSourceCompatibility = (String)mavenProject.getProperties().get("maven.compiler.source"); + String propertiesSourceCompatibility = (String) properties.get("maven.compiler.source"); if (sourceCompatibility == null && propertiesSourceCompatibility != null) { sourceCompatibility = propertiesSourceCompatibility; } - String propertiesTargetCompatibility = (String)mavenProject.getProperties().get("maven.compiler.target"); + String propertiesTargetCompatibility = (String) properties.get("maven.compiler.target"); if (targetCompatibility == null && propertiesTargetCompatibility != null) { targetCompatibility = propertiesTargetCompatibility; } @@ -96,8 +145,14 @@ public List generateProvenance(Path baseDir, MavenProject mavenProject) targetCompatibility = sourceCompatibility; } - BuildEnvironment buildEnvironment = BuildEnvironment.build(System::getenv); - return (List) Stream.of(buildEnvironment, this.gitProvenance(baseDir, buildEnvironment), OperatingSystemProvenance.current(), buildTool, new JavaVersion(Tree.randomId(), javaRuntimeVersion, javaVendor, sourceCompatibility, targetCompatibility), new JavaProject(Tree.randomId(), mavenProject.getName(), new JavaProject.Publication(mavenProject.getGroupId(), mavenProject.getArtifactId(), mavenProject.getVersion()))).filter(Objects::nonNull).collect(Collectors.toList()); + JavaVersion javaVersion = new JavaVersion( + Tree.randomId(), + javaRuntimeVersion, + javaVendor, + sourceCompatibility, + targetCompatibility + ); + return javaVersion; } private @Nullable GitProvenance gitProvenance(Path baseDir, @Nullable BuildEnvironment buildEnvironment) { diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/project/resource/ProjectResourceSetFactory.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/project/resource/ProjectResourceSetFactory.java index 27c65cabb..0ec191acd 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/project/resource/ProjectResourceSetFactory.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/project/resource/ProjectResourceSetFactory.java @@ -18,6 +18,7 @@ import lombok.RequiredArgsConstructor; import org.openrewrite.ExecutionContext; import org.openrewrite.SourceFile; +import org.openrewrite.xml.tree.Xml; import org.springframework.sbm.project.RewriteSourceFileWrapper; import java.nio.file.Path; @@ -41,4 +42,5 @@ public ProjectResourceSet create(Path baseDir, List sourceFiles) { public ProjectResourceSet createFromSourceFileHolders(List> rewriteSourceFileHolders) { return new ProjectResourceSet(rewriteSourceFileHolders, executionContext, rewriteMigrationResultMerger); } + } diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/JavaParserTypeCacheExaminationTest.java b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/JavaParserTypeCacheExaminationTest.java new file mode 100644 index 000000000..1eff76801 --- /dev/null +++ b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/JavaParserTypeCacheExaminationTest.java @@ -0,0 +1,330 @@ +/* + * 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.sbm.parsers; + +import org.apache.maven.model.Plugin; +import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.SourceFile; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.internal.JavaTypeCache; +import org.openrewrite.java.internal.TypesInUse; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.marker.Marker; +import org.openrewrite.maven.cache.LocalMavenArtifactCache; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.maven.tree.Scope; +import org.openrewrite.maven.utilities.MavenArtifactDownloader; +import org.openrewrite.xml.tree.Xml; +import org.springframework.core.io.Resource; +import org.springframework.sbm.parsers.maven.BuildFileParser; +import org.springframework.sbm.parsers.maven.MavenModuleParser; +import org.springframework.sbm.parsers.maven.MavenProvenanceMarkerFactory; +import org.springframework.sbm.parsers.maven.MavenRuntimeInformation; +import org.springframework.sbm.test.util.DummyResource; + +import java.nio.file.Path; +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Fabian Krüger + */ +public class JavaParserTypeCacheExaminationTest { + + private MavenModuleParser sut; + private ExecutionContext executionContext; + + @BeforeEach + void beforeEach() { + executionContext = new RewriteExecutionContext(); + LocalMavenArtifactCache artifactCache = new LocalMavenArtifactCache(Path.of(System.getProperty("user.home")).resolve(".m2/repository")); + sut = new MavenModuleParser(new ParserProperties(), new ModuleParser(), new ClasspathExtractor(new RewriteMavenArtifactDownloader(artifactCache, null, t -> {new RuntimeException(t);})), executionContext); + } + + /** + * Tests proving the behaviour of JavaParser in combination with JavaTypeCache + */ + @Nested + class JavaCompilerTypeResolutionTest { + + // Simple class A + String aSource = """ + package com.foo; + public class A{} + """; + + // Simple class B extending A + String bSource = """ + package com.bar; + import com.foo.A; + public class B extends A {} + """; + private JavaParser.Builder builder; + + @BeforeEach + void beforeEach() { + builder = JavaParser.fromJavaVersion(); + } + + // Parse A and B with one call to parse() on same instance + // Same parser, same call, same type cache + // -> Should resolve type A + @Test + @DisplayName("Same parser with shared type cache and one call should resolve types") + void sameParserWithSharedTypeCacheAndOneCallShouldResolveTypes() { + JavaParser javaParser = builder.build(); + + List parse = javaParser.parse(aSource, bSource).toList(); + + String fullyQualifiedName = ((JavaType.FullyQualified) ((J.CompilationUnit) parse.get(1)).getClasses().get(0).getExtends().getType()).getFullyQualifiedName(); + assertThat(fullyQualifiedName).isEqualTo("com.foo.A"); + } + + // Parse A and B with separate calls to parse() on separate instances with separate type cache + // Different parser, two calls, different type cache + // -> Should NOT resolve type A + @Test + @DisplayName("Different parsers with separate caches and separate calls should not resolve types") + void differentParsersWithSeparateCachesAndSeparateCallsShouldNotResolveTypes() { + // parser 1 with dedicated type cache + JavaTypeCache typeCache = new JavaTypeCache(); + builder.typeCache(typeCache); + JavaParser javaParser = builder.build(); + SourceFile a = javaParser.parse(aSource).toList().get(0); + + // parser 2 with dedicated type cache + JavaTypeCache typeCache2 = new JavaTypeCache(); + builder.typeCache(typeCache2); + JavaParser javaParser2 = builder.build(); + SourceFile b = javaParser2.parse(bSource).toList().get(0); + + // Type A used in B not resolved + String fullyQualifiedName2 = ((JavaType.FullyQualified) ((J.CompilationUnit) b).getClasses().get(0).getExtends().getType()).getFullyQualifiedName(); + String unknown = JavaType.Unknown.getInstance().getFullyQualifiedName(); + assertThat(fullyQualifiedName2).isEqualTo(unknown); + } + + // Parse A and B with separate calls to parse() on separate instances with SHARED type cache + // Different parser, two calls, same type cache + // -> Should NOT resolve type A + // -> Sharing the type cache is not enough! + @Test + @DisplayName("Different parsers with shared cache and separate calls should NOT resolve types") + void differentParsersWithSharedCacheAndSeparateCallsShouldNotResolveTypes() { + // Shared type cache + JavaTypeCache typeCache = new JavaTypeCache(); + builder.typeCache(typeCache); + + // parser 1 + JavaParser javaParser = builder.build(); + // parser 2 + JavaParser javaParser2 = builder.build(); + + SourceFile a = javaParser.parse(aSource).toList().get(0); + SourceFile b = javaParser2.parse(bSource).toList().get(0); + + // Type A used in B IS resolved + String fullyQualifiedName = ((JavaType.FullyQualified) ((J.CompilationUnit) b).getClasses().get(0).getExtends().getType()).getFullyQualifiedName(); + String unknown = JavaType.Unknown.getInstance().getFullyQualifiedName(); + assertThat(fullyQualifiedName).isEqualTo(unknown); + } + + // Parse A and B with separate calls to parse() on SAME instances with SHARED type cache + // Same parser, two calls, same type cache + // -> Should resolve type A + // -> Same parser, separate calls, same type cache + // --> WORKS! That's what we need. + // BUT! It'd require keeping and parsing the parser -> Can a parser parse the same resource twice? + // Yes, but it needs to be reset + // Type A can also be resolved from B then. + // So keeping and passing the used parser (per source set) would be an option. + @Test + @DisplayName("Same parser with shared cache in separate calls should resolve types") + void sameParserWithSharedCacheInSeparateCallsShouldResolveTypes() { + // One shared type cache + JavaTypeCache typeCache = new JavaTypeCache(); + builder.typeCache(typeCache); + JavaParser javaParser = builder.build(); + + // Same parser two calls + SourceFile a = javaParser.parse(aSource).toList().get(0); + SourceFile b1= javaParser.parse(bSource).toList().get(0); + // fails! need to call reset() + javaParser.reset(); + SourceFile b = javaParser.parse(bSource).toList().get(0); + + + J.CompilationUnit compilationUnitB = (J.CompilationUnit) b; + String fullyQualifiedName2 = ((JavaType.FullyQualified) compilationUnitB.getClasses().get(0).getExtends().getType()).getFullyQualifiedName(); + // A can be resolved + assertThat(fullyQualifiedName2).isEqualTo("com.foo.A"); + + List bTypesInUseFqNames = compilationUnitB.getTypesInUse().getTypesInUse().stream().map(fq -> ((JavaType.FullyQualified) fq).getFullyQualifiedName()).toList(); + + TypesInUse.FindTypesInUse findTypesInUse = new TypesInUse.FindTypesInUse(); + findTypesInUse.visit(compilationUnitB, 0); + System.out.println(findTypesInUse.getTypes()); + + // A is in typesInUse of B + assertThat(bTypesInUseFqNames).contains("com.foo.A"); + //No markers were set + assertThat(compilationUnitB.getMarkers().getMarkers()).isEmpty(); + } + } + + + /** + * Example of how a module could be parsed while the JavaParser gets reused by another module (here just a class). + */ + @Test + @DisplayName("reuse JavaParser from Marker example") + void reuseJavaParserFromMarkerExample() { + @Language("xml") + String pomXml = """ + + + 4.0.0 + com.example + app + 0.1.0-SNAPSHOT + + 17 + 17 + + + + org.junit.jupiter + junit-jupiter-api + 5.9.3test + test + + + javax.validation + validation-api + 2.0.1.Final + + + + """; + @Language("java") + String mainClass = """ + package com.example.app; + + public class MainClass { } + """; + @Language("java") + String testClass = """ + package com.example.app.test; + import org.junit.jupiter.api.Test; + import com.example.app.MainClass; + + public class SomeTest { + @Test + void someTest() { + MainClass m = new MainClass(); + } + } + """; + + Path baseDir = Path.of("./target").toAbsolutePath().normalize(); + + Path pomXmlPath = baseDir.resolve("pom.xml").normalize(); + List resources = List.of( + new DummyResource(pomXmlPath, pomXml), + new DummyResource(baseDir.resolve("src/main/java/com/example/app/MainClass.java"), mainClass), + new DummyResource(baseDir.resolve("src/test/java/com/example/app/test/SomeTest.java"), testClass) + ); + + MavenRuntimeInformation mavenRuntimeInfo = new MavenRuntimeInformation(); + Plugin compilerPlugin = null; + Properties properties = new Properties(); + String projectName = "app"; + String groupId = "com.example"; + String artifactId = "app"; + String version = "1.0.0-SNAPSHOT"; + + List provenanceMarkers = new MavenProvenanceMarkerFactory().generateProvenanceMarkers( + baseDir, + mavenRuntimeInfo, + compilerPlugin, + properties, + projectName, + groupId, + artifactId, + version + ); + Map> provenanceMarkersMap = Map.of(pomXmlPath, provenanceMarkers); + BuildFileParser buildFileParser = new BuildFileParser(); + List parsedBuildFiles = buildFileParser.parseBuildFiles(baseDir, List.of(resources.get(0)), List.of(), new RewriteExecutionContext(), false, provenanceMarkersMap); + Xml.Document document = parsedBuildFiles.get(0); + + ModuleParsingResult result = sut.parseLeafModule(baseDir, resources, document.getMarkers().findFirst(MavenResolutionResult.class).get()); + + // take JavaParser from Marker + JavaParser javaParser = result.sourceFiles().get(0).getMarkers().findFirst(JavaParserMarker.class).get().getJavaParser(); + J.CompilationUnit anotherClass = (J.CompilationUnit) javaParser.parse( + """ + package a.b; + import com.example.app.MainClass; + + public class AnotherClass extends MainClass {} + """ + ).toList().get(0); + + String extendsFullyQualifiedName = ((JavaType.FullyQualified) anotherClass.getClasses().get(0).getExtends().getType()).getFullyQualifiedName(); + // A can be resolved + assertThat(extendsFullyQualifiedName).isEqualTo("com.example.app.MainClass"); + + J.CompilationUnit myClass = (J.CompilationUnit) javaParser.parse(""" + package com.example.app; + import a.b.AnotherClass; + import javax.validation.constraints.Min; + + public class MyClass extends AnotherClass { + @Min("0") + private int number; + } + """) + .toList() + .get(0); + + J.ClassDeclaration myClassDecl = myClass.getClasses().get(0); + String firstExtendFqn = ((JavaType.FullyQualified) myClassDecl.getExtends().getType()).getFullyQualifiedName(); + assertThat(firstExtendFqn).isEqualTo("a.b.AnotherClass"); + + // validation-api should be transitively provided from module parsing + assertThat( + ((JavaType.FullyQualified)((J.VariableDeclarations)myClassDecl.getBody().getStatements().get(0)).getAllAnnotations().get(0).getType()).getFullyQualifiedName() + ).isEqualTo("javax.validation.constraints.Min"); + + // Now, let's see if we could add a dependency to the used classpath + Path depPath = Path.of(System.getProperty("user.home")).resolve(".m2/repository/org/jetbrains/annotations/24.0.1/annotations-24.0.1.jar"); + javaParser.setClasspath(List.of(depPath)); // Here we'd need to add the dependencies from lower modules, if any + + + + // PROBLEM: When using a shared parser to parse a class in lower module, the types from higher modules are cached. + // Assumption: reset(Collection) does not help, previous tests indicate that resolution is independant of state cleared in reset(). + } +} diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/MavenPartialProjectParsingTest.java b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/MavenPartialProjectParsingTest.java new file mode 100644 index 000000000..07f4e9434 --- /dev/null +++ b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/MavenPartialProjectParsingTest.java @@ -0,0 +1,237 @@ +/* + * 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.sbm.parsers; + +import org.checkerframework.checker.units.qual.A; +import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.io.TempDir; +import org.openrewrite.ExecutionContext; +import org.openrewrite.internal.InMemoryLargeSourceSet; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.tree.J; +import org.openrewrite.maven.AddDependencyVisitor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.io.Resource; +import org.springframework.sbm.boot.autoconfigure.SbmSupportRewriteConfiguration; +import org.springframework.sbm.project.resource.ProjectResourceSet; +import org.springframework.sbm.project.resource.ProjectResourceSetFactory; +import org.springframework.sbm.support.openrewrite.GenericOpenRewriteRecipe; +import org.springframework.sbm.test.util.DummyResource; + +import java.nio.file.Path; +import java.util.List; + +/** + * @author Fabian Krüger + */ +@SpringBootTest(classes = SbmSupportRewriteConfiguration.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class MavenPartialProjectParsingTest { + + @Autowired + RewriteProjectParser rewriteProjectParser; + + @Autowired + ProjectResourceSetFactory resourceSetFactory; + + @Autowired + private ExecutionContext executionContext; + + @TempDir Path baseDir; + + private static RewriteProjectParsingResult parsingResult; + + @Language("xml") + String parentPom = """ + + + 4.0.0 + com.example + example-1-parent + 0.1.0-SNAPSHOT + pom + + 17 + 17 + + + module-a + module-b + module-c + + + """; + + @Language("xml") + String moduleAPom = """ + + + 4.0.0 + + com.example + example-1-parent + 0.1.0-SNAPSHOT + + module-a + + 17 + 17 + + + + com.example + module-b + ${project.version} + + + javax.validation + validation-api + 2.0.1.Final + + + + """; + + @Language("xml") + String moduleBPom = """ + + + 4.0.0 + + com.example + example-1-parent + 0.1.0-SNAPSHOT + + module-b + + 17 + 17 + + + + com.example + module-c + ${project.version} + + + + """; + + @Language("xml") + String moduleCPom = """ + + + 4.0.0 + + com.example + example-1-parent + 0.1.0-SNAPSHOT + + module-c + + 17 + 17 + + + """; + + @Test + @Order(1) + @DisplayName("parse reactor project") + void parseReactorProject() { + List resources = List.of( + new DummyResource(baseDir.resolve("pom.xml"), parentPom), + new DummyResource(baseDir.resolve("module-a/pom.xml"), moduleAPom), + new DummyResource(baseDir.resolve("module-b/pom.xml"), moduleBPom), + new DummyResource(baseDir.resolve("module-b/src/main/java/MainB.java"), """ + public class MainB { + String name; + } + """), + new DummyResource(baseDir.resolve("module-c/pom.xml"), moduleCPom) + ); + + + parsingResult = rewriteProjectParser.parse(baseDir, resources); + // TODO: verify initial parsing result here + } + + @Test + @Order(2) + @DisplayName("adding dependency to B") + void addingDependencyToB() { + GenericOpenRewriteRecipe recipe = new GenericOpenRewriteRecipe<>(() -> new AddDependencyVisitor("com.example", "module-b", "0.1.0-SNAPSHOT", null, null, null, null, null, null, null)); + ProjectResourceSet resourceSet = resourceSetFactory.create(baseDir, parsingResult.sourceFiles()); + resourceSet.apply(recipe); + // TODO: assertion the classpath change + } + + @Test + @Order(3) + @DisplayName("add code using the new types") + void addCodeUsingTheNewTypes() { + JavaIsoVisitor visitor = new JavaIsoVisitor<>() { + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) { + J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, executionContext); +// cd.getBody().getStatements().get(0); +// ((J.VariableDeclarations)cd.getBody().getStatements().get(0)) + JavaTemplate.builder("@Nullable").javaParser() + return cd; + } + }; + + GenericOpenRewriteRecipe> recipe = new GenericOpenRewriteRecipe<>(() -> visitor); + ProjectResourceSet resourceSet = resourceSetFactory.create(baseDir, parsingResult.sourceFiles()); + resourceSet.apply(recipe); + } + + + + +/* + // used to calculate paths to other modules. + // TODO: Remove and filter all provided resources by module path segment + MavenProject mavenProject = null; + // used to retrieve sourcePath which can be calculated having the module path segment and baseDir + Xml.Document moduleBuildFile = null; + // required, can be taken from a resource in same source dir?! + List provenanceMarkers = null; + // Make this a Spring bean + List styles = null; + // provided as bean + ExecutionContext executionContext = null; + // required + Path baseDir = null; + sut.parseModuleSourceFiles(resources, mavenProject, moduleBuildFile, provenanceMarkers, styles, executionContext, baseDir); +*/ +// BuildFileParser buildFileParser = new BuildFileParser(); +// List parsedBuildFiles = buildFileParser.parseBuildFiles(baseDir, resources, List.of(), executionContext, false, Map.of()); +// +// Path locaMavenRepo = Path.of(System.getProperty("user.home")).resolve(".m2/repository"); +// MavenArtifactDownloader artifactDownloader = new RewriteMavenArtifactDownloader(new LocalMavenArtifactCache(locaMavenRepo), null, t -> {throw new RuntimeException(t);}); +// ClasspathExtractor classpathExtractor = new ClasspathExtractor(artifactDownloader); +// List classpath = classpathExtractor.extractClasspath(parsedBuildFiles.get(0).getMarkers().findFirst(MavenResolutionResult.class).get(), Scope.Compile); +// Collection classBytesClasspath = List.of(); +// String modulePathSegment = "module-c"; +// ModuleParsingResult result = sut.parseModule(baseDir, modulePathSegment, classpath, classBytesClasspath, resources); +// assertThat(result.sourceFiles()).hasSize(0); +// } + +} diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/RewriteProjectParserTest.java b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/RewriteProjectParserTest.java index fe9ae4395..8b5d19600 100644 --- a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/RewriteProjectParserTest.java +++ b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/RewriteProjectParserTest.java @@ -23,6 +23,7 @@ import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.Parser; import org.openrewrite.SourceFile; +import org.openrewrite.maven.cache.LocalMavenArtifactCache; import org.openrewrite.tree.ParsingEventListener; import org.openrewrite.tree.ParsingExecutionContextView; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -91,7 +92,10 @@ void parseSimpleMavenProject(@TempDir Path tempDir) { ParserProperties parserProperties = new ParserProperties(); ModuleParser mavenMojoParserPrivateMethods = new ModuleParser(); ExecutionContext executionContext = new InMemoryExecutionContext(t -> {throw new RuntimeException(t);}); - MavenModuleParser mavenModuleParser = new MavenModuleParser(parserProperties, mavenMojoParserPrivateMethods); + Path localMavenRepo = Path.of(System.getProperty("user.home")).resolve(".m2/repository"); + ClasspathExtractor classpathExtractor = new ClasspathExtractor(new RewriteMavenArtifactDownloader(new LocalMavenArtifactCache(localMavenRepo), null, t -> {throw new RuntimeException(t);})); + MavenModuleParser mavenModuleParser = new MavenModuleParser(parserProperties, mavenMojoParserPrivateMethods, classpathExtractor, executionContext); + ClasspathRegistry classpathRegistry = mock(ClasspathRegistry.class); RewriteProjectParser projectParser = new RewriteProjectParser( new ProvenanceMarkerFactory(new MavenProvenanceMarkerFactory()), new BuildFileParser(), @@ -104,7 +108,8 @@ void parseSimpleMavenProject(@TempDir Path tempDir) { mock(ConfigurableListableBeanFactory.class), new ProjectScanner(new DefaultResourceLoader(), parserProperties), executionContext, - new MavenProjectAnalyzer(mock(RewriteMavenArtifactDownloader.class)) + new MavenProjectAnalyzer(classpathRegistry), + classpathRegistry ); List parsedFiles = new ArrayList<>(); diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/MavenProjectAnalyzerTest.java b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/MavenProjectAnalyzerTest.java index b8cac286f..0fe2e3671 100644 --- a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/MavenProjectAnalyzerTest.java +++ b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/MavenProjectAnalyzerTest.java @@ -26,6 +26,8 @@ import org.mockito.Mockito; import org.openrewrite.maven.utilities.MavenArtifactDownloader; import org.springframework.core.io.Resource; +import org.springframework.sbm.parsers.ClasspathExtractor; +import org.springframework.sbm.parsers.ClasspathRegistry; import org.springframework.sbm.parsers.MavenProject; import org.springframework.sbm.parsers.RewriteMavenArtifactDownloader; import org.springframework.sbm.test.util.DummyResource; @@ -50,7 +52,9 @@ class MavenProjectAnalyzerTest { @BeforeEach void beforeEach() { MavenArtifactDownloader rewriteMavenArtifactDownloader = Mockito.mock(RewriteMavenArtifactDownloader.class); - sut = new MavenProjectAnalyzer(rewriteMavenArtifactDownloader); + ClasspathExtractor classpathExtractor = new ClasspathExtractor(rewriteMavenArtifactDownloader); + ClasspathRegistry classpathRegistry = new ClasspathRegistry(classpathExtractor); + sut = new MavenProjectAnalyzer(classpathRegistry); } @Nested diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/RewriteMavenProjectParserTest.java b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/RewriteMavenProjectParserTest.java index 325b683f9..1759e999f 100644 --- a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/RewriteMavenProjectParserTest.java +++ b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/RewriteMavenProjectParserTest.java @@ -16,7 +16,6 @@ package org.springframework.sbm.parsers.maven; import org.intellij.lang.annotations.Language; -import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; @@ -40,8 +39,6 @@ import org.openrewrite.maven.MavenExecutionContextView; import org.openrewrite.maven.MavenSettings; import org.openrewrite.maven.cache.*; -import org.openrewrite.maven.cache.LocalMavenArtifactCache; -import org.openrewrite.maven.cache.MavenArtifactCache; import org.openrewrite.maven.tree.MavenResolutionResult; import org.openrewrite.shaded.jgit.api.Git; import org.openrewrite.shaded.jgit.api.errors.GitAPIException; @@ -64,7 +61,6 @@ import java.io.File; import java.nio.charset.Charset; import java.nio.file.Path; -import java.nio.file.Paths; import java.time.Duration; import java.time.Instant; import java.time.ZoneId; @@ -273,10 +269,13 @@ void parseMultiModule1_WithCustomParser() { Path baseDir = getMavenProject("multi-module-1"); ModuleParser moduleParser = new ModuleParser(); + ClasspathExtractor classpathExtractor = new ClasspathExtractor(mock(RewriteMavenArtifactDownloader.class)); + RewriteExecutionContext executionContext = new RewriteExecutionContext(); + ClasspathRegistry classpathRegistry = new ClasspathRegistry(classpathExtractor); RewriteProjectParser rpp = new RewriteProjectParser( new ProvenanceMarkerFactory(new MavenProvenanceMarkerFactory()), new BuildFileParser(), - new SourceFileParser(new MavenModuleParser(parserProperties, moduleParser)), + new SourceFileParser(new MavenModuleParser(parserProperties, moduleParser, classpathExtractor, executionContext)), new StyleDetector(), parserProperties, mock(ParsingEventListener.class), @@ -284,8 +283,9 @@ void parseMultiModule1_WithCustomParser() { scanScope, beanFactory, new ProjectScanner(new DefaultResourceLoader(), parserProperties), - new RewriteExecutionContext(), - new MavenProjectAnalyzer(mock(RewriteMavenArtifactDownloader.class)) + executionContext, + new MavenProjectAnalyzer(classpathRegistry), + classpathRegistry ); Set ignoredPatters = Set.of();