1515 */
1616package io .javaoperatorsdk .operator .config .runtime ;
1717
18+ import java .io .IOException ;
19+ import java .io .UncheckedIOException ;
20+
1821import javax .tools .StandardLocation ;
1922
2023import org .junit .jupiter .api .Test ;
2528import com .google .testing .compile .JavaFileObjects ;
2629
2730import static io .javaoperatorsdk .operator .config .runtime .RuntimeControllerMetadata .RECONCILERS_RESOURCE_PATH ;
31+ import static org .assertj .core .api .Assertions .assertThat ;
2832
2933class ControllerConfigurationAnnotationProcessorTest {
3034
@@ -70,6 +74,26 @@ public void generateDoneableClassWithMultilevelHierarchy() {
7074 assertMapping (compilation , "io.MultilevelReconciler,io.MultilevelReconciler.MyCustomResource" );
7175 }
7276
77+ /**
78+ * When the reconciled resource is itself generic, the resolved type is a parameterized {@code
79+ * DeclaredType}. Only its erasure may be written to the mapping resource: {@link
80+ * ClassMappingProvider} loads the recorded name with {@code ClassUtils.getClass(String)}, which
81+ * cannot parse type arguments.
82+ */
83+ @ Test
84+ public void writesErasureOfGenericResourceType () {
85+ Compilation compilation =
86+ Compiler .javac ()
87+ .withProcessors (new ControllerConfigurationAnnotationProcessor ())
88+ .compile (
89+ JavaFileObjects .forResource ("compile-fixtures/GenericResourceReconciler.java" ));
90+ CompilationSubject .assertThat (compilation ).succeeded ();
91+ assertMapping (
92+ compilation ,
93+ "io.GenericResourceReconciler,io.GenericResourceReconciler.MyGenericCustomResource" );
94+ assertLoadableMapping (compilation );
95+ }
96+
7397 /**
7498 * Checks that the generated mapping resource contains the expected {@code
7599 * reconciler,resource-class} line, using the same fully qualified, dot separated names that
@@ -81,4 +105,40 @@ private static void assertMapping(Compilation compilation, String expectedMappin
81105 .contentsAsUtf8String ()
82106 .contains (expectedMapping );
83107 }
108+
109+ /**
110+ * Checks that every recorded name in the generated mapping resource is a plain binary-ish class
111+ * name, i.e. one that {@code ClassUtils.getClass(String)} can actually resolve, rather than a
112+ * generic type signature such as {@code io.Foo<java.lang.String>}.
113+ */
114+ private static void assertLoadableMapping (Compilation compilation ) {
115+ final var contents =
116+ compilation
117+ .generatedFile (StandardLocation .CLASS_OUTPUT , RECONCILERS_RESOURCE_PATH )
118+ .map (
119+ file -> {
120+ try {
121+ return file .getCharContent (true ).toString ();
122+ } catch (IOException e ) {
123+ throw new UncheckedIOException (e );
124+ }
125+ })
126+ .orElseThrow (() -> new AssertionError ("no mapping resource was generated" ));
127+ contents
128+ .lines ()
129+ .filter (line -> !line .isBlank ())
130+ .forEach (
131+ line -> {
132+ final var names = line .split ("," );
133+ assertThat (names ).as ("mapping line '%s'" , line ).hasSize (2 );
134+ for (String name : names ) {
135+ assertThat (name )
136+ .as ("recorded class name '%s' must be loadable at runtime" , name )
137+ .doesNotContain ("<" )
138+ .doesNotContain (">" )
139+ .doesNotContain (" " )
140+ .matches ("[\\ p{L}_$][\\ p{L}\\ p{N}_$]*(\\ .[\\ p{L}_$][\\ p{L}\\ p{N}_$]*)*" );
141+ }
142+ });
143+ }
84144}
0 commit comments