Project: jgraphx
/**
 * $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<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 <mxCodec.getId> and <mxCodec.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; 
 
 /**
  * Caches accessors for the given method names. 
  */
 
 protected Map<String, Method> accessors; 
 
 /**
  * Caches fields for faster access. 
  */
 
 protected Map<Class, Map<String, Field>> fields; 
 
 /**
  * Constructs a new codec for the specified template object. 
  */
 
 public mxObjectCodec(Object template) 
 
  this(template, nullnullnull); 
 
 
 /**
  * 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<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(); 
 
    // 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<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 mxCodec#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.mxCodec#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(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<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(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<Class, Map<String, Field>>(); 
  
 
  // Creates the fields cache entry for the given type 
  Map<String, Field> map = fields.get(type); 
 
  if (map == null
  
   map = new HashMap<String, Field>(); 
   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(01).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<String, Method>(); 
    
 
    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 <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(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 <template> and cached in <mxCodec.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 
  * <mxCodec.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(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; 
 
 
}