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 @@ -13,6 +13,12 @@
* within a source file. The source file's package declaration is set
* when the file is created via
* {@link Sources#createSourceFile(String, String, Consumer)}.
* <p>
* Import methods ({@link #import_(Type)}, {@link #import_(Class)},
* {@link #importStatic(Type, String)}, {@link #importModule(String)})
* may be called at any time before the source file is written, including
* from within nested creator callbacks (e.g., inside a
* {@link ClassCreator} or {@link MethodCreator} body).
*/
public sealed interface SourceFileCreator permits SourceFileCreatorImpl {

Expand Down
27 changes: 22 additions & 5 deletions src/main/java/io/smallrye/jdeparser/impl/AbstractCreator.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.smallrye.jdeparser.impl;

import io.smallrye.common.constraint.Assert;
import io.smallrye.jdeparser.SourceVersion;
import io.smallrye.jdeparser.Type;

Expand Down Expand Up @@ -59,17 +60,33 @@ protected SourceVersion version() {
return version;
}

/**
* Verifies that this creator has not been finalized.
* <p>
* Unlike {@link #checkActive()}, this permits calls while in the
* NESTED state. Use this for operations that are safe to perform
* concurrently with a child callback (e.g., adding imports).
*
* @throws IllegalStateException if the creator is DONE
*/
protected void checkNotDone() {
if (state == ST_DONE) {
throw new IllegalStateException("Creator has already been completed");
}
}

/**
* Verifies that this creator is in the ACTIVE state.
*
* @throws IllegalStateException if the creator is NESTED or DONE
*/
protected void checkActive() {
if (state != ST_ACTIVE) {
throw new IllegalStateException(
state == ST_NESTED
? "Cannot modify creator while a nested callback is active"
: "Creator has already been completed");
switch (state) {
case ST_ACTIVE -> {
}
case ST_NESTED -> throw new IllegalStateException("Cannot modify creator while a nested callback is active");
case ST_DONE -> throw new IllegalStateException("Creator has already been completed");
default -> throw Assert.impossibleSwitchCase(state);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ protected void registerUsedType(final Type type) {
/** {@inheritDoc} */
@Override
public void import_(final Type type) {
checkActive();
checkNotDone();
Assert.checkNotNullParam("type", type);
if (type instanceof ReferenceType ref) {
imports.add(ref.qualifiedName());
Expand All @@ -132,15 +132,15 @@ public void import_(final Type type) {
/** {@inheritDoc} */
@Override
public void import_(final Class<?> clazz) {
checkActive();
checkNotDone();
Assert.checkNotNullParam("clazz", clazz);
imports.add(clazz.getCanonicalName());
}

/** {@inheritDoc} */
@Override
public void importStatic(final Type type, final String member) {
checkActive();
checkNotDone();
Assert.checkNotNullParam("type", type);
Assert.checkNotNullParam("member", member);
Assert.checkNotEmptyParam("member", member);
Expand All @@ -152,7 +152,7 @@ public void importStatic(final Type type, final String member) {
/** {@inheritDoc} */
@Override
public void importModule(final String moduleName) {
checkActive();
checkNotDone();
Assert.checkNotNullParam("moduleName", moduleName);
Assert.checkNotEmptyParam("moduleName", moduleName);
version().require(LanguageFeature.MODULE_IMPORTS);
Expand Down
200 changes: 200 additions & 0 deletions src/test/java/io/smallrye/jdeparser/test/ImportTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,206 @@ void explicitJavaLangImportResolvesAmbiguityWithSamePackage() throws IOException
"Shadowed same-package type must be fully qualified, got:\n" + source);
}

// ── Imports from nested creators ─────────────────────────────────────

/**
* Verifies that {@link io.smallrye.jdeparser.creator.SourceFileCreator#import_(Type)}
* can be called from within a nested class creator callback and produces
* a correct import statement with simple name resolution.
*
* @throws IOException if source generation fails
*/
@Test
void importFromNestedClassCreator() throws IOException {
final Type listType = Type.named("java.util.List");
final Sources sources = createSources(SourceVersion.JAVA_17);
sources.createSourceFile("com.example", "NestedImport", sf -> {
sf.class_("NestedImport", cc -> {
cc.public_();
// import added from within the class creator callback
sf.import_(listType);
cc.field("items", fc -> {
fc.private_();
fc.type(listType);
});
});
});
sources.writeSources();
final String source = getSource("com.example", "NestedImport");
assertTrue(source.contains("import java.util.List;"),
"Import added from nested class creator should produce import statement, got:\n" + source);
assertTrue(source.contains("private List items;"),
"Type imported from nested creator should use simple name, got:\n" + source);
}

/**
* Verifies that {@link io.smallrye.jdeparser.creator.SourceFileCreator#import_(Type)}
* can be called from a deeply nested creator callback (inside a method
* body within a class) and still produces a correct import.
*
* @throws IOException if source generation fails
*/
@Test
void importFromDeeplyNestedCreator() throws IOException {
final Type listType = Type.named("java.util.List");
final Sources sources = createSources(SourceVersion.JAVA_17);
sources.createSourceFile("com.example", "DeepNested", sf -> {
sf.class_("DeepNested", cc -> {
cc.public_();
cc.method("getItems", mc -> {
mc.public_();
mc.returning(listType);
mc.body(b -> {
// import added from within the method body callback
sf.import_(listType);
b.return_(Expr.NULL);
});
});
});
});
sources.writeSources();
final String source = getSource("com.example", "DeepNested");
assertTrue(source.contains("import java.util.List;"),
"Import added from deeply nested creator should produce import statement, got:\n" + source);
assertTrue(source.contains("public List getItems()"),
"Type imported from deeply nested creator should use simple name, got:\n" + source);
}

/**
* Verifies that {@link io.smallrye.jdeparser.creator.SourceFileCreator#import_(Class)}
* can be called from within a nested creator callback.
*
* @throws IOException if source generation fails
*/
@Test
void importClassFromNestedCreator() throws IOException {
final Type listType = Type.of(java.util.List.class);
final Sources sources = createSources(SourceVersion.JAVA_17);
sources.createSourceFile("com.example", "NestedClassImport", sf -> {
sf.class_("NestedClassImport", cc -> {
cc.public_();
sf.import_(java.util.List.class);
cc.field("items", fc -> {
fc.private_();
fc.type(listType);
});
});
});
sources.writeSources();
final String source = getSource("com.example", "NestedClassImport");
assertTrue(source.contains("import java.util.List;"),
"Class import from nested creator should produce import statement, got:\n" + source);
assertTrue(source.contains("private List items;"),
"Type imported via Class from nested creator should use simple name, got:\n" + source);
}

/**
* Verifies that {@link io.smallrye.jdeparser.creator.SourceFileCreator#importStatic(Type, String)}
* can be called from within a nested creator callback.
*
* @throws IOException if source generation fails
*/
@Test
void staticImportFromNestedCreator() throws IOException {
final Type collectorsType = Type.named("java.util.stream.Collectors");
final Sources sources = createSources(SourceVersion.JAVA_17);
sources.createSourceFile("com.example", "NestedStaticImport", sf -> {
sf.class_("NestedStaticImport", cc -> {
cc.public_();
sf.importStatic(collectorsType, "toList");
cc.field("dummy", fc -> {
fc.private_();
fc.type(Type.INT);
});
});
});
sources.writeSources();
final String source = getSource("com.example", "NestedStaticImport");
assertTrue(source.contains("import static java.util.stream.Collectors.toList;"),
"Static import from nested creator should produce import statement, got:\n" + source);
}

/**
* Verifies that {@link io.smallrye.jdeparser.creator.SourceFileCreator#importModule(String)}
* can be called from within a nested creator callback.
*
* @throws IOException if source generation fails
*/
@Test
void moduleImportFromNestedCreator() throws IOException {
final Sources sources = createSources(SourceVersion.JAVA_25);
sources.createSourceFile("com.example", "NestedModuleImport", sf -> {
sf.class_("NestedModuleImport", cc -> {
cc.public_();
sf.importModule("java.base");
cc.field("dummy", fc -> {
fc.private_();
fc.type(Type.INT);
});
});
});
sources.writeSources();
final String source = getSource("com.example", "NestedModuleImport");
assertTrue(source.contains("import module java.base;"),
"Module import from nested creator should produce import statement, got:\n" + source);
}

/**
* Verifies that imports added both before and during a nested creator
* callback are all present and resolve correctly in the output.
*
* @throws IOException if source generation fails
*/
@Test
void importsFromMixedTopLevelAndNested() throws IOException {
final Type listType = Type.named("java.util.List");
final Type mapType = Type.named("java.util.Map");
final Type setType = Type.named("java.util.Set");
final Sources sources = createSources(SourceVersion.JAVA_17);
sources.createSourceFile("com.example", "MixedNesting", sf -> {
// import added before nesting
sf.import_(listType);
sf.class_("MixedNesting", cc -> {
cc.public_();
// import added during nesting
sf.import_(mapType);
cc.field("list", fc -> {
fc.private_();
fc.type(listType);
});
cc.field("map", fc -> {
fc.private_();
fc.type(mapType);
});
cc.method("process", mc -> {
mc.public_();
mc.body(b -> {
// import added from deeply nested callback
sf.import_(setType);
});
});
cc.field("set", fc -> {
fc.private_();
fc.type(setType);
});
});
});
sources.writeSources();
final String source = getSource("com.example", "MixedNesting");
assertTrue(source.contains("import java.util.List;"),
"Top-level import should be present, got:\n" + source);
assertTrue(source.contains("import java.util.Map;"),
"Import from nested class creator should be present, got:\n" + source);
assertTrue(source.contains("import java.util.Set;"),
"Import from deeply nested method body should be present, got:\n" + source);
assertTrue(source.contains("private List list;"),
"List should use simple name, got:\n" + source);
assertTrue(source.contains("private Map map;"),
"Map should use simple name, got:\n" + source);
assertTrue(source.contains("private Set set;"),
"Set should use simple name, got:\n" + source);
}

// ── Mixed scenarios ─────────────────────────────────────────────────

/**
Expand Down
Loading