Created
October 15, 2011 23:55
-
-
Save benjchristensen/1290326 to your computer and use it in GitHub Desktop.
XMLUtilty
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.LinkedHashMap; | |
import java.util.Map; | |
import java.util.logging.Level; | |
import java.util.logging.Logger; | |
import junit.framework.TestCase; | |
import org.junit.Test; | |
/** | |
* Utility for generating XML from Maps/Lists | |
*/ | |
public class XMLUtility { | |
private static Logger logger = Logger.getLogger(XMLUtility.class.getName()); | |
/** | |
* Pass in a Map and this method will return an XML string. | |
* <p> | |
* The map can contain Objects, int[], Object[] and Collections and they will be converted into string representations. | |
* <p> | |
* Nested maps can be included as values and the XML will have nested object notation. | |
* <p> | |
* Arrays/Collections can have Maps in them as well. | |
* <p> | |
* See the unit tests for examples. | |
* | |
* @param xmlData | |
* @return | |
*/ | |
public static String xmlFromMap(Map<String, Object> xmlData) { | |
return xmlFromMap(null, xmlData); | |
} | |
private static String xmlFromMap(String elementName, Map<String, Object> dataMap) { | |
try { | |
XMLString xml = new XMLString(); | |
for (String key : dataMap.keySet()) { | |
Object data = dataMap.get(key); | |
if (data instanceof Map) { | |
addMap(xml, key, (Map<String, Object>) data); | |
} else if (data instanceof Object[]) { | |
/* it's an object array, so we'll iterate the elements and put them all in here */ | |
xml.addEntity(stringArrayFromObjectArray(key, (Object[]) data)); | |
} else if (data instanceof Collection) { | |
/* it's a collection, so we'll iterate the elements and put them all in here */ | |
xml.addEntity(stringArrayFromObjectArray(key, ((Collection) data).toArray())); | |
} else if (data instanceof int[]) { | |
xml.addEntity(stringArrayFromPrimitiveArray(key, (int[]) data)); | |
} else if (data instanceof XMLCapableObject) { | |
xml.addElement(key, xmlFromMap(key, ((XMLCapableObject) data).xmlMap())); | |
} else { | |
/* all other objects we assume we are to just put the string value in */ | |
xml.addElement(key, String.valueOf(data)); | |
} | |
} | |
logger.log(Level.FINER, "created xml from map => " + xml.toString()); | |
return xml.toString(); | |
} catch (Exception e) { | |
logger.log(Level.SEVERE, "Could not create XML from Map. ", e); | |
return "{}"; | |
} | |
} | |
/** | |
* Return a string if all values are only String/Object/Primitives otherwise NULL. | |
* <p> | |
* Instead of returning a boolean if true then looping again to build it, we do the check and build the string in a single loop. | |
* | |
* @param xmlData | |
* @return | |
*/ | |
private static String getMapAsAttributeXMLString(String elementName, Map<String, Object> xmlData) { | |
XMLString xml = new XMLString(); | |
xml.startAttributeElement(elementName); | |
for (String key : xmlData.keySet()) { | |
Object data = xmlData.get(key); | |
if (data instanceof Map) { | |
return null; | |
} else if (data instanceof Object[]) { | |
return null; | |
} else if (data instanceof Collection) { | |
return null; | |
} else if (data instanceof int[]) { | |
return null; | |
} else if (data instanceof XMLCapableObject) { | |
return null; | |
} else { | |
xml.addAttribute(key, String.valueOf(data)); | |
} | |
} | |
xml.endElementWithoutBody(); | |
return xml.toString(); | |
} | |
/* | |
* return a string like: <key>one</key><key>two</key><key>three</key> | |
*/ | |
private static String stringArrayFromObjectArray(String key, Object[] data) { | |
XMLString xml = new XMLString(); | |
for (Object o : data) { | |
if (o instanceof Map) { | |
addMap(xml, key, (Map<String, Object>) o); | |
} else if (o instanceof XMLCapableObject) { | |
addMap(xml, key, ((XMLCapableObject) o).xmlMap()); | |
} else { | |
xml.addElement(key, String.valueOf(o)); | |
} | |
} | |
return xml.toString(); | |
} | |
/** | |
* Add a Map<String, Object> to the given XML and determine if it should have attributes or not. | |
* | |
* @param xml | |
* @param key | |
* @param map | |
*/ | |
private static void addMap(XMLString xml, String key, Map<String, Object> map) { | |
String entityAsAttributes = getMapAsAttributeXMLString(key, map); | |
if (entityAsAttributes != null) { | |
/* the map was able to be formatted as a single element with attributes, so add it directly */ | |
xml.addEntity(entityAsAttributes); | |
} else { | |
/* it's a nested map, so we'll recursively add the XML of this map to the current XML */ | |
xml.addElement(key, xmlFromMap(key, map)); | |
} | |
} | |
/* | |
* return a string like: <key>1</key><key>2</key><key>3</key> | |
*/ | |
private static String stringArrayFromPrimitiveArray(String key, int[] data) { | |
XMLString xml = new XMLString(); | |
for (Object o : data) { | |
xml.addElement(key, String.valueOf(o)); | |
} | |
return xml.toString(); | |
} | |
private static class XMLString { | |
StringBuilder xml = new StringBuilder(); | |
public XMLString startAttributeElement(String key) { | |
xml.append("<").append(key); | |
return this; | |
} | |
public XMLString startElement(String key) { | |
xml.append("<").append(key).append(">"); | |
return this; | |
} | |
public XMLString addEntity(String value) { | |
xml.append(value); | |
return this; | |
} | |
public XMLString endElement(String key) { | |
xml.append("</").append(key).append(">"); | |
return this; | |
} | |
public XMLString endElementWithoutBody() { | |
xml.append("/>"); | |
return this; | |
} | |
public XMLString addAttribute(String key, String value) { | |
xml.append(" ").append(key).append("=\"").append(value).append("\""); | |
return this; | |
} | |
public XMLString addElement(String key, String value) { | |
startElement(key); | |
xml.append(value); | |
endElement(key); | |
return this; | |
} | |
public String toString() { | |
return xml.toString(); | |
} | |
} | |
public static class UnitTest extends TestCase { | |
// I'm using LinkedHashMap in the testing so I get consistent ordering for the expected results | |
public void testSimpleOne() { | |
Map<String, Object> xmlData = new LinkedHashMap<String, Object>(); | |
xmlData.put("myKey", "myValue"); | |
String xml = xmlFromMap(xmlData); | |
String expected = "<myKey>myValue</myKey>"; | |
assertEquals(expected, xml); | |
} | |
public void testSimpleTwo() { | |
Map<String, Object> xmlData = new LinkedHashMap<String, Object>(); | |
xmlData.put("myKey", "myValue"); | |
xmlData.put("myKey2", "myValue2"); | |
String xml = xmlFromMap(xmlData); | |
String expected = "<myKey>myValue</myKey><myKey2>myValue2</myKey2>"; | |
assertEquals(expected, xml); | |
} | |
public void testMapAsAttributesFromList() { | |
List<Map<String, Object>> elements = new ArrayList<Map<String, Object>>(); | |
for (int i = 0; i < 2; i++) { | |
Map<String, Object> element = new LinkedHashMap<String, Object>(); | |
element.put("myKey", "myValue"); | |
element.put("myKey2", "myValue2"); | |
elements.add(element); | |
} | |
Map<String, Object> xmlData = new LinkedHashMap<String, Object>(); | |
xmlData.put("item", elements); | |
String xml = xmlFromMap(xmlData); | |
String expected = "<item myKey=\"myValue\" myKey2=\"myValue2\"/><item myKey=\"myValue\" myKey2=\"myValue2\"/>"; | |
assertEquals(expected, xml); | |
} | |
public void testMapAsAttributesInMap() { | |
Map<String, Object> element = new LinkedHashMap<String, Object>(); | |
element.put("myKey", "myValue"); | |
element.put("myKey2", "myValue2"); | |
Map<String, Object> xmlData = new LinkedHashMap<String, Object>(); | |
xmlData.put("item", element); | |
xmlData.put("key", "value"); | |
String xml = xmlFromMap(xmlData); | |
String expected = "<item myKey=\"myValue\" myKey2=\"myValue2\"/><key>value</key>"; | |
assertEquals(expected, xml); | |
} | |
public void testNestedMapOne() { | |
Map<String, Object> xmlData = new LinkedHashMap<String, Object>(); | |
xmlData.put("myKey", "myValue"); | |
Map<String, Object> xmlData2 = new LinkedHashMap<String, Object>(); | |
xmlData2.put("myNestedKey", "myNestedValue"); | |
xmlData.put("myNestedData", xmlData2); | |
String xml = xmlFromMap(xmlData); | |
String expected = "<myKey>myValue</myKey><myNestedData myNestedKey=\"myNestedValue\"/>"; | |
assertEquals(expected, xml); | |
} | |
public void testNestedMapTwo() { | |
Map<String, Object> xmlData = new LinkedHashMap<String, Object>(); | |
xmlData.put("myKey", "myValue"); | |
Map<String, Object> xmlData2 = new LinkedHashMap<String, Object>(); | |
xmlData2.put("myNestedKey", "myNestedValue"); | |
xmlData2.put("myNestedKey2", "myNestedValue2"); | |
xmlData.put("myNestedData", xmlData2); | |
String xml = xmlFromMap(xmlData); | |
String expected = "<myKey>myValue</myKey><myNestedData myNestedKey=\"myNestedValue\" myNestedKey2=\"myNestedValue2\"/>"; | |
assertEquals(expected, xml); | |
} | |
public void testArrayOne() { | |
Map<String, Object> xmlData = new LinkedHashMap<String, Object>(); | |
int[] numbers = { 1, 2, 3, 4 }; | |
xmlData.put("myKey", numbers); | |
String xml = xmlFromMap(xmlData); | |
String expected = "<myKey>1</myKey><myKey>2</myKey><myKey>3</myKey><myKey>4</myKey>"; | |
assertEquals(expected, xml); | |
} | |
public void testArrayTwo() { | |
Map<String, Object> xmlData = new LinkedHashMap<String, Object>(); | |
String[] values = { "one", "two", "three", "four" }; | |
xmlData.put("myKey", values); | |
String xml = xmlFromMap(xmlData); | |
String expected = "<myKey>one</myKey><myKey>two</myKey><myKey>three</myKey><myKey>four</myKey>"; | |
assertEquals(expected, xml); | |
} | |
public void testCollectionOne() { | |
Map<String, Object> xmlData = new LinkedHashMap<String, Object>(); | |
ArrayList<String> values = new ArrayList<String>(); | |
values.add("one"); | |
values.add("two"); | |
values.add("three"); | |
values.add("four"); | |
xmlData.put("myKey", values); | |
String xml = xmlFromMap(xmlData); | |
String expected = "<myKey>one</myKey><myKey>two</myKey><myKey>three</myKey><myKey>four</myKey>"; | |
assertEquals(expected, xml); | |
} | |
public void testMapAndList() { | |
Map<String, Object> xmlData = new LinkedHashMap<String, Object>(); | |
xmlData.put("myKey", "myValue"); | |
int[] numbers = { 1, 2, 3, 4 }; | |
xmlData.put("myNumbers", numbers); | |
Map<String, Object> xmlData2 = new LinkedHashMap<String, Object>(); | |
xmlData2.put("myNestedKey", "myNestedValue"); | |
xmlData2.put("myNestedKey2", "myNestedValue2"); | |
String[] values = { "one", "two", "three", "four" }; | |
xmlData2.put("myStringNumbers", values); | |
xmlData.put("myNestedData", xmlData2); | |
String xml = xmlFromMap(xmlData); | |
String expected = "<myKey>myValue</myKey><myNumbers>1</myNumbers><myNumbers>2</myNumbers><myNumbers>3</myNumbers><myNumbers>4</myNumbers><myNestedData><myNestedKey>myNestedValue</myNestedKey><myNestedKey2>myNestedValue2</myNestedKey2><myStringNumbers>one</myStringNumbers><myStringNumbers>two</myStringNumbers><myStringNumbers>three</myStringNumbers><myStringNumbers>four</myStringNumbers></myNestedData>"; | |
assertEquals(expected, xml); | |
} | |
@Test | |
public void testArrayOfMaps() { | |
Map<String, Object> xmlData = new LinkedHashMap<String, Object>(); | |
ArrayList<Map<String, Object>> messages = new ArrayList<Map<String, Object>>(); | |
Map<String, Object> message1 = new LinkedHashMap<String, Object>(); | |
message1.put("a", "valueA1"); | |
message1.put("b", "valueB1"); | |
messages.add(message1); | |
Map<String, Object> message2 = new LinkedHashMap<String, Object>(); | |
message2.put("a", "valueA2"); | |
message2.put("b", "valueB2"); | |
messages.add(message2); | |
xmlData.put("messages", messages); | |
String xml = xmlFromMap(xmlData); | |
String expected = "<messages a=\"valueA1\" b=\"valueB1\"/><messages a=\"valueA2\" b=\"valueB2\"/>"; | |
assertEquals(expected, xml); | |
} | |
} | |
public static interface XMLCapableObject { | |
Map<String, Object> xmlMap(); | |
} | |
} |
Example of how this can be used in a RESTful app that can respond with JSON and XML:
@GET
@Path("{id}")
@Produces("application/xml")
public Response getXML(@PathParam("id") String id) {
return Response.ok(XMLUtility.xmlFromMap(get(id))).type(MediaType.APPLICATION_XML).build();
}
@GET
@Path("{id}")
@Produces("application/json")
public Response getJSON(@PathParam("id") String id) {
return Response.ok(JSONUtility.jsonFromMap(get(id))).type(MediaType.APPLICATION_JSON).build();
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is a companion to JSONUtilty so that a Map<String, Object> can be output as either JSON or XML.