Skip to content

Commit b9e5755

Browse files
committed
More work on #38, adding name-handling improvements for arrays
1 parent 66ba4e7 commit b9e5755

File tree

4 files changed

+143
-58
lines changed

4 files changed

+143
-58
lines changed

src/main/java/com/fasterxml/jackson/dataformat/xml/XmlTypeResolverBuilder.java

+4-29
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import java.util.Collection;
44

55
import com.fasterxml.jackson.annotation.JsonTypeInfo;
6-
76
import com.fasterxml.jackson.databind.*;
87
import com.fasterxml.jackson.databind.cfg.MapperConfig;
98
import com.fasterxml.jackson.databind.jsontype.NamedType;
@@ -12,6 +11,7 @@
1211
import com.fasterxml.jackson.databind.jsontype.impl.MinimalClassNameIdResolver;
1312
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
1413
import com.fasterxml.jackson.databind.type.TypeFactory;
14+
import com.fasterxml.jackson.dataformat.xml.util.StaxUtil;
1515

1616
/**
1717
* Custom specialization of {@link StdTypeResolverBuilder}; needed so that
@@ -25,7 +25,7 @@ public StdTypeResolverBuilder init(JsonTypeInfo.Id idType, TypeIdResolver idRes)
2525
{
2626
super.init(idType, idRes);
2727
if (_typeProperty != null) {
28-
_typeProperty = sanitizeXmlTypeName(_typeProperty);
28+
_typeProperty = StaxUtil.sanitizeXmlTypeName(_typeProperty);
2929
}
3030
return this;
3131
}
@@ -37,7 +37,7 @@ public StdTypeResolverBuilder typeProperty(String typeIdPropName)
3737
if (typeIdPropName == null || typeIdPropName.length() == 0) {
3838
typeIdPropName = _idType.getDefaultPropertyName();
3939
}
40-
_typeProperty = sanitizeXmlTypeName(typeIdPropName);
40+
_typeProperty = StaxUtil.sanitizeXmlTypeName(typeIdPropName);
4141
return this;
4242
}
4343

@@ -65,32 +65,7 @@ protected TypeIdResolver idResolver(MapperConfig<?> config,
6565
/* Internal helper methods
6666
/**********************************************************************
6767
*/
68-
69-
/**
70-
* Since XML names can not contain all characters JSON names can, we may
71-
* need to replace characters. Let's start with trivial replacement of
72-
* ASCII characters that can not be included.
73-
*/
74-
protected static String sanitizeXmlTypeName(String name)
75-
{
76-
StringBuilder sb = new StringBuilder(name);
77-
int changes = 0;
78-
for (int i = 0, len = name.length(); i < len; ++i) {
79-
char c = name.charAt(i);
80-
if (c > 127) continue;
81-
if (c >= 'a' && c <= 'z') continue;
82-
if (c >= 'A' && c <= 'Z') continue;
83-
if (c >= '0' && c <= '9') continue;
84-
if (c == '_' || c == '.' || c == '-') continue;
85-
// Ok, need to replace
86-
++changes;
87-
sb.setCharAt(i, '_');
88-
}
89-
if (changes == 0) {
90-
return name;
91-
}
92-
return sb.toString();
93-
}
68+
9469

9570
/**
9671
* Helper method for encoding regular Java class name in form that

src/main/java/com/fasterxml/jackson/dataformat/xml/util/StaxUtil.java

+46
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,50 @@ public static <T> T throwXmlAsIOException(XMLStreamException e) throws IOExcepti
2323
if (t instanceof RuntimeException) throw (RuntimeException) t;
2424
throw new IOException(t);
2525
}
26+
27+
/**
28+
* Since XML names can not contain all characters JSON names can, we may
29+
* need to replace characters. Let's start with trivial replacement of
30+
* ASCII characters that can not be included.
31+
*/
32+
public static String sanitizeXmlTypeName(String name)
33+
{
34+
StringBuilder sb;
35+
int changes = 0;
36+
// First things first: remove array types' trailing[]...
37+
if (name.endsWith("[]")) {
38+
do {
39+
name = name.substring(0, name.length() - 2);
40+
++changes;
41+
} while (name.endsWith("[]"));
42+
sb = new StringBuilder(name);
43+
// do trivial pluralization attempt
44+
if (name.endsWith("s")) {
45+
sb.append("es");
46+
} else {
47+
sb.append('s');
48+
}
49+
} else {
50+
sb = new StringBuilder(name);
51+
}
52+
for (int i = 0, len = name.length(); i < len; ++i) {
53+
char c = name.charAt(i);
54+
if (c > 127) continue;
55+
if (c >= 'a' && c <= 'z') continue;
56+
if (c >= 'A' && c <= 'Z') continue;
57+
if (c >= '0' && c <= '9') continue;
58+
if (c == '_' || c == '.' || c == '-') continue;
59+
// Ok, need to replace
60+
++changes;
61+
if (c == '$') {
62+
sb.setCharAt(i, '.');
63+
} else {
64+
sb.setCharAt(i, '_');
65+
}
66+
}
67+
if (changes == 0) {
68+
return name;
69+
}
70+
return sb.toString();
71+
}
2672
}

src/main/java/com/fasterxml/jackson/dataformat/xml/util/XmlRootNameLookup.java

+32-27
Original file line numberDiff line numberDiff line change
@@ -48,36 +48,41 @@ public QName findRootName(Class<?> rootType, MapperConfig<?> config)
4848
QName name;
4949
synchronized (_rootNames) {
5050
name = _rootNames.get(key);
51-
if (name == null) {
52-
BeanDescription beanDesc = config.introspectClassAnnotations(rootType);
53-
AnnotationIntrospector intr = config.getAnnotationIntrospector();
54-
AnnotatedClass ac = beanDesc.getClassInfo();
55-
String localName = null;
56-
String ns = null;
51+
}
52+
if (name != null) {
53+
return name;
54+
}
5755

58-
PropertyName root = intr.findRootName(ac);
59-
if (root != null) {
60-
localName = root.getSimpleName();
61-
ns = root.getNamespace();
62-
}
63-
// No answer so far? Let's just default to using simple class name
64-
if (localName == null || localName.length() == 0) {
65-
// Should we strip out enclosing class tho? For now, nope:
66-
localName = rootType.getSimpleName();
67-
name = new QName("", localName);
68-
} else {
69-
// Otherwise let's see if there's namespace, too (if we are missing it)
70-
if (ns == null || ns.length() == 0) {
71-
ns = findNamespace(intr, ac);
72-
}
73-
}
74-
if (ns == null) { // some QName impls barf on nulls...
75-
ns = "";
76-
}
77-
name = new QName(ns, localName);
78-
_rootNames.put(key, name);
56+
BeanDescription beanDesc = config.introspectClassAnnotations(rootType);
57+
AnnotationIntrospector intr = config.getAnnotationIntrospector();
58+
AnnotatedClass ac = beanDesc.getClassInfo();
59+
String localName = null;
60+
String ns = null;
61+
62+
PropertyName root = intr.findRootName(ac);
63+
if (root != null) {
64+
localName = root.getSimpleName();
65+
ns = root.getNamespace();
66+
}
67+
// No answer so far? Let's just default to using simple class name
68+
if (localName == null || localName.length() == 0) {
69+
// Should we strip out enclosing class tho? For now, nope:
70+
// one caveat: array simple names end with "[]"; also, "$" needs replacing
71+
localName = StaxUtil.sanitizeXmlTypeName(rootType.getSimpleName());
72+
name = new QName("", localName);
73+
} else {
74+
// Otherwise let's see if there's namespace, too (if we are missing it)
75+
if (ns == null || ns.length() == 0) {
76+
ns = findNamespace(intr, ac);
7977
}
8078
}
79+
if (ns == null) { // some QName impls barf on nulls...
80+
ns = "";
81+
}
82+
name = new QName(ns, localName);
83+
synchronized (_rootNames) {
84+
_rootNames.put(key, name);
85+
}
8186
return name;
8287
}
8388

src/test/java/com/fasterxml/jackson/dataformat/xml/TestRootListHandling.java

+61-2
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,19 @@ public void testRenamedRootItem() throws Exception
7575

7676
// for [Issue#38] -- root-level Collections not supported
7777
public void testListSerialization() throws Exception
78+
{
79+
_testListSerialization(true);
80+
_testListSerialization(false);
81+
}
82+
83+
private void _testListSerialization(boolean useWrapping) throws Exception
7884
{
7985
JacksonXmlModule module = new JacksonXmlModule();
80-
// module.setDefaultUseWrapper(true);
86+
module.setDefaultUseWrapper(useWrapping);
8187
XmlMapper xmlMapper = new XmlMapper(module);
8288
AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();
8389
xmlMapper.setAnnotationIntrospector(introspector);
84-
90+
8591
SampleResource r1 = new SampleResource();
8692
r1.setId(123L);
8793
r1.setName("Albert");
@@ -125,6 +131,59 @@ public void testListSerialization() throws Exception
125131
assertEquals(SampleResource.class, resultList.get(1).getClass());
126132
SampleResource rr = (SampleResource) resultList.get(1);
127133
assertEquals("William", rr.getName());
134+
}
128135

136+
// Related to #38 as well
137+
public void testArraySerialization() throws Exception
138+
{
139+
_testArraySerialization(true);
140+
_testArraySerialization(false);
129141
}
142+
143+
private void _testArraySerialization(boolean useWrapping) throws Exception
144+
{
145+
JacksonXmlModule module = new JacksonXmlModule();
146+
module.setDefaultUseWrapper(useWrapping);
147+
XmlMapper xmlMapper = new XmlMapper(module);
148+
AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();
149+
xmlMapper.setAnnotationIntrospector(introspector);
150+
151+
SampleResource r1 = new SampleResource();
152+
r1.setId(123L);
153+
r1.setName("Albert");
154+
r1.setDescription("desc");
155+
156+
SampleResource r2 = new SampleResource();
157+
r2.setId(123L);
158+
r2.setName("William");
159+
r2.setDescription("desc2");
160+
161+
SampleResource[] input = new SampleResource[] { r1, r2 };
162+
163+
// to see what JAXB might do, uncomment:
164+
//System.out.println("By JAXB: "+jaxbSerialized(input));
165+
166+
String xml = xmlMapper
167+
.writerWithDefaultPrettyPrinter()
168+
.writeValueAsString(input)
169+
.trim();
170+
171+
// first trivial sanity checks
172+
assertNotNull(xml);
173+
// Is this good name? If not, what should be used instead?
174+
if (xml.indexOf("<SampleResources>") < 0) {
175+
fail("Unexpected output: should have <SampleResources> as root element, got: "+xml);
176+
}
177+
178+
// and then try reading back
179+
SampleResource[] result = xmlMapper.reader(SampleResource[].class).readValue(xml);
180+
assertNotNull(result);
181+
182+
// System.err.println("XML -> "+xmlMapper.writerWithDefaultPrettyPrinter().writeValueAsString(ob));
183+
184+
assertEquals(2, result.length);
185+
SampleResource rr = result[1];
186+
assertEquals("desc2", rr.getDescription());
187+
}
188+
130189
}

0 commit comments

Comments
 (0)