2525import com .networknt .schema .SchemaRegistry ;
2626import com .predic8 .membrane .annot .Grammar ;
2727import com .predic8 .membrane .annot .MCAttribute ;
28+ import com .predic8 .membrane .annot .MCElement ;
29+ import com .predic8 .membrane .annot .MCTextContent ;
2830import com .predic8 .membrane .annot .beanregistry .BeanDefinition ;
2931import com .predic8 .membrane .annot .beanregistry .BeanLifecycleManager ;
3032import com .predic8 .membrane .annot .beanregistry .BeanRegistry ;
3840
3941import java .io .IOException ;
4042import java .io .InputStream ;
43+ import java .lang .annotation .Annotation ;
4144import java .lang .reflect .InvocationTargetException ;
4245import java .lang .reflect .Method ;
4346import java .util .ArrayList ;
5154import static java .nio .charset .StandardCharsets .UTF_8 ;
5255import static java .util .List .of ;
5356import static java .util .UUID .randomUUID ;
57+ import static org .springframework .util .ReflectionUtils .doWithMethods ;
5458
5559public class GenericYamlParser {
5660 private static final Logger log = LoggerFactory .getLogger (GenericYamlParser .class );
@@ -188,16 +192,18 @@ public static <T> T createAndPopulateNode(ParsingContext<?> ctx, Class<T> clazz,
188192 return configObj ;
189193 }
190194
191- // scalar inline form for @MCElement(singleAttribute=true)
192- if (!node .isObject ()) {
193- if (isSingleAttribute (clazz )) {
194- applySingleAttributeScalar (clazz , node , configObj );
195- return handlePostConstructAndPreDestroy (ctx , configObj );
195+ // scalar inline form for @MCElement(collapsed=true)
196+ if (isCollapsed (clazz )) {
197+ if (node .isNull ()) {
198+ throw new ParsingException ("Collapsed element must not be null." , node );
196199 }
197- // anything non-object is invalid here
198- ensureMappingStart (node );
200+ if (node .isArray () || node .isObject ()) {
201+ throw new ParsingException ("Element is collapsed; expected an inline scalar value, not " +
202+ (node .isArray () ? "an array" : "an object" ) + "." , node );
203+ }
204+ applyCollapsedScalar (clazz , node , configObj );
205+ return handlePostConstructAndPreDestroy (ctx , configObj );
199206 }
200-
201207 ensureMappingStart (node );
202208 if (isNoEnvelope (clazz ))
203209 throw new RuntimeException ("Class " + clazz .getName () + " is annotated with @MCElement(noEnvelope=true), but the YAML/JSON structure does not contain a list." );
@@ -343,7 +349,7 @@ private static <T> T handlePostConstructAndPreDestroy(ParsingContext<?> ctx, T b
343349 if (bean instanceof BeanRegistryAware beanRegistryAware ) {
344350 beanRegistryAware .setRegistry (ctx .registry ());
345351 }
346- ReflectionUtils . doWithMethods (bean .getClass (), method -> {
352+ doWithMethods (bean .getClass (), method -> {
347353 if (method .isAnnotationPresent (PostConstruct .class )) {
348354 try {
349355 method .setAccessible (true );
@@ -362,19 +368,27 @@ private static <T> T handlePostConstructAndPreDestroy(ParsingContext<?> ctx, T b
362368 return bean ;
363369 }
364370
365- private static <T > void applySingleAttributeScalar (Class <T > clazz , JsonNode node , T target ) {
371+ private static <T > void applyCollapsedScalar (Class <T > clazz , JsonNode node , T target ) {
366372 if (node == null || node .isNull ()) {
367- throw new ParsingException ("singleAttribute element must not be null." , node );
373+ throw new ParsingException ("Collapsed element must not be null." , node );
368374 }
369375
370- Method setter = findSingleAttributeSetter (clazz );
376+ Method attributeSetter = findSingleSetterOrNullForAnnotation (clazz , MCAttribute .class );
377+ Method textSetter = findSingleSetterOrNullForAnnotation (clazz , MCTextContent .class );
378+
379+ if ((attributeSetter == null ) == (textSetter == null )) {
380+ // both null or both non-null -> invalid
381+ throw new ParsingException ("@MCElement(collapsed=true) requires exactly one @MCAttribute setter OR exactly one @MCTextContent setter." , node );
382+ }
371383
384+ Method setter = (attributeSetter != null ) ? attributeSetter : textSetter ;
372385 Class <?> paramType = setter .getParameterTypes ()[0 ];
386+
373387 Object value ;
374388 try {
375389 value = SCALAR_MAPPER .convertValue (node , paramType );
376390 } catch (IllegalArgumentException e ) {
377- throw new ParsingException ("Cannot convert scalar value to " + paramType .getSimpleName () + "." , node );
391+ throw new ParsingException ("Cannot convert inline value to " + paramType .getSimpleName () + "." , node );
378392 }
379393
380394 try {
@@ -387,20 +401,22 @@ private static <T> void applySingleAttributeScalar(Class<T> clazz, JsonNode node
387401 }
388402 }
389403
390- private static Method findSingleAttributeSetter (Class <?> clazz ) {
404+ private static Method findSingleSetterOrNullForAnnotation (Class <?> clazz , Class <? extends java . lang . annotation . Annotation > annotation ) {
391405 List <Method > setters = new ArrayList <>();
392- ReflectionUtils . doWithMethods (clazz , m -> {
393- if (m .isAnnotationPresent (MCAttribute . class ) && m .getParameterCount () == 1 ) {
406+ doWithMethods (clazz , m -> {
407+ if (m .isAnnotationPresent (annotation ) && m .getParameterCount () == 1 ) {
394408 setters .add (m );
395409 }
396410 });
397411
412+ if (setters .isEmpty ()) return null ;
398413 if (setters .size () != 1 ) {
399414 throw new ParsingException (
400- "@MCElement(singleAttribute=true) requires exactly one @MCAttribute setter, but found " + setters . size () + "." ,
415+ "Multiple @%s setters found for collapsed element." . formatted ( annotation . getSimpleName ()) ,
401416 JsonNodeFactory .instance .nullNode ()
402417 );
403418 }
404419 return setters .getFirst ();
405420 }
421+
406422}
0 commit comments