Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,10 @@ public class RecipeClassLoader extends URLClassLoader {
"org.openrewrite.java.TypeNameMatcher",
"org.openrewrite.java.internal.TypesInUse",
"org.openrewrite.java.TypeNameMatcher",
// Cursor-message wiring (TYPE_FACTORY_PROVIDER_KEY) passes a Provider
// implementation across the recipe/parent classloader boundary; the
// interface must be shared so the cast in JavaTemplateParser succeeds.
// JavaSourceSet#getTypeFactory crosses the recipe/parent classloader
// boundary when JavaTemplate reads it from the enclosing source file's
// marker; the interface must be shared so the cast in JavaTemplateParser
// succeeds.
"org.openrewrite.java.internal.JavaTypeFactory",
"org.openrewrite.maven.MavenDownloadingException",
"org.openrewrite.maven.MavenDownloadingExceptions",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,12 @@ public Builder kotlinParser(KotlinParser.Builder kotlinParser) {
}

/**
* Forward a {@link JavaTypeFactory.Provider} to both the Groovy and Kotlin
* sub-parsers so either DSL variant produces types from the caller's factory.
* Forward a {@link JavaTypeFactory} to both the Groovy and Kotlin sub-parsers
* so either DSL variant produces types from the caller's factory.
*/
public Builder typeFactoryProvider(JavaTypeFactory.Provider provider) {
this.groovyParser.typeFactoryProvider(provider);
this.kotlinParser.typeFactoryProvider(provider);
public Builder typeFactory(JavaTypeFactory typeFactory) {
this.groovyParser.typeFactory(typeFactory);
this.kotlinParser.typeFactory(typeFactory);
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,6 @@ public static class Builder extends Parser.Builder {
@Nullable
private JavaTypeFactory typeFactory;

private JavaTypeFactory.@Nullable Provider typeFactoryProvider;

private boolean logCompilationWarningsAndErrors = false;
private final List<NamedStyles> styles = new ArrayList<>();
private final List<Consumer<CompilerConfiguration>> compilerCustomizers = new ArrayList<>();
Expand All @@ -234,7 +232,6 @@ public Builder(Builder base) {
this.artifactNames = base.artifactNames;
this.typeCache = base.typeCache;
this.typeFactory = base.typeFactory;
this.typeFactoryProvider = base.typeFactoryProvider;
this.logCompilationWarningsAndErrors = base.logCompilationWarningsAndErrors;
this.styles.addAll(base.styles);
this.compilerCustomizers.addAll(base.compilerCustomizers);
Expand Down Expand Up @@ -277,9 +274,9 @@ public Builder addClasspathEntry(Path entry) {
}

/**
* @deprecated Configure a {@link JavaTypeFactory} via {@link #typeFactory} or
* {@link #typeFactoryProvider} instead. The cache becomes an implementation
* detail of the default {@link DefaultJavaTypeFactory}.
* @deprecated Configure a {@link JavaTypeFactory} via {@link #typeFactory} instead.
* The cache becomes an implementation detail of the default
* {@link DefaultJavaTypeFactory}.
*/
@Deprecated
@SuppressWarnings("unused")
Expand All @@ -294,12 +291,6 @@ public Builder typeFactory(JavaTypeFactory typeFactory) {
return this;
}

@SuppressWarnings("unused")
public Builder typeFactoryProvider(JavaTypeFactory.Provider provider) {
this.typeFactoryProvider = provider;
return this;
}

public Builder styles(Iterable<? extends NamedStyles> styles) {
for (NamedStyles style : styles) {
this.styles.add(style);
Expand Down Expand Up @@ -331,9 +322,6 @@ public GroovyParser.Builder compilerCustomizers(Iterable<Consumer<CompilerConfig
public GroovyParser build() {
Collection<Path> cp = resolvedClasspath();
JavaTypeFactory factory = typeFactory;
if (factory == null && typeFactoryProvider != null) {
factory = typeFactoryProvider.create(cp == null ? new ArrayList<>() : new ArrayList<>(cp), null);
}
if (factory == null) {
factory = new DefaultJavaTypeFactory(typeCache);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1056,10 +1056,15 @@ class B { class B {
List<J.Modifier> paramModifiers = getModifiers();
TypeTree paramType;
if (param.isDynamicTyped()) {
// Dynamic-typed Groovy params are implicitly java.lang.Object.
// param.getType() returns ClassHelper.OBJECT_TYPE; route through
// typeMapping.type so the parser's canonical computeClass path
// populates the body rather than seeding a ShallowClass.
JavaType objectType = typeMapping.type(param.getType());
if (sourceStartsWith("java.lang.Object")) {
paramType = new J.Identifier(randomId(), whitespace(), Markers.EMPTY, emptyList(), skip(name()), JavaType.ShallowClass.build("java.lang.Object"), null);
paramType = new J.Identifier(randomId(), whitespace(), Markers.EMPTY, emptyList(), skip(name()), objectType, null);
} else {
paramType = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), "", JavaType.ShallowClass.build("java.lang.Object"), null);
paramType = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), "", objectType, null);
}
} else {
paramType = visitTypeTree(param.getType());
Expand Down Expand Up @@ -1530,7 +1535,11 @@ public void visitClassExpression(ClassExpression clazz) {
skip(classSuffix);
}
}
queue.add(TypeTree.build(name)
// Intermediate segments inside the dotted name get fresh
// ShallowClasses from TypeTree.build; the outermost type comes from
// the resolved ClassNode and overrides what TypeTree.build assigned
// for the leaf.
queue.add(TypeTree.build(name, null)
.withType(typeMapping.type(type))
.withPrefix(prefix));
}
Expand Down Expand Up @@ -1793,13 +1802,15 @@ public void visitCatchStatement(CatchStatement node) {
Parameter param = node.getVariable();
TypeTree paramType;
Space paramPrefix = whitespace();
// Groovy allows catch variables to omit their type, shorthand for being of type java.lang.Exception
// Can't use isSynthetic() here because groovy doesn't record the line number on the Parameter
// Groovy allows catch variables to omit their type, shorthand for being of type java.lang.Exception.
// Can't use isSynthetic() here because groovy doesn't record the line number on the Parameter.
// param.getType() is the synthesized ClassNode for Exception; route through typeMapping.type so the
// parser's canonical computeClass path populates the body rather than seeding a ShallowClass.
if (Exception.class.getName().equals(param.getType().getName()) &&
!source.startsWith("Exception", cursor) &&
!source.startsWith("java.lang.Exception", cursor)) {
paramType = new J.Identifier(randomId(), paramPrefix, Markers.EMPTY, emptyList(), "",
JavaType.ShallowClass.build(Exception.class.getName()), null);
typeMapping.type(param.getType()), null);
} else {
paramType = visitTypeTree(param.getOriginType()).withPrefix(paramPrefix);
}
Expand Down Expand Up @@ -3446,7 +3457,7 @@ private JRightPadded<Statement> convertTopLevelStatement(SourceUnit unit, ASTNod
Space importPrefix = sourceBefore("import");
JLeftPadded<Boolean> statik = importNode.isStatic() ? padLeft(sourceBefore("static"), true) : padLeft(EMPTY, false);
Space space = whitespace();
J.FieldAccess qualid = TypeTree.build(name()).withPrefix(space);
J.FieldAccess qualid = buildImportQualid(importNode, space);
JLeftPadded<J.Identifier> alias = null;
if (sourceStartsWith("as", "\n", " ")) {
alias = padLeft(sourceBefore("as"), new J.Identifier(randomId(), whitespace(), Markers.EMPTY, emptyList(), name(), null, null));
Expand Down Expand Up @@ -3551,7 +3562,7 @@ private Expression recoverFoldedAnnotationMemberValue(org.codehaus.groovy.ast.ex
if (Character.isJavaIdentifierStart(c) && !isKeywordLiteral) {
String name = name();
if (!name.isEmpty()) {
return TypeTree.build(name)
return TypeTree.build(name, null)
.withType(typeMapping.type(value.getType()))
.withPrefix(prefix);
}
Expand Down Expand Up @@ -3683,16 +3694,30 @@ private <T extends TypeTree & Expression> T typeTree(@Nullable ClassNode classNo
String maybeFullyQualified = name();
String[] parts = maybeFullyQualified.split("\\.");

String fullName = "";
// Walk classNode's outer-class chain (outermost first) and align it
// with the trailing segments of the source name. The leaf class is
// always the last segment of any reference; everything before the
// class-chain alignment is package prefix.
List<ClassNode> classChain = new ArrayList<>();
for (ClassNode c = classNode; c != null; c = c.getOuterClass()) {
classChain.add(0, c);
}
int classChainStart = parts.length - classChain.size();

Expression expr = null;
for (int i = 0; i < parts.length; i++) {
String part = parts[i];

JavaType segmentType = null;
int classIdx = i - classChainStart;
if (classIdx >= 0 && classIdx < classChain.size()) {
JavaType t = typeMapping.type(classChain.get(classIdx));
segmentType = t instanceof JavaType.Parameterized ? ((JavaType.Parameterized) t).getType() : t;
}

if (i == 0) {
fullName = part;
expr = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), part, typeMapping.type(classNode), null);
expr = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), part, segmentType, null);
} else {
fullName += "." + part;

Matcher whitespacePrefix = whitespacePrefixPattern.matcher(part);
Space identFmt = whitespacePrefix.matches() ? format(whitespacePrefix.group(0)) : EMPTY;

Expand All @@ -3707,9 +3732,7 @@ private <T extends TypeTree & Expression> T typeTree(@Nullable ClassNode classNo
Markers.EMPTY,
expr,
padLeft(namePrefix, new J.Identifier(randomId(), identFmt, Markers.EMPTY, emptyList(), part.trim(), null, null)),
(Character.isUpperCase(part.charAt(0)) || i == parts.length - 1) ?
JavaType.ShallowClass.build(fullName) :
null
segmentType
);
}
}
Expand Down Expand Up @@ -4123,6 +4146,117 @@ private String name() {
return result;
}

/**
* Build the {@link J.FieldAccess} qualid of a {@link J.Import} by walking the
* source dotted name segment-by-segment, assigning each class-level segment
* its resolved {@link JavaType} directly from the {@link ImportNode}'s
* {@link ClassNode} chain.
* <p>
* Layout of the dotted name:
* <pre>
* pkg.pkg...pkg . OuterClass.NestedClass...LeafClass [ . staticMember ] [ . * ]
* ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^ ^
* types=null types from classChain type=null skipped
* </pre>
* The trailing {@code *} of a star import is read by the caller (or stops the
* walk here), so this method only produces typed FieldAccess nodes for
* identifier segments.
*/
private J.FieldAccess buildImportQualid(ImportNode importNode, Space prefix) {
// Build the [outermost, ..., leaf] chain of resolved class types so each class segment
// carries its own type. ClassNode.getOuterClass() is unreliable for resolved import types
// (it returns null even for nested classes like java.util.Map.Entry), so instead we walk
// the owning-class links of the attributed leaf type, which proper type attribution
// populates from the JVM enclosing-class chain. Empty for star/package imports and for
// types that can't be resolved (asFullyQualified returns null for JavaType.Unknown).
List<JavaType.FullyQualified> classChain = new ArrayList<>();
if (importNode.getType() != null) {
JavaType leaf = typeMapping.type(importNode.getType());
if (leaf instanceof JavaType.Parameterized) {
leaf = ((JavaType.Parameterized) leaf).getType();
}
for (JavaType.FullyQualified c = TypeUtils.asFullyQualified(leaf); c != null; c = c.getOwningClass()) {
classChain.add(0, c);
}
}
int packageSegmentCount = 0;
if (!classChain.isEmpty()) {
String pkg = classChain.get(0).getPackageName();
if (!pkg.isEmpty()) {
packageSegmentCount = 1;
for (int i = 0; i < pkg.length(); i++) {
if (pkg.charAt(i) == '.') packageSegmentCount++;
}
}
}

// Whitespace model (mirrors TypeTree.build):
// identifier prefix = whitespace AFTER the dot, BEFORE the identifier
// JLeftPadded.before = whitespace BEFORE the dot (= previous segment's trailing whitespace)
// FieldAccess.prefix = whitespace before the whole expression (set on outermost at the end)
Expression expr = null;
Space beforeIdent = EMPTY;
Space beforeDot = EMPTY;
int segmentIndex = 0;
while (cursor < source.length()) {
char c = source.charAt(cursor);
String segment;
if (c == '*') {
segment = "*";
cursor++;
} else if (Character.isJavaIdentifierStart(c)) {
int identStart = cursor;
while (cursor < source.length() && isJavaIdentifierPart(source.charAt(cursor))) {
cursor++;
}
segment = source.substring(identStart, cursor);
} else {
break;
}

JavaType segmentType = null;
if (!"*".equals(segment)) {
int classIdx = segmentIndex - packageSegmentCount;
if (classIdx >= 0 && classIdx < classChain.size()) {
segmentType = classChain.get(classIdx);
}
}

if (segmentIndex == 0) {
expr = new J.Identifier(randomId(), beforeIdent, Markers.EMPTY, emptyList(),
segment, segmentType, null);
} else {
expr = new J.FieldAccess(randomId(), EMPTY, Markers.EMPTY, expr,
new JLeftPadded<>(beforeDot, new J.Identifier(randomId(), beforeIdent,
Markers.EMPTY, emptyList(), segment, null, null), Markers.EMPTY),
segmentType);
}
segmentIndex++;

if ("*".equals(segment)) {
break;
}

int savedCursor = cursor;
Space trailingWs = whitespace();
if (cursor < source.length() && source.charAt(cursor) == '.') {
cursor++;
beforeDot = trailingWs;
beforeIdent = whitespace();
} else {
cursor = savedCursor;
break;
}
}

// Outermost prefix carries the whitespace after `import`/`static`. Setting
// it on the outer FieldAccess (rather than the innermost Identifier)
// matches what TypeTree.build did and keeps the prefix attached to the
// qualid replacement target for recipes like ChangePackage.
//noinspection ConstantConditions
return ((J.FieldAccess) expr).withPrefix(prefix);
}

/*
Visit the value filled into a parameterized type, such as in a variable declaration.
Not to be confused with the declaration of a type parameter on a class or method.
Expand Down
Loading