Search code examples
javaxstream

Is it possible to use NamedMapConverter in annotation? How?


I want to use a certain variant of NamedMapConverter (from XStream library).

  new NamedMapConverter(xstream.getMapper(), "mapping", "value", String.class, "color", HexColor.class, true, true, xstream.getConverterLookup());

But I want to use it as an annotation. My guess is underneath. The SingleValueConverter for HexColor is already implemented.

  @XStreamConverter(value = NamedMapConverter.class, strings = { "mapping", "value", "color" }, booleans = { true, true }, types = { String.class, HexColor.class })
  private Map<String, HexColor> colorMappings;

But, surprisingly, it is not working. What am I doing wrong? Sample result beneath.

  <colorMappings>
        <mapping>
              <value>Something</value>
              <color>007cc2</color>
         </mapping>
  </colorMappings>

I found the reason of the strange behaviour. It is caused by a bug in XStream library. In AnnotationMapper class, there is a cacheConverter method.

private Converter cacheConverter(final XStreamConverter annotation,
    final Class targetType) {
    Converter result = null;
    final Object[] args;
    final List<Object> parameter = new ArrayList<Object>();
    if (targetType != null && annotation.useImplicitType()) {
        parameter.add(targetType);
    }
    final List<Object> arrays = new ArrayList<Object>();
    arrays.add(annotation.booleans());
    arrays.add(annotation.bytes());
    arrays.add(annotation.chars());
    arrays.add(annotation.doubles());
    arrays.add(annotation.floats());
    arrays.add(annotation.ints());
    arrays.add(annotation.longs());
    arrays.add(annotation.shorts());
    arrays.add(annotation.strings());
    arrays.add(annotation.types());
    for(Object array : arrays) {
        if (array != null) {
            int length = Array.getLength(array);
            for (int i = 0; i < length; i++ ) {
                Object object = Array.get(array, i);
                if (!parameter.contains(object)) {
                    parameter.add(object);
                }
            }
        }
    }

The key fragment is repeated beneath.

                if (!parameter.contains(object)) {
                    parameter.add(object);
                }

As a result, if there are two similar values in any of the arrays (in this case - booleans true and true, but the problem would also occur for other duplicate values) only one of them would be added to parameters. Consequently, the parameters list would be wrong (one boolean missing) and would not match the proper constructor.

However, at the moment I have no idea how to code around that issue.


Solution

  • A simple (but ugly) fix is to create new Converter that extends NamedMapConverter

    public class BetterNamedMapConverter extends NamedMapConverter {
    
        public BetterNamedMapConverter(Mapper mapper, String entryName, String keyName, Class keyType, String valueName, Class valueType,
                boolean asAttributes, ConverterLookup lookup) {
            super(mapper, entryName, keyName, keyType, valueName, valueType, asAttributes, asAttributes, lookup);
        }
    
    }
    

    and change annotation to

    @XStreamConverter(value = BetterNamedMapConverter.class, strings = { "mapping", "value", "color" }, booleans = { true }, types = { String.class, HexColor.class }, useImplicitType = false)
    

    Still, the bug puzzles me (and there are cases when working around it would need even worse hacks).