diff --git a/rewrite-benchmarks/src/jmh/java/org/openrewrite/benchmarks/java/BuilderFactoryBenchmark.java b/rewrite-benchmarks/src/jmh/java/org/openrewrite/benchmarks/java/BuilderFactoryBenchmark.java
new file mode 100644
index 00000000000..13417d6da2b
--- /dev/null
+++ b/rewrite-benchmarks/src/jmh/java/org/openrewrite/benchmarks/java/BuilderFactoryBenchmark.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2026 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openrewrite.benchmarks.java;
+
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.infra.Blackhole;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openrewrite.java.JavaParser;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Microbenchmark for JavaParser.fromJavaVersion() builder factory.
+ * Measures the overhead of reflection vs MethodHandle invocation.
+ */
+@Fork(value = 2, warmups = 1)
+@Measurement(iterations = 5, time = 1)
+@Warmup(iterations = 3, time = 1)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@State(Scope.Benchmark)
+public class BuilderFactoryBenchmark {
+
+ /**
+ * Measures the cost of repeatedly calling fromJavaVersion() to obtain
+ * a new builder instance. This exercises the cached supplier's invoke path.
+ */
+ @Benchmark
+ public void builderFactory(Blackhole bh) {
+ JavaParser.Builder extends JavaParser, ?> builder = JavaParser.fromJavaVersion();
+ bh.consume(builder);
+ }
+
+ public static void main(String[] args) throws RunnerException {
+ Options opt = new OptionsBuilder()
+ .include(BuilderFactoryBenchmark.class.getSimpleName())
+ .shouldFailOnError(true)
+ .build();
+ new Runner(opt).run();
+ }
+}
diff --git a/rewrite-benchmarks/src/jmh/java/org/openrewrite/benchmarks/java/JavaSourceSetBenchmark.java b/rewrite-benchmarks/src/jmh/java/org/openrewrite/benchmarks/java/JavaSourceSetBenchmark.java
index e21aab4dd12..75ffeb5074b 100644
--- a/rewrite-benchmarks/src/jmh/java/org/openrewrite/benchmarks/java/JavaSourceSetBenchmark.java
+++ b/rewrite-benchmarks/src/jmh/java/org/openrewrite/benchmarks/java/JavaSourceSetBenchmark.java
@@ -29,10 +29,4 @@ public void setup() {
public void jarIOBenchmark() {
JavaSourceSet.build("main", classpath);
}
-
- @Benchmark
- public void classgraphBenchmark() {
- //noinspection deprecation
- JavaSourceSet.build("main", classpath, new JavaTypeCache(), false);
- }
}
diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java
index 0fe766a526c..e194ca9169a 100644
--- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java
+++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java
@@ -380,6 +380,8 @@ public ReloadableJava11Parser build() {
private static class ByteArrayCapableJavacFileManager extends JavacFileManager {
private final List classByteClasspath;
+ private final IdentityHashMap inferBinaryNameCache = new IdentityHashMap<>();
+ private final HashMap> listCache = new HashMap<>();
public ByteArrayCapableJavacFileManager(Context context,
boolean register,
@@ -396,19 +398,52 @@ public String inferBinaryName(Location location, JavaFileObject file) {
if (file instanceof PackageAwareJavaFileObject) {
return ((PackageAwareJavaFileObject) file).getClassName();
}
- return super.inferBinaryName(location, file);
+ String cached = inferBinaryNameCache.get(file);
+ if (cached != null) {
+ return cached;
+ }
+ String result = super.inferBinaryName(location, file);
+ if (result != null) {
+ inferBinaryNameCache.put(file, result);
+ }
+ return result;
+ }
+
+ @Override
+ public void flush() {
+ super.flush();
+ inferBinaryNameCache.clear();
+ listCache.clear();
+ }
+
+ @Override
+ public void setLocationFromPaths(Location location, Collection extends Path> paths) throws IOException {
+ super.setLocationFromPaths(location, paths);
+ inferBinaryNameCache.clear();
+ listCache.clear();
}
@Override
public Iterable list(Location location, String packageName, Set kinds, boolean recurse) throws IOException {
+ String key = location.getName() + ':' + packageName + ':' + kinds + ':' + recurse;
+ List cached = listCache.get(key);
+ if (cached != null) {
+ return cached;
+ }
+ List result;
if (StandardLocation.CLASS_PATH.equals(location)) {
Iterable listed = super.list(location, packageName, kinds, recurse);
- return Stream.concat(classByteClasspath.stream()
+ result = Stream.concat(classByteClasspath.stream()
.filter(jfo -> jfo.getPackage().equals(packageName)),
StreamSupport.stream(listed.spliterator(), false)
).collect(toList());
+ } else {
+ Iterable listed = super.list(location, packageName, kinds, recurse);
+ result = listed instanceof List ? (List) listed :
+ StreamSupport.stream(listed.spliterator(), false).collect(toList());
}
- return super.list(location, packageName, kinds, recurse);
+ listCache.put(key, result);
+ return result;
}
}
diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java
index 73f23946e1e..bdbe7cc5ad4 100644
--- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java
+++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java
@@ -351,6 +351,10 @@ public ReloadableJava17Parser build() {
private static class ByteArrayCapableJavacFileManager extends JavacFileManager {
private final List classByteClasspath;
+ private final IdentityHashMap inferBinaryNameCache = new IdentityHashMap<>();
+ private final HashMap> listCache = new HashMap<>();
+
+ private record ListCacheKey(Location location, String packageName, Set kinds, boolean recurse) {}
public ByteArrayCapableJavacFileManager(Context context,
boolean register,
@@ -367,20 +371,52 @@ public String inferBinaryName(Location location, JavaFileObject file) {
if (file instanceof PackageAwareJavaFileObject) {
return ((PackageAwareJavaFileObject) file).getClassName();
}
- return super.inferBinaryName(location, file);
+ String cached = inferBinaryNameCache.get(file);
+ if (cached != null) {
+ return cached;
+ }
+ String result = super.inferBinaryName(location, file);
+ if (result != null) {
+ inferBinaryNameCache.put(file, result);
+ }
+ return result;
+ }
+
+ @Override
+ public void flush() {
+ super.flush();
+ inferBinaryNameCache.clear();
+ listCache.clear();
+ }
+
+ @Override
+ public void setLocationFromPaths(Location location, Collection extends Path> paths) throws IOException {
+ super.setLocationFromPaths(location, paths);
+ inferBinaryNameCache.clear();
+ listCache.clear();
}
@Override
public Iterable list(Location location, String packageName, Set kinds, boolean recurse) throws IOException {
- if (StandardLocation.CLASS_PATH.equals(location)) {
+ ListCacheKey key = new ListCacheKey(location, packageName, kinds, recurse);
+ List cached = listCache.get(key);
+ if (cached != null) {
+ return cached;
+ }
+ List result;
+ if (StandardLocation.CLASS_PATH.equals(location) && !classByteClasspath.isEmpty()) {
Iterable listed = super.list(location, packageName, kinds, recurse);
- return classByteClasspath.isEmpty() ? listed :
- Stream.concat(classByteClasspath.stream()
+ result = Stream.concat(classByteClasspath.stream()
.filter(jfo -> jfo.getPackage().equals(packageName)),
StreamSupport.stream(listed.spliterator(), false)
).collect(toList());
+ } else {
+ Iterable listed = super.list(location, packageName, kinds, recurse);
+ result = listed instanceof List ? (List) listed :
+ StreamSupport.stream(listed.spliterator(), false).collect(toList());
}
- return super.list(location, packageName, kinds, recurse);
+ listCache.put(key, result);
+ return result;
}
}
diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21Parser.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21Parser.java
index fb4e271213c..6ac1fc3d4f6 100644
--- a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21Parser.java
+++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21Parser.java
@@ -351,6 +351,10 @@ public ReloadableJava21Parser build() {
private static class ByteArrayCapableJavacFileManager extends JavacFileManager {
private final List classByteClasspath;
+ private final IdentityHashMap inferBinaryNameCache = new IdentityHashMap<>();
+ private final HashMap> listCache = new HashMap<>();
+
+ private record ListCacheKey(Location location, String packageName, Set kinds, boolean recurse) {}
public ByteArrayCapableJavacFileManager(Context context,
boolean register,
@@ -367,20 +371,52 @@ public String inferBinaryName(Location location, JavaFileObject file) {
if (file instanceof PackageAwareJavaFileObject) {
return ((PackageAwareJavaFileObject) file).getClassName();
}
- return super.inferBinaryName(location, file);
+ String cached = inferBinaryNameCache.get(file);
+ if (cached != null) {
+ return cached;
+ }
+ String result = super.inferBinaryName(location, file);
+ if (result != null) {
+ inferBinaryNameCache.put(file, result);
+ }
+ return result;
+ }
+
+ @Override
+ public void flush() {
+ super.flush();
+ inferBinaryNameCache.clear();
+ listCache.clear();
+ }
+
+ @Override
+ public void setLocationFromPaths(Location location, Collection extends Path> paths) throws IOException {
+ super.setLocationFromPaths(location, paths);
+ inferBinaryNameCache.clear();
+ listCache.clear();
}
@Override
public Iterable list(Location location, String packageName, Set kinds, boolean recurse) throws IOException {
- if (StandardLocation.CLASS_PATH.equals(location)) {
+ ListCacheKey key = new ListCacheKey(location, packageName, kinds, recurse);
+ List cached = listCache.get(key);
+ if (cached != null) {
+ return cached;
+ }
+ List result;
+ if (StandardLocation.CLASS_PATH.equals(location) && !classByteClasspath.isEmpty()) {
Iterable listed = super.list(location, packageName, kinds, recurse);
- return classByteClasspath.isEmpty() ? listed :
- Stream.concat(classByteClasspath.stream()
+ result = Stream.concat(classByteClasspath.stream()
.filter(jfo -> jfo.getPackage().equals(packageName)),
StreamSupport.stream(listed.spliterator(), false)
).collect(toList());
+ } else {
+ Iterable listed = super.list(location, packageName, kinds, recurse);
+ result = listed instanceof List ? (List) listed :
+ StreamSupport.stream(listed.spliterator(), false).collect(toList());
}
- return super.list(location, packageName, kinds, recurse);
+ listCache.put(key, result);
+ return result;
}
}
diff --git a/rewrite-java-25/src/main/java/org/openrewrite/java/isolated/ReloadableJava25Parser.java b/rewrite-java-25/src/main/java/org/openrewrite/java/isolated/ReloadableJava25Parser.java
index 745ffcc72c2..0f4f31bed34 100644
--- a/rewrite-java-25/src/main/java/org/openrewrite/java/isolated/ReloadableJava25Parser.java
+++ b/rewrite-java-25/src/main/java/org/openrewrite/java/isolated/ReloadableJava25Parser.java
@@ -351,6 +351,10 @@ public ReloadableJava25Parser build() {
private static class ByteArrayCapableJavacFileManager extends JavacFileManager {
private final List classByteClasspath;
+ private final IdentityHashMap inferBinaryNameCache = new IdentityHashMap<>();
+ private final HashMap> listCache = new HashMap<>();
+
+ private record ListCacheKey(Location location, String packageName, Set kinds, boolean recurse) {}
public ByteArrayCapableJavacFileManager(Context context,
boolean register,
@@ -367,20 +371,52 @@ public String inferBinaryName(Location location, JavaFileObject file) {
if (file instanceof PackageAwareJavaFileObject) {
return ((PackageAwareJavaFileObject) file).getClassName();
}
- return super.inferBinaryName(location, file);
+ String cached = inferBinaryNameCache.get(file);
+ if (cached != null) {
+ return cached;
+ }
+ String result = super.inferBinaryName(location, file);
+ if (result != null) {
+ inferBinaryNameCache.put(file, result);
+ }
+ return result;
+ }
+
+ @Override
+ public void flush() {
+ super.flush();
+ inferBinaryNameCache.clear();
+ listCache.clear();
+ }
+
+ @Override
+ public void setLocationFromPaths(Location location, Collection extends Path> paths) throws IOException {
+ super.setLocationFromPaths(location, paths);
+ inferBinaryNameCache.clear();
+ listCache.clear();
}
@Override
public Iterable list(Location location, String packageName, Set kinds, boolean recurse) throws IOException {
- if (StandardLocation.CLASS_PATH.equals(location)) {
+ ListCacheKey key = new ListCacheKey(location, packageName, kinds, recurse);
+ List cached = listCache.get(key);
+ if (cached != null) {
+ return cached;
+ }
+ List result;
+ if (StandardLocation.CLASS_PATH.equals(location) && !classByteClasspath.isEmpty()) {
Iterable listed = super.list(location, packageName, kinds, recurse);
- return classByteClasspath.isEmpty() ? listed :
- Stream.concat(classByteClasspath.stream()
+ result = Stream.concat(classByteClasspath.stream()
.filter(jfo -> jfo.getPackage().equals(packageName)),
StreamSupport.stream(listed.spliterator(), false)
).collect(toList());
+ } else {
+ Iterable listed = super.list(location, packageName, kinds, recurse);
+ result = listed instanceof List ? (List) listed :
+ StreamSupport.stream(listed.spliterator(), false).collect(toList());
}
- return super.list(location, packageName, kinds, recurse);
+ listCache.put(key, result);
+ return result;
}
}
diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8Parser.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8Parser.java
index 15f05bb8edd..0202b878a1e 100644
--- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8Parser.java
+++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8Parser.java
@@ -310,6 +310,8 @@ public void reset(Collection uris) {
private static class ByteArrayCapableJavacFileManager extends JavacFileManager {
private final List classByteClasspath;
+ private final IdentityHashMap inferBinaryNameCache = new IdentityHashMap<>();
+ private final HashMap> listCache = new HashMap<>();
public ByteArrayCapableJavacFileManager(Context context,
boolean register,
@@ -331,20 +333,53 @@ public String inferBinaryName(Location location, JavaFileObject file) {
if (file instanceof PackageAwareJavaFileObject) {
return ((PackageAwareJavaFileObject) file).getClassName();
}
- return super.inferBinaryName(location, file);
+ String cached = inferBinaryNameCache.get(file);
+ if (cached != null) {
+ return cached;
+ }
+ String result = super.inferBinaryName(location, file);
+ if (result != null) {
+ inferBinaryNameCache.put(file, result);
+ }
+ return result;
+ }
+
+ @Override
+ public void flush() {
+ super.flush();
+ inferBinaryNameCache.clear();
+ listCache.clear();
+ }
+
+ @Override
+ public void setLocation(Location location, Iterable extends File> files) throws IOException {
+ super.setLocation(location, files);
+ inferBinaryNameCache.clear();
+ listCache.clear();
}
@Override
public Iterable list(Location location, String packageName, Set kinds, boolean recurse) throws IOException {
+ String cacheKey = location.getName() + ':' + packageName + ':' + kinds + ':' + recurse;
+ List cached = listCache.get(cacheKey);
+ if (cached != null) {
+ return cached;
+ }
+ List result;
if (StandardLocation.CLASS_PATH == location) {
Iterable listed = super.list(location, packageName, kinds, recurse);
- return Stream.concat(
+ result = Stream.concat(
classByteClasspath.stream()
.filter(jfo -> jfo.getPackage().equals(packageName)),
StreamSupport.stream(listed.spliterator(), false)
).collect(toList());
+ } else {
+ Iterable listed = super.list(location, packageName, kinds, recurse);
+ result = listed instanceof List ? (List) listed :
+ StreamSupport.stream(listed.spliterator(), false).collect(toList());
}
- return super.list(location, packageName, kinds, recurse);
+ listCache.put(cacheKey, result);
+ return result;
}
}
diff --git a/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java b/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java
index 1e4ba58c09a..dfd0eeff068 100644
--- a/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java
+++ b/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java
@@ -243,7 +243,7 @@ public static SourceSpec> sourceSet(SourceSpec> sourceSpec, String sourceSet
public static UncheckedConsumer> addTypesToSourceSet(String sourceSetName, List extendsFrom, List classpath) {
return sourceFiles -> {
- JavaSourceSet sourceSet = JavaSourceSet.build(sourceSetName, classpath, new JavaTypeCache(), false);
+ JavaSourceSet sourceSet = JavaSourceSet.build(sourceSetName, classpath);
for (int i = 0; i < sourceFiles.size(); i++) {
SourceFile sourceFile = sourceFiles.get(i);
diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java
index 92fc9ec6c36..5c1394b6615 100644
--- a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java
+++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java
@@ -29,6 +29,9 @@
import org.openrewrite.style.NamedStyles;
import java.io.ByteArrayInputStream;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.net.URI;
import java.nio.charset.Charset;
@@ -366,21 +369,22 @@ default Path sourcePathFromSourceText(Path prefix, String sourceCode) {
return resolveSourcePathFromSourceText(prefix, sourceCode);
}
+ Pattern SOURCE_PATH_PACKAGE_PATTERN = Pattern.compile("^package\\s+([^;]+);");
+ Pattern SOURCE_PATH_CLASS_PATTERN = Pattern.compile("(class|interface|enum|record)\\s*(<[^>]*>)?\\s+(\\w+)");
+ Pattern SOURCE_PATH_PUBLIC_CLASS_PATTERN = Pattern.compile("public\\s+" + SOURCE_PATH_CLASS_PATTERN.pattern());
+
static Path resolveSourcePathFromSourceText(Path prefix, String sourceCode) {
- Pattern packagePattern = Pattern.compile("^package\\s+([^;]+);");
- Pattern classPattern = Pattern.compile("(class|interface|enum|record)\\s*(<[^>]*>)?\\s+(\\w+)");
- Pattern publicClassPattern = Pattern.compile("public\\s+" + classPattern.pattern());
Function simpleName = sourceStr -> {
- Matcher classMatcher = publicClassPattern.matcher(sourceStr);
+ Matcher classMatcher = SOURCE_PATH_PUBLIC_CLASS_PATTERN.matcher(sourceStr);
if (classMatcher.find()) {
return classMatcher.group(3);
}
- classMatcher = classPattern.matcher(sourceStr);
+ classMatcher = SOURCE_PATH_CLASS_PATTERN.matcher(sourceStr);
return classMatcher.find() ? classMatcher.group(3) : null;
};
- Matcher packageMatcher = packagePattern.matcher(sourceCode);
+ Matcher packageMatcher = SOURCE_PATH_PACKAGE_PATTERN.matcher(sourceCode);
String pkg = packageMatcher.find() ? packageMatcher.group(1).replace('.', '/') + "/" : "";
String className = Optional.ofNullable(simpleName.apply(sourceCode))
@@ -479,16 +483,21 @@ class JdkParserBuilderCache {
private static @Nullable Supplier> tryCreateBuilderSupplier(String className) {
try {
Class> clazz = Class.forName(className);
- Method builderMethod = clazz.getDeclaredMethod("builder");
+ // Use MethodHandle instead of reflection for faster invocation
+ MethodHandles.Lookup lookup = MethodHandles.publicLookup();
+ // Get the actual return type from the method (e.g., Java21Parser.Builder)
+ Method method = clazz.getMethod("builder");
+ MethodHandle builderHandle = lookup.findStatic(clazz, "builder",
+ MethodType.methodType(method.getReturnType()));
return () -> {
try {
//noinspection rawtypes,unchecked
- return (JavaParser.Builder) builderMethod.invoke(null);
+ return (JavaParser.Builder) builderHandle.invoke();
} catch (Throwable e) {
throw new RuntimeException("Failed to invoke builder() on " + className, e);
}
};
- } catch (ClassNotFoundException | NoSuchMethodException e) {
+ } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) {
return null; // This parser version isn't available
}
}
diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/TypeTable.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/TypeTable.java
index ffa56a2f2e6..7b3ca4a623b 100644
--- a/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/TypeTable.java
+++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/parser/TypeTable.java
@@ -98,6 +98,8 @@ public class TypeTable implements JavaParserClasspathLoader {
public static final String DEFAULT_RESOURCE_PATH = "META-INF/rewrite/classpath.tsv.gz";
private static final Map> classesDirByArtifact = new ConcurrentHashMap<>();
+ private static final Map artifactPatternCache = new ConcurrentHashMap<>();
+ private static final Pattern PIPE = Pattern.compile("\\|");
public static @Nullable TypeTable fromClasspath(ExecutionContext ctx, Collection artifactNames) {
try {
@@ -160,7 +162,8 @@ private static void read(URL url, Collection artifactNames, ExecutionCon
private static Collection artifactsNotYetWritten(Collection artifactNames) {
Collection notWritten = new ArrayList<>(artifactNames);
for (String artifactName : artifactNames) {
- Pattern artifactPattern = Pattern.compile(artifactName + ".*");
+ Pattern artifactPattern = artifactPatternCache.computeIfAbsent(artifactName,
+ name -> Pattern.compile(name + ".*"));
for (GroupArtifactVersion groupArtifactVersion : classesDirByArtifact.keySet()) {
if (artifactPattern
.matcher(groupArtifactVersion.getArtifactId() + "-" + groupArtifactVersion.getVersion())
@@ -287,7 +290,7 @@ public void parseTsvAndProcess(InputStream is, Options options,
name,
fields[5].isEmpty() ? null : fields[5],
fields[6].isEmpty() ? null : fields[6],
- fields[7].isEmpty() ? null : fields[7].split("\\|"),
+ fields[7].isEmpty() ? null : PIPE.split(fields[7]),
fields.length > 14 && !fields[14].isEmpty() ? fields[14] : null, // elementAnnotations - raw string (may have | delimiters)
fields.length > 17 && !fields[17].isEmpty() ? fields[17] : null // constantValue moved to column 17
));
@@ -305,8 +308,8 @@ public void parseTsvAndProcess(InputStream is, Options options,
fields[9],
fields[10],
fields[11].isEmpty() ? null : fields[11],
- fields[12].isEmpty() ? null : fields[12].split("\\|"),
- fields[13].isEmpty() ? null : fields[13].split("\\|"),
+ fields[12].isEmpty() ? null : PIPE.split(fields[12]),
+ fields[13].isEmpty() ? null : PIPE.split(fields[13]),
fields.length > 14 && !fields[14].isEmpty() ? fields[14] : null, // elementAnnotations - raw string
fields.length > 15 && !fields[15].isEmpty() ? fields[15] : null,
fields.length > 16 && !fields[16].isEmpty() ? TsvEscapeUtils.splitAnnotationList(fields[16], '|') : null, // typeAnnotations - keep `|` delimiter between different type contexts
@@ -601,9 +604,11 @@ public static Writer newWriter(OutputStream out) {
@Override
public @Nullable Path load(String artifactName) {
+ Pattern artifactPattern = artifactPatternCache.computeIfAbsent(artifactName,
+ name -> Pattern.compile(name + ".*"));
for (Map.Entry> gavAndClassesDir : classesDirByArtifact.entrySet()) {
GroupArtifactVersion gav = gavAndClassesDir.getKey();
- if (Pattern.compile(artifactName + ".*")
+ if (artifactPattern
.matcher(gav.getArtifactId() + "-" + gav.getVersion())
.matches()) {
return gavAndClassesDir.getValue().join();
diff --git a/rewrite-java/src/main/java/org/openrewrite/java/marker/JavaSourceSet.java b/rewrite-java/src/main/java/org/openrewrite/java/marker/JavaSourceSet.java
index 97c82154eac..51f53921c2a 100644
--- a/rewrite-java/src/main/java/org/openrewrite/java/marker/JavaSourceSet.java
+++ b/rewrite-java/src/main/java/org/openrewrite/java/marker/JavaSourceSet.java
@@ -55,51 +55,6 @@ public class JavaSourceSet implements SourceSet {
*/
Map> gavToTypes;
- /**
- * Extract type information from the provided classpath.
- * Uses ClassGraph to compute the classpath.
- *
- * Does not support gavToTypes or typeToGav mapping
- *
- * @param fullTypeInformation Not used, does not do anything, to be deleted
- * @param ignore Not used, does not do anything, to be deleted
- */
- @Deprecated
- public static JavaSourceSet build(String sourceSetName, Collection classpath,
- JavaTypeCache ignore, boolean fullTypeInformation) {
- if (fullTypeInformation) {
- throw new UnsupportedOperationException();
- }
-
- List typeNames;
- if (!classpath.iterator().hasNext()) {
- // Only load JRE-provided types
- try (ScanResult scanResult = new ClassGraph()
- .enableClassInfo()
- .enableSystemJarsAndModules()
- .acceptPackages("java")
- .ignoreClassVisibility()
- .scan()) {
- typeNames = packagesToTypeDeclarations(scanResult);
- }
- } else {
- // Load types from the classpath
- try (ScanResult scanResult = new ClassGraph()
- .overrideClasspath(classpath)
- .enableSystemJarsAndModules()
- .enableClassInfo()
- .ignoreClassVisibility()
- .scan()) {
- typeNames = packagesToTypeDeclarations(scanResult);
- }
- }
-
- // Peculiarly, Classgraph will not return a ClassInfo for java.lang.Object, although it does for all other java.lang types
- typeNames.add("java.lang.Object");
- return new JavaSourceSet(randomId(), sourceSetName, typesFrom(typeNames), emptyMap());
- }
-
-
/*
* Create a map of package names to types contained within that package. Type names are not fully qualified, except for type parameter bounds.
* e.g.: "java.util" -> [List, Date]
@@ -194,6 +149,16 @@ private static List typesFrom(List typeNames) {
// Purely IO-based classpath scanning below this point
+ /**
+ * @deprecated Use {@link #build(String, Collection)} instead. The {@code JavaTypeCache} and
+ * {@code fullTypeInformation} parameters are no longer used.
+ */
+ @Deprecated
+ public static JavaSourceSet build(String sourceSetName, Collection classpath,
+ JavaTypeCache ignore, boolean fullTypeInformation) {
+ return build(sourceSetName, classpath);
+ }
+
/**
* Extract type information from the provided classpath.
* Uses file I/O to compute the classpath.
diff --git a/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java b/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java
index 9752d878f1e..65ea96e26e6 100644
--- a/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java
+++ b/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java
@@ -538,7 +538,7 @@ private void countIndents(String space, boolean isContinuation, IndentStatistics
}
private static class ImportLayoutStatistics {
- List> blocksPerSourceFile = new ArrayList<>();
+ List> blocksPerSourceFile = new ArrayList<>(20);
Map pkgToBlockPattern = new LinkedHashMap<>();
int staticAtTopCount = 0;
int staticAtBotCount = 0;
@@ -588,10 +588,11 @@ public ImportLayoutStyle getImportLayoutStyle() {
int nonStaticPos = 0;
int staticPos = 0;
- List nonStaticBlocks = new ArrayList<>(); // Isolate static imports to add at top or bottom of layout.
- List staticBlocks = new ArrayList<>(); // Isolate static imports to add at top or bottom of layout.
- List countOfBlocksInNonStaticGroups = new ArrayList<>();
- List countOfBlocksInStaticGroups = new ArrayList<>();
+ int blockCount = longestBlocks.size();
+ List nonStaticBlocks = new ArrayList<>(blockCount); // Isolate static imports to add at top or bottom of layout.
+ List staticBlocks = new ArrayList<>(blockCount); // Isolate static imports to add at top or bottom of layout.
+ List countOfBlocksInNonStaticGroups = new ArrayList<>(blockCount);
+ List countOfBlocksInStaticGroups = new ArrayList<>(blockCount);
for (Block block : longestBlocks) {
if (BlockType.ImportStatic == block.type) {
diff --git a/rewrite-java/src/main/java/org/openrewrite/java/style/ImportLayoutStyle.java b/rewrite-java/src/main/java/org/openrewrite/java/style/ImportLayoutStyle.java
index 374d7d40449..d67e164de49 100644
--- a/rewrite-java/src/main/java/org/openrewrite/java/style/ImportLayoutStyle.java
+++ b/rewrite-java/src/main/java/org/openrewrite/java/style/ImportLayoutStyle.java
@@ -43,6 +43,7 @@
import java.io.IOException;
import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
@@ -295,15 +296,19 @@ public List> addImport(List> origi
JRightPadded finalToAdd = paddedToAdd;
JRightPadded finalAfter = after;
+ // Cache atomic values to avoid repeated volatile reads in lambda
+ boolean shouldStarFold = starFold.get();
+ int foldFrom = starFoldFrom.get();
+ int foldTo = starFoldTo.get();
return ListUtils.flatMap(originalImports, (i, anImport) -> {
- if (starFold.get() && i >= starFoldFrom.get() && i < starFoldTo.get()) {
- return i == starFoldFrom.get() ?
+ if (shouldStarFold && i >= foldFrom && i < foldTo) {
+ return i == foldFrom ?
finalToAdd /* only add the star import once */ :
null;
} else if (finalAfter != null && anImport.getElement().isScope(finalAfter.getElement())) {
- if (starFold.get()) {
+ if (shouldStarFold) {
// The added import is always folded, and is the first package occurrence in the imports.
- if (starFoldFrom.get() == starFoldTo.get()) {
+ if (foldFrom == foldTo) {
return Arrays.asList(finalToAdd, finalAfter);
} else {
return finalAfter;
@@ -585,8 +590,9 @@ private void setJVMClassNames() {
}
private Map> mapNamesInPackageToPackages() {
- Map> nameToPackages = new HashMap<>();
- Set checkPackageForClasses = new HashSet<>();
+ int importCount = originalImports.size();
+ Map> nameToPackages = new HashMap<>(importCount * 4 / 3 + 1);
+ Set checkPackageForClasses = new HashSet<>(importCount * 4 / 3 + 1);
for (JRightPadded anImport : originalImports) {
checkPackageForClasses.add(packageOrOuterClassName(anImport));
@@ -669,22 +675,44 @@ class ImportPackage implements Block {
// VisibleForTesting
final static Comparator> IMPORT_SORTING = (i1, i2) -> {
- String[] import1 = i1.getElement().getQualid().printTrimmed().split("\\.");
- String[] import2 = i2.getElement().getQualid().printTrimmed().split("\\.");
+ String s1 = i1.getElement().getQualid().printTrimmed();
+ String s2 = i2.getElement().getQualid().printTrimmed();
+ return compareImportStrings(s1, s2);
+ };
- for (int i = 0; i < Math.min(import1.length, import2.length); i++) {
- int diff = import1[i].compareTo(import2[i]);
- if (diff != 0) {
- return diff;
+ static int compareImportStrings(String s1, String s2) {
+ int len1 = s1.length();
+ int len2 = s2.length();
+ int pos1 = 0, pos2 = 0;
+ while (pos1 < len1 && pos2 < len2) {
+ int dot1 = s1.indexOf('.', pos1);
+ int dot2 = s2.indexOf('.', pos2);
+ int end1 = dot1 == -1 ? len1 : dot1;
+ int end2 = dot2 == -1 ? len2 : dot2;
+ int segLen1 = end1 - pos1;
+ int segLen2 = end2 - pos2;
+ int segLen = Math.min(segLen1, segLen2);
+ for (int i = 0; i < segLen; i++) {
+ int diff = s1.charAt(pos1 + i) - s2.charAt(pos2 + i);
+ if (diff != 0) {
+ return diff;
+ }
+ }
+ if (segLen1 != segLen2) {
+ return segLen1 - segLen2;
}
+ pos1 = end1 + 1;
+ pos2 = end2 + 1;
}
-
- if (import1.length == import2.length) {
+ boolean has1 = pos1 < len1;
+ boolean has2 = pos2 < len2;
+ if (has1 == has2) {
return 0;
}
+ return has1 ? 1 : -1;
+ }
- return import1.length > import2.length ? 1 : -1;
- };
+ private static final ConcurrentHashMap PATTERN_CACHE = new ConcurrentHashMap<>();
private final Boolean statik;
@Getter
@@ -692,9 +720,10 @@ class ImportPackage implements Block {
public ImportPackage(Boolean statik, String packageWildcard, boolean withSubpackages) {
this.statik = statik;
- this.packageWildcard = Pattern.compile(packageWildcard
+ String regex = packageWildcard
.replace(".", "\\.")
- .replace("*", withSubpackages ? ".+" : "[^.]+"));
+ .replace("*", withSubpackages ? ".+" : "[^.]+");
+ this.packageWildcard = PATTERN_CACHE.computeIfAbsent(regex, Pattern::compile);
}
public boolean isStatic() {
@@ -711,22 +740,27 @@ public boolean accept(JRightPadded anImport) {
public List> orderedImports(LayoutState layoutState, int classCountToUseStarImport, int nameCountToUseStarImport, ImportLayoutConflictDetection importLayoutConflictDetection, List packagesToFold) {
List> imports = layoutState.getImports(this);
- Map>> groupedImports = imports
- .stream()
- .sorted(IMPORT_SORTING)
- .collect(groupingBy(
- ImportLayoutStyle::packageOrOuterClassName,
- LinkedHashMap::new, // Use an ordered map to preserve sorting
- toList()
- ));
+ // Sort a copy and group into a LinkedHashMap to preserve sorted order
+ List> sorted = new ArrayList<>(imports);
+ sorted.sort(IMPORT_SORTING);
+
+ Map>> groupedImports = new LinkedHashMap<>();
+ for (JRightPadded imp : sorted) {
+ groupedImports.computeIfAbsent(packageOrOuterClassName(imp), k -> new ArrayList<>()).add(imp);
+ }
List> ordered = new ArrayList<>(imports.size());
for (List> importGroup : groupedImports.values()) {
JRightPadded toStar = importGroup.get(0);
int threshold = toStar.getElement().isStatic() ? nameCountToUseStarImport : classCountToUseStarImport;
- boolean starImportExists = importGroup.stream()
- .anyMatch(it -> "*".equals(it.getElement().getQualid().getSimpleName()));
+ boolean starImportExists = false;
+ for (JRightPadded it : importGroup) {
+ if ("*".equals(it.getElement().getQualid().getSimpleName())) {
+ starImportExists = true;
+ break;
+ }
+ }
if (importLayoutConflictDetection.isPackageFoldable(packageOrOuterClassName(toStar)) &&
(isPackageAlwaysFolded(packagesToFold, toStar.getElement()) || importGroup.size() >= threshold || (starImportExists && importGroup.size() > 1))) {
@@ -734,18 +768,28 @@ public List> orderedImports(LayoutState layoutState, int
J.FieldAccess qualid = toStar.getElement().getQualid();
J.Identifier name = qualid.getName();
- Set typeNamesInThisGroup = importGroup.stream()
- .map(im -> im.getElement().getClassName())
- .collect(toSet());
+ Set typeNamesInThisGroup = new HashSet<>(importGroup.size());
+ for (JRightPadded im : importGroup) {
+ typeNamesInThisGroup.add(im.getElement().getClassName());
+ }
- Optional oneOfTheTypesIsInAnotherGroupToo = groupedImports.values().stream()
- .filter(group -> group != importGroup)
- .flatMap(group -> group.stream()
- .filter(im -> typeNamesInThisGroup.contains(im.getElement().getClassName())))
- .map(im -> im.getElement().getTypeName())
- .findAny();
+ String conflictTypeName = null;
+ for (List> group : groupedImports.values()) {
+ if (group == importGroup) {
+ continue;
+ }
+ for (JRightPadded im : group) {
+ if (typeNamesInThisGroup.contains(im.getElement().getClassName())) {
+ conflictTypeName = im.getElement().getTypeName();
+ break;
+ }
+ }
+ if (conflictTypeName != null) {
+ break;
+ }
+ }
- if (starImportExists || !oneOfTheTypesIsInAnotherGroupToo.isPresent()) {
+ if (starImportExists || conflictTypeName == null) {
ordered.add(toStar.withElement(toStar.getElement().withQualid(qualid.withName(name.withSimpleName("*")))));
continue;
}
diff --git a/rewrite-kotlin/src/main/java/org/openrewrite/kotlin/KotlinParser.java b/rewrite-kotlin/src/main/java/org/openrewrite/kotlin/KotlinParser.java
index b3c32512617..f854e066604 100644
--- a/rewrite-kotlin/src/main/java/org/openrewrite/kotlin/KotlinParser.java
+++ b/rewrite-kotlin/src/main/java/org/openrewrite/kotlin/KotlinParser.java
@@ -255,8 +255,7 @@ public JavaSourceSet getSourceSet(ExecutionContext ctx) {
if (ctx.getMessage(SKIP_SOURCE_SET_TYPE_GENERATION, false)) {
sourceSetProvenance = new JavaSourceSet(Tree.randomId(), sourceSet, emptyList(), emptyMap());
} else {
- sourceSetProvenance = JavaSourceSet.build(sourceSet, classpath == null ? emptyList() : classpath,
- typeCache, false);
+ sourceSetProvenance = JavaSourceSet.build(sourceSet, classpath == null ? emptyList() : classpath);
}
}
return sourceSetProvenance;