0001: /*
0002: * This program is free software; you can redistribute it and/or modify
0003: * it under the terms of the GNU General Public License as published by
0004: * the Free Software Foundation; either version 2 of the License, or
0005: * (at your option) any later version.
0006: *
0007: * This program is distributed in the hope that it will be useful,
0008: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0009: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0010: * GNU General Public License for more details.
0011: *
0012: * You should have received a copy of the GNU General Public License
0013: * along with this program; if not, write to the Free Software
0014: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
0015: */
0016:
0017: /*
0018: * XMLSerialization.java
0019: * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
0020: *
0021: */
0022:
0023: package weka.core.xml;
0024:
0025: import weka.core.Utils;
0026: import weka.core.Version;
0027:
0028: import java.beans.BeanInfo;
0029: import java.beans.Introspector;
0030: import java.beans.PropertyDescriptor;
0031: import java.io.BufferedInputStream;
0032: import java.io.BufferedOutputStream;
0033: import java.io.File;
0034: import java.io.FileInputStream;
0035: import java.io.FileOutputStream;
0036: import java.io.InputStream;
0037: import java.io.ObjectInputStream;
0038: import java.io.ObjectOutputStream;
0039: import java.io.OutputStream;
0040: import java.io.Reader;
0041: import java.io.Writer;
0042: import java.lang.reflect.Array;
0043: import java.lang.reflect.Constructor;
0044: import java.lang.reflect.Method;
0045: import java.util.Enumeration;
0046: import java.util.Hashtable;
0047: import java.util.Vector;
0048:
0049: import org.w3c.dom.Document;
0050: import org.w3c.dom.Element;
0051:
0052: /**
0053: * With this class objects can be serialized to XML instead into a binary
0054: * format. It uses introspection (cf. beans) to retrieve the data from the
0055: * given object, i.e. it can only access beans-conform fields automatically.
0056: * <p>
0057: * The generic approach of writing data as XML can be overriden by adding
0058: * custom methods for reading/writing in a derived class
0059: * (cf. <code>m_Properties</code>, <code>m_CustomMethods</code>).<br>
0060: * Custom read and write methods must have the same signature (and also be
0061: * <code>public</code>!) as the <code>readFromXML</code> and <code>writeToXML</code>
0062: * methods. Methods that apply to the naming rule <code>read + property name</code>
0063: * are added automatically to the list of methods by the method
0064: * <code>XMLSerializationMethodHandler.addMethods(...)</code>.
0065: * <p>
0066: * Other properties that are not conform the bean set/get-methods have to be
0067: * processed manually in a derived class (cf. <code>readPostProcess(Object)</code>,
0068: * <code>writePostProcess(Object)</code>).
0069: * <p>
0070: * For a complete XML serialization/deserialization have a look at the
0071: * <code>KOML</code> class.
0072: * <p>
0073: * If a stored class has a constructor that takes a String to initialize
0074: * (e.g. String or Double) then the content of the tag will used for the
0075: * constructor, e.g. from
0076: * <pre><object name="name" class="String" primitive="no">Smith</object></pre>
0077: * "Smith" will be used to instantiate a String object as constructor argument.
0078: * <p>
0079: *
0080: * @see KOML
0081: * @see #fromXML(Document)
0082: * @see #toXML(Object)
0083: * @see #m_Properties
0084: * @see #m_CustomMethods
0085: * @see #readPostProcess(Object)
0086: * @see #writePostProcess(Object)
0087: * @see #readFromXML(Element)
0088: * @see #writeToXML(Element, Object, String)
0089: *
0090: * @author FracPete (fracpete at waikato dot ac dot nz)
0091: * @version $Revision: 1.15 $
0092: */
0093: public class XMLSerialization {
0094: /** for debugging purposes only */
0095: protected static boolean DEBUG = false;
0096:
0097: /** the node that is currently processed, in case of writing the parent node
0098: * (something might go wrong writing the new child) and in case of reading
0099: * the actual node that is tried to process */
0100: protected Element m_CurrentNode = null;
0101:
0102: /** the tag for an object */
0103: public final static String TAG_OBJECT = "object";
0104:
0105: /** the version attribute */
0106: public final static String ATT_VERSION = XMLDocument.ATT_VERSION;
0107:
0108: /** the tag for the name */
0109: public final static String ATT_NAME = XMLDocument.ATT_NAME;
0110:
0111: /** the tag for the class */
0112: public final static String ATT_CLASS = "class";
0113:
0114: /** the tag whether primitive or not (yes/no) */
0115: public final static String ATT_PRIMITIVE = "primitive";
0116:
0117: /** the tag whether array or not (yes/no) */
0118: public final static String ATT_ARRAY = "array";
0119:
0120: /** the tag whether null or not (yes/no) */
0121: public final static String ATT_NULL = "null";
0122:
0123: /** the value "yes" for the primitive and array attribute */
0124: public final static String VAL_YES = XMLDocument.VAL_YES;
0125:
0126: /** the value "no" for the primitive and array attribute */
0127: public final static String VAL_NO = XMLDocument.VAL_NO;
0128:
0129: /** the value of the name for the root node */
0130: public final static String VAL_ROOT = "__root__";
0131:
0132: /** the root node of the XML document */
0133: public final static String ROOT_NODE = TAG_OBJECT;
0134:
0135: /** default value for attribute ATT_PRIMITIVE
0136: * @see #ATT_PRIMITIVE */
0137: public final static String ATT_PRIMITIVE_DEFAULT = VAL_NO;
0138:
0139: /** default value for attribute ATT_ARRAY
0140: * @see #ATT_ARRAY */
0141: public final static String ATT_ARRAY_DEFAULT = VAL_NO;
0142:
0143: /** default value for attribute ATT_NULL
0144: * @see #ATT_NULL */
0145: public final static String ATT_NULL_DEFAULT = VAL_NO;
0146:
0147: /** the DOCTYPE for the serialization */
0148: public final static String DOCTYPE = "<!"
0149: + XMLDocument.DTD_DOCTYPE
0150: + " "
0151: + ROOT_NODE
0152: + "\n"
0153: + "[\n"
0154: + " <!"
0155: + XMLDocument.DTD_ELEMENT
0156: + " "
0157: + TAG_OBJECT
0158: + " ("
0159: + XMLDocument.DTD_PCDATA
0160: + XMLDocument.DTD_SEPARATOR
0161: + TAG_OBJECT
0162: + ")"
0163: + XMLDocument.DTD_ZERO_OR_MORE
0164: + ">\n"
0165: + " <!"
0166: + XMLDocument.DTD_ATTLIST
0167: + " "
0168: + TAG_OBJECT
0169: + " "
0170: + ATT_NAME
0171: + " "
0172: + XMLDocument.DTD_CDATA
0173: + " "
0174: + XMLDocument.DTD_REQUIRED
0175: + ">\n"
0176: + " <!"
0177: + XMLDocument.DTD_ATTLIST
0178: + " "
0179: + TAG_OBJECT
0180: + " "
0181: + ATT_CLASS
0182: + " "
0183: + XMLDocument.DTD_CDATA
0184: + " "
0185: + XMLDocument.DTD_REQUIRED
0186: + ">\n"
0187: + " <!"
0188: + XMLDocument.DTD_ATTLIST
0189: + " "
0190: + TAG_OBJECT
0191: + " "
0192: + ATT_PRIMITIVE
0193: + " "
0194: + XMLDocument.DTD_CDATA
0195: + " \""
0196: + ATT_PRIMITIVE_DEFAULT
0197: + "\">\n"
0198: + " <!"
0199: + XMLDocument.DTD_ATTLIST
0200: + " "
0201: + TAG_OBJECT
0202: + " "
0203: + ATT_ARRAY
0204: + " "
0205: + XMLDocument.DTD_CDATA
0206: + " \""
0207: + ATT_ARRAY_DEFAULT
0208: + "\"> <!-- the dimensions of the array; no=0, yes=1 -->\n"
0209: + " <!" + XMLDocument.DTD_ATTLIST + " " + TAG_OBJECT
0210: + " " + ATT_NULL + " " + XMLDocument.DTD_CDATA + " \""
0211: + ATT_NULL_DEFAULT + "\">\n" + " <!"
0212: + XMLDocument.DTD_ATTLIST + " " + TAG_OBJECT + " "
0213: + ATT_VERSION + " " + XMLDocument.DTD_CDATA + " \""
0214: + Version.VERSION + "\">\n" + "]\n" + ">";
0215:
0216: /** the XMLDocument that performs the transformation to and fro XML */
0217: protected XMLDocument m_Document = null;
0218:
0219: /** for handling properties (ignored/allowed) */
0220: protected PropertyHandler m_Properties = null;
0221:
0222: /** for handling custom read/write methods */
0223: protected XMLSerializationMethodHandler m_CustomMethods = null;
0224:
0225: /** for overriding class names (Class <-> Classname (String))
0226: * @see #overrideClassname(Object) */
0227: protected Hashtable m_ClassnameOverride = null;
0228:
0229: /**
0230: * initializes the serialization
0231: *
0232: * @throws Exception if initialization fails
0233: */
0234: public XMLSerialization() throws Exception {
0235: super ();
0236: clear();
0237: }
0238:
0239: /**
0240: * used for debugging purposes, i.e. only if DEBUG is set to true.
0241: * needs a newly generated Throwable instance to get the method/line from
0242: * @param t a throwable instance, generated in the calling method
0243: * @param msg a message to pring
0244: * @see #DEBUG
0245: */
0246: protected void trace(Throwable t, String msg) {
0247: if ((DEBUG) && (t.getStackTrace().length > 0)) {
0248: System.out.println("trace: " + t.getStackTrace()[0] + ": "
0249: + msg);
0250: }
0251: }
0252:
0253: /**
0254: * generates internally a new XML document and clears also the IgnoreList and
0255: * the mappings for the Read/Write-Methods
0256: *
0257: * @throws Exception if something goes wrong
0258: */
0259: public void clear() throws Exception {
0260: m_Document = new XMLDocument();
0261: m_Document.setValidating(true);
0262: m_Document.newDocument(DOCTYPE, ROOT_NODE);
0263:
0264: m_Properties = new PropertyHandler();
0265: m_CustomMethods = new XMLSerializationMethodHandler(this );
0266:
0267: m_ClassnameOverride = new Hashtable();
0268: // java.io.File is sometimes represented as another class:
0269: // - Win32: sun.awt.shell.Win32ShellFolder2
0270: // - Linux: sun.awt.shell.DefaultShellFolder
0271: // -> we set it to "java.io.File"
0272: m_ClassnameOverride.put(java.io.File.class, java.io.File.class
0273: .getName());
0274:
0275: setVersion(Version.VERSION);
0276:
0277: m_CurrentNode = null;
0278: }
0279:
0280: /**
0281: * sets the given version string in the XML document
0282: *
0283: * @param version the new version string
0284: */
0285: private void setVersion(String version) {
0286: Document doc;
0287:
0288: doc = m_Document.getDocument();
0289: doc.getDocumentElement().setAttribute(ATT_VERSION, version);
0290: }
0291:
0292: /**
0293: * returns the WEKA version with which the serialized object was created
0294: *
0295: * @return the current version
0296: * @see Version
0297: */
0298: public String getVersion() {
0299: Document doc;
0300: String result;
0301:
0302: doc = m_Document.getDocument();
0303: result = doc.getDocumentElement().getAttribute(ATT_VERSION);
0304:
0305: return result;
0306: }
0307:
0308: /**
0309: * Checks the version in the current Document with the one of the current
0310: * release. If the version differ, a warning is printed.
0311: */
0312: private void checkVersion() {
0313: String versionStr;
0314: Version version;
0315:
0316: version = new Version();
0317: versionStr = getVersion();
0318: if (versionStr.equals(""))
0319: System.out.println("WARNING: has no version!");
0320: else if (version.isOlder(versionStr))
0321: System.out.println("WARNING: loading a newer version ("
0322: + versionStr + " > " + Version.VERSION + ")!");
0323: else if (version.isNewer(versionStr))
0324: System.out.println("NOTE: loading an older version ("
0325: + versionStr + " < " + Version.VERSION + ")!");
0326: }
0327:
0328: /**
0329: * returns a hashtable with PropertyDescriptors that have "get" and "set"
0330: * methods indexed by the property name.
0331: *
0332: * @see java.beans.PropertyDescriptor
0333: * @param o the object to retrieve the descriptors from
0334: * @return the PropertyDescriptors indexed by name of the property
0335: * @throws Exception if the introspection fails
0336: */
0337: protected Hashtable getDescriptors(Object o) throws Exception {
0338: BeanInfo info;
0339: PropertyDescriptor[] desc;
0340: int i;
0341: Hashtable result;
0342:
0343: result = new Hashtable();
0344:
0345: info = Introspector.getBeanInfo(o.getClass());
0346: desc = info.getPropertyDescriptors();
0347: for (i = 0; i < desc.length; i++) {
0348: // get AND set method?
0349: if ((desc[i].getReadMethod() != null)
0350: && (desc[i].getWriteMethod() != null)) {
0351: // in ignore list, i.e. a general ignore without complete path?
0352: if (m_Properties.isIgnored(desc[i].getDisplayName()))
0353: continue;
0354:
0355: // in ignore list of the class?
0356: if (m_Properties.isIgnored(o, desc[i].getDisplayName()))
0357: continue;
0358:
0359: // not an allowed property
0360: if (!m_Properties
0361: .isAllowed(o, desc[i].getDisplayName()))
0362: continue;
0363:
0364: result.put(desc[i].getDisplayName(), desc[i]);
0365: }
0366: }
0367:
0368: return result;
0369: }
0370:
0371: /**
0372: * returns the path of the "name" attribute from the root down to this node
0373: * (including it).
0374: *
0375: * @param node the node to get the path for
0376: * @return the complete "name" path of this node
0377: */
0378: protected String getPath(Element node) {
0379: String result;
0380:
0381: result = node.getAttribute(ATT_NAME);
0382:
0383: while (node.getParentNode() != node.getOwnerDocument()) {
0384: node = (Element) node.getParentNode();
0385: result = node.getAttribute(ATT_NAME) + "." + result;
0386: }
0387:
0388: return result;
0389: }
0390:
0391: /**
0392: * returns either <code>VAL_YES</code> or <code>VAL_NO</code> depending
0393: * on the value of <code>b</code>
0394: *
0395: * @param b the boolean to turn into a string
0396: * @return the value in string representation
0397: */
0398: protected String booleanToString(boolean b) {
0399: if (b)
0400: return VAL_YES;
0401: else
0402: return VAL_NO;
0403: }
0404:
0405: /**
0406: * turns the given string into a boolean, if a positive number is given,
0407: * then zero is considered FALSE, every other number TRUE; the empty string
0408: * is also considered being FALSE
0409: *
0410: * @param s the string to turn into a boolean
0411: * @return the string as boolean
0412: */
0413: protected boolean stringToBoolean(String s) {
0414: if (s.equals(""))
0415: return false;
0416: else if (s.equals(VAL_YES))
0417: return true;
0418: else if (s.equalsIgnoreCase("true"))
0419: return true;
0420: else if (s.replaceAll("[0-9]*", "").equals(""))
0421: return (Integer.parseInt(s) != 0);
0422: else
0423: return false;
0424: }
0425:
0426: /**
0427: * appends a new node to the parent with the given parameters (a non-array)
0428: *
0429: * @param parent the parent of this node. if it is <code>null</code> the
0430: * document root element is used
0431: * @param name the name of the node
0432: * @param classname the classname for this node
0433: * @param primitive whether it is a primitve data type or not (i.e. an object)
0434: * @return the generated node
0435: */
0436: protected Element addElement(Element parent, String name,
0437: String classname, boolean primitive) {
0438: return addElement(parent, name, classname, primitive, 0);
0439: }
0440:
0441: /**
0442: * appends a new node to the parent with the given parameters
0443: *
0444: * @param parent the parent of this node. if it is <code>null</code> the
0445: * document root element is used
0446: * @param name the name of the node
0447: * @param classname the classname for this node
0448: * @param primitive whether it is a primitve data type or not (i.e. an object)
0449: * @param array the dimensions of the array (0 if not an array)
0450: * @return the generated node
0451: */
0452: protected Element addElement(Element parent, String name,
0453: String classname, boolean primitive, int array) {
0454: return addElement(parent, name, classname, primitive, array,
0455: false);
0456: }
0457:
0458: /**
0459: * appends a new node to the parent with the given parameters
0460: *
0461: * @param parent the parent of this node. if it is <code>null</code> the
0462: * document root element is used
0463: * @param name the name of the node
0464: * @param classname the classname for this node
0465: * @param primitive whether it is a primitve data type or not (i.e. an object)
0466: * @param array the dimensions of the array (0 if not an array)
0467: * @param isnull whether it is null
0468: * @return the generated node
0469: */
0470: protected Element addElement(Element parent, String name,
0471: String classname, boolean primitive, int array,
0472: boolean isnull) {
0473: Element result;
0474:
0475: if (parent == null)
0476: result = m_Document.getDocument().getDocumentElement();
0477: else
0478: result = (Element) parent.appendChild(m_Document
0479: .getDocument().createElement(TAG_OBJECT));
0480:
0481: // attributes
0482: // mandatory attributes:
0483: result.setAttribute(ATT_NAME, name);
0484: result.setAttribute(ATT_CLASS, classname);
0485:
0486: // add following attributes only if necessary, i.e., different from default:
0487: if (!booleanToString(primitive).equals(ATT_PRIMITIVE_DEFAULT))
0488: result.setAttribute(ATT_PRIMITIVE,
0489: booleanToString(primitive));
0490:
0491: // multi-dimensional array?
0492: if (array > 1) {
0493: result.setAttribute(ATT_ARRAY, Integer.toString(array));
0494: }
0495: // backwards compatible: 0 -> no array ("no"), 1 -> 1-dim. array ("yes")
0496: else {
0497: if (!booleanToString(array == 1).equals(ATT_ARRAY_DEFAULT))
0498: result.setAttribute(ATT_ARRAY,
0499: booleanToString(array == 1));
0500: }
0501:
0502: if (!booleanToString(isnull).equals(ATT_NULL_DEFAULT))
0503: result.setAttribute(ATT_NULL, booleanToString(isnull));
0504:
0505: return result;
0506: }
0507:
0508: /**
0509: * if the class of the given object (or one of its ancestors) is stored in
0510: * the classname override hashtable, then the override name is returned
0511: * otherwise the classname of the given object.
0512: *
0513: * @param o the object to check for overriding its classname
0514: * @return if overridden then the classname stored in the hashtable,
0515: * otherwise the classname of the given object
0516: * @see #m_ClassnameOverride
0517: */
0518: protected String overrideClassname(Object o) {
0519: Enumeration enm;
0520: String result;
0521: Class currentCls;
0522:
0523: result = o.getClass().getName();
0524:
0525: // check overrides
0526: enm = m_ClassnameOverride.keys();
0527: while (enm.hasMoreElements()) {
0528: currentCls = (Class) enm.nextElement();
0529: if (currentCls.isInstance(o)) {
0530: result = (String) m_ClassnameOverride.get(currentCls);
0531: break;
0532: }
0533: }
0534:
0535: return result;
0536: }
0537:
0538: /**
0539: * if the given classname is stored in the classname override hashtable,
0540: * then the override name is returned otherwise the given classname.
0541: * <b>Note:</b> in contrast to <code>overrideClassname(Object)</code> does
0542: * this method only look for exact name matches. The other method checks
0543: * whether the class of the given object is a subclass of any of the stored
0544: * overrides.
0545: *
0546: * @param classname the classname to check for overriding
0547: * @return if overridden then the classname stored in the hashtable,
0548: * otherwise the given classname
0549: * @see #m_ClassnameOverride
0550: * @see #overrideClassname(Object)
0551: */
0552: protected String overrideClassname(String classname) {
0553: Enumeration enm;
0554: String result;
0555: Class currentCls;
0556:
0557: result = classname;
0558:
0559: // check overrides
0560: enm = m_ClassnameOverride.keys();
0561: while (enm.hasMoreElements()) {
0562: currentCls = (Class) enm.nextElement();
0563: if (currentCls.getName().equals(classname)) {
0564: result = (String) m_ClassnameOverride.get(currentCls);
0565: break;
0566: }
0567: }
0568:
0569: return result;
0570: }
0571:
0572: /**
0573: * returns a property descriptor if possible, otherwise <code>null</code>
0574: *
0575: * @param className the name of the class to get the descriptor for
0576: * @param displayName the name of the property
0577: * @return the descriptor if available, otherwise <code>null</code>
0578: */
0579: protected PropertyDescriptor determineDescriptor(String className,
0580: String displayName) {
0581: PropertyDescriptor result;
0582:
0583: result = null;
0584:
0585: try {
0586: result = new PropertyDescriptor(displayName, Class
0587: .forName(className));
0588: } catch (Exception e) {
0589: result = null;
0590: }
0591:
0592: return result;
0593: }
0594:
0595: /**
0596: * adds the given primitive to the DOM structure.
0597: * @param parent the parent of this object, e.g. the class this object is a member of
0598: * @param o the primitive to describe in XML
0599: * @param name the name of the primitive
0600: * @return the node that was created
0601: * @throws Exception if the DOM creation fails
0602: */
0603: protected Element writeBooleanToXML(Element parent, boolean o,
0604: String name) throws Exception {
0605: Element node;
0606:
0607: // for debugging only
0608: if (DEBUG)
0609: trace(new Throwable(), name);
0610:
0611: m_CurrentNode = parent;
0612:
0613: node = addElement(parent, name, Boolean.TYPE.getName(), true);
0614: node.appendChild(node.getOwnerDocument().createTextNode(
0615: new Boolean(o).toString()));
0616:
0617: return node;
0618: }
0619:
0620: /**
0621: * adds the given primitive to the DOM structure.
0622: * @param parent the parent of this object, e.g. the class this object is a member of
0623: * @param o the primitive to describe in XML
0624: * @param name the name of the primitive
0625: * @return the node that was created
0626: * @throws Exception if the DOM creation fails
0627: */
0628: protected Element writeByteToXML(Element parent, byte o, String name)
0629: throws Exception {
0630: Element node;
0631:
0632: // for debugging only
0633: if (DEBUG)
0634: trace(new Throwable(), name);
0635:
0636: m_CurrentNode = parent;
0637:
0638: node = addElement(parent, name, Byte.TYPE.getName(), true);
0639: node.appendChild(node.getOwnerDocument().createTextNode(
0640: new Byte(o).toString()));
0641:
0642: return node;
0643: }
0644:
0645: /**
0646: * adds the given primitive to the DOM structure.
0647: * @param parent the parent of this object, e.g. the class this object is a member of
0648: * @param o the primitive to describe in XML
0649: * @param name the name of the primitive
0650: * @return the node that was created
0651: * @throws Exception if the DOM creation fails
0652: */
0653: protected Element writeCharToXML(Element parent, char o, String name)
0654: throws Exception {
0655: Element node;
0656:
0657: // for debugging only
0658: if (DEBUG)
0659: trace(new Throwable(), name);
0660:
0661: m_CurrentNode = parent;
0662:
0663: node = addElement(parent, name, Character.TYPE.getName(), true);
0664: node.appendChild(node.getOwnerDocument().createTextNode(
0665: new Character(o).toString()));
0666:
0667: return node;
0668: }
0669:
0670: /**
0671: * adds the given primitive to the DOM structure.
0672: * @param parent the parent of this object, e.g. the class this object is a member of
0673: * @param o the primitive to describe in XML
0674: * @param name the name of the primitive
0675: * @return the node that was created
0676: * @throws Exception if the DOM creation fails
0677: */
0678: protected Element writeDoubleToXML(Element parent, double o,
0679: String name) throws Exception {
0680: Element node;
0681:
0682: // for debugging only
0683: if (DEBUG)
0684: trace(new Throwable(), name);
0685:
0686: m_CurrentNode = parent;
0687:
0688: node = addElement(parent, name, Double.TYPE.getName(), true);
0689: node.appendChild(node.getOwnerDocument().createTextNode(
0690: new Double(o).toString()));
0691:
0692: return node;
0693: }
0694:
0695: /**
0696: * adds the given primitive to the DOM structure.
0697: * @param parent the parent of this object, e.g. the class this object is a member of
0698: * @param o the primitive to describe in XML
0699: * @param name the name of the primitive
0700: * @return the node that was created
0701: * @throws Exception if the DOM creation fails
0702: */
0703: protected Element writeFloatToXML(Element parent, float o,
0704: String name) throws Exception {
0705: Element node;
0706:
0707: // for debugging only
0708: if (DEBUG)
0709: trace(new Throwable(), name);
0710:
0711: m_CurrentNode = parent;
0712:
0713: node = addElement(parent, name, Float.TYPE.getName(), true);
0714: node.appendChild(node.getOwnerDocument().createTextNode(
0715: new Float(o).toString()));
0716:
0717: return node;
0718: }
0719:
0720: /**
0721: * adds the given primitive to the DOM structure.
0722: * @param parent the parent of this object, e.g. the class this object is a member of
0723: * @param o the primitive to describe in XML
0724: * @param name the name of the primitive
0725: * @return the node that was created
0726: * @throws Exception if the DOM creation fails
0727: */
0728: protected Element writeIntToXML(Element parent, int o, String name)
0729: throws Exception {
0730: Element node;
0731:
0732: // for debugging only
0733: if (DEBUG)
0734: trace(new Throwable(), name);
0735:
0736: m_CurrentNode = parent;
0737:
0738: node = addElement(parent, name, Integer.TYPE.getName(), true);
0739: node.appendChild(node.getOwnerDocument().createTextNode(
0740: new Integer(o).toString()));
0741:
0742: return node;
0743: }
0744:
0745: /**
0746: * adds the given primitive to the DOM structure.
0747: * @param parent the parent of this object, e.g. the class this object is a member of
0748: * @param o the primitive to describe in XML
0749: * @param name the name of the primitive
0750: * @return the node that was created
0751: * @throws Exception if the DOM creation fails
0752: */
0753: protected Element writeLongToXML(Element parent, long o, String name)
0754: throws Exception {
0755: Element node;
0756:
0757: // for debugging only
0758: if (DEBUG)
0759: trace(new Throwable(), name);
0760:
0761: m_CurrentNode = parent;
0762:
0763: node = addElement(parent, name, Long.TYPE.getName(), true);
0764: node.appendChild(node.getOwnerDocument().createTextNode(
0765: new Long(o).toString()));
0766:
0767: return node;
0768: }
0769:
0770: /**
0771: * adds the given primitive to the DOM structure.
0772: * @param parent the parent of this object, e.g. the class this object is a member of
0773: * @param o the primitive to describe in XML
0774: * @param name the name of the primitive
0775: * @return the node that was created
0776: * @throws Exception if the DOM creation fails
0777: */
0778: protected Element writeShortToXML(Element parent, short o,
0779: String name) throws Exception {
0780: Element node;
0781:
0782: // for debugging only
0783: if (DEBUG)
0784: trace(new Throwable(), name);
0785:
0786: m_CurrentNode = parent;
0787:
0788: node = addElement(parent, name, Short.TYPE.getName(), true);
0789: node.appendChild(node.getOwnerDocument().createTextNode(
0790: new Short(o).toString()));
0791:
0792: return node;
0793: }
0794:
0795: /**
0796: * checks whether the innermost class is a primitive class (handles
0797: * multi-dimensional arrays)
0798: * @param c the array class to inspect
0799: * @return whether the array consists of primitive elements
0800: */
0801: protected boolean isPrimitiveArray(Class c) {
0802: if (c.getComponentType().isArray())
0803: return isPrimitiveArray(c.getComponentType());
0804: else
0805: return c.getComponentType().isPrimitive();
0806: }
0807:
0808: /**
0809: * adds the given Object to a DOM structure.
0810: * (only public due to reflection).<br>
0811: * <b>Note:</b> <code>overrideClassname(Object)</code> is not invoked in case of
0812: * arrays, since the array class could be a superclass, whereas the elements of
0813: * the array can be specialized subclasses. In case of an array the method
0814: * <code>overrideClassname(String)</code> is invoked, which searches for an
0815: * exact match of the classname in the override hashtable.
0816: *
0817: * @param parent the parent of this object, e.g. the class this object is a member of
0818: * @param o the Object to describe in XML
0819: * @param name the name of the object
0820: * @return the node that was created
0821: * @throws Exception if the DOM creation fails
0822: * @see #overrideClassname(Object)
0823: * @see #overrideClassname(String)
0824: * @see #m_ClassnameOverride
0825: */
0826: public Element writeToXML(Element parent, Object o, String name)
0827: throws Exception {
0828: String classname;
0829: Element node;
0830: Hashtable memberlist;
0831: Enumeration enm;
0832: Object member;
0833: String memberName;
0834: Method method;
0835: PropertyDescriptor desc;
0836: boolean primitive;
0837: int array;
0838: int i;
0839: Object obj;
0840: String tmpStr;
0841:
0842: node = null;
0843:
0844: // for debugging only
0845: if (DEBUG)
0846: trace(new Throwable(), name);
0847:
0848: // special handling of null-objects
0849: if (o == null) {
0850: node = addElement(parent, name, "" + null, false, 0, true);
0851: return node;
0852: }
0853:
0854: // used for overriding the classname
0855: obj = null;
0856:
0857: // get information about object
0858: array = 0;
0859: if (o.getClass().isArray())
0860: array = Utils.getArrayDimensions(o);
0861: if (array > 0) {
0862: classname = Utils.getArrayClass(o.getClass()).getName();
0863: primitive = isPrimitiveArray(o.getClass());
0864: } else {
0865: // try to get property descriptor to determine real class
0866: // (for primitives the getClass() method returns the corresponding Object-Class!)
0867: desc = null;
0868: if (parent != null)
0869: desc = determineDescriptor(parent
0870: .getAttribute(ATT_CLASS), name);
0871:
0872: if (desc != null)
0873: primitive = desc.getPropertyType().isPrimitive();
0874: else
0875: primitive = o.getClass().isPrimitive();
0876:
0877: // for primitives: retrieve primitive type, otherwise the object's real
0878: // class. For non-primitives we can't use the descriptor, since that
0879: // might only return an interface as class!
0880: if (primitive) {
0881: classname = desc.getPropertyType().getName();
0882: } else {
0883: obj = o;
0884: classname = o.getClass().getName();
0885: }
0886: }
0887:
0888: // fix class/primitive if parent is array of primitives, thanks to
0889: // reflection the elements of the array are objects and not primitives!
0890: if ((parent != null)
0891: && (!parent.getAttribute(ATT_ARRAY).equals(""))
0892: && (!parent.getAttribute(ATT_ARRAY).equals(VAL_NO))
0893: && (stringToBoolean(parent.getAttribute(ATT_PRIMITIVE)))) {
0894: primitive = true;
0895: classname = parent.getAttribute(ATT_CLASS);
0896: obj = null;
0897: }
0898:
0899: // perhaps we need to override the classname
0900: if (obj != null)
0901: classname = overrideClassname(obj); // for non-arrays
0902: else
0903: classname = overrideClassname(classname); // for arrays
0904:
0905: // create node for current object
0906: node = addElement(parent, name, classname, primitive, array);
0907:
0908: // array? -> save as child with 'name="<index>"'
0909: if (array > 0) {
0910: for (i = 0; i < Array.getLength(o); i++) {
0911: invokeWriteToXML(node, Array.get(o, i), Integer
0912: .toString(i));
0913: }
0914: }
0915: // non-array
0916: else {
0917: // primitive? -> only toString()
0918: if (primitive) {
0919: node.appendChild(node.getOwnerDocument()
0920: .createTextNode(o.toString()));
0921: }
0922: // object
0923: else {
0924: // process recursively members of this object
0925: memberlist = getDescriptors(o);
0926: // if no get/set methods -> we assume it has String-Constructor
0927: if (memberlist.size() == 0) {
0928: if (!o.toString().equals("")) {
0929: tmpStr = o.toString();
0930: // these five entities are recognized by every XML processor
0931: // see http://www.xml.com/pub/a/2001/03/14/trxml10.html
0932: tmpStr = tmpStr.replaceAll("&", "&")
0933: .replaceAll("\"", """).replaceAll(
0934: "'", "'").replaceAll("<",
0935: "<").replaceAll(">", ">");
0936: // in addition, replace some other entities as well
0937: tmpStr = tmpStr.replaceAll("\n", " ")
0938: .replaceAll("\r", " ").replaceAll(
0939: "\t", "	");
0940: node.appendChild(node.getOwnerDocument()
0941: .createTextNode(tmpStr));
0942: }
0943: } else {
0944: enm = memberlist.keys();
0945: while (enm.hasMoreElements()) {
0946: memberName = enm.nextElement().toString();
0947:
0948: // in ignore list?
0949: if ((m_Properties.isIgnored(memberName))
0950: || (m_Properties
0951: .isIgnored(getPath(node) + "."
0952: + memberName))
0953: || (m_Properties.isIgnored(o,
0954: getPath(node) + "."
0955: + memberName)))
0956: continue;
0957:
0958: // is it allowed?
0959: if (!m_Properties.isAllowed(o, memberName))
0960: continue;
0961:
0962: desc = (PropertyDescriptor) memberlist
0963: .get(memberName);
0964: method = desc.getReadMethod();
0965: member = method.invoke(o, (Object[]) null);
0966: invokeWriteToXML(node, member, memberName);
0967: }
0968: }
0969: }
0970: }
0971:
0972: return node;
0973: }
0974:
0975: /**
0976: * either invokes a custom method to write a specific property/class or the standard
0977: * method <code>writeToXML(Element,Object,String)</code>
0978: *
0979: * @param parent the parent XML node
0980: * @param o the object's content will be added as children to the given parent node
0981: * @param name the name of the object
0982: * @return the node that was created
0983: * @throws Exception if invocation or turning into XML fails
0984: */
0985: protected Element invokeWriteToXML(Element parent, Object o,
0986: String name) throws Exception {
0987: Method method;
0988: Class[] methodClasses;
0989: Object[] methodArgs;
0990: boolean array;
0991: Element node;
0992: boolean useDefault;
0993:
0994: node = null;
0995: method = null;
0996: useDefault = false;
0997:
0998: m_CurrentNode = parent;
0999:
1000: // default, if null
1001: if (o == null)
1002: useDefault = true;
1003:
1004: try {
1005: if (!useDefault) {
1006: array = o.getClass().isArray();
1007:
1008: // display name?
1009: if (m_CustomMethods.write().contains(name))
1010: method = (Method) m_CustomMethods.write().get(
1011: o.getClass());
1012: else
1013: // class?
1014: if ((!array)
1015: && (m_CustomMethods.write().contains(o
1016: .getClass())))
1017: method = (Method) m_CustomMethods.write().get(
1018: o.getClass());
1019: else
1020: method = null;
1021:
1022: useDefault = (method == null);
1023: }
1024:
1025: // custom
1026: if (!useDefault) {
1027: methodClasses = new Class[3];
1028: methodClasses[0] = Element.class;
1029: methodClasses[1] = Object.class;
1030: methodClasses[2] = String.class;
1031: methodArgs = new Object[3];
1032: methodArgs[0] = parent;
1033: methodArgs[1] = o;
1034: methodArgs[2] = name;
1035: node = (Element) method.invoke(this , methodArgs);
1036: }
1037: // standard
1038: else {
1039: node = writeToXML(parent, o, name);
1040: }
1041: } catch (Exception e) {
1042: if (DEBUG)
1043: e.printStackTrace();
1044:
1045: if (m_CurrentNode != null) {
1046: System.out.println("Happened near: "
1047: + getPath(m_CurrentNode));
1048: // print it only once!
1049: m_CurrentNode = null;
1050: }
1051: System.out.println("PROBLEM (write): " + name);
1052:
1053: throw (Exception) e.fillInStackTrace();
1054: }
1055:
1056: return node;
1057: }
1058:
1059: /**
1060: * enables derived classes to due some pre-processing on the objects, that's
1061: * about to be serialized. Right now it only returns the object.
1062: *
1063: * @param o the object that is serialized into XML
1064: * @return the possibly altered object
1065: * @throws Exception if post-processing fails
1066: */
1067: protected Object writePreProcess(Object o) throws Exception {
1068: return o;
1069: }
1070:
1071: /**
1072: * enables derived classes to add other properties to the DOM tree, e.g.
1073: * ones that do not apply to the get/set convention of beans. only implemented
1074: * with empty method body.
1075: *
1076: * @param o the object that is serialized into XML
1077: * @throws Exception if post-processing fails
1078: */
1079: protected void writePostProcess(Object o) throws Exception {
1080: }
1081:
1082: /**
1083: * extracts all accesible properties from the given object
1084: *
1085: * @param o the object to turn into an XML representation
1086: * @return the generated DOM document
1087: * @throws Exception if XML generation fails
1088: */
1089: public XMLDocument toXML(Object o) throws Exception {
1090: clear();
1091: invokeWriteToXML(null, writePreProcess(o), VAL_ROOT);
1092: writePostProcess(o);
1093: return m_Document;
1094: }
1095:
1096: /**
1097: * returns a descriptor for a given objet by providing the name
1098: *
1099: * @param o the object the get the descriptor for
1100: * @param name the display name of the descriptor
1101: * @return the Descriptor, if found, otherwise <code>null</code>
1102: * @throws Exception if introsepction fails
1103: */
1104: protected PropertyDescriptor getDescriptorByName(Object o,
1105: String name) throws Exception {
1106: PropertyDescriptor result;
1107: PropertyDescriptor[] desc;
1108: int i;
1109:
1110: result = null;
1111:
1112: desc = Introspector.getBeanInfo(o.getClass())
1113: .getPropertyDescriptors();
1114: for (i = 0; i < desc.length; i++) {
1115: if (desc[i].getDisplayName().equals(name)) {
1116: result = desc[i];
1117: break;
1118: }
1119: }
1120:
1121: return result;
1122: }
1123:
1124: /**
1125: * returns the associated class for the given name
1126: *
1127: * @param name the name of the class to return a Class object for
1128: * @return the class if it could be retrieved
1129: * @throws Exception if it class retrieval fails
1130: */
1131: protected Class determineClass(String name) throws Exception {
1132: Class result;
1133:
1134: if (name.equals(Boolean.TYPE.getName()))
1135: result = Boolean.TYPE;
1136: else if (name.equals(Byte.TYPE.getName()))
1137: result = Byte.TYPE;
1138: else if (name.equals(Character.TYPE.getName()))
1139: result = Character.TYPE;
1140: else if (name.equals(Double.TYPE.getName()))
1141: result = Double.TYPE;
1142: else if (name.equals(Float.TYPE.getName()))
1143: result = Float.TYPE;
1144: else if (name.equals(Integer.TYPE.getName()))
1145: result = Integer.TYPE;
1146: else if (name.equals(Long.TYPE.getName()))
1147: result = Long.TYPE;
1148: else if (name.equals(Short.TYPE.getName()))
1149: result = Short.TYPE;
1150: else
1151: result = Class.forName(name);
1152:
1153: return result;
1154: }
1155:
1156: /**
1157: * returns an Object representing the primitive described by the given node.
1158: * Here we use a trick to return an object even though its a primitive: by
1159: * creating a primitive array with reflection of length 1, setting the
1160: * primtive value as real object and then returning the "object" at
1161: * position 1 of the array.
1162: *
1163: * @param node the node to return the value as "primitive" object
1164: * @return the primitive as "pseudo" object
1165: * @throws Exception if the instantiation of the array fails or any of the
1166: * String conversions fails
1167: */
1168: protected Object getPrimitive(Element node) throws Exception {
1169: Object result;
1170: Object tmpResult;
1171: Class cls;
1172:
1173: cls = determineClass(node.getAttribute(ATT_CLASS));
1174: tmpResult = Array.newInstance(cls, 1);
1175:
1176: if (cls == Boolean.TYPE)
1177: Array.set(tmpResult, 0, new Boolean(XMLDocument
1178: .getContent(node)));
1179: else if (cls == Byte.TYPE)
1180: Array.set(tmpResult, 0, new Byte(XMLDocument
1181: .getContent(node)));
1182: else if (cls == Character.TYPE)
1183: Array.set(tmpResult, 0, new Character(XMLDocument
1184: .getContent(node).charAt(0)));
1185: else if (cls == Double.TYPE)
1186: Array.set(tmpResult, 0, new Double(XMLDocument
1187: .getContent(node)));
1188: else if (cls == Float.TYPE)
1189: Array.set(tmpResult, 0, new Float(XMLDocument
1190: .getContent(node)));
1191: else if (cls == Integer.TYPE)
1192: Array.set(tmpResult, 0, new Integer(XMLDocument
1193: .getContent(node)));
1194: else if (cls == Long.TYPE)
1195: Array.set(tmpResult, 0, new Long(XMLDocument
1196: .getContent(node)));
1197: else if (cls == Short.TYPE)
1198: Array.set(tmpResult, 0, new Short(XMLDocument
1199: .getContent(node)));
1200: else
1201: throw new Exception("Cannot get primitive for class '"
1202: + cls.getName() + "'!");
1203:
1204: result = Array.get(tmpResult, 0);
1205:
1206: return result;
1207: }
1208:
1209: /**
1210: * builds the primitive from the given DOM node.
1211: *
1212: * @param node the associated XML node
1213: * @return the primitive created from the XML description
1214: * @throws Exception if instantiation fails
1215: */
1216: public boolean readBooleanFromXML(Element node) throws Exception {
1217: // for debugging only
1218: if (DEBUG)
1219: trace(new Throwable(), node.getAttribute(ATT_NAME));
1220:
1221: m_CurrentNode = node;
1222:
1223: return ((Boolean) getPrimitive(node)).booleanValue();
1224: }
1225:
1226: /**
1227: * builds the primitive from the given DOM node.
1228: *
1229: * @param node the associated XML node
1230: * @return the primitive created from the XML description
1231: * @throws Exception if instantiation fails
1232: */
1233: public byte readByteFromXML(Element node) throws Exception {
1234: // for debugging only
1235: if (DEBUG)
1236: trace(new Throwable(), node.getAttribute(ATT_NAME));
1237:
1238: m_CurrentNode = node;
1239:
1240: return ((Byte) getPrimitive(node)).byteValue();
1241: }
1242:
1243: /**
1244: * builds the primitive from the given DOM node.
1245: *
1246: * @param node the associated XML node
1247: * @return the primitive created from the XML description
1248: * @throws Exception if instantiation fails
1249: */
1250: public char readCharFromXML(Element node) throws Exception {
1251: // for debugging only
1252: if (DEBUG)
1253: trace(new Throwable(), node.getAttribute(ATT_NAME));
1254:
1255: m_CurrentNode = node;
1256:
1257: return ((Character) getPrimitive(node)).charValue();
1258: }
1259:
1260: /**
1261: * builds the primitive from the given DOM node.
1262: *
1263: * @param node the associated XML node
1264: * @return the primitive created from the XML description
1265: * @throws Exception if instantiation fails
1266: */
1267: public double readDoubleFromXML(Element node) throws Exception {
1268: // for debugging only
1269: if (DEBUG)
1270: trace(new Throwable(), node.getAttribute(ATT_NAME));
1271:
1272: m_CurrentNode = node;
1273:
1274: return ((Double) getPrimitive(node)).doubleValue();
1275: }
1276:
1277: /**
1278: * builds the primitive from the given DOM node.
1279: *
1280: * @param node the associated XML node
1281: * @return the primitive created from the XML description
1282: * @throws Exception if instantiation fails
1283: */
1284: public float readFloatFromXML(Element node) throws Exception {
1285: // for debugging only
1286: if (DEBUG)
1287: trace(new Throwable(), node.getAttribute(ATT_NAME));
1288:
1289: m_CurrentNode = node;
1290:
1291: return ((Float) getPrimitive(node)).floatValue();
1292: }
1293:
1294: /**
1295: * builds the primitive from the given DOM node.
1296: *
1297: * @param node the associated XML node
1298: * @return the primitive created from the XML description
1299: * @throws Exception if instantiation fails
1300: */
1301: public int readIntFromXML(Element node) throws Exception {
1302: // for debugging only
1303: if (DEBUG)
1304: trace(new Throwable(), node.getAttribute(ATT_NAME));
1305:
1306: m_CurrentNode = node;
1307:
1308: return ((Integer) getPrimitive(node)).intValue();
1309: }
1310:
1311: /**
1312: * builds the primitive from the given DOM node.
1313: *
1314: * @param node the associated XML node
1315: * @return the primitive created from the XML description
1316: * @throws Exception if instantiation fails
1317: */
1318: public long readLongFromXML(Element node) throws Exception {
1319: // for debugging only
1320: if (DEBUG)
1321: trace(new Throwable(), node.getAttribute(ATT_NAME));
1322:
1323: m_CurrentNode = node;
1324:
1325: return ((Long) getPrimitive(node)).longValue();
1326: }
1327:
1328: /**
1329: * builds the primitive from the given DOM node.
1330: *
1331: * @param node the associated XML node
1332: * @return the primitive created from the XML description
1333: * @throws Exception if instantiation fails
1334: */
1335: public short readShortFromXML(Element node) throws Exception {
1336: // for debugging only
1337: if (DEBUG)
1338: trace(new Throwable(), node.getAttribute(ATT_NAME));
1339:
1340: m_CurrentNode = node;
1341:
1342: return ((Short) getPrimitive(node)).shortValue();
1343: }
1344:
1345: /**
1346: * adds the specific node to the object via a set method
1347: *
1348: * @param o the object to set a property
1349: * @param name the name of the object for which to set a property
1350: * (only for information reasons)
1351: * @param child the value of the property to add
1352: * @return the provided object, but augmented by the child
1353: * @throws Exception if something goes wrong
1354: */
1355: public Object readFromXML(Object o, String name, Element child)
1356: throws Exception {
1357: Object result;
1358: Hashtable descriptors;
1359: PropertyDescriptor descriptor;
1360: String methodName;
1361: Method method;
1362: Object[] methodArgs;
1363: Object tmpResult;
1364: Class paramClass;
1365:
1366: result = o;
1367: descriptors = getDescriptors(result);
1368: methodName = child.getAttribute(ATT_NAME);
1369:
1370: // in ignore list?
1371: if (m_Properties.isIgnored(getPath(child)))
1372: return result;
1373:
1374: // in ignore list of class?
1375: if (m_Properties.isIgnored(result, getPath(child)))
1376: return result;
1377:
1378: // is it allowed?
1379: if (!m_Properties.isAllowed(result, methodName))
1380: return result;
1381:
1382: descriptor = (PropertyDescriptor) descriptors.get(methodName);
1383:
1384: // unknown property?
1385: if (descriptor == null) {
1386: if (!m_CustomMethods.read().contains(methodName))
1387: System.out.println("WARNING: unknown property '" + name
1388: + "." + methodName + "'!");
1389: return result;
1390: }
1391:
1392: method = descriptor.getWriteMethod();
1393: methodArgs = new Object[1];
1394: tmpResult = invokeReadFromXML(child);
1395: paramClass = method.getParameterTypes()[0];
1396:
1397: // array?
1398: if (paramClass.isArray()) {
1399: // no data?
1400: if (Array.getLength(tmpResult) == 0)
1401: return result;
1402: methodArgs[0] = (Object[]) tmpResult;
1403: }
1404: // non-array
1405: else {
1406: methodArgs[0] = tmpResult;
1407: }
1408:
1409: method.invoke(result, methodArgs);
1410:
1411: return result;
1412: }
1413:
1414: /**
1415: * returns an array with the dimensions of the array stored in XML
1416: * @param node the node to determine the dimensions for
1417: * @return the dimensions of the array
1418: */
1419: protected int[] getArrayDimensions(Element node) {
1420: Vector children;
1421: Vector tmpVector;
1422: int[] tmp;
1423: int[] result;
1424: int i;
1425:
1426: // have we reached the innermost dimension?
1427: if (stringToBoolean(node.getAttribute(ATT_ARRAY)))
1428: children = XMLDocument.getChildTags(node);
1429: else
1430: children = null;
1431:
1432: if (children != null) {
1433: tmpVector = new Vector();
1434:
1435: if (children.size() > 0) {
1436: // are children also arrays?
1437: tmp = getArrayDimensions((Element) children.get(0));
1438:
1439: // further dimensions
1440: if (tmp != null) {
1441: for (i = tmp.length - 1; i >= 0; i--)
1442: tmpVector.add(new Integer(tmp[i]));
1443: }
1444:
1445: // add current dimension
1446: tmpVector.add(0, new Integer(children.size()));
1447: } else {
1448: tmpVector.add(new Integer(0));
1449: }
1450:
1451: // generate result
1452: result = new int[tmpVector.size()];
1453: for (i = 0; i < result.length; i++)
1454: result[i] = ((Integer) tmpVector.get(tmpVector.size()
1455: - i - 1)).intValue();
1456: } else {
1457: result = null;
1458: }
1459:
1460: return result;
1461: }
1462:
1463: /**
1464: * builds the object from the given DOM node.
1465: * (only public due to reflection)
1466: *
1467: * @param node the associated XML node
1468: * @return the instance created from the XML description
1469: * @throws Exception if instantiation fails
1470: */
1471: public Object readFromXML(Element node) throws Exception {
1472: String classname;
1473: String name;
1474: boolean primitive;
1475: boolean array;
1476: boolean isnull;
1477: Class cls;
1478: Vector children;
1479: Object result;
1480: int i;
1481: Constructor constructor;
1482: Class[] methodClasses;
1483: Object[] methodArgs;
1484: Element child;
1485:
1486: // for debugging only
1487: if (DEBUG)
1488: trace(new Throwable(), node.getAttribute(ATT_NAME));
1489:
1490: m_CurrentNode = node;
1491:
1492: result = null;
1493:
1494: name = node.getAttribute(ATT_NAME);
1495: classname = node.getAttribute(ATT_CLASS);
1496: primitive = stringToBoolean(node.getAttribute(ATT_PRIMITIVE));
1497: array = stringToBoolean(node.getAttribute(ATT_ARRAY));
1498: isnull = stringToBoolean(node.getAttribute(ATT_NULL));
1499:
1500: // special handling of null
1501: if (isnull)
1502: return result;
1503:
1504: children = XMLDocument.getChildTags(node);
1505: cls = determineClass(classname);
1506:
1507: // array
1508: if (array) {
1509: result = Array.newInstance(cls, getArrayDimensions(node));
1510: for (i = 0; i < children.size(); i++) {
1511: child = (Element) children.get(i);
1512: Array.set(result, Integer.parseInt(child
1513: .getAttribute(ATT_NAME)),
1514: invokeReadFromXML(child));
1515: }
1516: }
1517: // non-array
1518: else {
1519: // primitive/String-constructor
1520: if (children.size() == 0) {
1521: // primitive
1522: if (primitive) {
1523: result = getPrimitive(node);
1524: }
1525: // assumed String-constructor
1526: else {
1527: methodClasses = new Class[1];
1528: methodClasses[0] = String.class;
1529: methodArgs = new Object[1];
1530: methodArgs[0] = XMLDocument.getContent(node);
1531: try {
1532: constructor = cls.getConstructor(methodClasses);
1533: result = constructor.newInstance(methodArgs);
1534: } catch (Exception e) {
1535: // if it's not a class with String constructor, let's try standard constructor
1536: try {
1537: result = cls.newInstance();
1538: } catch (Exception e2) {
1539: // sorry, can't instantiate!
1540: result = null;
1541: System.out
1542: .println("ERROR: Can't instantiate '"
1543: + classname + "'!");
1544: }
1545: }
1546: }
1547: }
1548: // normal get/set methods
1549: else {
1550: result = cls.newInstance();
1551: for (i = 0; i < children.size(); i++)
1552: result = readFromXML(result, name,
1553: (Element) children.get(i));
1554: }
1555: }
1556:
1557: return result;
1558: }
1559:
1560: /**
1561: * either invokes a custom method to read a specific property/class or the standard
1562: * method <code>readFromXML(Element)</code>
1563: *
1564: * @param node the associated XML node
1565: * @return the instance created from the XML description
1566: * @throws Exception if instantiation fails
1567: */
1568: protected Object invokeReadFromXML(Element node) throws Exception {
1569: Method method;
1570: Class[] methodClasses;
1571: Object[] methodArgs;
1572: boolean array;
1573: boolean useDefault;
1574:
1575: useDefault = false;
1576: method = null;
1577: m_CurrentNode = node;
1578:
1579: try {
1580: // special handling of null values
1581: if (stringToBoolean(node.getAttribute(ATT_NULL)))
1582: useDefault = true;
1583:
1584: if (!useDefault) {
1585: array = stringToBoolean(node.getAttribute(ATT_ARRAY));
1586:
1587: // display name?
1588: if (m_CustomMethods.read().contains(
1589: node.getAttribute(ATT_NAME)))
1590: method = (Method) m_CustomMethods.read().get(
1591: node.getAttribute(ATT_NAME));
1592: else
1593: // class name?
1594: if ((!array)
1595: && (m_CustomMethods.read()
1596: .contains(determineClass(node
1597: .getAttribute(ATT_CLASS)))))
1598: method = (Method) m_CustomMethods.read()
1599: .get(
1600: determineClass(node
1601: .getAttribute(ATT_CLASS)));
1602: else
1603: method = null;
1604:
1605: useDefault = (method == null);
1606: }
1607:
1608: // custom method
1609: if (!useDefault) {
1610: methodClasses = new Class[1];
1611: methodClasses[0] = Element.class;
1612: methodArgs = new Object[1];
1613: methodArgs[0] = node;
1614: return method.invoke(this , methodArgs);
1615: }
1616: // standard
1617: else {
1618: return readFromXML(node);
1619: }
1620: } catch (Exception e) {
1621: if (DEBUG)
1622: e.printStackTrace();
1623:
1624: if (m_CurrentNode != null) {
1625: System.out.println("Happened near: "
1626: + getPath(m_CurrentNode));
1627: // print it only once!
1628: m_CurrentNode = null;
1629: }
1630: System.out.println("PROBLEM (read): "
1631: + node.getAttribute("name"));
1632:
1633: throw (Exception) e.fillInStackTrace();
1634: }
1635: }
1636:
1637: /**
1638: * additional pre-processing can happen in derived classes before the
1639: * actual reading from XML (working on the raw XML). right now it does
1640: * nothing with the document.
1641: *
1642: * @param document the document to pre-process
1643: * @return the processed object
1644: * @throws Exception if post-processing fails
1645: */
1646: protected Document readPreProcess(Document document)
1647: throws Exception {
1648: return document;
1649: }
1650:
1651: /**
1652: * additional post-processing can happen in derived classes after reading
1653: * from XML. right now it only returns the object as it is.
1654: *
1655: * @param o the object to perform some additional processing on
1656: * @return the processed object
1657: * @throws Exception if post-processing fails
1658: */
1659: protected Object readPostProcess(Object o) throws Exception {
1660: return o;
1661: }
1662:
1663: /**
1664: * returns the given DOM document as an instance of the specified class
1665: *
1666: * @param document the parsed DOM document representing the object
1667: * @return the XML as object
1668: * @throws Exception if object instantiation fails
1669: */
1670: public Object fromXML(Document document) throws Exception {
1671: if (!document.getDocumentElement().getNodeName().equals(
1672: ROOT_NODE))
1673: throw new Exception("Expected '" + ROOT_NODE
1674: + "' as root element, but found '"
1675: + document.getDocumentElement().getNodeName()
1676: + "'!");
1677: m_Document.setDocument(readPreProcess(document));
1678: checkVersion();
1679: return readPostProcess(invokeReadFromXML(m_Document
1680: .getDocument().getDocumentElement()));
1681: }
1682:
1683: /**
1684: * parses the given XML string (can be XML or a filename) and returns an
1685: * Object generated from the representation
1686: *
1687: * @param xml the xml to parse (if "<?xml" is not found then it is considered a file)
1688: * @return the generated instance
1689: * @throws Exception if something goes wrong with the parsing
1690: */
1691: public Object read(String xml) throws Exception {
1692: return fromXML(m_Document.read(xml));
1693: }
1694:
1695: /**
1696: * parses the given file and returns a DOM document
1697: *
1698: * @param file the XML file to parse
1699: * @return the parsed DOM document
1700: * @throws Exception if something goes wrong with the parsing
1701: */
1702: public Object read(File file) throws Exception {
1703: return fromXML(m_Document.read(file));
1704: }
1705:
1706: /**
1707: * parses the given stream and returns a DOM document
1708: *
1709: * @param stream the XML stream to parse
1710: * @return the parsed DOM document
1711: * @throws Exception if something goes wrong with the parsing
1712: */
1713: public Object read(InputStream stream) throws Exception {
1714: return fromXML(m_Document.read(stream));
1715: }
1716:
1717: /**
1718: * parses the given reader and returns a DOM document
1719: *
1720: * @param reader the XML reader to parse
1721: * @return the parsed DOM document
1722: * @throws Exception if something goes wrong with the parsing
1723: */
1724: public Object read(Reader reader) throws Exception {
1725: return fromXML(m_Document.read(reader));
1726: }
1727:
1728: /**
1729: * writes the given object into the file
1730: *
1731: * @param file the filename to write to
1732: * @param o the object to serialize as XML
1733: * @throws Exception if something goes wrong with the parsing
1734: */
1735: public void write(String file, Object o) throws Exception {
1736: toXML(o).write(file);
1737: }
1738:
1739: /**
1740: * writes the given object into the file
1741: *
1742: * @param file the filename to write to
1743: * @param o the object to serialize as XML
1744: * @throws Exception if something goes wrong with the parsing
1745: */
1746: public void write(File file, Object o) throws Exception {
1747: toXML(o).write(file);
1748: }
1749:
1750: /**
1751: * writes the given object into the stream
1752: *
1753: * @param stream the filename to write to
1754: * @param o the object to serialize as XML
1755: * @throws Exception if something goes wrong with the parsing
1756: */
1757: public void write(OutputStream stream, Object o) throws Exception {
1758: toXML(o).write(stream);
1759: }
1760:
1761: /**
1762: * writes the given object into the writer
1763: *
1764: * @param writer the filename to write to
1765: * @param o the object to serialize as XML
1766: * @throws Exception if something goes wrong with the parsing
1767: */
1768: public void write(Writer writer, Object o) throws Exception {
1769: toXML(o).write(writer);
1770: }
1771:
1772: /**
1773: * for testing only. if the first argument is a filename with ".xml"
1774: * as extension it tries to generate an instance from the XML description
1775: * and does a <code>toString()</code> of the generated object.
1776: */
1777: public static void main(String[] args) throws Exception {
1778: if (args.length > 0) {
1779: // read xml and print
1780: if (args[0].toLowerCase().endsWith(".xml")) {
1781: System.out.println(new XMLSerialization().read(args[0])
1782: .toString());
1783: }
1784: // read binary and print generated XML
1785: else {
1786: // read
1787: FileInputStream fi = new FileInputStream(args[0]);
1788: ObjectInputStream oi = new ObjectInputStream(
1789: new BufferedInputStream(fi));
1790: Object o = oi.readObject();
1791: oi.close();
1792: // print to stdout
1793: //new XMLSerialization().write(System.out, o);
1794: new XMLSerialization().write(new BufferedOutputStream(
1795: new FileOutputStream(args[0] + ".xml")), o);
1796: // print to binary file
1797: FileOutputStream fo = new FileOutputStream(args[0]
1798: + ".exp");
1799: ObjectOutputStream oo = new ObjectOutputStream(
1800: new BufferedOutputStream(fo));
1801: oo.writeObject(o);
1802: oo.close();
1803: }
1804: }
1805: }
1806: }
|