Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.mxgraph.io.mxObjectCodec Maven / Gradle / Ivy
Go to download
JGraphX Swing Component - Java Graph Visualization Library
This is a binary & source redistribution of the original, unmodified JGraphX library originating from:
"https://github.com/jgraph/jgraphx/archive/v3.4.1.3.zip".
The purpose of this redistribution is to make the library available to other Maven projects.
/**
* $Id: mxObjectCodec.java,v 1.41 2012/08/21 10:55:11 gaudenz Exp $
* Copyright (c) 2006, Gaudenz Alder
*/
package com.mxgraph.io;
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.HashMap;
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.util.mxUtils;
/**
* Generic codec for Java objects. See below for a detailed description of
* the encoding/decoding scheme.
*
* Note: Since booleans are numbers in JavaScript, all boolean values are
* encoded into 1 for true and 0 for false.
*/
@SuppressWarnings("unchecked")
public class mxObjectCodec
{
/**
* Immutable empty set.
*/
private static Set EMPTY_SET = new HashSet();
/**
* 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 exclude;
/**
* Array containing the variable names that should be turned into or
* converted from references. See and .
*/
protected Set idrefs;
/**
* Maps from from fieldnames to XML attribute names.
*/
protected Map mapping;
/**
* Maps from from XML attribute names to fieldnames.
*/
protected Map reverse;
/**
* Caches accessors for the given method names.
*/
protected Map accessors;
/**
* Caches fields for faster access.
*/
protected Map> fields;
/**
* Constructs a new codec for the specified template object.
*/
public mxObjectCodec(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 mxObjectCodec(Object template, String[] exclude, String[] idrefs,
Map mapping)
{
this.template = template;
if (exclude != null)
{
this.exclude = new HashSet();
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();
for (int i = 0; i < idrefs.length; i++)
{
this.idrefs.add(idrefs[i]);
}
}
else
{
this.idrefs = EMPTY_SET;
}
if (mapping == null)
{
mapping = new Hashtable();
}
this.mapping = mapping;
reverse = new Hashtable();
Iterator> it = mapping.entrySet().iterator();
while (it.hasNext())
{
Map.Entry 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:
*
* mxCodecRegistry.register(new mxCellCodec()
* {
* public String getName()
* {
* return "anotherName";
* }
* });
*
*/
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();
// Skips text nodes
while (node != null && !(node instanceof Element))
{
node = node.getNextSibling();
}
if (node != null && node instanceof Element
&& ((Element) node).hasAttribute("as"))
{
obj = new Hashtable();
}
}
}
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:
*
*
* If the variable name is in {@link #exclude} then it is ignored.
* If the variable name is in {@link #idrefs} then
* {@link mxCodec#getId(Object)} is used to replace the object with its ID.
*
* The variable name is mapped using {@link #mapping}.
* If obj is an array and the variable name is numeric (ie. an index) then it
* is not encoded.
* 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.
* Else, if {@link com.mxgraph.io.mxCodec#isEncodeDefaults()} is true or
* the value differs from the template value, then ...
*
* ... if obj is not an array, then the value is mapped to an
* attribute.
* ... 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.
*
*
*
*
*
* 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(mxCodec 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(mxCodec enc, Object obj, Node node)
{
mxCodec.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(mxCodec enc, Object obj, Node node)
{
// LATER: Use PropertyDescriptors in Introspector.getBeanInfo(clazz)
// see http://forum.jgraph.com/questions/1424
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(mxCodec 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 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(mxCodec, 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(mxCodec 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("mxObjectCodec.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(mxCodec enc, Object obj, String attr,
Object value, Node node)
{
value = convertValueToXml(value);
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(mxCodec enc, Object obj,
String attr, Object value, Node node)
{
if (attr == null || obj instanceof Map)
{
Node child = enc.document.createElement("add");
if (attr != null)
{
mxCodec.setAttribute(child, "as", attr);
}
mxCodec.setAttribute(child, "value", value);
node.appendChild(child);
}
else
{
mxCodec.setAttribute(node, attr, value);
}
}
/**
* Writes the given value as a child node of the given node.
*/
protected void writeComplexAttribute(mxCodec enc, Object obj, String attr,
Object value, Node node)
{
Node child = enc.encode(value);
if (child != null)
{
if (attr != null)
{
mxCodec.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();
// Creates the fields cache
if (fields == null)
{
fields = new HashMap>();
}
// Creates the fields cache entry for the given type
Map map = fields.get(type);
if (map == null)
{
map = new HashMap();
fields.put(type, map);
}
// Tries to get cached field
Field field = map.get(fieldname);
if (field != null)
{
return field;
}
while (type != null)
{
try
{
field = type.getDeclaredField(fieldname);
if (field != null)
{
// Adds field to fields cache
map.put(fieldname, field);
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;
}
Method method = (accessors != null) ? accessors.get(name) : null;
if (method == null)
{
try
{
if (isGetter)
{
method = getMethod(obj, name, null);
}
else
{
method = getMethod(obj, name,
new Class[] { field.getType() });
}
}
catch (Exception e1)
{
// ignore
}
// Adds accessor to cache
if (method != null)
{
if (accessors == null)
{
accessors = new Hashtable();
}
accessors.put(name, method);
}
}
return method;
}
/**
* 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)
{
if (Modifier.isPublic(field.getModifiers()))
{
value = field.get(obj);
}
else
{
value = getFieldValueWithAccessor(obj, field);
}
}
}
catch (IllegalAccessException e1)
{
value = getFieldValueWithAccessor(obj, field);
}
catch (Exception e)
{
// ignore
}
}
return value;
}
/**
* Returns the value of the field using the accessor for the field if one exists.
*/
protected Object getFieldValueWithAccessor(Object obj, Field field)
{
Object value = null;
if (field != null)
{
try
{
Method method = getAccessor(obj, field, true);
if (method != null)
{
value = method.invoke(obj, (Object[]) null);
}
}
catch (Exception e2)
{
// 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 != null)
{
if (field.getType() == Boolean.class)
{
value = (value.equals("1") || String.valueOf(value)
.equalsIgnoreCase("true")) ? Boolean.TRUE
: Boolean.FALSE;
}
if (Modifier.isPublic(field.getModifiers()))
{
field.set(obj, value);
}
else
{
setFieldValueWithAccessor(obj, field, value);
}
}
}
catch (IllegalAccessException e1)
{
setFieldValueWithAccessor(obj, field, value);
}
catch (Exception e)
{
// ignore
}
}
/**
* Sets the value of the given field using the accessor if one exists.
*/
protected void setFieldValueWithAccessor(Object obj, Field field,
Object value)
{
if (field != null)
{
try
{
Method method = getAccessor(obj, field, false);
if (method != null)
{
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() + "."
+ field.getName() + " ("
+ field.getType().getSimpleName() + ") = " + value
+ " (" + value.getClass().getSimpleName() + ")");
}
}
}
/**
* 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(mxCodec 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 .
*
* 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(mxCodec 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(mxCodec 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 and cached in .
*
* This implementation decodes all attributes and childs of a node according
* to the following rules:
* - If the variable name is in or if the attribute name is "id"
* or "as" then it is ignored. - If the variable name is in then
* is used to replace the reference with an object. -
* The variable name is mapped using a reverse . - 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 .
*
* If no object exists for an ID in 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(mxCodec 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(mxCodec 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(mxCodec 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(mxCodec 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(mxCodec 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(mxCodec 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(mxCodec 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(mxCodec, 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(mxCodec, 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(mxCodec 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(mxCodec, 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(mxCodec dec, Node node, Object obj)
{
return obj;
}
}