spring-hateoas-ext/src/main/java/de/escalon/hypermedia/spring/xhtml/XhtmlWriter.java
private void recurseBeanProperties(Class<?> beanType, ActionDescriptor actionDescriptor, ActionInputParameter
            actionInputParameter, Object currentCallValue, String parentParamName) throws IOException {
        // TODO support Option provider by other method args?
        final BeanInfo beanInfo = getBeanInfo(beanType);
        final PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        // TODO collection and map

        // TODO: do not add two inputs for setter and ctor

        // TODO almost duplicate of HtmlResourceMessageConverter.recursivelyCreateObject
        if (RequestMethod.POST == RequestMethod.valueOf(actionDescriptor.getHttpMethod())) {
            try {
                Constructor[] constructors = beanType.getConstructors();
                // find default ctor
                Constructor constructor = PropertyUtils.findDefaultCtor(constructors);
                // find ctor with JsonCreator ann
                if (constructor == null) {
                    constructor = PropertyUtils.findJsonCreator(constructors, JsonCreator.class);
                }
                Assert.notNull(constructor, "no default constructor or JsonCreator found for type " + beanType
                        .getName());
                int parameterCount = constructor.getParameterTypes().length;
                if (parameterCount > 0) {
                    Annotation[][] annotationsOnParameters = constructor.getParameterAnnotations();

                    Class[] parameters = constructor.getParameterTypes();
                    int paramIndex = 0;
                    for (Annotation[] annotationsOnParameter : annotationsOnParameters) {
                        for (Annotation annotation : annotationsOnParameter) {
                            if (JsonProperty.class == annotation.annotationType()) {
                                JsonProperty jsonProperty = (JsonProperty) annotation;
                                // TODO use required attribute of JsonProperty
                                String paramName = jsonProperty.value();
                                Class parameterType = parameters[paramIndex];

                                // TODO duplicate below for PropertyDescriptors and in appendForm
                                if (DataType.isSingleValueType(parameterType)) {

                                    if (actionInputParameter.isIncluded(paramName)) {

                                        Object propertyValue = PropertyUtils.getPropertyOrFieldValue(currentCallValue,
                                                paramName);

                                        ActionInputParameter constructorParamInputParameter = new
												SpringActionInputParameter
                                                (new MethodParameter(constructor, paramIndex), propertyValue);

                                        final Object[] possibleValues =
                                                actionInputParameter.getPossibleValues(
                                                        constructor, paramIndex, actionDescriptor);

                                        appendInputOrSelect(actionInputParameter, parentParamName + paramName,
                                                constructorParamInputParameter, possibleValues);
                                    }
                                } else if (DataType.isArrayOrCollection(parameterType)) {
                                    Object[] callValues = actionInputParameter.getValues();
                                    int items = callValues.length;
                                    for (int i = 0; i < items; i++) {
                                        Object value;
                                        if (i < callValues.length) {
                                            value = callValues[i];
                                        } else {
                                            value = null;
                                        }
                                        recurseBeanProperties(actionInputParameter.getParameterType(),
                                                actionDescriptor, actionInputParameter, value, parentParamName);
                                    }
                                } else {
                                    beginDiv();
                                    write(paramName + ":");
                                    Object propertyValue = PropertyUtils.getPropertyOrFieldValue(currentCallValue,
                                            paramName);
                                    recurseBeanProperties(parameterType, actionDescriptor, actionInputParameter,
                                            propertyValue, paramName + ".");
                                    endDiv();
                                }
                                paramIndex++; // increase for each @JsonProperty
                            }
                        }
                    }
                    Assert.isTrue(parameters.length == paramIndex,
                            "not all constructor arguments of @JsonCreator " + constructor.getName() +
                                    " are annotated with @JsonProperty");
                }
            } catch (Exception e) {
                throw new RuntimeException("Failed to write input fields for constructor", e);
            }
        } else { // non-POST

            // TODO non-writable properties and public fields: make sure the inputs are part of a form
            // write input field for every setter
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                final Method writeMethod = propertyDescriptor.getWriteMethod();
                if (writeMethod == null) {
                    continue;
                }
                final Class<?> propertyType = propertyDescriptor.getPropertyType();

                String propertyName = propertyDescriptor.getName();

                if (DataType.isSingleValueType(propertyType)) {

                    final Property property = new Property(beanType, propertyDescriptor.getReadMethod(),
                            propertyDescriptor.getWriteMethod(), propertyDescriptor.getName());

                    Object propertyValue = PropertyUtils.getPropertyOrFieldValue(currentCallValue, propertyName);
                    MethodParameter methodParameter = new MethodParameter(propertyDescriptor.getWriteMethod(), 0);
                    ActionInputParameter propertySetterInputParameter = new SpringActionInputParameter(methodParameter,
                            propertyValue);
                    final Object[] possibleValues = actionInputParameter.getPossibleValues(propertyDescriptor
                                    .getWriteMethod(), 0,
                            actionDescriptor);
                    appendInputOrSelect(actionInputParameter, propertyName, propertySetterInputParameter,
							possibleValues);
                } else if (actionInputParameter.isArrayOrCollection()) {
                    Object[] callValues = actionInputParameter.getValues();
                    int items = callValues.length;
                    for (int i = 0; i < items; i++) {
                        Object value;
                        if (i < callValues.length) {
                            value = callValues[i];
                        } else {
                            value = null;
                        }
                        recurseBeanProperties(actionInputParameter.getParameterType(), actionDescriptor,
                                actionInputParameter, value, parentParamName);
                    }
                } else {
                    beginDiv();
                    write(propertyName + ":");
                    Object propertyValue = PropertyUtils.getPropertyValue(currentCallValue, propertyDescriptor);
                    recurseBeanProperties(propertyType, actionDescriptor, actionInputParameter, propertyValue,
							parentParamName);
                    endDiv();
                }
            }
        }
    }
private void recurseBeanProperties(Class<?> beanType, ActionDescriptor actionDescriptor, ActionInputParameter
            actionInputParameter, Object currentCallValue, String parentParamName) throws IOException {
        // TODO support Option provider by other method args?
        final BeanInfo beanInfo = getBeanInfo(beanType);
        final PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        // TODO collection and map

        // TODO: do not add two inputs for setter and ctor

        // TODO almost duplicate of HtmlResourceMessageConverter.recursivelyCreateObject
        if (RequestMethod.POST == RequestMethod.valueOf(actionDescriptor.getHttpMethod())) {
            try {
                Constructor[] constructors = beanType.getConstructors();
                // find default ctor
                Constructor constructor = PropertyUtils.findDefaultCtor(constructors);
                // find ctor with JsonCreator ann
                if (constructor == null) {
                    constructor = PropertyUtils.findJsonCreator(constructors, JsonCreator.class);
                }
                Assert.notNull(constructor, "no default constructor or JsonCreator found for type " + beanType
                        .getName());
                int parameterCount = constructor.getParameterTypes().length;
                if (parameterCount > 0) {
                    Annotation[][] annotationsOnParameters = constructor.getParameterAnnotations();

                    Class[] parameters = constructor.getParameterTypes();
                    int paramIndex = 0;
                    for (Annotation[] annotationsOnParameter : annotationsOnParameters) {
                        for (Annotation annotation : annotationsOnParameter) {
                            if (JsonProperty.class == annotation.annotationType()) {
                                JsonProperty jsonProperty = (JsonProperty) annotation;
                                // TODO use required attribute of JsonProperty
                                String paramName = jsonProperty.value();
                                Class parameterType = parameters[paramIndex];

                                // TODO duplicate below for PropertyDescriptors and in appendForm
                                if (DataType.isSingleValueType(parameterType)) {

                                    if (actionInputParameter.isIncluded(paramName)) {

                                        Object propertyValue = PropertyUtils.getPropertyOrFieldValue(currentCallValue,
                                                paramName);

                                        ActionInputParameter constructorParamInputParameter = new
												SpringActionInputParameter
                                                (new MethodParameter(constructor, paramIndex), propertyValue);

                                        final Object[] possibleValues =
                                                actionInputParameter.getPossibleValues(
                                                        constructor, paramIndex, actionDescriptor);

                                        appendInputOrSelect(actionInputParameter, parentParamName + paramName,
                                                constructorParamInputParameter, possibleValues);
                                    }
                                } else if (DataType.isArrayOrCollection(parameterType)) {
                                    Object[] callValues = actionInputParameter.getValues();
                                    int items = callValues.length;
                                    for (int i = 0; i < items; i++) {
                                        Object value;
                                        if (i < callValues.length) {
                                            value = callValues[i];
                                        } else {
                                            value = null;
                                        }
                                        recurseBeanProperties(actionInputParameter.getParameterType(),
                                                actionDescriptor, actionInputParameter, value, parentParamName);
                                    }
                                } else {
                                    beginDiv();
                                    write(paramName + ":");
                                    Object propertyValue = PropertyUtils.getPropertyOrFieldValue(currentCallValue,
                                            paramName);
                                    recurseBeanProperties(parameterType, actionDescriptor, actionInputParameter,
                                            propertyValue, paramName + ".");
                                    endDiv();
                                }
                                paramIndex++; // increase for each @JsonProperty
                            }
                        }
                    }
                    Assert.isTrue(parameters.length == paramIndex,
                            "not all constructor arguments of @JsonCreator " + constructor.getName() +
                                    " are annotated with @JsonProperty");
                }
            } catch (Exception e) {
                throw new RuntimeException("Failed to write input fields for constructor", e);
            }
        } else { // non-POST

            // TODO non-writable properties and public fields: make sure the inputs are part of a form
            // write input field for every setter
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                final Method writeMethod = propertyDescriptor.getWriteMethod();
                if (writeMethod == null) {
                    continue;
                }
                final Class<?> propertyType = propertyDescriptor.getPropertyType();

                String propertyName = propertyDescriptor.getName();

                if (DataType.isSingleValueType(propertyType)) {

                    final Property property = new Property(beanType, propertyDescriptor.getReadMethod(),
                            propertyDescriptor.getWriteMethod(), propertyDescriptor.getName());

                    Object propertyValue = PropertyUtils.getPropertyOrFieldValue(currentCallValue, propertyName);
                    MethodParameter methodParameter = new MethodParameter(propertyDescriptor.getWriteMethod(), 0);
                    ActionInputParameter propertySetterInputParameter = new SpringActionInputParameter(methodParameter,
                            propertyValue);
                    final Object[] possibleValues = actionInputParameter.getPossibleValues(propertyDescriptor
                                    .getWriteMethod(), 0,
                            actionDescriptor);
                    appendInputOrSelect(actionInputParameter, propertyName, propertySetterInputParameter,
							possibleValues);
                } else if (actionInputParameter.isArrayOrCollection()) {
                    Object[] callValues = actionInputParameter.getValues();
                    int items = callValues.length;
                    for (int i = 0; i < items; i++) {
                        Object value;
                        if (i < callValues.length) {
                            value = callValues[i];
                        } else {
                            value = null;
                        }
                        recurseBeanProperties(actionInputParameter.getParameterType(), actionDescriptor,
                                actionInputParameter, value, parentParamName);
                    }
                } else {
                    beginDiv();
                    write(propertyName + ":");
                    Object propertyValue = PropertyUtils.getPropertyValue(currentCallValue, propertyDescriptor);
                    recurseBeanProperties(propertyType, actionDescriptor, actionInputParameter, propertyValue,
							parentParamName);
                    endDiv();
                }
            }
        }
    }
private void recurseBeanProperties ( Class < ? > beanType , ActionDescriptor actionDescriptor , ActionInputParameter actionInputParameter , Object currentCallValue , String parentParamName ) throws IOException { // TODO support Option provider by other method args? final BeanInfo beanInfo = getBeanInfo ( beanType ) ; final PropertyDescriptor [ ] propertyDescriptors = beanInfo . getPropertyDescriptors ( ) ; // TODO collection and map // TODO: do not add two inputs for setter and ctor // TODO almost duplicate of HtmlResourceMessageConverter.recursivelyCreateObject if ( RequestMethod . POST == RequestMethod . valueOf ( actionDescriptor . getHttpMethod ( ) ) ) { try { Constructor [ ] constructors = beanType . getConstructors ( ) ; // find default ctor Constructor constructor = PropertyUtils . findDefaultCtor ( constructors ) ; // find ctor with JsonCreator ann if ( constructor == null ) { constructor = PropertyUtils . findJsonCreator ( constructors , JsonCreator . class ) ; } Assert . notNull ( constructor , "no default constructor or JsonCreator found for type " + beanType . getName ( ) ) ; int parameterCount = constructor . getParameterTypes ( ) . length ; if ( parameterCount > 0 ) { Annotation [ ] [ ] annotationsOnParameters = constructor . getParameterAnnotations ( ) ; Class [ ] parameters = constructor . getParameterTypes ( ) ; int paramIndex = 0 ; for ( Annotation [ ] annotationsOnParameter : annotationsOnParameters ) { for ( Annotation annotation : annotationsOnParameter ) { if ( JsonProperty . class == annotation . annotationType ( ) ) { JsonProperty jsonProperty = ( JsonProperty ) annotation ; // TODO use required attribute of JsonProperty String paramName = jsonProperty . value ( ) ; Class parameterType = parameters [ paramIndex ] ; // TODO duplicate below for PropertyDescriptors and in appendForm if ( DataType . isSingleValueType ( parameterType ) ) { if ( actionInputParameter . isIncluded ( paramName ) ) { Object propertyValue = PropertyUtils . getPropertyOrFieldValue ( currentCallValue , paramName ) ; ActionInputParameter constructorParamInputParameter = new SpringActionInputParameter ( new MethodParameter ( constructor , paramIndex ) , propertyValue ) ; final Object [ ] possibleValues = actionInputParameter . getPossibleValues ( constructor , paramIndex , actionDescriptor ) ; appendInputOrSelect ( actionInputParameter , parentParamName + paramName , constructorParamInputParameter , possibleValues ) ; } } else if ( DataType . isArrayOrCollection ( parameterType ) ) { Object [ ] callValues = actionInputParameter . getValues ( ) ; int items = callValues . length ; for ( int i = 0 ; i < items ; i ++ ) { Object value ; if ( i < callValues . length ) { value = callValues [ i ] ; } else { value = null ; } recurseBeanProperties ( actionInputParameter . getParameterType ( ) , actionDescriptor , actionInputParameter , value , parentParamName ) ; } } else { beginDiv ( ) ; write ( paramName + ":" ) ; Object propertyValue = PropertyUtils . getPropertyOrFieldValue ( currentCallValue , paramName ) ; recurseBeanProperties ( parameterType , actionDescriptor , actionInputParameter , propertyValue , paramName + "." ) ; endDiv ( ) ; } paramIndex ++ ; // increase for each @JsonProperty } } } Assert . isTrue ( parameters . length == paramIndex , "not all constructor arguments of @JsonCreator " + constructor . getName ( ) + " are annotated with @JsonProperty" ) ; } } catch ( Exception e ) { throw new RuntimeException ( "Failed to write input fields for constructor" , e ) ; } } else { // non-POST // TODO non-writable properties and public fields: make sure the inputs are part of a form // write input field for every setter for ( PropertyDescriptor propertyDescriptor : propertyDescriptors ) { final Method writeMethod = propertyDescriptor . getWriteMethod ( ) ; if ( writeMethod == null ) { continue ; } final Class < ? > propertyType = propertyDescriptor . getPropertyType ( ) ; String propertyName = propertyDescriptor . getName ( ) ; if ( DataType . isSingleValueType ( propertyType ) ) { final Property property = new Property ( beanType , propertyDescriptor . getReadMethod ( ) , propertyDescriptor . getWriteMethod ( ) , propertyDescriptor . getName ( ) ) ; Object propertyValue = PropertyUtils . getPropertyOrFieldValue ( currentCallValue , propertyName ) ; MethodParameter methodParameter = new MethodParameter ( propertyDescriptor . getWriteMethod ( ) , 0 ) ; ActionInputParameter propertySetterInputParameter = new SpringActionInputParameter ( methodParameter , propertyValue ) ; final Object [ ] possibleValues = actionInputParameter . getPossibleValues ( propertyDescriptor . getWriteMethod ( ) , 0 , actionDescriptor ) ; appendInputOrSelect ( actionInputParameter , propertyName , propertySetterInputParameter , possibleValues ) ; } else if ( actionInputParameter . isArrayOrCollection ( ) ) { Object [ ] callValues = actionInputParameter . getValues ( ) ; int items = callValues . length ; for ( int i = 0 ; i < items ; i ++ ) { Object value ; if ( i < callValues . length ) { value = callValues [ i ] ; } else { value = null ; } recurseBeanProperties ( actionInputParameter . getParameterType ( ) , actionDescriptor , actionInputParameter , value , parentParamName ) ; } } else { beginDiv ( ) ; write ( propertyName + ":" ) ; Object propertyValue = PropertyUtils . getPropertyValue ( currentCallValue , propertyDescriptor ) ; recurseBeanProperties ( propertyType , actionDescriptor , actionInputParameter , propertyValue , parentParamName ) ; endDiv ( ) ; } } } }
Renders input fields for bean properties of bean to add or update or patch.

@param beanType
to render
@param actionDescriptor
which describes the method
@param actionInputParameter
which requires the bean
@param currentCallValue
sample call value
@throws IOException
Renders input fields for bean properties of bean to add or update or patch .