'How to use AsmVisitorWrapper to visit an array inside an annotation and merge its content with another array

(repository with an example of what I'm trying to do is here)

It's my first time using ByteBuddy and I've found myself struggling to merge the contents of two @RequestMapping annotations. Something like this:

@Validated
@RequestMapping(
        path = "/somePrefix",
        produces = {"application/text", "application/xml"},
        consumes = {"application/text", "application/xml"},
        params = {"exampleParam1", "exampleParam2"},
        headers = {"key=val", "nokey=val"}
)
public interface ExampleInterface {

    @RequestMapping(value = {"/someEndpoint"}, method = {RequestMethod.POST}, produces = {"application/json"}, consumes = {"application/json"})
    ResponseEntity<String> someEndpointMethod(String value);

}

And pretending to having this:

public interface ExampleInterface {

    @RequestMapping(
        value = {"/somePrefix/someEndpoint"},
        method = {RequestMethod.POST},
        produces = {"application/text", ,application/xml","application/json"},
        consumes = {"application/text", "application/xml","application/json"},
        params = {"exampleParam1", "exampleParam2"},
        headers = {"key=val", "nokey=val"}
    )
    ResponseEntity<String> someEndpointMethod(String value);

}

I've seen that editing the values can be done replacing the value when the method annotation object is visited, for example:

[...]

@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {

    if (Type.getDescriptor(RequestMapping.class).equals(descriptor)) {

        return new AnnotationVisitor(Opcodes.ASM9, super.visitAnnotation(descriptor, visible)) {

            @Override
            public AnnotationVisitor visitArray(String name) {

                if ("produces".equals(name)) {

                    return new AnnotationVisitor(Opcodes.ASM9, super.visitArray(name)) {
                        @Override
                        public void visit(String name, Object value) {

                            // I'd like to receive an array as value, so I can provide one with all values merged

                            boolean tryToMerge = false;

                            if (tryToMerge) {

                                //I cannot return array with everything
                                Object[] newValue = new Object[]{value};
                                value = Arrays.copyOf(newValue, newValue.length + originalAnnotation.produces().length);
                                System.arraycopy(originalAnnotation.produces(), 0, value, newValue.length, originalAnnotation.produces().length);

                            } else {

                                //I can only replace a single value
                                value = originalAnnotation.produces()[0];

                            }

                            // How to set an array in produces?

                            super.visit(name, value);

                        }
                    };
                } else {

                    return super.visitArray(name);

                }
            }
        };

    } else {

        return super.visitAnnotation(descriptor, visible);

    }

}

[...]

However, I'm receiving the array values one per one via visit() and I cannot just return an array with the two values I want to merge (["application/text", "application/xml"]) because it's expecting an String object. I can substitute the value that I'm receiving but I cannot add more.

Besides that, the headers and params arrays are not being visited, which seems logical because no values are on those arrays. However, I'm not sure how I should visit those fields in the @RequestMapping on the method so I can insert the values picked from the class one.

What I am missing here?

Thanks in advance.



Solution 1:[1]

In such a case, you can override the onEnd() method of AnnotationVisitor. When invoked, you know that all values are processed and you can invoke super.onValue() from there before delegating to super.onEnd(). If you know the replacements beforehand, you can also simply leave the onValue method empty to drop all existing values and then repopulate the array in the end.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Rafael Winterhalter