package com.skaringa.javaxml.serializers;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import com.skaringa.javaxml.PropertyKeys;
import com.skaringa.javaxml.SerializerException;
import com.skaringa.javaxml.handler.AttrImpl;
import com.skaringa.javaxml.handler.DocumentOutputHandlerInterface;
import com.skaringa.javaxml.impl.NSConstants;
import com.skaringa.javaxml.impl.PropertyHelper;
import com.skaringa.util.Log;
/**
* Abstract class that provides methods common for all serializers.
*/
public abstract class AbstractSerializer
implements com.skaringa.javaxml.serializers.ComponentSerializer {
/**
* This is used down in getFieldsToSerialize as a template for toArray().
*/
protected static final Field[] FIELD_TEMPLATE = new Field[0];
/**
* Write an element start tag to the output.
* It includes test for null and setting the type attribute.
* @param obj The object to write.
* @param type The type of the object.
* @param name The name of the object.
* @param propertyMap The output properties.
* @param output The handler used to write the output.
* @throws SerializerException If the element can't be written.
*/
protected final void startElement(
Object obj,
String type,
String name,
Map propertyMap,
DocumentOutputHandlerInterface output)
throws SerializerException {
boolean omitXsi =
PropertyHelper.parseBoolean(propertyMap, PropertyKeys.OMIT_XSI_TYPE);
boolean omitNil =
PropertyHelper.parseBoolean(propertyMap, PropertyKeys.OMIT_XSI_NIL);
AttrImpl attrs = new AttrImpl();
if (!omitXsi) {
attrs.addAttribute(
NSConstants.SCHEMA_INSTANCE_NS_NAME,
NSConstants.SCHEMA_INSTANCE_NS_PREFIX,
"type",
type);
}
if (obj == null && !omitNil) {
attrs.addAttribute(
NSConstants.SCHEMA_INSTANCE_NS_NAME,
NSConstants.SCHEMA_INSTANCE_NS_PREFIX,
"nil",
"true");
}
output.startElement(name, attrs);
}
/**
* Get the type of a field of an object.
* @param obj The object.
* @param fieldName Then field name.
* @return The type of the field or null if the field doesn't exist.
*/
public static Class getFieldType(Object obj, String fieldName) {
try {
Log.debug("getFieldType", fieldName);
Field field = getField(obj, fieldName);
return field.getType();
}
catch (NoSuchFieldException e) {
return null; // field not found
}
}
/**
* Get a field of an object. The field is identified by its name.
* @param obj The object.
* @param fieldName Then field name.
* @return The field.
* @exception NoSuchFieldException If the object doesn't have a field
* with the specified name.
*/
public static Field getField(Object obj, String fieldName)
throws NoSuchFieldException {
// get class of obj
Class c = obj.getClass();
while (!c.equals(java.lang.Object.class)) {
try {
// get field
return c.getDeclaredField(fieldName);
// BREAK
}
catch (NoSuchFieldException e) {
// examine the super class of this
c = c.getSuperclass();
}
// until the super class is java.lang.Object
}
throw new NoSuchFieldException(fieldName); // field not found
}
/**
* Get all fields to serialize for a type.
* These are all fields that are not internal, static, nor transient.
* @param type The type.
* @param fieldComparator The comparator used to order the fields.
* @return Array of fields to serialize.
*/
public static Field[] getFieldsToSerialize(
Class type,
Comparator fieldComparator) {
List result = new ArrayList();
Field[] fields = type.getDeclaredFields();
for (int i = 0; i < fields.length; ++i) {
String fieldName = fields[i].getName();
int modifiers = fields[i].getModifiers();
if (fieldName.indexOf('$') < 0
&& !Modifier.isTransient(modifiers)
&& !Modifier.isStatic(modifiers)) {
// surpress internal, static and transient fields
result.add(fields[i]);
}
}
if (fieldComparator != null) {
Collections.sort(result, fieldComparator);
}
return (Field[]) result.toArray(FIELD_TEMPLATE);
}
/**
* Write the XML schema definition for a collection.
* @param componentElementName The name of the elements of the collection.
* @param componentElementType The type of the elements of the collection.
* @param output The handler used to write the output.
* @throws SerializerException If the definition can't be written.
*/
protected final void writeXMLCollectionDef(
String componentElementName,
Class componentElementType,
DocumentOutputHandlerInterface output)
throws SerializerException {
AttrImpl attrs = new AttrImpl();
attrs.addAttribute("name", getXMLTypeName());
output.startElement("xsd:complexType", attrs);
output.startElement("xsd:sequence");
attrs = new AttrImpl();
attrs.addAttribute("name", componentElementName);
attrs.addAttribute("minOccurs", "0");
attrs.addAttribute("maxOccurs", "unbounded");
if (componentElementType == null) {
attrs.addAttribute("type", "xsd:anyType");
}
else {
ComponentSerializer ser =
SerializerRegistry.getInstance().getSerializer(componentElementType);
attrs.addAttribute("type", ser.getXMLTypeName());
}
attrs.addAttribute("nillable", "true");
output.startElement("xsd:element", attrs);
output.endElement("xsd:element");
output.endElement("xsd:sequence");
output.endElement("xsd:complexType");
}
/**
* Write the XML schema definition for a type that only extends a base type.
* @param output The handler used to write the output.
* @param baseType The XML type name of the base type.
* @throws SerializerException If the definition can't be written.
*/
protected final void writeXMLExtensionDef(
DocumentOutputHandlerInterface output,
String baseType)
throws SerializerException {
AttrImpl attr = new AttrImpl();
attr.addAttribute("name", getXMLTypeName());
output.startElement("xsd:complexType", attr);
output.startElement("xsd:complexContent");
attr = new AttrImpl();
attr.addAttribute("base", baseType);
output.startElement("xsd:extension", attr);
output.endElement("xsd:extension");
output.endElement("xsd:complexContent");
output.endElement("xsd:complexType");
}
protected void printEncodedStr(String text, PrintStream output) {
char[] ctext = text.toCharArray();
for (int i = 0; i < ctext.length; ++i) {
char c = ctext[i];
if (Character.isISOControl(c)) {
switch (c) {
case '\b':
output.print("\\b");
break;
case '\f':
output.print("\\f");
break;
case '\n':
output.print("\\n");
break;
case '\r':
output.print("\\r");
break;
case '\t':
output.print("\\t");
break;
default:
String us = Integer.toHexString(c);
output.print("\\u0000".substring(0, 6 - us.length()) + us);
break;
}
} else if (c == '"') {
output.print("\\\"");
} else if (c == '\\') {
output.print("\\\\");
} else {
output.print(c);
}
}
}
/**
* Substitute all occurences of oldString in target by newString.
* @param target The target of the substitution.
* @param oldString The old string to be replaced.
* @param newString The new string.
* @return The target with all occurences of oldString replaced by newString.
*/
protected static String subst(
String target,
String oldString,
String newString) {
StringBuffer res = new StringBuffer();
int pos = 0;
int rpos = target.indexOf(oldString, pos);
while (rpos >= 0) {
res.append(target.substring(pos, rpos));
res.append(newString);
pos = rpos + oldString.length();
rpos = target.indexOf(oldString, pos);
}
res.append(target.substring(pos));
return res.toString();
}
}