@@ -153,28 +153,10 @@ public static boolean isRegisteredAsBuildable(TypeRef typeRef) {
153153 }
154154
155155 public static boolean isBuildable (TypeRef typeRef ) {
156- if (isRegisteredAsBuildable (typeRef ) && canBeBuilt (typeRef )) {
157- return true ;
158- }
159156 if (!(typeRef instanceof ClassRef )) {
160157 return false ;
161158 }
162- // Fallback for cross-module @BuildableReference: check if a sundrio-generated *Builder exists
163- // on the APT classpath. @Buildable has SOURCE retention so compiled JARs don't carry it;
164- // using getTypeElement() is the only JDK-version-agnostic way to detect a pre-compiled builder.
165- // We verify it's a sundrio builder (not e.g. java.lang.StringBuilder or ProcessBuilder) by
166- // checking it declares a build() method.
167- String builderFQCN = ((ClassRef ) typeRef ).getFullyQualifiedName () + "Builder" ;
168- Elements elements = BuilderContextManager .getContext ().getElements ();
169- if (elements == null ) {
170- return false ;
171- }
172- TypeElement builderElement = elements .getTypeElement (builderFQCN );
173- if (builderElement == null ) {
174- return false ;
175- }
176- return builderElement .getEnclosedElements ().stream ()
177- .anyMatch (e -> e .getKind () == ElementKind .METHOD && e .getSimpleName ().contentEquals ("build" ));
159+ return (isRegisteredAsBuildable (typeRef ) && canBeBuilt (typeRef )) || hasBuilder ((ClassRef ) typeRef );
178160 }
179161
180162 public static boolean isRegisteredAsBuildable (TypeDef typeDef ) {
@@ -192,29 +174,39 @@ public static boolean isBuildable(TypeDef typeDef) {
192174 * @return True if buildable repository contains the ref or builder for the reference is present.
193175 */
194176 public static boolean isBuildable (ClassRef ref ) {
195- if (BuilderContextManager .getContext ().getBuildableRepository ().isBuildable (ref )) {
196- return true ;
197- }
177+ return BuilderContextManager .getContext ().getBuildableRepository ().isBuildable (ref ) || hasBuilder (ref );
178+ }
198179
180+ /**
181+ * Checks if a sundrio-generated Builder class exists for the given {@link ClassRef}.
182+ *
183+ * Looks first in the definition repository (same-module types), then falls back to the APT
184+ * {@link Elements} API for cross-module types whose {@code @Buildable} annotation is not visible
185+ * at compile time (SOURCE retention). A class is considered to have a builder if a type named
186+ * {@code <FQCN>Builder} exists and declares a {@code build()} method.
187+ *
188+ * @param ref The class reference to check.
189+ * @return True if a matching builder is found.
190+ */
191+ public static boolean hasBuilder (ClassRef ref ) {
199192 String builderFQCN = ref .getFullyQualifiedName () + "Builder" ;
200193 TypeDef builder = BuilderContextManager .getContext ().getDefinitionRepository ().getDefinition (builderFQCN );
201- if (builder == null ) {
202- Elements elements = BuilderContextManager .getContext ().getElements ();
203- if (elements == null ) {
204- return false ;
205- }
206- TypeElement builderElement = elements .getTypeElement (builderFQCN );
207- return builderElement != null && builderElement .getEnclosedElements ().stream ()
208- .anyMatch (e -> e .getKind () == ElementKind .METHOD && e .getSimpleName ().contentEquals ("build" ));
209- }
210-
211- return builder .getMethods ()
212- .stream ()
213- .filter (m -> "build" .equals (m .getName ()))
214- .filter (m -> m .getReturnType () instanceof ClassRef )
215- .map (m -> (ClassRef ) m .getReturnType ())
216- .filter (r -> Assignable .isAssignable (r ).from (ref ))
217- .count () > 0 ;
194+ if (builder != null ) {
195+ return builder .getMethods ()
196+ .stream ()
197+ .filter (m -> "build" .equals (m .getName ()))
198+ .filter (m -> m .getReturnType () instanceof ClassRef )
199+ .map (m -> (ClassRef ) m .getReturnType ())
200+ .anyMatch (r -> Assignable .isAssignable (r ).from (ref ));
201+ }
202+
203+ Elements elements = BuilderContextManager .getContext ().getElements ();
204+ if (elements == null ) {
205+ return false ;
206+ }
207+ TypeElement builderElement = elements .getTypeElement (builderFQCN );
208+ return builderElement != null && builderElement .getEnclosedElements ().stream ()
209+ .anyMatch (e -> e .getKind () == ElementKind .METHOD && e .getSimpleName ().contentEquals ("build" ));
218210 }
219211
220212 public static boolean isOrHasBuildableDescendants (Field field ) {
0 commit comments