package com.vci.rmip.workflow.client.editor.code;
|
|
import java.lang.reflect.Array;
|
import java.lang.reflect.Field;
|
import java.lang.reflect.Method;
|
import java.lang.reflect.Modifier;
|
import java.util.Collection;
|
import java.util.HashSet;
|
import java.util.Hashtable;
|
import java.util.Iterator;
|
import java.util.Map;
|
import java.util.Set;
|
|
import org.w3c.dom.Element;
|
import org.w3c.dom.NamedNodeMap;
|
import org.w3c.dom.Node;
|
|
import com.mxgraph.io.mxCodecRegistry;
|
import com.mxgraph.io.mxObjectCodec;
|
import com.mxgraph.util.mxUtils;
|
import com.vci.rmip.workflow.client.editor.ui.IProcessProperty;
|
|
public class GraphObjectCodec {
|
|
/**
|
* Immutable empty set.
|
*/
|
private static Set<String> EMPTY_SET = new HashSet<String>();
|
|
/**
|
* Holds the template object associated with this codec.
|
*/
|
protected Object template;
|
|
/**
|
* Array containing the variable names that should be ignored by the codec.
|
*/
|
protected Set<String> exclude;
|
|
/**
|
* Array containing the variable names that should be turned into or
|
* converted from references. See <GraphCodec.getId> and <GraphCodec.getObject>.
|
*/
|
protected Set<String> idrefs;
|
|
/**
|
* Maps from from fieldnames to XML attribute names.
|
*/
|
protected Map<String, String> mapping;
|
|
/**
|
* Maps from from XML attribute names to fieldnames.
|
*/
|
protected Map<String, String> reverse;
|
|
/**
|
* Constructs a new codec for the specified template object.
|
*/
|
public GraphObjectCodec(Object template)
|
{
|
this(template, null, null, null);
|
}
|
|
/**
|
* Constructs a new codec for the specified template object. The variables
|
* in the optional exclude array are ignored by the codec. Variables in the
|
* optional idrefs array are turned into references in the XML. The
|
* optional mapping may be used to map from variable names to XML
|
* attributes. The argument is created as follows:
|
*
|
* @param template Prototypical instance of the object to be encoded/decoded.
|
* @param exclude Optional array of fieldnames to be ignored.
|
* @param idrefs Optional array of fieldnames to be converted to/from references.
|
* @param mapping Optional mapping from field- to attributenames.
|
*/
|
public GraphObjectCodec(Object template, String[] exclude, String[] idrefs,
|
Map<String, String> mapping)
|
{
|
this.template = template;
|
|
if (exclude != null)
|
{
|
this.exclude = new HashSet<String>();
|
|
for (int i = 0; i < exclude.length; i++)
|
{
|
this.exclude.add(exclude[i]);
|
}
|
}
|
else
|
{
|
this.exclude = EMPTY_SET;
|
}
|
|
if (idrefs != null)
|
{
|
this.idrefs = new HashSet<String>();
|
|
for (int i = 0; i < idrefs.length; i++)
|
{
|
this.idrefs.add(idrefs[i]);
|
}
|
}
|
else
|
{
|
this.idrefs = EMPTY_SET;
|
}
|
|
if (mapping == null)
|
{
|
mapping = new Hashtable<String, String>();
|
}
|
|
this.mapping = mapping;
|
|
reverse = new Hashtable<String, String>();
|
Iterator<Map.Entry<String, String>> it = mapping.entrySet().iterator();
|
|
while (it.hasNext())
|
{
|
Map.Entry<String, String> e = it.next();
|
reverse.put(e.getValue(), e.getKey());
|
}
|
}
|
|
/**
|
* Returns the name used for the nodenames and lookup of the codec when
|
* classes are encoded and nodes are decoded. For classes to work with
|
* this the codec registry automatically adds an alias for the classname
|
* if that is different than what this returns. The default implementation
|
* returns the classname of the template class.
|
*
|
* Here is an example on how to use this for renaming mxCell nodes:
|
* <code>
|
* mxCodecRegistry.register(new mxCellCodec()
|
* {
|
* public String getName()
|
* {
|
* return "anotherName";
|
* }
|
* });
|
* </code>
|
*/
|
public String getName()
|
{
|
return mxCodecRegistry.getName(getTemplate());
|
}
|
|
/**
|
* Returns the template object associated with this codec.
|
*
|
* @return Returns the template object.
|
*/
|
public Object getTemplate()
|
{
|
return template;
|
}
|
|
/**
|
* Returns a new instance of the template object for representing the given
|
* node.
|
*
|
* @param node XML node that the object is going to represent.
|
* @return Returns a new template instance.
|
*/
|
protected Object cloneTemplate(Node node)
|
{
|
Object obj = null;
|
|
try
|
{
|
if (template.getClass().isEnum())
|
{
|
obj = template.getClass().getEnumConstants()[0];
|
}
|
else
|
{
|
obj = template.getClass().newInstance();
|
}
|
|
// Special case: Check if the collection
|
// should be a map. This is if the first
|
// child has an "as"-attribute. This
|
// assumes that all childs will have
|
// as attributes in this case. This is
|
// required because in JavaScript, the
|
// map and array object are the same.
|
if (obj instanceof Collection)
|
{
|
node = node.getFirstChild();
|
|
if (node != null && node instanceof Element
|
&& ((Element) node).hasAttribute("as"))
|
{
|
obj = new Hashtable<Object, Object>();
|
}
|
}
|
}
|
catch (InstantiationException e)
|
{
|
// ignore
|
e.printStackTrace();
|
}
|
catch (IllegalAccessException e)
|
{
|
// ignore
|
e.printStackTrace();
|
}
|
|
return obj;
|
}
|
|
/**
|
* Returns true if the given attribute is to be ignored by the codec. This
|
* implementation returns true if the given fieldname is in
|
* {@link #exclude}.
|
*
|
* @param obj Object instance that contains the field.
|
* @param attr Fieldname of the field.
|
* @param value Value of the field.
|
* @param write Boolean indicating if the field is being encoded or
|
* decoded. write is true if the field is being encoded, else it is
|
* being decoded.
|
* @return Returns true if the given attribute should be ignored.
|
*/
|
public boolean isExcluded(Object obj, String attr, Object value,
|
boolean write)
|
{
|
return exclude.contains(attr);
|
}
|
|
/**
|
* Returns true if the given fieldname is to be treated as a textual
|
* reference (ID). This implementation returns true if the given fieldname
|
* is in {@link #idrefs}.
|
*
|
* @param obj Object instance that contains the field.
|
* @param attr Fieldname of the field.
|
* @param value Value of the field.
|
* @param isWrite Boolean indicating if the field is being encoded or
|
* decoded. isWrite is true if the field is being encoded, else it is being
|
* decoded.
|
* @return Returns true if the given attribute should be handled as a
|
* reference.
|
*/
|
public boolean isReference(Object obj, String attr, Object value,
|
boolean isWrite)
|
{
|
return idrefs.contains(attr);
|
}
|
|
/**
|
* Encodes the specified object and returns a node representing then given
|
* object. Calls beforeEncode after creating the node and afterEncode
|
* with the resulting node after processing.
|
*
|
* Enc is a reference to the calling encoder. It is used to encode complex
|
* objects and create references.
|
*
|
* This implementation encodes all variables of an object according to the
|
* following rules:
|
*
|
* <ul>
|
* <li>If the variable name is in {@link #exclude} then it is ignored.</li>
|
* <li>If the variable name is in {@link #idrefs} then
|
* {@link GraphCodec#getId(Object)} is used to replace the object with its ID.
|
* </li>
|
* <li>The variable name is mapped using {@link #mapping}.</li>
|
* <li>If obj is an array and the variable name is numeric (ie. an index) then it
|
* is not encoded.</li>
|
* <li>If the value is an object, then the codec is used to create a child
|
* node with the variable name encoded into the "as" attribute.</li>
|
* <li>Else, if {@link com.mxgraph.io.GraphCodec#isEncodeDefaults()} is true or
|
* the value differs from the template value, then ...
|
* <ul>
|
* <li>... if obj is not an array, then the value is mapped to an
|
* attribute.</li>
|
* <li>... else if obj is an array, the value is mapped to an add child
|
* with a value attribute or a text child node, if the value is a function.
|
* </li>
|
* </ul>
|
* </li>
|
* </ul>
|
*
|
* If no ID exists for a variable in {@link #idrefs} or if an object cannot be
|
* encoded, a warning is printed to System.err.
|
*
|
* @param enc Codec that controls the encoding process.
|
* @param obj Object to be encoded.
|
* @return Returns the resulting XML node that represents the given object.
|
*/
|
public Node encode(GraphCodec enc, Object obj)
|
{
|
Node node = enc.document.createElement(getName());
|
|
obj = beforeEncode(enc, obj, node);
|
encodeObject(enc, obj, node);
|
|
return afterEncode(enc, obj, node);
|
}
|
|
/**
|
* Encodes the value of each member in then given obj
|
* into the given node using {@link #encodeFields(mxCodec, Object, Node)}
|
* and {@link #encodeElements(mxCodec, Object, Node)}.
|
*
|
* @param enc Codec that controls the encoding process.
|
* @param obj Object to be encoded.
|
* @param node XML node that contains the encoded object.
|
*/
|
protected void encodeObject(GraphCodec enc, Object obj, Node node)
|
{
|
GraphCodec.setAttribute(node, "id", enc.getId(obj));
|
encodeFields(enc, obj, node);
|
encodeElements(enc, obj, node);
|
}
|
|
/**
|
* Encodes the declared fields of the given object into the given node.
|
*
|
* @param enc Codec that controls the encoding process.
|
* @param obj Object whose fields should be encoded.
|
* @param node XML node that contains the encoded object.
|
*/
|
protected void encodeFields(GraphCodec enc, Object obj, Node node)
|
{
|
Class<?> type = obj.getClass();
|
|
while (type != null)
|
{
|
Field[] fields = type.getDeclaredFields();
|
|
for (int i = 0; i < fields.length; i++)
|
{
|
Field f = fields[i];
|
|
if ((f.getModifiers() & Modifier.TRANSIENT) != Modifier.TRANSIENT)
|
{
|
String fieldname = f.getName();
|
Object value = getFieldValue(obj, fieldname);
|
encodeValue(enc, obj, fieldname, value, node);
|
}
|
}
|
|
type = type.getSuperclass();
|
}
|
}
|
|
/**
|
* Encodes the child objects of arrays, maps and collections.
|
*
|
* @param enc Codec that controls the encoding process.
|
* @param obj Object whose child objects should be encoded.
|
* @param node XML node that contains the encoded object.
|
*/
|
protected void encodeElements(GraphCodec enc, Object obj, Node node)
|
{
|
if (obj.getClass().isArray())
|
{
|
Object[] tmp = (Object[]) obj;
|
|
for (int i = 0; i < tmp.length; i++)
|
{
|
encodeValue(enc, obj, null, tmp[i], node);
|
}
|
}
|
else if (obj instanceof Map)
|
{
|
Iterator<Map.Entry> it = ((Map) obj).entrySet().iterator();
|
|
while (it.hasNext())
|
{
|
Map.Entry e = it.next();
|
encodeValue(enc, obj, String.valueOf(e.getKey()), e.getValue(),
|
node);
|
}
|
}
|
else if (obj instanceof Collection)
|
{
|
Iterator<?> it = ((Collection<?>) obj).iterator();
|
|
while (it.hasNext())
|
{
|
Object value = it.next();
|
encodeValue(enc, obj, null, value, node);
|
}
|
}
|
}
|
|
/**
|
* Converts the given value according to the mappings
|
* and id-refs in this codec and uses
|
* {@link #writeAttribute(GraphCodec, Object, String, Object, Node)}
|
* to write the attribute into the given node.
|
*
|
* @param enc Codec that controls the encoding process.
|
* @param obj Object whose field is going to be encoded.
|
* @param fieldname Name if the field to be encoded.
|
* @param value Value of the property to be encoded.
|
* @param node XML node that contains the encoded object.
|
*/
|
protected void encodeValue(GraphCodec enc, Object obj, String fieldname,
|
Object value, Node node)
|
{
|
if (value != null && !isExcluded(obj, fieldname, value, true))
|
{
|
if (isReference(obj, fieldname, value, true))
|
{
|
Object tmp = enc.getId(value);
|
|
if (tmp == null)
|
{
|
System.err.println("GraphObjectCodec.encode: No ID for "
|
+ getName() + "." + fieldname + "=" + value);
|
return; // exit
|
}
|
|
value = tmp;
|
}
|
|
Object defaultValue = getFieldValue(template, fieldname);
|
|
if (fieldname == null || enc.isEncodeDefaults()
|
|| defaultValue == null || !defaultValue.equals(value))
|
{
|
writeAttribute(enc, obj, getAttributeName(fieldname), value,
|
node);
|
}
|
}
|
}
|
|
/**
|
* Returns true if the given object is a primitive value.
|
*
|
* @param value Object that should be checked.
|
* @return Returns true if the given object is a primitive value.
|
*/
|
protected boolean isPrimitiveValue(Object value)
|
{
|
return value instanceof String || value instanceof Boolean
|
|| value instanceof Character || value instanceof Byte
|
|| value instanceof Short || value instanceof Integer
|
|| value instanceof Long || value instanceof Float
|
|| value instanceof Double || value.getClass().isPrimitive();
|
}
|
|
/**
|
* Writes the given value into node using writePrimitiveAttribute
|
* or writeComplexAttribute depending on the type of the value.
|
*/
|
protected void writeAttribute(GraphCodec enc, Object obj, String attr,
|
Object value, Node node)
|
{
|
value = convertValueToXml(value);
|
if(value instanceof IProcessProperty){
|
GraphCodec.setAttribute(node, attr, value.toString());
|
}else{
|
if (isPrimitiveValue(value))
|
{
|
writePrimitiveAttribute(enc, obj, attr, value, node);
|
}
|
else
|
{
|
writeComplexAttribute(enc, obj, attr, value, node);
|
}
|
}
|
}
|
|
/**
|
* Writes the given value as an attribute of the given node.
|
*/
|
protected void writePrimitiveAttribute(GraphCodec enc, Object obj,
|
String attr, Object value, Node node)
|
{
|
if (attr == null || obj instanceof Map)
|
{
|
Node child = enc.document.createElement("add");
|
|
if (attr != null)
|
{
|
GraphCodec.setAttribute(child, "as", attr);
|
}
|
|
GraphCodec.setAttribute(child, "value", value);
|
node.appendChild(child);
|
}
|
else
|
{
|
GraphCodec.setAttribute(node, attr, value);
|
}
|
}
|
|
/**
|
* Writes the given value as a child node of the given node.
|
*/
|
protected void writeComplexAttribute(GraphCodec enc, Object obj, String attr,
|
Object value, Node node)
|
{
|
Node child = enc.encode(value);
|
|
if (child != null)
|
{
|
if (attr != null)
|
{
|
GraphCodec.setAttribute(child, "as", attr);
|
}
|
|
node.appendChild(child);
|
}
|
else
|
{
|
System.err.println("mxObjectCodec.encode: No node for " + getName()
|
+ "." + attr + ": " + value);
|
}
|
}
|
|
/**
|
* Converts true to "1" and false to "0". All other values are ignored.
|
*/
|
protected Object convertValueToXml(Object value)
|
{
|
if (value instanceof Boolean)
|
{
|
value = ((Boolean) value).booleanValue() ? "1" : "0";
|
}
|
|
return value;
|
}
|
|
/**
|
* Converts XML attribute values to object of the given type.
|
*/
|
protected Object convertValueFromXml(Class<?> type, Object value)
|
{
|
if (value instanceof String)
|
{
|
String tmp = (String) value;
|
|
if (type.equals(boolean.class) || type == Boolean.class)
|
{
|
if (tmp.equals("1") || tmp.equals("0"))
|
{
|
tmp = (tmp.equals("1")) ? "true" : "false";
|
}
|
|
value = Boolean.valueOf(tmp);
|
}
|
else if (type.equals(char.class) || type == Character.class)
|
{
|
value = Character.valueOf(tmp.charAt(0));
|
}
|
else if (type.equals(byte.class) || type == Byte.class)
|
{
|
value = Byte.valueOf(tmp);
|
}
|
else if (type.equals(short.class) || type == Short.class)
|
{
|
value = Short.valueOf(tmp);
|
}
|
else if (type.equals(int.class) || type == Integer.class)
|
{
|
value = Integer.valueOf(tmp);
|
}
|
else if (type.equals(long.class) || type == Long.class)
|
{
|
value = Long.valueOf(tmp);
|
}
|
else if (type.equals(float.class) || type == Float.class)
|
{
|
value = Float.valueOf(tmp);
|
}
|
else if (type.equals(double.class) || type == Double.class)
|
{
|
value = Double.valueOf(tmp);
|
}
|
}
|
|
return value;
|
}
|
|
/**
|
* Returns the XML node attribute name for the given Java field name. That
|
* is, it returns the mapping of the field name.
|
*/
|
protected String getAttributeName(String fieldname)
|
{
|
if (fieldname != null)
|
{
|
Object mapped = mapping.get(fieldname);
|
|
if (mapped != null)
|
{
|
fieldname = mapped.toString();
|
}
|
}
|
|
return fieldname;
|
}
|
|
/**
|
* Returns the Java field name for the given XML attribute name. That is, it
|
* returns the reverse mapping of the attribute name.
|
*
|
* @param attributename
|
* The attribute name to be mapped.
|
* @return String that represents the mapped field name.
|
*/
|
protected String getFieldName(String attributename)
|
{
|
if (attributename != null)
|
{
|
Object mapped = reverse.get(attributename);
|
|
if (mapped != null)
|
{
|
attributename = mapped.toString();
|
}
|
}
|
|
return attributename;
|
}
|
|
/**
|
* Returns the field with the specified name.
|
*/
|
protected Field getField(Object obj, String fieldname)
|
{
|
Class<?> type = obj.getClass();
|
|
while (type != null)
|
{
|
try
|
{
|
Field field = type.getDeclaredField(fieldname);
|
|
if (field != null)
|
{
|
return field;
|
}
|
}
|
catch (Exception e)
|
{
|
// ignore
|
}
|
|
type = type.getSuperclass();
|
}
|
|
return null;
|
}
|
|
/**
|
* Returns the accessor (getter, setter) for the specified field.
|
*/
|
protected Method getAccessor(Object obj, Field field, boolean isGetter)
|
{
|
String name = field.getName();
|
name = name.substring(0, 1).toUpperCase() + name.substring(1);
|
|
if (!isGetter)
|
{
|
name = "set" + name;
|
}
|
else if (boolean.class.isAssignableFrom(field.getType()))
|
{
|
name = "is" + name;
|
}
|
else
|
{
|
name = "get" + name;
|
}
|
|
try
|
{
|
if (isGetter)
|
{
|
return getMethod(obj, name, null);
|
}
|
else
|
{
|
return getMethod(obj, name, new Class[] { field.getType() });
|
}
|
}
|
catch (Exception e1)
|
{
|
// ignore
|
}
|
|
return null;
|
}
|
|
/**
|
* Returns the method with the specified signature.
|
*/
|
protected Method getMethod(Object obj, String methodname, Class[] params)
|
{
|
Class<?> type = obj.getClass();
|
|
while (type != null)
|
{
|
try
|
{
|
Method method = type.getDeclaredMethod(methodname, params);
|
|
if (method != null)
|
{
|
return method;
|
}
|
}
|
catch (Exception e)
|
{
|
// ignore
|
}
|
|
type = type.getSuperclass();
|
}
|
return null;
|
}
|
|
/**
|
* Returns the value of the field with the specified name in the specified
|
* object instance.
|
*/
|
protected Object getFieldValue(Object obj, String fieldname)
|
{
|
Object value = null;
|
|
if (obj != null && fieldname != null)
|
{
|
Field field = getField(obj, fieldname);
|
|
try
|
{
|
if (field != null)
|
{
|
value = field.get(obj);
|
}
|
}
|
catch (IllegalAccessException e1)
|
{
|
if (field != null)
|
{
|
try
|
{
|
Method method = getAccessor(obj, field, true);
|
value = method.invoke(obj, (Object[]) null);
|
}
|
catch (Exception e2)
|
{
|
// ignore
|
}
|
}
|
}
|
catch (Exception e)
|
{
|
// ignore
|
}
|
}
|
|
return value;
|
}
|
|
/**
|
* Sets the value of the field with the specified name
|
* in the specified object instance.
|
*/
|
protected void setFieldValue(Object obj, String fieldname, Object value)
|
{
|
Field field = null;
|
|
try
|
{
|
field = getField(obj, fieldname);
|
|
if (field.getType() == Boolean.class)
|
{
|
value = (value.equals("1") || String.valueOf(value)
|
.equalsIgnoreCase("true")) ? Boolean.TRUE
|
: Boolean.FALSE;
|
}
|
|
field.set(obj, value);
|
}
|
catch (IllegalAccessException e1)
|
{
|
if (field != null)
|
{
|
try
|
{
|
Method method = getAccessor(obj, field, false);
|
Class<?> type = method.getParameterTypes()[0];
|
value = convertValueFromXml(type, value);
|
|
// Converts collection to a typed array before setting
|
if (type.isArray() && value instanceof Collection)
|
{
|
Collection<?> coll = (Collection<?>) value;
|
value = coll.toArray((Object[]) Array.newInstance(
|
type.getComponentType(), coll.size()));
|
}
|
|
method.invoke(obj, new Object[] { value });
|
}
|
catch (Exception e2)
|
{
|
System.err.println("setFieldValue: " + e2 + " on "
|
+ obj.getClass().getSimpleName() + "." + fieldname
|
+ " (" + field.getType().getSimpleName() + ") = "
|
+ value + " (" + value.getClass().getSimpleName()
|
+ ")");
|
}
|
}
|
}
|
catch (Exception e)
|
{
|
// ignore
|
}
|
}
|
|
/**
|
* Hook for subclassers to pre-process the object before encoding. This
|
* returns the input object. The return value of this function is used in
|
* encode to perform the default encoding into the given node.
|
*
|
* @param enc Codec that controls the encoding process.
|
* @param obj Object to be encoded.
|
* @param node XML node to encode the object into.
|
* @return Returns the object to be encoded by the default encoding.
|
*/
|
public Object beforeEncode(GraphCodec enc, Object obj, Node node)
|
{
|
return obj;
|
}
|
|
/**
|
* Hook for subclassers to post-process the node for the given object after
|
* encoding and return the post-processed node. This implementation returns
|
* the input node. The return value of this method is returned to the
|
* encoder from <encode>.
|
*
|
* Parameters:
|
*
|
* @param enc Codec that controls the encoding process.
|
* @param obj Object to be encoded.
|
* @param node XML node that represents the default encoding.
|
* @return Returns the resulting node of the encoding.
|
*/
|
public Node afterEncode(GraphCodec enc, Object obj, Node node)
|
{
|
return node;
|
}
|
|
/**
|
* Parses the given node into the object or returns a new object
|
* representing the given node.
|
*
|
* @param dec Codec that controls the encoding process.
|
* @param node XML node to be decoded.
|
* @return Returns the resulting object that represents the given XML node.
|
*/
|
public Object decode(GraphCodec dec, Node node)
|
{
|
return decode(dec, node, null);
|
}
|
|
/**
|
* Parses the given node into the object or returns a new object
|
* representing the given node.
|
*
|
* Dec is a reference to the calling decoder. It is used to decode complex
|
* objects and resolve references.
|
*
|
* If a node has an id attribute then the object cache is checked for the
|
* object. If the object is not yet in the cache then it is constructed
|
* using the constructor of <template> and cached in <GraphCodec.objects>.
|
*
|
* This implementation decodes all attributes and childs of a node according
|
* to the following rules:
|
* - If the variable name is in <exclude> or if the attribute name is "id"
|
* or "as" then it is ignored. - If the variable name is in <idrefs> then
|
* <GraphCodec.getObject> is used to replace the reference with an object. -
|
* The variable name is mapped using a reverse <mapping>. - If the value has
|
* a child node, then the codec is used to create a child object with the
|
* variable name taken from the "as" attribute. - If the object is an array
|
* and the variable name is empty then the value or child object is appended
|
* to the array. - If an add child has no value or the object is not an
|
* array then the child text content is evaluated using <mxUtils.eval>.
|
*
|
* If no object exists for an ID in <idrefs> a warning is issued in
|
* System.err.
|
*
|
* @param dec Codec that controls the encoding process.
|
* @param node XML node to be decoded.
|
* @param into Optional object to encode the node into.
|
* @return Returns the resulting object that represents the given XML node
|
* or the object given to the method as the into parameter.
|
*/
|
public Object decode(GraphCodec dec, Node node, Object into)
|
{
|
Object obj = null;
|
|
if (node instanceof Element)
|
{
|
String id = ((Element) node).getAttribute("id");
|
obj = dec.objects.get(id);
|
|
if (obj == null)
|
{
|
obj = into;
|
|
if (obj == null)
|
{
|
obj = cloneTemplate(node);
|
}
|
|
if (id != null && id.length() > 0)
|
{
|
dec.putObject(id, obj);
|
}
|
}
|
|
node = beforeDecode(dec, node, obj);
|
decodeNode(dec, node, obj);
|
obj = afterDecode(dec, node, obj);
|
}
|
|
return obj;
|
}
|
|
/**
|
* Calls decodeAttributes and decodeChildren for the given node.
|
*/
|
protected void decodeNode(GraphCodec dec, Node node, Object obj)
|
{
|
if (node != null)
|
{
|
decodeAttributes(dec, node, obj);
|
decodeChildren(dec, node, obj);
|
}
|
}
|
|
/**
|
* Decodes all attributes of the given node using decodeAttribute.
|
*/
|
protected void decodeAttributes(GraphCodec dec, Node node, Object obj)
|
{
|
NamedNodeMap attrs = node.getAttributes();
|
|
if (attrs != null)
|
{
|
for (int i = 0; i < attrs.getLength(); i++)
|
{
|
Node attr = attrs.item(i);
|
decodeAttribute(dec, attr, obj);
|
}
|
}
|
}
|
|
/**
|
* Reads the given attribute into the specified object.
|
*/
|
protected void decodeAttribute(GraphCodec dec, Node attr, Object obj)
|
{
|
String name = attr.getNodeName();
|
|
if (!name.equalsIgnoreCase("as") && !name.equalsIgnoreCase("id"))
|
{
|
Object value = attr.getNodeValue();
|
String fieldname = getFieldName(name);
|
|
if (isReference(obj, fieldname, value, false))
|
{
|
Object tmp = dec.getObject(String.valueOf(value));
|
|
if (tmp == null)
|
{
|
System.err.println("mxObjectCodec.decode: No object for "
|
+ getName() + "." + fieldname + "=" + value);
|
return; // exit
|
}
|
|
value = tmp;
|
}
|
|
if (!isExcluded(obj, fieldname, value, false))
|
{
|
setFieldValue(obj, fieldname, value);
|
}
|
}
|
}
|
|
/**
|
* Decodec all children of the given node using decodeChild.
|
*/
|
protected void decodeChildren(GraphCodec dec, Node node, Object obj)
|
{
|
Node child = node.getFirstChild();
|
|
while (child != null)
|
{
|
if (child.getNodeType() == Node.ELEMENT_NODE
|
&& !processInclude(dec, child, obj))
|
{
|
decodeChild(dec, child, obj);
|
}
|
|
child = child.getNextSibling();
|
}
|
}
|
|
/**
|
* Reads the specified child into the given object.
|
*/
|
protected void decodeChild(GraphCodec dec, Node child, Object obj)
|
{
|
String fieldname = getFieldName(((Element) child).getAttribute("as"));
|
|
if (fieldname == null || !isExcluded(obj, fieldname, child, false))
|
{
|
Object template = getFieldTemplate(obj, fieldname, child);
|
Object value = null;
|
|
if (child.getNodeName().equals("add"))
|
{
|
value = ((Element) child).getAttribute("value");
|
|
if (value == null)
|
{
|
value = child.getTextContent();
|
}
|
}
|
else
|
{
|
value = dec.decode(child, template);
|
// System.out.println("Decoded " + child.getNodeName() + "."
|
// + fieldname + "=" + value);
|
}
|
|
addObjectValue(obj, fieldname, value, template);
|
}
|
}
|
|
/**
|
* Returns the template instance for the given field. This returns the
|
* value of the field, null if the value is an array or an empty collection
|
* if the value is a collection. The value is then used to populate the
|
* field for a new instance. For strongly typed languages it may be
|
* required to override this to return the correct collection instance
|
* based on the encoded child.
|
*/
|
protected Object getFieldTemplate(Object obj, String fieldname, Node child)
|
{
|
Object template = getFieldValue(obj, fieldname);
|
|
// Arrays are replaced completely
|
if (template != null && template.getClass().isArray())
|
{
|
template = null;
|
}
|
// Collections are cleared
|
else if (template instanceof Collection)
|
{
|
((Collection<?>) template).clear();
|
}
|
|
return template;
|
}
|
|
/**
|
* Sets the decoded child node as a value of the given object. If the
|
* object is a map, then the value is added with the given fieldname as a
|
* key. If the fieldname is not empty, then setFieldValue is called or
|
* else, if the object is a collection, the value is added to the
|
* collection. For strongly typed languages it may be required to
|
* override this with the correct code to add an entry to an object.
|
*/
|
protected void addObjectValue(Object obj, String fieldname, Object value,
|
Object template)
|
{
|
if (value != null && !value.equals(template))
|
{
|
if (fieldname != null && obj instanceof Map)
|
{
|
((Map) obj).put(fieldname, value);
|
}
|
else if (fieldname != null && fieldname.length() > 0)
|
{
|
setFieldValue(obj, fieldname, value);
|
}
|
// Arrays are treated as collections and
|
// converted in setFieldValue
|
else if (obj instanceof Collection)
|
{
|
((Collection) obj).add(value);
|
}
|
}
|
}
|
|
/**
|
* Returns true if the given node is an include directive and executes the
|
* include by decoding the XML document. Returns false if the given node is
|
* not an include directive.
|
*
|
* @param dec Codec that controls the encoding/decoding process.
|
* @param node XML node to be checked.
|
* @param into Optional object to pass-thru to the codec.
|
* @return Returns true if the given node was processed as an include.
|
*/
|
public boolean processInclude(GraphCodec dec, Node node, Object into)
|
{
|
if (node.getNodeType() == Node.ELEMENT_NODE
|
&& node.getNodeName().equalsIgnoreCase("include"))
|
{
|
String name = ((Element) node).getAttribute("name");
|
|
if (name != null)
|
{
|
try
|
{
|
Node xml = mxUtils.loadDocument(
|
mxObjectCodec.class.getResource(name).toString())
|
.getDocumentElement();
|
|
if (xml != null)
|
{
|
dec.decode(xml, into);
|
}
|
}
|
catch (Exception e)
|
{
|
System.err.println("Cannot process include: " + name);
|
}
|
}
|
|
return true;
|
}
|
|
return false;
|
}
|
|
/**
|
* Hook for subclassers to pre-process the node for the specified object
|
* and return the node to be used for further processing by
|
* {@link #decode(GraphCodec, Node)}. The object is created based on the
|
* template in the calling method and is never null.
|
*
|
* This implementation returns the input node. The return value of this
|
* function is used in {@link #decode(GraphCodec, Node)} to perform the
|
* default decoding into the given object.
|
*
|
* @param dec Codec that controls the decoding process.
|
* @param node XML node to be decoded.
|
* @param obj Object to encode the node into.
|
* @return Returns the node used for the default decoding.
|
*/
|
public Node beforeDecode(GraphCodec dec, Node node, Object obj)
|
{
|
return node;
|
}
|
|
/**
|
* Hook for subclassers to post-process the object after decoding. This
|
* implementation returns the given object without any changes. The return
|
* value of this method is returned to the decoder from
|
* {@link #decode(GraphCodec, Node)}.
|
*
|
* @param dec Codec that controls the decoding process.
|
* @param node XML node to be decoded.
|
* @param obj Object that represents the default decoding.
|
* @return Returns the result of the decoding process.
|
*/
|
public Object afterDecode(GraphCodec dec, Node node, Object obj)
|
{
|
return obj;
|
}
|
}
|