package com.skaringa.javaxml.serializers;
import java.io.PrintStream;
import java.lang.reflect.Array;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.Vector;
import org.xml.sax.Attributes;
import com.skaringa.javaxml.DeserializerException;
import com.skaringa.javaxml.SerializerException;
import com.skaringa.javaxml.handler.DocumentOutputHandlerInterface;
/**
* Implementation of ComponentSerializer for arrays.
*/
public final class ArraySerializer extends AbstractSerializer {
/**
* Type code prefix for arrays.
*/
static final String TYPENAME_PREFIX = "array";
private String _xmlTypeName;
private Class _type;
private Class _componentType;
/**
* Construct an array serializer for a given array class.
* @param type The type of the array.
*/
ArraySerializer(Class type) {
_type = type;
_componentType = type.getComponentType();
computeXMLTypeName();
}
/**
* Construct an array deserializer for a given XML type.
* @param xmlTypeName The XML type name of the array.
* @throws DeserializerException If xmlTypeName is not valid.
*/
ArraySerializer(String xmlTypeName) throws DeserializerException {
_xmlTypeName = xmlTypeName;
// defer the determination of component type to startDeserialize
// (because of using user class loader).
}
/**
* @see ComponentSerializer#serialize(Object, Class, String, Map, Map, DocumentOutputHandlerInterface)
*/
public void serialize(
Object obj,
Class type,
String name,
Map propertyMap,
Map objectIdMap,
DocumentOutputHandlerInterface output)
throws SerializerException {
startElement(obj, _xmlTypeName, name, propertyMap, output);
if (obj != null) {
int len = java.lang.reflect.Array.getLength(obj);
for (int j = 0; j < len; ++j) {
Object comp = java.lang.reflect.Array.get(obj, j);
Class fieldType = (comp == null) ? _componentType : comp.getClass();
ComponentSerializer ser =
SerializerRegistry.getInstance().getSerializer(fieldType);
ser.serialize(comp, fieldType, "el", propertyMap, objectIdMap, output);
}
}
output.endElement(name);
}
/**
* @see ComponentSerializer#getXMLTypeName()
*/
public String getXMLTypeName() {
return _xmlTypeName;
}
/**
* @see ComponentSerializer#startDeserialize(String, Attributes, Object, Stack, ClassLoader)
*/
public Object startDeserialize(
String name,
Attributes attrs,
Object parent,
Stack objStack,
ClassLoader classLoader)
throws DeserializerException {
Vector helperVector = null;
String nullAttr = attrs.getValue("xsi:nil");
if (nullAttr == null || nullAttr.equals("false")) {
helperVector = new Vector();
}
if (_type == null) {
try {
computeType(classLoader);
_componentType = _type.getComponentType();
}
catch (DeserializerException ex) {
if (!TYPENAME_PREFIX.equals(_xmlTypeName)) {
throw ex;
}
}
}
Class componentType = _componentType;
if (componentType == null && TYPENAME_PREFIX.equals(_xmlTypeName)) {
// try to get the component type from the parent
// this is for backward compatibility (typename was "array" only)
componentType = getFieldType(parent, name).getComponentType();
if (componentType == null) {
throw new DeserializerException(
"Can't get component type for: " + _xmlTypeName);
}
}
return new ArrayHelper(helperVector, componentType);
}
/**
* @see ComponentSerializer#setMember(Object, String, Object)
*/
public void setMember(Object parent, String name, Object value)
throws DeserializerException {
checkSequence(parent);
ArrayHelper helper = (ArrayHelper) parent;
if (helper.getVector() == null) {
throw new DeserializerException("child of null object is forbidden");
}
helper.add(value);
}
/**
* @see ComponentSerializer#endDeserialize(Object, String)
*/
public Object endDeserialize(Object obj, String text)
throws DeserializerException {
checkSequence(obj);
ArrayHelper helper = (ArrayHelper) obj;
return helper.getArray();
}
/**
* Check if an object is of type ArrayHelper.
* @param obj The object to check.
* @throws DeserializerException If the object is not of type ArrayHelper.
*/
private void checkSequence(Object obj) throws DeserializerException {
if (!(obj instanceof ArrayHelper)) {
throw new DeserializerException(
"invalid sequence: array expected, but was: "
+ obj.getClass().getName());
}
}
/**
* @see com.skaringa.javaxml.serializers.ComponentSerializer#writeXMLTypeDefinition(Class, Map, DocumentOutputHandlerInterface)
*/
public void writeXMLTypeDefinition(
Class type,
Map propertyMap,
DocumentOutputHandlerInterface output)
throws SerializerException {
writeXMLCollectionDef("el", _componentType, output);
}
/**
* @see ComponentSerializer#addUsedClasses(Class, Set)
*/
public void addUsedClasses(Class base, Set usedClasses)
throws SerializerException {
usedClasses.add(base);
if (_componentType != null) {
ComponentSerializer ser =
SerializerRegistry.getInstance().getSerializer(_componentType);
ser.addUsedClasses(_componentType, usedClasses);
}
}
/**
* Compute the XML type name from the Java type.
*/
private void computeXMLTypeName() {
String javaTypeName = _type.getName();
StringBuffer xmlTypeName = new StringBuffer(TYPENAME_PREFIX);
int dim = 0;
int i = 0;
while (javaTypeName.charAt(i) == '[') {
++i;
++dim;
}
if (dim > 1) {
xmlTypeName.append(dim);
}
xmlTypeName.append("_of_");
switch (javaTypeName.charAt(i)) {
case 'B' :
xmlTypeName.append("byte");
break;
case 'C' :
xmlTypeName.append("char");
break;
case 'D' :
xmlTypeName.append("double");
break;
case 'F' :
xmlTypeName.append("float");
break;
case 'I' :
xmlTypeName.append("int");
break;
case 'J' :
xmlTypeName.append("long");
break;
case 'S' :
xmlTypeName.append("short");
break;
case 'Z' :
xmlTypeName.append("boolean");
break;
case 'L' :
computeXMLTypeNameForObject(javaTypeName, xmlTypeName, i);
break;
default :
throw new RuntimeException("FATAL: unknown array type string");
}
_xmlTypeName = xmlTypeName.toString();
}
/**
* Compute the XML type name from the special
* Java array type string of objects.
* @param javaTypeName The Java array type name.
* @param xmlTypeName A buffer to append the XML type name to.
* @param lpos The position of the 'L' in javaTypeName.
*/
private void computeXMLTypeNameForObject(
String javaTypeName,
StringBuffer xmlTypeName,
int lpos) {
int j = ++lpos;
while (javaTypeName.charAt(j) != ';') {
++j;
}
xmlTypeName.append(
ObjectSerializer.fixXMLTypeNameForInnerClass(
javaTypeName.substring(lpos, j)));
}
/**
* Compute the Java type from the XML type name.
* @param classLoader The class loader used to load the array components.
* @throws DeserializerException If the XML type name is not a valid name
* for an array.
*/
private void computeType(ClassLoader classLoader) throws DeserializerException {
if (!_xmlTypeName.startsWith(TYPENAME_PREFIX)) {
throw new DeserializerException(
"Invalid array type name prefix: " + _xmlTypeName);
}
String compTypeName = null;
try {
int i = TYPENAME_PREFIX.length();
int s = i;
// dimension
while (_xmlTypeName.charAt(i) != '_') {
++i;
}
int dim;
if (s == i) {
dim = 1;
}
else {
dim = Integer.parseInt(_xmlTypeName.substring(s, i));
}
int[] dimensions = new int[dim];
// _of_
s = i;
i = s + "_of_".length();
if (!_xmlTypeName.substring(s, i).equals("_of_")) {
throw new DeserializerException(
"Invalid array type name: _of_ expected: "
+ _xmlTypeName.substring(s, i));
}
// component type name
compTypeName = _xmlTypeName.substring(i);
Class compType;
if (compTypeName.equals("byte")) {
compType = byte.class;
}
else if (compTypeName.equals("char")) {
compType = char.class;
}
else if (compTypeName.equals("double")) {
compType = double.class;
}
else if (compTypeName.equals("float")) {
compType = float.class;
}
else if (compTypeName.equals("int")) {
compType = int.class;
}
else if (compTypeName.equals("long")) {
compType = long.class;
}
else if (compTypeName.equals("short")) {
compType = short.class;
}
else if (compTypeName.equals("boolean")) {
compType = boolean.class;
}
else {
compType =
Class.forName(
ObjectSerializer.fixJavaTypeNameForInnerClass(compTypeName),
true,
classLoader);
}
Object array = Array.newInstance(compType, dimensions);
_type = array.getClass();
}
catch (ClassNotFoundException ex) {
throw new DeserializerException(
"can't get java class for: " + compTypeName + ": " + ex.toString());
}
catch (StringIndexOutOfBoundsException ex) {
throw new DeserializerException(
"Invalid array type name: " + _xmlTypeName);
}
}
/**
* @see CollectionSerializer#toJson(Object, Class, Map, PrintStream)
*/
public void toJson(Object obj, Class type, Map propertyMap,
PrintStream output) throws SerializerException {
if (obj == null) {
output.print("null");
} else {
output.print("[");
int len = java.lang.reflect.Array.getLength(obj);
for (int j = 0; j < len; ++j) {
Object comp = java.lang.reflect.Array.get(obj, j);
Class fieldType = (comp == null) ? _componentType : comp.getClass();
ComponentSerializer ser =
SerializerRegistry.getInstance().getSerializer(fieldType);
ser.toJson(comp, fieldType, propertyMap, output);
if (j < len - 1) {
output.print(",");
}
}
output.print("]");
}
}
}