diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlTypeResolverBuilder.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlTypeResolverBuilder.java index 64482abd6..8314a9ef6 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlTypeResolverBuilder.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlTypeResolverBuilder.java @@ -3,7 +3,6 @@ import java.util.Collection; import com.fasterxml.jackson.annotation.JsonTypeInfo; - import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.cfg.MapperConfig; import com.fasterxml.jackson.databind.jsontype.NamedType; @@ -12,6 +11,7 @@ import com.fasterxml.jackson.databind.jsontype.impl.MinimalClassNameIdResolver; import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder; import com.fasterxml.jackson.databind.type.TypeFactory; +import com.fasterxml.jackson.dataformat.xml.util.StaxUtil; /** * Custom specialization of {@link StdTypeResolverBuilder}; needed so that @@ -25,7 +25,7 @@ public StdTypeResolverBuilder init(JsonTypeInfo.Id idType, TypeIdResolver idRes) { super.init(idType, idRes); if (_typeProperty != null) { - _typeProperty = sanitizeXmlTypeName(_typeProperty); + _typeProperty = StaxUtil.sanitizeXmlTypeName(_typeProperty); } return this; } @@ -37,7 +37,7 @@ public StdTypeResolverBuilder typeProperty(String typeIdPropName) if (typeIdPropName == null || typeIdPropName.length() == 0) { typeIdPropName = _idType.getDefaultPropertyName(); } - _typeProperty = sanitizeXmlTypeName(typeIdPropName); + _typeProperty = StaxUtil.sanitizeXmlTypeName(typeIdPropName); return this; } @@ -65,32 +65,7 @@ protected TypeIdResolver idResolver(MapperConfig config, /* Internal helper methods /********************************************************************** */ - - /** - * Since XML names can not contain all characters JSON names can, we may - * need to replace characters. Let's start with trivial replacement of - * ASCII characters that can not be included. - */ - protected static String sanitizeXmlTypeName(String name) - { - StringBuilder sb = new StringBuilder(name); - int changes = 0; - for (int i = 0, len = name.length(); i < len; ++i) { - char c = name.charAt(i); - if (c > 127) continue; - if (c >= 'a' && c <= 'z') continue; - if (c >= 'A' && c <= 'Z') continue; - if (c >= '0' && c <= '9') continue; - if (c == '_' || c == '.' || c == '-') continue; - // Ok, need to replace - ++changes; - sb.setCharAt(i, '_'); - } - if (changes == 0) { - return name; - } - return sb.toString(); - } + /** * Helper method for encoding regular Java class name in form that diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/util/StaxUtil.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/util/StaxUtil.java index a8f0ead7a..f8384256e 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/util/StaxUtil.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/util/StaxUtil.java @@ -23,4 +23,50 @@ public static T throwXmlAsIOException(XMLStreamException e) throws IOExcepti if (t instanceof RuntimeException) throw (RuntimeException) t; throw new IOException(t); } + + /** + * Since XML names can not contain all characters JSON names can, we may + * need to replace characters. Let's start with trivial replacement of + * ASCII characters that can not be included. + */ + public static String sanitizeXmlTypeName(String name) + { + StringBuilder sb; + int changes = 0; + // First things first: remove array types' trailing[]... + if (name.endsWith("[]")) { + do { + name = name.substring(0, name.length() - 2); + ++changes; + } while (name.endsWith("[]")); + sb = new StringBuilder(name); + // do trivial pluralization attempt + if (name.endsWith("s")) { + sb.append("es"); + } else { + sb.append('s'); + } + } else { + sb = new StringBuilder(name); + } + for (int i = 0, len = name.length(); i < len; ++i) { + char c = name.charAt(i); + if (c > 127) continue; + if (c >= 'a' && c <= 'z') continue; + if (c >= 'A' && c <= 'Z') continue; + if (c >= '0' && c <= '9') continue; + if (c == '_' || c == '.' || c == '-') continue; + // Ok, need to replace + ++changes; + if (c == '$') { + sb.setCharAt(i, '.'); + } else { + sb.setCharAt(i, '_'); + } + } + if (changes == 0) { + return name; + } + return sb.toString(); + } } diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/util/XmlRootNameLookup.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/util/XmlRootNameLookup.java index a92c80708..381303300 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/util/XmlRootNameLookup.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/util/XmlRootNameLookup.java @@ -48,36 +48,41 @@ public QName findRootName(Class rootType, MapperConfig config) QName name; synchronized (_rootNames) { name = _rootNames.get(key); - if (name == null) { - BeanDescription beanDesc = config.introspectClassAnnotations(rootType); - AnnotationIntrospector intr = config.getAnnotationIntrospector(); - AnnotatedClass ac = beanDesc.getClassInfo(); - String localName = null; - String ns = null; + } + if (name != null) { + return name; + } - PropertyName root = intr.findRootName(ac); - if (root != null) { - localName = root.getSimpleName(); - ns = root.getNamespace(); - } - // No answer so far? Let's just default to using simple class name - if (localName == null || localName.length() == 0) { - // Should we strip out enclosing class tho? For now, nope: - localName = rootType.getSimpleName(); - name = new QName("", localName); - } else { - // Otherwise let's see if there's namespace, too (if we are missing it) - if (ns == null || ns.length() == 0) { - ns = findNamespace(intr, ac); - } - } - if (ns == null) { // some QName impls barf on nulls... - ns = ""; - } - name = new QName(ns, localName); - _rootNames.put(key, name); + BeanDescription beanDesc = config.introspectClassAnnotations(rootType); + AnnotationIntrospector intr = config.getAnnotationIntrospector(); + AnnotatedClass ac = beanDesc.getClassInfo(); + String localName = null; + String ns = null; + + PropertyName root = intr.findRootName(ac); + if (root != null) { + localName = root.getSimpleName(); + ns = root.getNamespace(); + } + // No answer so far? Let's just default to using simple class name + if (localName == null || localName.length() == 0) { + // Should we strip out enclosing class tho? For now, nope: + // one caveat: array simple names end with "[]"; also, "$" needs replacing + localName = StaxUtil.sanitizeXmlTypeName(rootType.getSimpleName()); + name = new QName("", localName); + } else { + // Otherwise let's see if there's namespace, too (if we are missing it) + if (ns == null || ns.length() == 0) { + ns = findNamespace(intr, ac); } } + if (ns == null) { // some QName impls barf on nulls... + ns = ""; + } + name = new QName(ns, localName); + synchronized (_rootNames) { + _rootNames.put(key, name); + } return name; } diff --git a/src/test/java/com/fasterxml/jackson/dataformat/xml/TestRootListHandling.java b/src/test/java/com/fasterxml/jackson/dataformat/xml/TestRootListHandling.java index 912c874a6..0f0b59e06 100644 --- a/src/test/java/com/fasterxml/jackson/dataformat/xml/TestRootListHandling.java +++ b/src/test/java/com/fasterxml/jackson/dataformat/xml/TestRootListHandling.java @@ -75,13 +75,19 @@ public void testRenamedRootItem() throws Exception // for [Issue#38] -- root-level Collections not supported public void testListSerialization() throws Exception + { + _testListSerialization(true); + _testListSerialization(false); + } + + private void _testListSerialization(boolean useWrapping) throws Exception { JacksonXmlModule module = new JacksonXmlModule(); -// module.setDefaultUseWrapper(true); + module.setDefaultUseWrapper(useWrapping); XmlMapper xmlMapper = new XmlMapper(module); AnnotationIntrospector introspector = new JacksonAnnotationIntrospector(); xmlMapper.setAnnotationIntrospector(introspector); - + SampleResource r1 = new SampleResource(); r1.setId(123L); r1.setName("Albert"); @@ -125,6 +131,59 @@ public void testListSerialization() throws Exception assertEquals(SampleResource.class, resultList.get(1).getClass()); SampleResource rr = (SampleResource) resultList.get(1); assertEquals("William", rr.getName()); + } + // Related to #38 as well + public void testArraySerialization() throws Exception + { + _testArraySerialization(true); + _testArraySerialization(false); } + + private void _testArraySerialization(boolean useWrapping) throws Exception + { + JacksonXmlModule module = new JacksonXmlModule(); + module.setDefaultUseWrapper(useWrapping); + XmlMapper xmlMapper = new XmlMapper(module); + AnnotationIntrospector introspector = new JacksonAnnotationIntrospector(); + xmlMapper.setAnnotationIntrospector(introspector); + + SampleResource r1 = new SampleResource(); + r1.setId(123L); + r1.setName("Albert"); + r1.setDescription("desc"); + + SampleResource r2 = new SampleResource(); + r2.setId(123L); + r2.setName("William"); + r2.setDescription("desc2"); + + SampleResource[] input = new SampleResource[] { r1, r2 }; + + // to see what JAXB might do, uncomment: +//System.out.println("By JAXB: "+jaxbSerialized(input)); + + String xml = xmlMapper + .writerWithDefaultPrettyPrinter() + .writeValueAsString(input) + .trim(); + + // first trivial sanity checks + assertNotNull(xml); + // Is this good name? If not, what should be used instead? + if (xml.indexOf("") < 0) { + fail("Unexpected output: should have as root element, got: "+xml); + } + + // and then try reading back + SampleResource[] result = xmlMapper.reader(SampleResource[].class).readValue(xml); + assertNotNull(result); + +// System.err.println("XML -> "+xmlMapper.writerWithDefaultPrettyPrinter().writeValueAsString(ob)); + + assertEquals(2, result.length); + SampleResource rr = result[1]; + assertEquals("desc2", rr.getDescription()); + } + }