/*
* Copyright (C) 2011 Citrix Systems, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.cloud.bridge.util;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* @author Kelven Yang
*/
public class XSerializer {
protected final static Logger logger = Logger.getLogger(XSerializer.class);
private static Map<String, Class<?>> rootTypes = new HashMap<String, Class<?>>();
private XSerializerAdapter adapter;
private boolean flattenCollection;
private boolean omitNull;
public XSerializer(XSerializerAdapter adapter) {
this.adapter = adapter;
adapter.setSerializer(this);
// set default serialization options
flattenCollection = false;
omitNull = false;
}
public XSerializer(XSerializerAdapter adapter, boolean flattenCollection, boolean omitNull) {
this.adapter = adapter;
adapter.setSerializer(this);
this.flattenCollection = flattenCollection;
this.omitNull = omitNull;
}
public boolean getFlattenCollection() {
return flattenCollection;
}
public void setFlattenCollection(boolean value) {
flattenCollection = value;
}
public boolean flattenField(Field f) {
XFlatten flatten = f.getAnnotation(XFlatten.class);
if(flatten != null)
return flatten.value();
return flattenCollection;
}
public boolean omitNullField(Field f) {
XOmitNull omit= f.getAnnotation(XOmitNull.class);
if(omit != null)
return omit.value();
return omitNull;
}
public boolean getOmitNull() {
return omitNull;
}
public void setOmitNull(boolean value) {
omitNull = value;
}
public static void registerRootType(String elementName, Class<?> clz) {
rootTypes.put(elementName, clz);
}
public XSerializerAdapter getAdapter() {
return adapter;
}
public static Object mapElement(String elementName) {
Class<?> clz = rootTypes.get(elementName);
if(clz == null) {
logger.error("Object class is not registered for root element " + elementName);
throw new IllegalArgumentException("Object class is not registered for root element " + elementName);
}
try {
return clz.newInstance();
} catch (InstantiationException e) {
logger.error("Unable to instantiate object for root element due to InstantiationException, XML element: " + elementName);
throw new IllegalArgumentException("Unable to instantiate object for root element " + elementName);
} catch (IllegalAccessException e) {
logger.error("Unable to instantiate object for root element due to IllegalAccessException, XML element: " + elementName);
throw new IllegalArgumentException("Unable to instantiate object for root element due to IllegalAccessException, XML element: " + elementName);
}
}
public Object serializeFrom(String xmlString) {
try {
Document doc = XmlHelper.parse(xmlString);
Node node = XmlHelper.getRootNode(doc);
if(node == null) {
logger.error("Invalid XML document, no root element");
return null;
}
Object object = mapElement(node.getNodeName());
if(object == null) {
logger.error("Unable to map root element. Please remember to use XSerializer.registerRootType() to register the root object type");
return null;
}
if(object instanceof XSerializable)
((XSerializable)object).serializeFrom(this, object, node);
else
serializeFrom(object, object.getClass(), node);
return object;
} catch (IOException e) {
logger.error("Unable to parse XML input due to " + e.getMessage(), e);
}
return null;
}
private void serializeFrom(Object object, Class<?> clz, Node node) {
if(clz.getSuperclass() != null)
serializeFrom(object, clz.getSuperclass(), node);
Field[] fields = clz.getDeclaredFields();
for(int i = 0; i < fields.length; i++) {
Field f = fields[i];
if((f.getModifiers() & Modifier.STATIC) == 0) {
f.setAccessible(true);
Class<?> fieldType = f.getType();
XElement elem = f.getAnnotation(XElement.class);
if(elem == null)
continue;
try {
if(fieldType.isPrimitive()) {
setPrimitiveField(f, object, XmlHelper.getChildNodeTextContent(node, elem.name()));
} else if(fieldType.getSuperclass() == Number.class) {
setNumberField(f, object, XmlHelper.getChildNodeTextContent(node, elem.name()));
} else if(fieldType == String.class) {
f.set(object, XmlHelper.getChildNodeTextContent(node, elem.name()));
} else if(fieldType == Date.class) {
setDateField(f, object, XmlHelper.getChildNodeTextContent(node, elem.name()));
} else if(fieldType == Calendar.class) {
setCalendarField(f, object, XmlHelper.getChildNodeTextContent(node, elem.name()));
} else if(fieldType.isArray()) {
if(flattenField(f))
setArrayField(f, object, node, elem.item(), elem.itemClass());
else
setArrayField(f, object, XmlHelper.getChildNode(node, elem.name()), elem.item(), elem.itemClass());
} else if(Collection.class.isAssignableFrom(fieldType)) {
if(flattenField(f))
setCollectionField(f, object, node, elem.item(), elem.itemClass());
else
setCollectionField(f, object, XmlHelper.getChildNode(node, elem.name()), elem.item(), elem.itemClass());
} else {
Node childNode = XmlHelper.getChildNode(node, elem.name());
Object fieldObject = f.get(object);
if(fieldObject == null) {
try {
fieldObject = fieldType.newInstance();
} catch (InstantiationException e) {
logger.error("Unable to instantiate " + fieldType.getName() + " object, please make sure it has public constructor");
assert(false);
}
f.set(object, fieldObject);
}
serializeFrom(fieldObject, fieldType, childNode);
}
} catch(IllegalArgumentException e) {
logger.error("Unexpected exception " + e.getMessage(), e);
} catch(IllegalAccessException e) {
logger.error("Unexpected exception " + e.getMessage(), e);
}
}
}
}
private void setPrimitiveField(Field f, Object object, String valueContent)
throws IllegalArgumentException, IllegalAccessException {
String clzName = f.getType().getName();
if(clzName.equals("boolean")) {
if(valueContent != null && valueContent.equalsIgnoreCase("true"))
f.setBoolean(object, true);
else
f.setBoolean(object, false);
} else if(clzName.equals("byte")) {
byte value = 0;
if(valueContent != null)
value = Byte.parseByte(valueContent);
f.setByte(object, value);
} else if(clzName.equals("char")) {
char value = '\0';
if(valueContent != null) {
if(valueContent.charAt(0) == '\'')
value = valueContent.charAt(1);
else
value = valueContent.charAt(0);
}
f.setChar(object, value);
} else if(clzName.equals("short")) {
short value = 0;
if(valueContent != null)
value = Short.parseShort(valueContent);
f.setShort(object, value);
} else if(clzName.equals("int")) {
int value = 0;
if(valueContent != null)
value = Integer.parseInt(valueContent);
f.setInt(object, value);
} else if(clzName.equals("long")) {
long value = 0;
if(valueContent != null)
value = Long.parseLong(valueContent);
f.setLong(object, value);
} else if(clzName.equals("float")) {
float value = 0;
if(valueContent != null)
value = Float.parseFloat(valueContent);
f.setFloat(object, value);
} else if(clzName.equals("double")) {
double value = 0;
if(valueContent != null)
value = Double.parseDouble(valueContent);
f.setDouble(object, value);
} else {
logger.error("Assertion failed at setPrimitiveFiled");
assert(false);
}
}
private void setNumberField(Field f, Object object, String valueContent)
throws IllegalArgumentException, IllegalAccessException {
String clzName = f.getType().getName();
if(clzName.equals("Byte")) {
byte value = 0;
if(valueContent != null)
value = Byte.parseByte(valueContent);
f.set(object, new Byte(value));
} else if(clzName.equals("Short")) {
short value = 0;
if(valueContent != null)
value = Short.parseShort(valueContent);
f.set(object, new Short(value));
} else if(clzName.equals("Integer")) {
int value = 0;
if(valueContent != null)
value = Integer.parseInt(valueContent);
f.set(object, new Integer(value));
} else if(clzName.equals("Long")) {
long value = 0;
if(valueContent != null)
value = Long.parseLong(valueContent);
f.set(object, new Long(value));
} else if(clzName.equals("Float")) {
float value = 0;
if(valueContent != null)
value = Float.parseFloat(valueContent);
f.set(object, new Float(value));
} else if(clzName.equals("Double")) {
double value = 0;
if(valueContent != null)
value = Double.parseDouble(valueContent);
f.setDouble(object, new Double(value));
} else if(clzName.equals("AtomicInteger")) {
int value = 0;
if(valueContent != null)
value = Integer.parseInt(valueContent);
f.set(object, new AtomicInteger(value));
} else if(clzName.equals("AtomicLong")) {
long value = 0;
if(valueContent != null)
value = Long.parseLong(valueContent);
f.set(object, new AtomicLong(value));
} else if(clzName.equals("BigInteger")) {
logger.error("we don't support BigInteger for now");
assert(false);
} else if(clzName.equals("BigDecimal")) {
logger.error("we don't support BigInteger for now");
assert(false);
} else {
logger.error("Assertion failed at setPrimitiveFiled");
assert(false);
}
}
private void setDateField(Field f, Object object, String valueContent)
throws IllegalArgumentException, IllegalAccessException {
if(valueContent != null) {
valueContent = valueContent.replace('T', ' ');
valueContent = valueContent.replace('.', '\0');
SimpleDateFormat df = DateHelper.getGMTDateFormat("yyyy-MM-dd HH:mm:ss");
try {
Date value = df.parse(valueContent);
f.set(object, value);
} catch (ParseException e) {
logger.error("Unrecognized date/time format " + valueContent);
}
}
}
private void setCalendarField(Field f, Object object, String valueContent)
throws IllegalArgumentException, IllegalAccessException {
if(valueContent != null) {
valueContent = valueContent.replace('T', ' ');
valueContent = valueContent.replace('.', '\0');
SimpleDateFormat df = DateHelper.getGMTDateFormat("yyyy-MM-dd HH:mm:ss");
try {
Date value = df.parse(valueContent);
f.set(object, DateHelper.toCalendar(value));
} catch (ParseException e) {
logger.error("Unrecognized date/time format " + valueContent);
}
}
}
private void setArrayField(Field f, Object object, Node node, String itemElementName, String itemClass)
throws IllegalArgumentException, IllegalAccessException {
List<Object> arrayList = new ArrayList<Object>();
Class<?> itemClz = null;
try {
itemClz = this.getClass().forName(itemClass);
} catch (ClassNotFoundException e) {
logger.error("Unable to find class " + itemClass);
return;
}
if(node != null) {
NodeList l = node.getChildNodes();
if(l != null && l.getLength() > 0) {
for(int i = 0; i < l.getLength(); i++) {
try {
Node itemNode = l.item(i);
if(itemNode.getNodeName().equals(itemElementName)) {
Object item = itemClz.newInstance();
serializeFrom(item, itemClz, l.item(i));
arrayList.add(item);
}
} catch (InstantiationException e) {
logger.error("Unable to initiate object instance for class " + itemClass + ", make sure it has public constructor");
break;
}
}
}
}
Object arrary = Array.newInstance(f.getType().getComponentType(), arrayList.size());
arrayList.toArray((Object[])arrary);
f.set(object, arrary);
}
private void setCollectionField(Field f, Object object, Node node, String itemElementName, String itemClass)
throws IllegalArgumentException, IllegalAccessException {
Object fieldObject = f.get(object);
if(fieldObject == null) {
logger.error("Please initialize collection field " + f.getName() + " in class " + object.getClass().getName() + "'s constructor");
return;
}
Class<?> itemClz = null;
try {
itemClz = this.getClass().forName(itemClass);
} catch (ClassNotFoundException e) {
logger.error("Unable to find class " + itemClass);
return;
}
NodeList l = node.getChildNodes();
if(l != null && l.getLength() > 0) {
for(int i = 0; i < l.getLength(); i++) {
try {
Node itemNode = l.item(i);
if(itemNode.getNodeName().equals(itemElementName)) {
Object item = itemClz.newInstance();
serializeFrom(item, itemClz, l.item(i));
((Collection)fieldObject).add(item);
}
} catch (InstantiationException e) {
logger.error("Unable to initiate object instance for class " + itemClass + ", make sure it has public constructor");
break;
}
}
}
}
public void serializeTo(Object obj, String startElement, String namespace, int indentLevel, PrintWriter writer) {
if(startElement != null) {
adapter.beginElement(startElement, namespace, indentLevel, writer);
indentLevel++;
}
if(obj instanceof XSerializable) {
((XSerializable)obj).serializeTo(this, indentLevel, writer);
} else {
Class<?> clz = obj.getClass();
serializeTo(obj, clz, indentLevel, writer);
}
if(startElement != null) {
indentLevel--;
adapter.endElement(startElement, indentLevel, writer);
}
}
public String serializeTo(Object obj, String startElement, String namespace, int indentLevel) {
StringWriter writer = new StringWriter();
serializeTo(obj, startElement, namespace, indentLevel, new PrintWriter(writer));
return writer.toString();
}
private void serializeTo(Object obj, Class<?> clz, int indentLevel, PrintWriter writer) {
if(clz.getSuperclass() != null)
serializeTo(obj, clz.getSuperclass(), indentLevel, writer);
Field[] fields = clz.getDeclaredFields();
for(int i = 0; i < fields.length; i++) {
Field f = fields[i];
if((f.getModifiers() & Modifier.STATIC) == 0) {
f.setAccessible(true);
Class<?> fieldType = f.getType();
XElement elem = f.getAnnotation(XElement.class);
if(elem == null)
continue;
Object fieldValue = null;
try {
fieldValue = f.get(obj);
} catch (IllegalArgumentException e) {
logger.error("Unexpected exception " + e.getMessage(), e);
} catch (IllegalAccessException e) {
logger.error("Unexpected exception " + e.getMessage(), e);
}
adapter.writeElement(elem.name(), elem.item(), fieldValue, f, indentLevel, writer);
if(i < fields.length - 1) {
Field next = fields[i + 1];
if((next.getModifiers() & Modifier.STATIC) == 0 && next.getAnnotation(XElement.class) != null) {
adapter.writeSeparator(indentLevel, writer);
}
}
}
}
}
public boolean isComposite(Class<?> clz) {
if(clz.isPrimitive() || clz.getSuperclass() == Number.class ||
clz == String.class || clz == Date.class || clz == Calendar.class) {
return false;
}
return true;
}
}