Skip to content

Commit 23be80c

Browse files
JavaTypeFactory: type construction, classpath fast paths, TypeTable JARs (#7528)
Surface area for parser-time type construction is now centralized on JavaTypeFactory and threaded through every TypeMapping (Java 8/11/17/21/25, Groovy, Kotlin/KotlinIr, Scala, JavaReflection). Each compute* method returns the cached instance keyed by signature, or allocates a stub, registers it, and runs the supplied initializer. Registering the stub before the initializer runs is what makes recursive resolution safe — a recursive lookup for the same signature finds the stub instead of looping. The previous two-phase create*/populate* surface is gone. DefaultJavaTypeFactory implements compute* directly. Initializers call unsafeSet on the stub to populate fields, replacing the earlier typeFactory.populateXxx indirection. JavaTypeFactory.Provider is the parser-builder hand-off: parsers (JavaParser, Java*Parser, GroovyParser, KotlinParser, ScalaParser, GradleParser) and JavaTemplateParser all consume a Provider rather than a JavaTypeCache directly. JavaTypeCache itself is unchanged but the cache() configuration on parser builders is deprecated in favor of typeFactory(). RecipeScheduler exposes a rootCursorProvider so a recipe-scoped JavaTypeFactory can flow into JavaTemplate parses, keeping splice-time type identity consistent with the surrounding LST. JavaSourceSet gains two fast-path accessors for recipes that index by classpath: - findClasspathType(fqn) — sorted-bsearch on a per-source-set ClasspathIndex SPI, falls back to linear scan for legacy markers. - classpathTypesInPackage(pkg) — sorted-prefix range scan. Both go through ClasspathIndex.Subset, an SPI marker producers (serializer V4 lazy backings) implement to short-circuit. ImportLayoutStyle uses the per-package fast path. removeTypesMatching / removeTypesForGav also dispatch through ClasspathIndex#withGavsRemoved when the marker backing supports it, preserving lazy classpath views across recipe edits. JavaSourceSetCompat retains the legacy materialization path under @toBeRemoved(after = "2026-06-30") for recipes still calling JavaSourceSet.build(...) directly. RecipeClassLoader parent-loads JavaTypeFactory so recipe-side and parser-side factory instances unify. TypeTable now writes per-artifact JARs (instead of classes-directories) so javac uses ArchiveContainer with a cached central directory rather than DirectoryContainer, which calls openat() per list() during template parsing. Wall-mode profiling on AssertJ recipe / spring-data-commons: __open worker samples 921 → 157 (-83%); recipe wall time 1m23s → 1m16s. TypeTable annotations are now structured: AnnotationDeserializer is a public class, TypeSignature parses ASM signatures, and TypeTableSink is the abstract sink interface so writers don't go through string round-trip.
1 parent c3fc21b commit 23be80c

61 files changed

Lines changed: 4317 additions & 5350 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/superpowers/plans/2026-03-20-csharp-parenthesization-utility.md

Lines changed: 0 additions & 1353 deletions
This file was deleted.

docs/superpowers/specs/2026-03-19-csharp-parenthesization-utility-design.md

Lines changed: 0 additions & 267 deletions
This file was deleted.

rewrite-benchmarks/src/jmh/java/org/openrewrite/benchmarks/java/JavaCompilationUnitState.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.openrewrite.SourceFile;
2525
import org.openrewrite.internal.InMemoryLargeSourceSet;
2626
import org.openrewrite.java.JavaParser;
27+
import org.openrewrite.java.internal.DefaultJavaTypeFactory;
2728
import org.openrewrite.java.internal.JavaTypeCache;
2829
import org.xerial.snappy.Snappy;
2930

@@ -103,7 +104,7 @@ public void setup() throws URISyntaxException {
103104
// .logCompilationWarningsAndErrors(true)
104105

105106
typeCache = new MapJavaTypeCache();
106-
JavaParser parser = javaParser.typeCache(typeCache).build();
107+
JavaParser parser = javaParser.typeFactory(new DefaultJavaTypeFactory(typeCache)).build();
107108
sourceFiles = parser
108109
.parse(inputs, null, new InMemoryExecutionContext())
109110
.collect(toList());

rewrite-benchmarks/src/jmh/java/org/openrewrite/benchmarks/java/JavaParserBenchmark.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.openjdk.jmh.runner.options.OptionsBuilder;
2525
import org.openrewrite.InMemoryExecutionContext;
2626
import org.openrewrite.java.JavaParser;
27+
import org.openrewrite.java.internal.DefaultJavaTypeFactory;
2728
import org.openrewrite.java.internal.JavaTypeCache;
2829

2930
import java.net.URISyntaxException;
@@ -40,7 +41,7 @@ public class JavaParserBenchmark {
4041
@Benchmark
4142
public void snappy(JavaCompilationUnitState state, Blackhole bh) {
4243
JavaTypeCache typeCache = new JavaCompilationUnitState.SnappyJavaTypeCache();
43-
JavaParser parser = state.javaParser.typeCache(typeCache).build();
44+
JavaParser parser = state.javaParser.typeFactory(new DefaultJavaTypeFactory(typeCache)).build();
4445
parser
4546
.parse(state.inputs, null, new InMemoryExecutionContext())
4647
.forEach(bh::consume);
@@ -49,7 +50,7 @@ public void snappy(JavaCompilationUnitState state, Blackhole bh) {
4950
@Benchmark
5051
public void adaptiveRadix(JavaCompilationUnitState state, Blackhole bh) {
5152
JavaTypeCache typeCache = new JavaTypeCache();
52-
JavaParser parser = state.javaParser.typeCache(typeCache).build();
53+
JavaParser parser = state.javaParser.typeFactory(new DefaultJavaTypeFactory(typeCache)).build();
5354
parser
5455
.parse(state.inputs, null, new InMemoryExecutionContext())
5556
.forEach(bh::consume);

rewrite-core/src/main/java/org/openrewrite/RecipeScheduler.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,38 @@
2222
import org.openrewrite.table.SourcesFileErrors;
2323
import org.openrewrite.table.SourcesFileResults;
2424

25+
import org.jspecify.annotations.Nullable;
26+
2527
import java.io.IOException;
2628
import java.nio.file.Files;
2729
import java.nio.file.Path;
30+
import java.util.function.Supplier;
2831
import java.util.stream.Stream;
2932

3033
import static org.openrewrite.Recipe.PANIC;
3134
import static org.openrewrite.scheduling.WorkingDirectoryExecutionContextView.WORKING_DIRECTORY_ROOT;
3235

3336
public class RecipeScheduler {
3437

38+
@Nullable
39+
private Supplier<Cursor> rootCursorProvider;
40+
41+
/**
42+
* Set a provider for the root cursor used in each recipe cycle.
43+
* The provider is called once per cycle to create a fresh root cursor.
44+
* This allows injecting shared state (e.g., a type cache provider) that will be
45+
* visible to all visitors and template parsers during the cycle.
46+
* <p>
47+
* If not set, a default root cursor is created with {@code new Cursor(null, Cursor.ROOT_VALUE)}.
48+
*
49+
* @param provider supplies a configured root cursor for each cycle
50+
* @return this scheduler for chaining
51+
*/
52+
public RecipeScheduler rootCursorProvider(Supplier<Cursor> provider) {
53+
this.rootCursorProvider = provider;
54+
return this;
55+
}
56+
3557
public RecipeRun scheduleRun(Recipe recipe,
3658
LargeSourceSet sourceSet,
3759
ExecutionContext ctx,
@@ -70,7 +92,9 @@ private LargeSourceSet runRecipeCycles(Recipe recipe, LargeSourceSet sourceSet,
7092
// this root cursor is shared by all `TreeVisitor` instances used created from `getVisitor` and
7193
// single source applicable tests so that data can be shared at the root (especially for caching
7294
// use cases like sharing a `JavaTypeCache` between `JavaTemplate` parsers).
73-
Cursor rootCursor = new Cursor(null, Cursor.ROOT_VALUE);
95+
Cursor rootCursor = rootCursorProvider != null
96+
? rootCursorProvider.get()
97+
: new Cursor(null, Cursor.ROOT_VALUE);
7498
try {
7599
RecipeRunCycle<LargeSourceSet> cycle = createRecipeRunCycle(recipe, i, rootCursor, ctxWithWatch, recipeRunStats, searchResults, sourceFileResults, errorsTable);
76100
ctxWithWatch.putCycle(cycle);

rewrite-core/src/main/java/org/openrewrite/marketplace/RecipeClassLoader.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ public class RecipeClassLoader extends URLClassLoader {
7171
"org.openrewrite.ipc.http.HttpSender",
7272
"org.openrewrite.java.JavadocVisitor",
7373
"org.openrewrite.java.internal.TypesInUse",
74+
// Cursor-message wiring (TYPE_FACTORY_PROVIDER_KEY) passes a Provider
75+
// implementation across the recipe/parent classloader boundary; the
76+
// interface must be shared so the cast in JavaTemplateParser succeeds.
77+
"org.openrewrite.java.internal.JavaTypeFactory",
7478
"org.openrewrite.maven.MavenDownloadingException",
7579
"org.openrewrite.maven.MavenDownloadingExceptions",
7680
"org.openrewrite.maven.MavenExecutionContextView",

rewrite-gradle/src/main/java/org/openrewrite/gradle/GradleParser.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.openrewrite.groovy.GroovyParser;
2525
import org.openrewrite.groovy.tree.G;
2626
import org.openrewrite.java.JavaParser;
27+
import org.openrewrite.java.internal.JavaTypeFactory;
2728
import org.openrewrite.kotlin.KotlinParser;
2829

2930
import java.nio.file.Path;
@@ -169,6 +170,16 @@ public Builder kotlinParser(KotlinParser.Builder kotlinParser) {
169170
return this;
170171
}
171172

173+
/**
174+
* Forward a {@link JavaTypeFactory.Provider} to both the Groovy and Kotlin
175+
* sub-parsers so either DSL variant produces types from the caller's factory.
176+
*/
177+
public Builder typeFactoryProvider(JavaTypeFactory.Provider provider) {
178+
this.groovyParser.typeFactoryProvider(provider);
179+
this.kotlinParser.typeFactoryProvider(provider);
180+
return this;
181+
}
182+
172183
public Builder buildscriptClasspath(Collection<Path> classpath) {
173184
this.buildscriptClasspath = classpath;
174185
return this;

0 commit comments

Comments
 (0)