package er.extensions.appserver;
import java.io.UnsupportedEncodingException;
import java.sql.Date;
import java.util.Enumeration;
import org.apache.commons.lang3.CharEncoding;
import com.webobjects.appserver.xml.WOXMLCoder;
import com.webobjects.appserver.xml.WOXMLDecoder;
import com.webobjects.appserver.xml.WOXMLException;
import com.webobjects.appserver.xml._private._MappingModel;
import com.webobjects.eocontrol.EOEnterpriseObject;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSData;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSForwardException;
import com.webobjects.foundation.NSKeyValueCodingAdditions;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSTimestamp;
import er.extensions.foundation.ERXArrayUtilities;
import er.extensions.foundation.ERXStringUtilities;
/**
* WOXMLMappingCoder which adds sorting to attributes.
*
*
* @author ak
*/
public class ERXWOXMLCoder extends WOXMLCoder {
private _MappingModel _mappingModel;
/**
* Quick and dirty class to en- and decode the generic xml data to full-fledged objects that
* can be bound in the edit interface.
*
*
* @author ak
*/
public static class XMLData extends NSMutableDictionary {
/**
* Do I need to update serialVersionUID?
* See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the
* <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a>
*/
private static final long serialVersionUID = 1L;
public XMLData() {
}
public void completeDecoding() {
}
public void prepareForCoding() {
}
protected void takeValueForKeyPathIfNotPresent(Object object, String key) {
if(valueForKeyPath(key) == null) {
takeValueForKeyPath(object, key);
}
}
protected void clearEmptyValueForKeyPath(String key) {
if(valueForKeyPath(key) != null && ((String)valueForKeyPath(key)).length() == 0) {
takeValueForKeyPath(null, key);
}
}
protected void clearParentOnEmptyValueForKeyPath(String key) {
if(valueForKeyPath(key) != null && ((String)valueForKeyPath(key)).length() == 0) {
takeValueForKeyPath(null, ERXStringUtilities.keyPathWithoutLastProperty(key));
}
}
/**
* This works around a bug when the decoder reaches an empty tag an tries to create a dictionary from it.
* @param aValue value
* @param aKey key
*/
@Override
public void takeValueForKey(Object aValue, String aKey) {
if(aValue instanceof NSDictionary && ((NSDictionary)aValue).count() == 0) {
if(aValue.getClass() == NSMutableDictionary.class) {
aValue = null;
}
}
super.takeValueForKey(aValue, aKey);
}
/**
* Serializes to an XML string for the given data object conforming to the supplied model.
* @param data
* @param rootTag
* @param mappingUrl
* @return string representation
*/
public static String stringForData(XMLData data, String rootTag, String mappingUrl) {
data.prepareForCoding();
WOXMLCoder coder = new ERXWOXMLCoder(mappingUrl);
String result = coder.encodeRootObjectForKey(data, rootTag);
data.completeDecoding();
return result;
}
/**
* Deserializes the given string to an instance of XMLData.
* @param string
* @param mappingUrl
* @return xml data object
*/
public static XMLData dataForString(String string, String mappingUrl) {
WOXMLDecoder decoder = WOXMLDecoder.decoderWithMapping(mappingUrl);
XMLData data;
try {
data = (XMLData) decoder.decodeRootObject(new NSData(string.getBytes(CharEncoding.UTF_8)));
} catch (UnsupportedEncodingException e) {
throw NSForwardException._runtimeExceptionForThrowable(e);
}
data.completeDecoding();
return data;
}
}
public ERXWOXMLCoder(String s) {
_mappingModel = _MappingModel.mappingModelWithXMLFile(s);
}
@Override
public String xmlTagForClassNamed(String className) {
return _mappingModel.xmlTagForClassNamed(className);
}
@Override
public String xmlTagForPropertyKey(String key, String className) {
return _mappingModel.xmlTagForPropertyKey(key, className);
}
protected void _encodeEO(EOEnterpriseObject eoenterpriseobject) {
NSArray arr = sortedArray(eoenterpriseobject.attributeKeys());
for (Enumeration e = arr.objectEnumerator(); e.hasMoreElements();) {
String s = (String) e.nextElement();
encodeObjectForKey(eoenterpriseobject.valueForKey(s), s);
if (e.hasMoreElements())
cr();
}
}
protected Enumeration sortedEnumeration(Enumeration e) {
if(e != null) {
NSMutableArray arr = new NSMutableArray();
for (; e.hasMoreElements();) {
String element = (String) e.nextElement();
arr.addObject(element);
}
e = sortedArray(arr).objectEnumerator();
}
return e;
}
protected NSArray sortedArray(NSArray arr) {
if(arr != null) {
arr = ERXArrayUtilities.sortedArraySortedWithKey(arr, "toString");
}
return arr;
}
protected void encodeDictionaryWithXMLTag(NSDictionary dict, String tag) {
NSArray nsarray = sortedArray(dict.allKeys());
for (Enumeration e = nsarray.objectEnumerator(); e.hasMoreElements();) {
String key = (String) e.nextElement();
String tagForKey = _mappingModel.xmlTagForPropertyKeyInXMLTag(key, tag);
encodeObjectWithXMLTag(dict.objectForKey(key), tagForKey, false, _MappingModel.OUTPUT_PROPERTY_TAG);
if (e.hasMoreElements())
cr();
}
}
protected void encodeArrayWithXMLTag(NSArray arr, String tag, boolean codeBasedOnClass, int outputTags) {
for(Enumeration e = arr.objectEnumerator(); e.hasMoreElements(); ) {
encodeObjectWithXMLTag(e.nextElement(), tag, codeBasedOnClass, outputTags);
if (e.hasMoreElements())
cr();
}
}
@Override
public void encodeObjectForKey(Object obj, String key) {
String tag = _mappingModel.xmlTagForPropertyKey(key, encodedClassName());
encodeObjectWithXMLTag(obj, tag, false, _MappingModel.OUTPUT_PROPERTY_TAG);
}
public void encodeObjectWithXMLTag(Object obj, String baseTag, boolean codeBasedOnClass, int outputTags) {
if (obj instanceof NSArray) {
encodeArrayWithXMLTag((NSArray) obj, baseTag, codeBasedOnClass, outputTags);
} else {
boolean isBaseType = (obj instanceof String) || (obj instanceof Boolean) || (obj instanceof Date) || (obj instanceof Number);
String className = obj == null ? null : obj.getClass().getName();
String tagForClassName = className == null ? "" : xmlTagForClassNamed(className);
String tag = null;
if (outputTags != _MappingModel.OUTPUT_NEITHER_TAG && (!isBaseType || outputTags != _MappingModel.OUTPUT_CLASS_TAG)) {
tag = (outputTags & _MappingModel.OUTPUT_CLASS_TAG) == 0 || isBaseType ? baseTag : tagForClassName;
if (outputTags == _MappingModel.OUTPUT_BOTH_TAGS) {
_buffer.append('<');
_buffer.append(baseTag);
_buffer.append('>');
}
_buffer.append('<');
_buffer.append(tag);
if (obj != null) {
Enumeration attributes = codeBasedOnClass ? _mappingModel.attributeKeysForClassNamed(className) : _mappingModel.attributeKeysForXMLTag(baseTag);
attributes = sortedEnumeration(attributes);
if (attributes != null)
for (; attributes.hasMoreElements();) {
String key = (String) attributes.nextElement();
_buffer.append(' ');
_buffer.append(codeBasedOnClass ? xmlTagForPropertyKey(key, className) : _mappingModel.xmlTagForPropertyKeyInXMLTag(key, baseTag));
_buffer.append('=');
_buffer.append('"');
Object value = NSKeyValueCodingAdditions.Utility.valueForKeyPath(obj, key);
_buffer.append(value == null ? "" : escapeString(value.toString()));
_buffer.append('"');
}
}
_buffer.append('>');
}
if (obj != null) {
if (obj instanceof String) {
_buffer.append(escapeString((String) obj));
} else if (obj instanceof NSTimestamp) {
_buffer.append(obj);
} else if (obj instanceof Boolean) {
_buffer.append(obj);
} else if (obj instanceof Number) {
// FIXME AK: this will break when using BigDecimals and JDK 1.5
_buffer.append(obj);
} else if (codeBasedOnClass || _mappingModel.hasMappingForXMLTag(baseTag)) {
Enumeration contentKeys = codeBasedOnClass ? _mappingModel.contentsKeysForClassNamed(className) : _mappingModel.contentsKeysForXMLTag(baseTag);
contentKeys = sortedEnumeration(contentKeys);
if (contentKeys != null) {
_encodedClasses.push(className);
boolean hadContent = false;
for (; contentKeys.hasMoreElements(); ) {
cr();
hadContent = true;
String key = (String) contentKeys.nextElement();
Object value = NSKeyValueCodingAdditions.Utility.valueForKeyPath(obj, key);
String propertyTag = codeBasedOnClass ? xmlTagForPropertyKey(key, className) : _mappingModel.xmlTagForPropertyKeyInXMLTag(key, baseTag);
boolean codeBasedOnClassForPropertyKey = _mappingModel.codeBasedOnClassForPropertyKey(key, className);
int outputTagsForPropertyKey = _mappingModel.outputTagsForPropertyKey(key, className);
encodeObjectWithXMLTag(value, propertyTag, codeBasedOnClassForPropertyKey, outputTagsForPropertyKey);
}
_encodedClasses.pop();
if (hadContent)
cr();
}
} else if (obj instanceof NSDictionary) {
encodeDictionaryWithXMLTag((NSDictionary) obj, baseTag);
} else if (obj instanceof EOEnterpriseObject) {
_encodeEO((EOEnterpriseObject) obj);
} else {
throw new WOXMLException("Unable to encode in XML objects of class " + className);
}
}
if (outputTags != _MappingModel.OUTPUT_NEITHER_TAG && (!isBaseType || outputTags != _MappingModel.OUTPUT_CLASS_TAG)) {
_buffer.append('<');
_buffer.append('/');
_buffer.append(tag);
_buffer.append('>');
if (outputTags == _MappingModel.OUTPUT_BOTH_TAGS && !isBaseType) {
_buffer.append('<');
_buffer.append('/');
_buffer.append(baseTag);
_buffer.append('>');
}
}
}
}
@Override
public void encodeBooleanForKey(boolean flag, String s) {
encodeStringInTag(flag ? "True" : "False", xmlTagForPropertyKey(s, encodedClassName()), "boolean");
}
@Override
public void encodeIntForKey(int i, String s) {
encodeStringInTag(Integer.toString(i), xmlTagForPropertyKey(s, encodedClassName()), "int");
}
@Override
public void encodeFloatForKey(float f, String s) {
encodeStringInTag(Float.toString(f), xmlTagForPropertyKey(s, encodedClassName()), "float");
}
@Override
public void encodeDoubleForKey(double d, String s) {
encodeStringInTag(Double.toString(d), xmlTagForPropertyKey(s, encodedClassName()), "double");
}
@Override
protected void _encodeNullForKey(String s) {
encodeStringInTag("null", s, "?");
}
@Override
public synchronized String encodeRootObjectForKey(Object obj, String s) {
if (obj != null) {
_buffer = new StringBuffer(1024);
_buffer.append(xmlDeclaration);
encodeObjectWithXMLTag(obj, "ignored", true, _MappingModel.OUTPUT_CLASS_TAG);
return _buffer.toString();
}
return null;
}
}