|
// OptimizedReflectionMarshaller.java
package my;
import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List;
import org.jdom.Document; import org.jdom.Element; import org.jdom.output.XMLOutputter;
/* * This is a utility class that marshals a Java Object into an XML * String. Originally, this used StringBuffer to build the XML * String. However, it has been modified from it's original * version to build the XML with JDOM instead. * * @author Kirill at http://www.topxml.com/rbnews/XML/re-2909_A-simple-class-for-converting-any-Java-object-to-XML-string.aspx * * @date January 21, 2008 - modified * @author Jimmy Honeycutt - modified to use JDOM to create XML instead of StringBuffer. */ public class OptimizedReflectionMarshaller { // cache for getters private static HashMap gettersMap = new HashMap();
// cache for storing info on whether certain class implements Collection private static HashMap collectionsMap = new HashMap();
private static final String JAVA = "java."; private static final String JAVAX = "javax."; private static final Class[] EMPTYPARAMS = new Class[0]; /** * Info on a single field and the corresponding getter method */ private static class FieldMethodPair { private String fieldName;
private Method getterMethod;
public FieldMethodPair(String fieldName, Method getterMethod) { this.fieldName = fieldName; this.getterMethod = getterMethod; }
public String getFieldName() { return fieldName; }
public Method getGetterMethod() { return getterMethod; } }
/** * Returns the marshalled XML representation of the parameter object * @param obj the Java Object to be marshalled to XML * @throws IOException - thrown by JDOM * @throws IllegalAccesException - thrown by reflection * @throws InvocationTargetException - thrown by reflection */ public static String marshal(Object obj) throws IOException, IllegalAccessException, InvocationTargetException { Class clazz = obj.getClass();
// get class name without package name (i.e. com.acme.springapp.view.Product = Product) String className = clazz.getName(); // root node name = Element rootEl = new Element(formatNodeName(className)); marshal(obj, rootEl); Document doc = new Document(rootEl); return new XMLOutputter().outputString(doc); }
/** * Returns getter function for the specified field * @param clazz the Java Object's reflection Class * @param fieldName the field name (or attribute) in the object */ private static Method getGetter(Class clazz, String fieldName) { try { // for example, for `name` we will look for `getName` String getMethodName = fieldName.substring(0, 1); getMethodName = getMethodName.toUpperCase(); getMethodName = "get" + getMethodName + fieldName.substring(1, fieldName.length()); Method getMethod = clazz.getMethod(getMethodName, EMPTYPARAMS); return getMethod; } catch (NoSuchMethodException nsme) { return null; } }
/** * Returns a list of all fields that have getters * @param clazz the Java Object's reflection Class */ private static List getAllGetters(Class clazz) { try { // check if have in cache if (gettersMap.containsKey(clazz.getName())) return (List) gettersMap.get(clazz.getName());
List fieldList = new LinkedList(); Class currClazz = clazz; while (true) { Field[] fields = currClazz.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { Field currField = fields[i]; int modifiers = currField.getModifiers(); // check if not static and has getter if (!Modifier.isStatic(modifiers)) { Method getterMethod = getGetter(clazz, currField.getName()); if (getterMethod != null) { FieldMethodPair fmp = new FieldMethodPair(currField.getName(), getterMethod); fieldList.add(fmp); } } } currClazz = currClazz.getSuperclass(); if (currClazz == null) { break; } }
// store in cache gettersMap.put(clazz.getName(), fieldList);
return fieldList; } catch (Exception exc) { exc.printStackTrace(); return null; } }
/** * Checks whether the specified class implements Collection interface * @param class the Java Object's reflection Class */ private static boolean isImplementsCollection(Class clazz) { String className = clazz.getName(); // check in cache if (collectionsMap.containsKey(className)) { return ((Boolean) collectionsMap.get(className)).booleanValue(); }
boolean result = Collection.class.isAssignableFrom(clazz);
// store in cache collectionsMap.put(className, new Boolean(result)); return result; } /** * Format and append the data within object to JDOM element. * @param obj the Java object * @param el the jdom element */ private static void appendFormatted(Object obj, Element el) { String strRepresentation = obj.toString(); int len = strRepresentation.length(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < len; i++) { char c = strRepresentation.charAt(i); switch (c) { case '&': sb.append("&"); break; case '<': sb.append("<"); break; case '>': sb.append(">"); break; case '\'': sb.append("'"); break; case '\"': sb.append("\""); break; default: sb.append(c); } } el.addContent(sb.toString()); } /** * Marshalls the Java Object and appends data to * the XML JDOM root element. * @param obj the Java Object * @param root the root element * @throws IllegalAccessException * @throws InvocationTargetException */ private static void marshal(Object obj, Element root) throws IllegalAccessException, InvocationTargetException { Class clazz = obj.getClass(); String className = clazz.getName();
// check if implements Collection if (isImplementsCollection(clazz)) { Collection cobj = (Collection) obj; Iterator it = cobj.iterator(); while (it.hasNext()) { Object eobj = it.next(); Element childEl = new Element(formatNodeName(eobj.getClass().getName())); marshal(eobj, childEl); root.addContent(childEl); } return; }
// check for primitive types if (className.startsWith(JAVA) || className.startsWith(JAVAX)) { appendFormatted(obj, root); return; } // otherwise put all fields with getters List allGetters = getAllGetters(clazz); Iterator itGetters = allGetters.iterator(); while (itGetters.hasNext()) { FieldMethodPair currGetter = (FieldMethodPair) itGetters.next(); String currFieldName = currGetter.fieldName; Method currentGetter = currGetter.getterMethod; // call method Object val = currentGetter.invoke(obj, EMPTYPARAMS); if (val != null) { Element fieldEl = new Element(formatNodeName(currFieldName)); // call recursively marshal(val, fieldEl); root.addContent(fieldEl); } } } /** * Formats an XML node name (i.e. <Product></Product>) by removing * the package name (if exists) and making first letter upper-case. * @param name the name of the class or attribute for the XML node name * @return String */ private static String formatNodeName(String name) { // remove lengthy package name int startIndex = name.lastIndexOf("."); if(startIndex > 0) { name = name.substring(startIndex + 1, name.length()); } // upper-case the first character String firstChar = name.substring(0,1).toUpperCase(); return firstChar + name.substring(1); } }
|
E. Johnson said,
June 19, 2008 at 7:15 pm
Thanks very much!!
JP Robichaud said,
August 2, 2008 at 4:12 am
This is really neat (and fast too!)
Would it be feasible to create an ‘unmarshal’ function, that would do the opposite, based on the class instance? Something along:
MyObject obj = (MyObject) OptimizedReflectionMarshaller.unmarshal(MyObject.class, xmlString);
This would be really nice…
anon said,
November 27, 2009 at 3:20 am
have you looked at vtd-xml, which is the latest and more advanced/powerful XML Processing API
vtd-xml