package com.revolsys.record.io.format.esri.gdb.xml.model;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.namespace.QName;
import com.revolsys.datatype.DataTypes;
import com.revolsys.logging.Logs;
import com.revolsys.record.io.format.esri.gdb.xml.EsriGeodatabaseXmlConstants;
import com.revolsys.record.io.format.xml.XmlConstants;
import com.revolsys.record.io.format.xml.XmlWriter;
import com.revolsys.record.io.format.xml.XsiConstants;
import com.revolsys.util.CaseConverter;
import com.revolsys.util.JavaBeanUtil;
import com.revolsys.util.Property;
public class EsriGdbXmlSerializer implements EsriGeodatabaseXmlConstants {
public static String toString(final Object object) {
final StringWriter writer = new StringWriter();
final EsriGdbXmlSerializer serializer = new EsriGdbXmlSerializer(null, writer);
serializer.serialize(object);
writer.flush();
return writer.toString();
}
private final Map<Class<?>, Map<QName, Method>> classPropertyMethodMap = new HashMap<>();
private final Map<Class<?>, Set<QName>> classPropertyTagNamesMap = new HashMap<>();
private final Map<Class<?>, QName> classTagNameMap = new HashMap<>();
private final Map<Class<?>, QName> classTypeNameMap = new LinkedHashMap<>();
private final Map<Class<?>, QName> classXsiTagNameMap = new HashMap<>();
private XmlWriter out;
private final Map<QName, QName> tagNameChildTagNameMap = new HashMap<>();
private final Map<QName, QName> tagNameListElementTagNameMap = new HashMap<>();
private final Map<QName, QName> tagNameXsiTagNameMap = new HashMap<>();
private boolean writeFirstNamespace;
private boolean writeNamespaces;
private boolean writeNull;
private final Set<QName> xsiTypeTypeNames = new HashSet<>();
private EsriGdbXmlSerializer() {
addTagNameXsiTagName(METADATA, XML_PROPERTY_SET);
addTagNameChildTagName(METADATA, XML_DOC);
addClassProperties(SpatialReference.class, SPATIAL_REFERENCE, null, WKT, X_ORIGIN, Y_ORIGIN,
XY_SCALE, Z_ORIGIN, Z_SCALE, M_ORIGIN, M_SCALE, XY_TOLERANCE, Z_TOLERANCE, M_TOLERANCE,
HIGH_PRECISION, WKID);
addClassProperties(GeographicCoordinateSystem.class, SPATIAL_REFERENCE,
GEOGRAPHIC_COORDINATE_SYSTEM);
addClassProperties(ProjectedCoordinateSystem.class, SPATIAL_REFERENCE,
PROJECTED_COORDINATE_SYSTEM);
addClassProperties(GeometryDef.class, GEOMETRY_DEF, GEOMETRY_DEF, AVG_NUM_POINTS, GEOMETRY_TYPE,
HAS_M, HAS_Z, SPATIAL_REFERENCE, GRID_SIZE_0, GRID_SIZE_1, GRID_SIZE_2);
addClassProperties(Domain.class, DOMAIN, null, DOMAIN_NAME, FIELD_TYPE, MERGE_POLICY,
SPLIT_POLICY, DESCRIPTION, OWNER, CODED_VALUES);
addClassProperties(CodedValueDomain.class, DOMAIN, CODED_VALUE_DOMAIN);
addTagNameXsiTagName(CODED_VALUES, ARRAY_OF_CODED_VALUE);
addClassProperties(CodedValue.class, CODED_VALUE, CODED_VALUE, NAME, CODE);
addClassProperties(Field.class, FIELD, FIELD, NAME, TYPE, IS_NULLABLE, LENGTH, PRECISION, SCALE,
REQUIRED, EDIATBLE, DOMAIN_FIXED, GEOMETRY_DEF, ALIAS_NAME, MODEL_NAME, DEFAULT_VALUE,
DOMAIN);
addTagNameXsiTagName(FIELD_ARRAY, ARRAY_OF_FIELD);
addTagNameXsiTagName(FIELDS, FIELDS);
addTagNameChildTagName(FIELDS, FIELD_ARRAY);
addClassProperties(Index.class, INDEX, INDEX, NAME, IS_UNIQUE, IS_ASCENDING, FIELDS);
addTagNameXsiTagName(INDEX_ARRAY, ARRAY_OF_INDEX);
addTagNameXsiTagName(INDEXES, INDEXES);
addTagNameChildTagName(INDEXES, INDEX_ARRAY);
addClassProperties(PropertySetProperty.class, PROPERTY_SET_PROPERTY, null, KEY, VALUE);
addTagNameXsiTagName(PROPERTY_ARRAY, ARRAY_OF_PROPERTY_SET_PROPERTY);
addTagNameXsiTagName(EXTENSION_PROPERTIES, PROPERTY_SET);
addTagNameChildTagName(EXTENSION_PROPERTIES, PROPERTY_ARRAY);
addTagNameListElementTagName(RELATIONSHIP_CLASS_NAMES, NAME);
addTagNameXsiTagName(RELATIONSHIP_CLASS_NAMES, NAMES);
addTagNameListElementTagName(SUBTYPES, SUBTYPE);
addClassProperties(Subtype.class, SUBTYPE, null, SUBTYPE_NAME, SUBTYPE_CODE, FIELD_INFOS);
addClassProperties(SubtypeFieldInfo.class, SUBTYPE_FIELD_INFO, null, FIELD_NAME, DOMAIN_NAME,
DEFAULT_VALUE);
addClassProperties(EnvelopeN.class, ENVELOPE, ENVELOPE_N, X_MIN, Y_MIN, X_MAX, Y_MAX, Z_MIN,
Z_MAX, M_MIN, M_MAX, SPATIAL_REFERENCE);
addTagNameXsiTagName(CONTROLLER_MEMBERSHIPS, ARRAY_OF_CONTROLLER_MEMBERSHIP);
addClassProperties(DataElement.class, DATA_ELEMENT, DATA_ELEMENT, CATALOG_PATH, NAME,
CHILDREN_EXPANDED, FULL_PROPS_RETRIEVED, METADATA_RETRIEVED, METADATA, CHILDREN);
addClassProperties(DEDataset.class, DATA_ELEMENT, DE_DATASET, DATASET_TYPE, DSID, VERSIONED,
CAN_VERSION, CONFIGURATION_KEYWORD);
addClassProperties(DEGeoDataset.class, DATA_ELEMENT, DE_GEO_DATASET, EXTENT, SPATIAL_REFERENCE);
addClassProperties(DEFeatureDataset.class, DATA_ELEMENT, DE_FEATURE_DATASET, EXTENT,
SPATIAL_REFERENCE);
addClassProperties(DETable.class, DATA_ELEMENT, DE_TABLE, HAS_OID, OBJECT_ID_FIELD_NAME, FIELDS,
INDEXES, CLSID, EXTCLSID, RELATIONSHIP_CLASS_NAMES, ALIAS_NAME, MODEL_NAME, HAS_GLOBAL_ID,
GLOBAL_ID_FIELD_NAME, RASTER_FIELD_NAME, EXTENSION_PROPERTIES, SUBTYPE_FIELD_NAME,
DEFAULT_SUBTYPE_CODE, SUBTYPES, CONTROLLER_MEMBERSHIPS);
addClassProperties(DEFeatureClass.class, DATA_ELEMENT, DE_FEATURE_CLASS, FEATURE_TYPE,
SHAPE_TYPE, SHAPE_FIELD_NAME, HAS_M, HAS_Z, HAS_SPATIAL_INDEX, AREA_FIELD_NAME,
LENGTH_FIELD_NAME, EXTENT, SPATIAL_REFERENCE);
addTagNameXsiTagName(DATASET_DEFINITIONS, ARRAY_OF_DATA_ELEMENT);
addTagNameXsiTagName(DOMAINS, ARRAY_OF_DOMAIN);
addClassProperties(WorkspaceDefinition.class, WORKSPACE_DEFINITION, WORKSPACE_DEFINITION,
WORKSPACE_TYPE, VERSION, DOMAINS, DATASET_DEFINITIONS, METADATA);
addTagNameListElementTagName(WORKSPACE_DATA, DATASET_DATA);
addTagNameXsiTagName(WORKSPACE_DATA, WORKSPACE_DATA);
addClassProperties(Workspace.class, WORKSPACE, null, WORKSPACE_DEFINITION, WORKSPACE_DATA);
this.classTypeNameMap.put(Byte.class, XmlConstants.XS_BYTE);
this.classTypeNameMap.put(Short.class, XmlConstants.XS_SHORT);
this.classTypeNameMap.put(Integer.class, XmlConstants.XS_INT);
this.classTypeNameMap.put(Float.class, XmlConstants.XS_FLOAT);
this.classTypeNameMap.put(Double.class, XmlConstants.XS_DOUBLE);
this.classTypeNameMap.put(String.class, XmlConstants.XS_STRING);
this.xsiTypeTypeNames.add(CODE);
}
public EsriGdbXmlSerializer(final String esriNamespaceUri, final Writer out) {
this(out);
if (esriNamespaceUri != null) {
this.out.setNamespaceAlias(_NAMESPACE_URI, esriNamespaceUri);
}
}
public EsriGdbXmlSerializer(final Writer out) {
this();
this.out = new XmlWriter(out);
this.out.setIndent(true);
this.out.startDocument("UTF-8");
this.out.setPrefix(XmlConstants.XML_SCHEMA);
this.out.setPrefix(XsiConstants.TYPE);
this.writeNamespaces = false;
this.writeFirstNamespace = true;
}
private void addClassProperties(final Class<?> objectClass, final QName tagName,
final QName xsiTagName, final Collection<QName> propertyNames) {
this.classTagNameMap.put(objectClass, tagName);
addClassXsiTagName(objectClass, xsiTagName);
final Set<QName> allPropertyNames = new LinkedHashSet<>();
addSuperclassPropertyNames(allPropertyNames, objectClass.getSuperclass());
allPropertyNames.addAll(propertyNames);
this.classPropertyTagNamesMap.put(objectClass, allPropertyNames);
}
private void addClassProperties(final Class<?> objectClass, final QName tagName,
final QName xsiTagName, final QName... propertyNames) {
addClassProperties(objectClass, tagName, xsiTagName, Arrays.asList(propertyNames));
}
protected void addClassPropertyMethod(final Class<?> objectClass, final QName propertyName,
final String methodName) {
Map<QName, Method> classMethods = this.classPropertyMethodMap.get(objectClass);
if (classMethods == null) {
classMethods = new HashMap<>();
this.classPropertyMethodMap.put(objectClass, classMethods);
}
final Method method = JavaBeanUtil.getMethod(EsriGdbXmlSerializer.class, methodName,
Object.class);
classMethods.put(propertyName, method);
}
private void addClassXsiTagName(final Class<?> objectClass, final QName tagName) {
if (tagName != null) {
this.classXsiTagNameMap.put(objectClass, tagName);
}
}
private void addSuperclassPropertyNames(final Set<QName> allPropertyNames,
final Class<?> objectClass) {
if (!objectClass.equals(Object.class)) {
addSuperclassPropertyNames(allPropertyNames, objectClass.getSuperclass());
final Set<QName> propertyNames = this.classPropertyTagNamesMap.get(objectClass);
if (propertyNames != null) {
allPropertyNames.addAll(propertyNames);
}
}
}
private void addTagNameChildTagName(final QName tagName, final QName xsiTagName) {
this.tagNameChildTagNameMap.put(tagName, xsiTagName);
}
private void addTagNameListElementTagName(final QName tagName, final QName xsiTagName) {
this.tagNameListElementTagNameMap.put(tagName, xsiTagName);
}
private void addTagNameXsiTagName(final QName tagName, final QName xsiTagName) {
this.tagNameXsiTagNameMap.put(tagName, xsiTagName);
}
public void close() {
this.out.flush();
this.out.close();
}
private void endTag(final QName tagName) {
final QName childTagName = this.tagNameChildTagNameMap.get(tagName);
if (childTagName != null) {
endTag(childTagName);
}
this.out.endTag();
}
private Method getClassPropertyMethod(final Class<?> objectClass, final QName propertyName) {
final Map<QName, Method> propertyMethodMap = this.classPropertyMethodMap.get(objectClass);
if (propertyMethodMap == null) {
return null;
} else {
return propertyMethodMap.get(propertyName);
}
}
public void serialize(final Object object) {
final Class<? extends Object> objectClass = object.getClass();
QName tagName = this.classTagNameMap.get(objectClass);
if (tagName == null) {
final Package classPackage = objectClass.getPackage();
final String packageName = classPackage.getName();
final String className = objectClass.getSimpleName();
tagName = new QName(packageName, className);
}
if (!startTag(tagName)) {
writeXsiTypeAttribute(tagName, objectClass);
}
serializeObjectProperties(tagName, object);
endTag(tagName);
}
@SuppressWarnings("rawtypes")
private void serializeObjectProperties(final QName tagName, final Object object) {
if (object != null) {
final Class<? extends Object> objectClass = object.getClass();
final Collection<QName> propertyTagNames = this.classPropertyTagNamesMap.get(objectClass);
if (propertyTagNames == null) {
if (object instanceof List) {
final Collection list = (Collection)object;
if (list.isEmpty()) {
this.out.closeStartTag();
this.out.setElementHasContent();
} else {
final QName listElementTagName = this.tagNameListElementTagNameMap.get(tagName);
if (listElementTagName == null) {
for (final Object value : list) {
serialize(value);
}
} else {
for (final Object value : list) {
if (!startTag(listElementTagName)) {
writeXsiTypeAttribute(listElementTagName, value);
}
serializeObjectProperties(listElementTagName, value);
endTag(listElementTagName);
}
}
}
} else {
String string;
if (object instanceof Double) {
final Double value = (Double)object;
if (Double.isFinite(value)) {
string = DataTypes.toString(value);
} else {
string = "0";
}
} else {
string = DataTypes.toString(object);
}
this.out.text(string);
}
} else {
for (final QName propertyTagName : propertyTagNames) {
String propertyName = propertyTagName.getLocalPart();
if (propertyName.length() > 1 && Character.isLowerCase(propertyName.charAt(1))) {
propertyName = CaseConverter.toLowerFirstChar(propertyName);
}
final String propertyName1 = propertyName;
final Object value = Property.getSimple(object, propertyName1);
if (this.writeNull || value != null) {
final Method method = getClassPropertyMethod(objectClass, propertyTagName);
if (method == null) {
if (!startTag(propertyTagName)) {
writeXsiTypeAttribute(propertyTagName, value);
}
serializeObjectProperties(propertyTagName, value);
endTag(propertyTagName);
} else {
JavaBeanUtil.method(method, this, value);
}
}
}
}
}
}
private boolean startTag(final QName tagName) {
if (this.writeNamespaces || this.writeFirstNamespace) {
this.out.startTag(tagName);
this.writeFirstNamespace = false;
} else {
this.out.startTag(null, tagName.getLocalPart());
}
final QName xsiTagName = this.tagNameXsiTagNameMap.get(tagName);
boolean hasXsi = false;
if (xsiTagName != null) {
this.out.xsiTypeAttribute(xsiTagName);
hasXsi = true;
}
final QName childTagName = this.tagNameChildTagNameMap.get(tagName);
if (childTagName != null) {
startTag(childTagName);
}
return hasXsi;
}
private void writeXsiTypeAttribute(final QName tagName,
final Class<? extends Object> objectClass) {
QName xsiTagName = this.classXsiTagNameMap.get(objectClass);
if (xsiTagName == null) {
if (this.xsiTypeTypeNames.contains(tagName)) {
xsiTagName = this.classTypeNameMap.get(objectClass);
if (xsiTagName == null) {
Logs.error(this, "No xsi:type configuration for class " + objectClass);
}
}
}
if (xsiTagName != null) {
this.out.xsiTypeAttribute(xsiTagName);
}
}
private void writeXsiTypeAttribute(final QName tagName, final Object value) {
if (value != null) {
final Class<?> valueClass = value.getClass();
writeXsiTypeAttribute(tagName, valueClass);
}
}
}