Skip to content

Commit

Permalink
More work on #38, adding name-handling improvements for arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Aug 15, 2013
1 parent 66ba4e7 commit b9e5755
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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;
}
Expand All @@ -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;
}

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,50 @@ public static <T> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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("<SampleResources>") < 0) {
fail("Unexpected output: should have <SampleResources> 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());
}

}

0 comments on commit b9e5755

Please sign in to comment.