1818 */
1919package org .apache .causeway .core .metamodel .spec .impl ;
2020
21+ import java .lang .annotation .Annotation ;
2122import java .util .ArrayList ;
2223import java .util .Collections ;
2324import java .util .HashSet ;
3334import org .jspecify .annotations .Nullable ;
3435
3536import org .apache .causeway .applib .annotation .Action ;
37+ import org .apache .causeway .applib .annotation .Collection ;
3638import org .apache .causeway .applib .annotation .Introspection .IntrospectionPolicy ;
39+ import org .apache .causeway .applib .annotation .Property ;
3740import org .apache .causeway .applib .exceptions .unrecoverable .MetaModelException ;
3841import org .apache .causeway .commons .collections .Can ;
3942import org .apache .causeway .commons .internal .base ._NullSafe ;
@@ -216,6 +219,10 @@ private void createCollectionFacetedMethodsFromAccessors(
216219 var mmc = getMetaModelContext ();
217220
218221 for (final ResolvedMethod accessorMethod : accessorMethods ) {
222+ if (!satisfiesIntrospectionPolicy (accessorMethod , Collection .class )) {
223+ continue ;
224+ }
225+
219226 if (log .isDebugEnabled ()) {
220227 log .debug (" identified accessor method representing collection: {}" , accessorMethod );
221228 }
@@ -240,7 +247,9 @@ private void createCollectionFacetedMethodsFromAccessors(
240247 .orElse (Object .class );
241248
242249 // skip if class substitutor says so
243- if (classSubstitutorRegistry .getSubstitution (elementType ).isNeverIntrospect ()) continue ;
250+ if (classSubstitutorRegistry .getSubstitution (elementType ).isNeverIntrospect ()) {
251+ continue ;
252+ }
244253
245254 onNewFacetMethod .accept (facetedMethod .withElementType (elementType ));
246255 }
@@ -251,12 +260,18 @@ private void createPropertyFacetedMethodsFromAccessors(
251260 final Consumer <FacetedMethod > onNewFacetedMethod ) throws MetaModelException {
252261
253262 for (final ResolvedMethod accessorMethod : accessorMethods ) {
263+ if (!satisfiesIntrospectionPolicy (accessorMethod , Property .class )) {
264+ continue ;
265+ }
266+
254267 log .debug (" identified accessor method representing property: {}" , accessorMethod );
255268
256269 final Class <?> returnType = accessorMethod .returnType ();
257270
258271 // skip if class strategy says so.
259- if (classSubstitutorRegistry .getSubstitution (returnType ).isNeverIntrospect ()) continue ;
272+ if (classSubstitutorRegistry .getSubstitution (returnType ).isNeverIntrospect ()) {
273+ continue ;
274+ }
260275
261276 // create a 1:1 association peer
262277 var facetedMethod = FacetedMethod
@@ -369,18 +384,32 @@ private boolean isAllParamTypesValid(final MethodFacade actionMethod) {
369384 return true ;
370385 }
371386
387+ /**
388+ * At time of writing only filtering for Java Records,
389+ * because those have not type-hierarchy
390+ * (except potentially interfaces),
391+ * where the check for explicit annotations is simple.
392+ */
393+ private <A extends Annotation > boolean satisfiesIntrospectionPolicy (
394+ final ResolvedMethod accessorMethod ,
395+ final Class <A > requiredAnnotationType ) {
396+ return inspectedTypeSpec .getCorrespondingClass ().isRecord ()
397+ && introspectionPolicy ().getMemberAnnotationPolicy ().isMemberAnnotationsRequired ()
398+ ? _Annotations .isPresent (accessorMethod .method (), requiredAnnotationType )
399+ : true ;
400+ }
401+
372402 private boolean representsAction (final ResolvedMethod actionMethod ) {
373403
374404 //[CAUSEWAY-3556] if this throws, we have a framework bug (synthetic methods should no longer appear here)
375405 _Reflect .guardAgainstSynthetic (actionMethod .method ());
376406
377407 // ensure we can load returned element type; otherwise ignore method
378408 var anyLoadedAsNull = TypeExtractor .streamMethodReturn (actionMethod )
379- .map (typeToLoad ->specLoaderInternal ().loadSpecification (typeToLoad , IntrospectionRequest .TYPE_ONLY ))
380- .anyMatch (Objects ::isNull );
381- if (anyLoadedAsNull ) {
409+ .map (typeToLoad ->specLoaderInternal ().loadSpecification (typeToLoad , IntrospectionRequest .TYPE_ONLY ))
410+ .anyMatch (Objects ::isNull );
411+ if (anyLoadedAsNull )
382412 return false ;
383- }
384413
385414 if (isMixinMain (actionMethod )) {
386415 // we are introspecting a mixin type and its main method,
@@ -431,10 +460,9 @@ private boolean isMixinMain(final ResolvedMethod method) {
431460 .orElse (null );
432461 if (mixinFacet ==null ) return false ;
433462
434- if (!inspectedTypeSpec .isFullyIntrospected ()) {
463+ if (!inspectedTypeSpec .isFullyIntrospected ())
435464 // members are not introspected yet, so make a guess
436465 return mixinFacet .isCandidateForMain (method );
437- }
438466
439467 return inspectedTypeSpec
440468 .lookupMixedInAction (inspectedTypeSpec )
0 commit comments