/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.xmlcode; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.StringWriter; import java.util.Enumeration; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Vector; import java.util.regex.Pattern; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import org.jdom2.Attribute; import org.jdom2.DocType; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.Text; import org.jdom2.output.Format; import org.jdom2.output.LineSeparator; import org.jdom2.output.XMLOutputter; import org.openflexo.xmlcode.XMLMapId.NoMapIdEntryException; import org.xml.sax.SAXException; /** * <p> * Utility class providing XML coding facilities * </p> * This class allow you to encode object to an XML string or streams according to a mapping you define externaly (see {@link XMLMapping}).<br> * <p> * If you want to encode an object <code>anObject</code>, just do: * * <pre> * XMLMapping myMapping = new XMLMapping(aModelFile); * * String result = XMLCoder.encodeObjectWithMapping(anObject, myMapping); * </pre> * * or * * <pre> * String result = XMLCoder.encodeObjectWithMappingFile(anObject, aModelFile); * </pre> * * if you want to work with <code>String</code> objects * </p> * <p> * But you can directly work with <code>OutputStream</code> objects, by doing: * * <pre> * XMLMapping myMapping = new XMLMapping(exampleModelFile); * XMLCoder.encodeObjectWithMapping(myCommand, myMapping, out); * </pre> * * or * * <pre> * XMLCoder.encodeObjectWithMappingFile(myCommand, exampleModelFile, out); * </pre> * * where <code>out</code> is a <code>OutputStream</code> object. * </p> * * NB: To work properly, <code>XMLCoder</code> may require that you specify TransformerFactory implementation to use (see * {@link #setTransformerFactoryClass(String)}), for example by calling * <code>XMLCoder.setTransformerFactoryClass("org.apache.xalan.processor.TransformerFactoryImpl");</code> once, if you want to use Apache * Xalan transformer, which is provided with this distribution. * * @author <a href="mailto:Sylvain.Guerin@enst-bretagne.fr">Sylvain Guerin</a> * @see XMLDecoder * @see XMLMapping */ public class XMLCoder { /** Stores mapping that will be used for decoding */ protected XMLMapping xmlMapping; /** * Stores already serialized objects where key is the serialized object and value is a * * <pre> * Integer * </pre> * * instance coding the unique identifier of the object */ private Hashtable<Object, Object> alreadySerialized; private Hashtable<Object, Object> serializationIdentifierForObject; protected OrderedElementReferenceList orderedElementReferenceList; // Keys are objects and values are ObjectReference private Hashtable<Object, ObjectReference> objectReferences; private StringEncoder stringEncoder; /** * Internaly used to get a unique identifier */ private int nextReference; /** * Set TransformerFactory class implementation to use for coding objects.<br> * To work properly, TransformerFactory requires to get an implementation (a a class to load) to get new instances of transformer. The * following ordered lookup procedure to determine the TransformerFactory implementation class to load is applied: * <ul> * <li>Use the javax.xml.transform.TransformerFactory system property</li> * <li>Use the properties file "lib/jaxp.properties" in the JRE directory. This configuration file is in standard java.util.Properties * format and contains the fully qualified name of the implementation class with the key being the system property defined above</li> * <li>Use the Services API (as detailed in the JAR specification), if available, to determine the classname. The Services API will look * for a classname in the file META-INF/services/javax.xml.transform.TransformerFactory in jars available to the runtime</li> * <li>Platform default TransformerFactory instance</li> * </ul> * This method allows to statically sets the system property to the value you want. * * @param transformerFactoryClassName * a <code>String</code> value (full qualified name of the TransformerFactory class, for example * <code>org.apache.xalan.processor.TransformerFactoryImpl</code>) */ public static void setTransformerFactoryClass(String transformerFactoryClassName) { Properties systemProps = System.getProperties(); systemProps.setProperty("javax.xml.transform.TransformerFactory", transformerFactoryClassName); } private SerializationHandler _serializationHandler; public SerializationHandler getSerializationHandler() { return _serializationHandler; } public void setSerializationHandler(SerializationHandler handler) { _serializationHandler = handler; } /** * Creates a new <code>XMLCoder</code> instance, given a mapping file * * @param modelFile * a <code>File</code> value * @exception IOException * if an IOException error occurs (eg. file not found) * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs */ public XMLCoder(File modelFile) throws IOException, SAXException, ParserConfigurationException, InvalidModelException, InvalidModelException { this(new XMLMapping(modelFile), StringEncoder.getDefaultInstance()); } /** * Creates a new <code>XMLCoder</code> instance, given a mapping stream * * @param modelInputStream * a <code>InputStream</code> value * @exception IOException * if an IOException error occurs (eg. file not found) * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs */ public XMLCoder(InputStream modelInputStream) throws IOException, SAXException, ParserConfigurationException, InvalidModelException, InvalidModelException { this(new XMLMapping(modelInputStream), StringEncoder.getDefaultInstance()); } /** * Creates a new <code>XMLCoder</code> instance, given a mapping file * * @param modelFile * a <code>File</code> value * * @param encoder * the string encoder to use * @exception IOException * if an IOException error occurs (eg. file not found) * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs */ public XMLCoder(File modelFile, StringEncoder encoder) throws IOException, SAXException, ParserConfigurationException, InvalidModelException, InvalidModelException { this(new XMLMapping(modelFile), encoder); } /** * Creates a new <code>XMLCoder</code> instance given an <code>XMLMapping</code> object * * @param anXmlMapping * an <code>XMLMapping</code> value */ public XMLCoder(XMLMapping anXmlMapping) { this(anXmlMapping, StringEncoder.getDefaultInstance()); } /** * Creates a new <code>XMLCoder</code> instance given an <code>XMLMapping</code> object * * @param anXmlMapping * an <code>XMLMapping</code> value * @param stringEncoder * the string encoder to use */ public XMLCoder(XMLMapping anXmlMapping, StringEncoder encoder) { super(); xmlMapping = anXmlMapping; alreadySerialized = new Hashtable<Object, Object>(); serializationIdentifierForObject = new Hashtable<Object, Object>(); orderedElementReferenceList = new OrderedElementReferenceList(); objectReferences = new Hashtable<Object, ObjectReference>(); nextReference = 0; stringEncoder = encoder; } private void delete() { alreadySerialized.clear(); serializationIdentifierForObject.clear(); orderedElementReferenceList.delete(); for (ObjectReference next : objectReferences.values()) { next.delete(); } objectReferences.clear(); alreadySerialized = null; serializationIdentifierForObject = null; orderedElementReferenceList = null; objectReferences = null; xmlMapping = null; stringEncoder = null; _serializationHandler = null; } /** * Encode to an XML string object <code>anObject</code> according to mapping <code>xmlMapping</code>, and returns this newly created * string * * @param anObject * an <code>Object</code> value * @param xmlMapping * a <code>XMLMapping</code> value * @return an <code>Object</code> value * @deprecated use the same method but with the StringEncoder argument * {@link #encodeObjectWithMapping(XMLSerializable, XMLMapping, StringEncoder)} * @exception InvalidObjectSpecificationException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ @Deprecated public static String encodeObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { return encodeObjectWithMapping(anObject, xmlMapping, (SerializationHandler) null); } /** * Encode to an XML string object <code>anObject</code> according to mapping <code>xmlMapping</code>, and returns this newly created * string * * @param anObject * an <code>Object</code> value * @param xmlMapping * a <code>XMLMapping</code> value * @param serializationHandler * TODO * @deprecated use the same method with the StringEncoder argument * {@link #encodeObjectWithMapping(XMLSerializable, XMLMapping, StringEncoder, SerializationHandler)} * @return an <code>Object</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ @Deprecated public static String encodeObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping, SerializationHandler serializationHandler) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { XMLCoder encoder = new XMLCoder(xmlMapping, StringEncoder.getDefaultInstance()); encoder.setSerializationHandler(serializationHandler); return encoder.encodeObject(anObject); } /** * @param resourceData * @param currentMapping * @param stringEncoder2 * @return * @throws DuplicateSerializationIdentifierException * @throws AccessorInvocationException * @throws InvalidModelException * @throws InvalidObjectSpecificationException */ public static String encodeObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping, StringEncoder stringEncoder) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { return encodeObjectWithMapping(anObject, xmlMapping, stringEncoder, null); } /** * @param serializationHandler * TODO * @param resourceData * @param currentMapping * @param stringEncoder2 * @return * @throws DuplicateSerializationIdentifierException * @throws AccessorInvocationException * @throws InvalidModelException * @throws InvalidObjectSpecificationException */ public static String encodeObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping, StringEncoder stringEncoder, SerializationHandler serializationHandler) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { XMLCoder encoder = new XMLCoder(xmlMapping, stringEncoder); encoder.setSerializationHandler(serializationHandler); return encoder.encodeObject(anObject); } /** * Encode to an XML string object <code>anObject</code> according to mapping defined in file <code>modelFile</code>, and returns this * newly created string * * @param anObject * an <code>Object</code> value * @param modelFile * a <code>File</code> value * @return an <code>Object</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception IOException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ public static String encodeObjectWithMappingFile(XMLSerializable anObject, File modelFile) throws InvalidObjectSpecificationException, IOException, SAXException, ParserConfigurationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { return encodeObjectWithMappingFile(anObject, modelFile, (SerializationHandler) null); } /** * Encode to an XML string object <code>anObject</code> according to mapping defined in stream <code>modelInputStream</code>, and * returns this newly created string * * @param anObject * an <code>Object</code> value * @param modelInputStream * a <code>InputStream</code> value * @return an <code>Object</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception IOException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ public static String encodeObjectWithMappingStream(XMLSerializable anObject, InputStream modelInputStream) throws InvalidObjectSpecificationException, IOException, SAXException, ParserConfigurationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { return encodeObjectWithMappingStream(anObject, modelInputStream, (SerializationHandler) null); } /** * Encode to an XML string object <code>anObject</code> according to mapping defined in file <code>modelFile</code>, and returns this * newly created string * * @param anObject * an <code>Object</code> value * @param modelFile * a <code>File</code> value * @param serializationHandler * TODO * @return an <code>Object</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception IOException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ public static String encodeObjectWithMappingFile(XMLSerializable anObject, File modelFile, SerializationHandler serializationHandler) throws InvalidObjectSpecificationException, IOException, SAXException, ParserConfigurationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { XMLCoder encoder = new XMLCoder(modelFile); encoder.setSerializationHandler(serializationHandler); return encoder.encodeObject(anObject); } /** * Encode to an XML string object <code>anObject</code> according to mapping defined in stream <code>modelInputStream</code>, and * returns this newly created string * * @param anObject * an <code>Object</code> value * @param modelInputStream * a <code>InputStream</code> value * @param serializationHandler * TODO * @return an <code>Object</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception IOException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ public static String encodeObjectWithMappingStream(XMLSerializable anObject, InputStream modelInputStream, SerializationHandler serializationHandler) throws InvalidObjectSpecificationException, IOException, SAXException, ParserConfigurationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { XMLCoder encoder = new XMLCoder(modelInputStream); encoder.setSerializationHandler(serializationHandler); return encoder.encodeObject(anObject); } /** * Encode to an XML string object <code>anObject</code> according to mapping <code>xmlMapping</code>, and writes it to output stream * <code>out</code>. * * @param anObject * an <code>Object</code> value * @param xmlMapping * a <code>XMLMapping</code> value * @param out * an <code>OutputStream</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ public static void encodeObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping, OutputStream out) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { encodeObjectWithMapping(anObject, xmlMapping, out, (SerializationHandler) null); } /** * Encode to an XML string object <code>anObject</code> according to mapping <code>xmlMapping</code>, and writes it to output stream * <code>out</code>. * * @param anObject * an <code>Object</code> value * @param xmlMapping * a <code>XMLMapping</code> value * @param out * an <code>OutputStream</code> value * @param serializationHandler * TODO * @exception InvalidObjectSpecificationException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ public static void encodeObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping, OutputStream out, SerializationHandler serializationHandler) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { XMLCoder encoder = new XMLCoder(xmlMapping, StringEncoder.getDefaultInstance()); encoder.setSerializationHandler(serializationHandler); encoder.encodeObject(anObject, out); } /** * Encode to an XML string object <code>anObject</code> according to mapping <code>xmlMapping</code>, and writes it to output stream * <code>out</code>. * * @param anObject * an <code>Object</code> value * @param xmlMapping * a <code>XMLMapping</code> value * @param out * an <code>OutputStream</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ public static void encodeObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping, OutputStream out, StringEncoder stringEncoder) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { encodeObjectWithMapping(anObject, xmlMapping, out, stringEncoder, (SerializationHandler) null); } /** * Encode to an XML string object <code>anObject</code> according to mapping <code>xmlMapping</code>, and writes it to output stream * <code>out</code>. * * @param anObject * an <code>Object</code> value * @param xmlMapping * a <code>XMLMapping</code> value * @param out * an <code>OutputStream</code> value * @param serializationHandler * TODO * @exception InvalidObjectSpecificationException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ public static void encodeObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping, OutputStream out, StringEncoder stringEncoder, SerializationHandler serializationHandler) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { XMLCoder encoder = new XMLCoder(xmlMapping, stringEncoder); encoder.setSerializationHandler(serializationHandler); encoder.encodeObject(anObject, out); } public static void encodeObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping, OutputStream out, Format format) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { encodeObjectWithMapping(anObject, xmlMapping, out, format, (SerializationHandler) null); } public static void encodeObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping, OutputStream out, Format format, SerializationHandler serializationHandler) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { XMLCoder encoder = new XMLCoder(xmlMapping, StringEncoder.getDefaultInstance()); encoder.setSerializationHandler(serializationHandler); encoder.encodeObject(anObject, out, format); } public static void encodeObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping, OutputStream out, Format format, StringEncoder stringEncoder) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { encodeObjectWithMapping(anObject, xmlMapping, out, format, stringEncoder, (SerializationHandler) null); } public static void encodeObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping, OutputStream out, Format format, StringEncoder stringEncoder, SerializationHandler serializationHandler) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { XMLCoder encoder = new XMLCoder(xmlMapping, stringEncoder); encoder.setSerializationHandler(serializationHandler); encoder.encodeObject(anObject, out, format); } /** * Encode to an XML string object <code>anObject</code> according to mapping defined in file <code>modelFile</code>, and writes it to * output stream <code>out</code>. * * @param anObject * an <code>Object</code> value * @param modelFile * a <code>File</code> value * @param out * an <code>OutputStream</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception IOException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ public static void encodeObjectWithMappingFile(XMLSerializable anObject, File modelFile, OutputStream out) throws InvalidObjectSpecificationException, IOException, SAXException, ParserConfigurationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { encodeObjectWithMappingFile(anObject, modelFile, out, (SerializationHandler) null); } /** * Encode to an XML string object <code>anObject</code> according to mapping defined in file <code>modelFile</code>, and writes it to * output stream <code>out</code>. * * @param anObject * an <code>Object</code> value * @param modelFile * a <code>File</code> value * @param out * an <code>OutputStream</code> value * @param serializationHandler * TODO * @exception InvalidObjectSpecificationException * if an error occurs * @exception IOException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ public static void encodeObjectWithMappingFile(XMLSerializable anObject, File modelFile, OutputStream out, SerializationHandler serializationHandler) throws InvalidObjectSpecificationException, IOException, SAXException, ParserConfigurationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { XMLCoder encoder = new XMLCoder(modelFile); encoder.setSerializationHandler(serializationHandler); encoder.encodeObject(anObject, out); } /** * Encode to an XML string object <code>anObject</code> according to mapping <code>xmlMapping</code>, and writes it to output stream * <code>out</code>. * * @param anObject * an <code>Object</code> value * @param xmlMapping * a <code>XMLMapping</code> value * @param out * an <code>OutputStream</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ public static void encodeObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping, OutputStream out, DocType docType) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { encodeObjectWithMapping(anObject, xmlMapping, out, docType, (SerializationHandler) null); } /** * Encode to an XML string object <code>anObject</code> according to mapping <code>xmlMapping</code>, and writes it to output stream * <code>out</code>. * * @param anObject * an <code>Object</code> value * @param xmlMapping * a <code>XMLMapping</code> value * @param out * an <code>OutputStream</code> value * @param serializationHandler * TODO * @exception InvalidObjectSpecificationException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ public static void encodeObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping, OutputStream out, DocType docType, SerializationHandler serializationHandler) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { XMLCoder encoder = new XMLCoder(xmlMapping, StringEncoder.getDefaultInstance()); encoder.setSerializationHandler(serializationHandler); encoder.encodeObject(anObject, out, docType); } /** * Encode to an XML string object <code>anObject</code> according to mapping defined in file <code>modelFile</code>, and writes it to * output stream <code>out</code>. * * @param anObject * an <code>Object</code> value * @param modelFile * a <code>File</code> value * @param out * an <code>OutputStream</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception IOException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ public static void encodeObjectWithMappingFile(XMLSerializable anObject, File modelFile, OutputStream out, DocType docType) throws InvalidObjectSpecificationException, IOException, SAXException, ParserConfigurationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { encodeObjectWithMappingFile(anObject, modelFile, out, docType, (SerializationHandler) null); } /** * Encode to an XML string object <code>anObject</code> according to mapping defined in file <code>modelFile</code>, and writes it to * output stream <code>out</code>. * * @param anObject * an <code>Object</code> value * @param modelFile * a <code>File</code> value * @param out * an <code>OutputStream</code> value * @param serializationHandler * TODO * @exception InvalidObjectSpecificationException * if an error occurs * @exception IOException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ public static void encodeObjectWithMappingFile(XMLSerializable anObject, File modelFile, OutputStream out, DocType docType, SerializationHandler serializationHandler) throws InvalidObjectSpecificationException, IOException, SAXException, ParserConfigurationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { XMLCoder encoder = new XMLCoder(modelFile); encoder.setSerializationHandler(serializationHandler); encoder.encodeObject(anObject, out, docType); } /** * Encode to an XML string object <code>anObject</code> according to mapping defined for this <code>XMLCoder</code>, and returns this * newly created string. * * @param anObject * an <code>Object</code> value * @return an <code>Object</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if no valid mapping nor mapping file were specified * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ public String encodeObject(XMLSerializable anObject) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { if (xmlMapping == null) { throw new InvalidModelException("No mapping specified."); } StringWriter writer = new StringWriter(); buildDocumentAndSetStringWriter(anObject, writer); delete(); return writer.toString(); } /** * Encode to an XML string object <code>anObject</code> according to mapping defined for this <code>XMLCoder</code>, and writes it to * output stream <code>out</code>. * * @param anObject * an <code>Object</code> value * @param out * an <code>OutputStream</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if no valid mapping nor mapping file were specified * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ public void encodeObject(XMLSerializable anObject, OutputStream out) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { if (xmlMapping == null) { throw new InvalidModelException("No mapping specified."); } buildDocumentAndSendToOutputStream(anObject, out, null); delete(); } public void encodeObject(XMLSerializable anObject, OutputStream out, Format format) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { if (xmlMapping == null) { throw new InvalidModelException("No mapping specified."); } buildDocumentAndSendToOutputStream(anObject, out, null, format); delete(); } /** * Encode to an XML string object <code>anObject</code> according to mapping defined for this <code>XMLCoder</code>, and writes it to * output stream <code>out</code>. * * @param anObject * an <code>Object</code> value * @param out * an <code>OutputStream</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if no valid mapping nor mapping file were specified * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ public void encodeObject(XMLSerializable anObject, OutputStream out, DocType docType) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { if (xmlMapping == null) { throw new InvalidModelException("No mapping specified."); } buildDocumentAndSendToOutputStream(anObject, out, docType); delete(); } /** * Internally used during coding process.<br> * Build XML document from object, transform it, and writes result of coding to a <code>StringWriter</code> object * * @param anObject * an <code>Object</code> value * @param aWriter * a <code>StringWriter</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception TransformerConfigurationException * if an error occurs * @exception TransformerException * if an error occurs * @exception InvalidModelException * if an error occurs * @throws DuplicateSerializationIdentifierException * @throws AccessorInvocationException */ protected void buildDocumentAndSetStringWriter(Object anObject, StringWriter aWriter) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { Document builtDocument = buildDocument(anObject); Format prettyFormat = Format.getPrettyFormat(); prettyFormat.setLineSeparator(LineSeparator.SYSTEM); XMLOutputter outputter = new XMLOutputter(prettyFormat); try { outputter.output(builtDocument, aWriter); } catch (IOException e) { e.printStackTrace(); } } /** * Internally used during coding process.<br> * Build XML document from object, transform it, and writes result of coding to an <code>OutputStream</code> object * * @param anObject * an <code>Object</code> value * @param out * an <code>OutputStream</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception TransformerConfigurationException * if an error occurs * @exception TransformerException * if an error occurs * @exception InvalidModelException * if an error occurs * @throws DuplicateSerializationIdentifierException * @throws AccessorInvocationException */ protected void buildDocumentAndSendToOutputStream(Object anObject, OutputStream out, DocType docType) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { Format prettyFormat = Format.getPrettyFormat(); prettyFormat.setLineSeparator(LineSeparator.SYSTEM); buildDocumentAndSendToOutputStream(anObject, out, docType, prettyFormat); } protected void buildDocumentAndSendToOutputStream(Object anObject, OutputStream out, DocType docType, Format format) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { Document builtDocument = buildDocument(anObject); if (docType != null) { builtDocument.setDocType(docType); } XMLOutputter outputter = new XMLOutputter(format); try { outputter.output(builtDocument, out); } catch (IOException e) { e.printStackTrace(); } } /** * Internally used during coding process.<br> * Returns root element given an XML document <code>aDocument</code> * * @param aDocument * a <code>Document</code> value * @return a <code>Node</code> value * @exception InvalidXMLDataException * if an error occurs */ /*protected Node getRootElement(Document aDocument) throws InvalidXMLDataException { NodeList childNodes; Node rootElement; childNodes = aDocument.getChildNodes(); if (childNodes.getLength() != 1) { throw new InvalidXMLDataException("XML data should have one and only one root element (" + childNodes.getLength() + " elements found)"); } rootElement = aDocument.getFirstChild(); if (rootElement.getNodeType() != Node.ELEMENT_NODE) { throw new InvalidXMLDataException("Invalid root element found in XML data"); } return rootElement; }*/ /** * Internally used during coding process.<br> * Build and returns XML document given an object <code>anObject</code> * * @param anObject * an <code>Object</code> value * @return a <code>Document</code> value * @exception ParserConfigurationException * if an error occurs * @exception InvalidObjectSpecificationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ protected Document buildDocument(Object anObject) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { Document returnedDocument; // First, instanciate a new document returnedDocument = new Document(); // Now, we build the document... Element rootElement = buildNewElementFrom(anObject, null, returnedDocument); postProcess(rootElement); returnedDocument.setRootElement(rootElement); // ...and we return it return returnedDocument; } /** * Internally used during coding process.<br> * Build and returns new element given an object <code>anObject</code> and an XML document <code>aDocument</code>, and accept that the * encoded object could be a String (and not defined in the model). In this case, use the specified property <code>aProperty</code>. * * @param anObject * an <code>Object</code> value * @param aDocument * a <code>Document</code> value * @return an <code>Element</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ protected Element buildNewElementFrom(Object anObject, Document aDocument, ModelProperty aProperty) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { try { OrderedElementReferenceList.OrderedElementReference orderedElementReference = null; if (xmlMapping.serializationMode == XMLMapping.ORDERED_PSEUDO_TREE) { orderedElementReference = orderedElementReferenceList.initEntry(); } Element returned = buildNewElementFrom(anObject, aProperty.getXmlTags(), aDocument); if (xmlMapping.serializationMode == XMLMapping.PSEUDO_TREE || xmlMapping.serializationMode == XMLMapping.ORDERED_PSEUDO_TREE) { // In those cases, try to handle references if (!(anObject instanceof XMLSerializable)) { throw new InvalidObjectSpecificationException("This object is not XML-serializable, object=" + anObject); } ObjectReference ref = objectReferences.get(anObject); if (ref != null) { ref.notifyNewElementReference(aProperty, returned); } else { ref = new ObjectReference((XMLSerializable) anObject, aProperty, returned); objectReferences.put(anObject, ref); } if (xmlMapping.serializationMode == XMLMapping.ORDERED_PSEUDO_TREE) { orderedElementReferenceList.updateEntry(returned, orderedElementReference, ref); } } return returned; } catch (InvalidModelException e) { // Remove stack trace because tests failed because of out of memory // TODO: investigate this: we should not come into this section !!!!! // e.printStackTrace(); if (aProperty != null) { String textValue = null; if (stringEncoder._isEncodable(anObject.getClass())) { textValue = stringEncoder._encodeObject(anObject); } if (textValue != null) { String xmlTag; if (aProperty.getXmlTags().length > 0) { xmlTag = aProperty.getXmlTags()[0]; } else { throw e; } Element returnedElement = new Element(xmlTag); returnedElement.addContent(new Text(textValue)); return returnedElement; } } throw e; } } /** * Internally used during coding process.<br> * Build and returns new element given an object <code>anObject</code> and an XML document <code>aDocument</code> * * @param anObject * an <code>Object</code> value * @param aDocument * a <code>Document</code> value * @return an <code>Element</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException */ protected Element buildNewElementFrom(Object anObject, String[] someXmlTags, Document aDocument) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { ModelEntity modelEntity = null; String xmlTag = null; boolean xmlTagIsCompound = false; // Debugging.debug ("buildNewElementFrom(Object,Document) called with // "+anObject); if (anObject == null) { return null; } // First search the right ModelEntity from class name // NB: the best one is the more specialized. Class searchedClass = anObject.getClass(); while (searchedClass != null && xmlMapping.entityWithClassName(searchedClass.getName()) == null) { searchedClass = searchedClass.getSuperclass(); } if (searchedClass != null) { modelEntity = xmlMapping.entityWithClassName(searchedClass.getName()); if (modelEntity != null && modelEntity.isAbstract()) { throw new InvalidModelException("Entity matching '" + anObject.getClass().getName() + "' (" + modelEntity.getName() + ") is declared to be abstract in this model and could subsequently not be serialized."); } } if (someXmlTags == null) { try { xmlTag = modelEntity.getDefaultXmlTag(); // System.out.println("Coding a "+anObject.getClass().getName()+": chosing TAG "+xmlTag); } catch (Exception e) { e.printStackTrace(); throw new InvalidModelException("Unexpected exception " + e.getMessage()); } } else if (someXmlTags.length == 1) { xmlTag = someXmlTags[0]; } else { // xmlTag if compound, take the most appropriate xmlTagIsCompound = true; if (modelEntity == null) { throw new InvalidModelException("Tag matching '" + anObject.getClass().getName() + "' not found in model"); } String[] entityTags = modelEntity.getXmlTags(); if (entityTags == null) { throw new InvalidModelException("XML tags for entity '" + modelEntity.getName() + "' not found in model (while trying to encode " + anObject.getClass().getName() + ")"); } String tag; for (int j = 0; j < someXmlTags.length; j++) { tag = someXmlTags[j]; for (int i = 0; i < entityTags.length; i++) { if (tag.equals(entityTags[i]) && xmlTag == null) { xmlTag = tag; break; // Debugging.debug ("Look up with tag "+xmlTag); } } if (xmlTag != null) { break; } } if (xmlTag == null) { // Could notLook up tag, choosing first one StringBuilder sb = new StringBuilder(); for (String tags : someXmlTags) { if (sb.length() > 0) { sb.append(","); } sb.append(tags); } xmlTag = someXmlTags[0]; System.err.println("SEVERE: None of " + sb + " seemed to match " + modelEntity.getName() + " using first one " + xmlTag); } } if (modelEntity == null) { throw new InvalidModelException("Tag matching '" + anObject.getClass().getName() + "' not found in model"); } else if (modelEntity != xmlMapping.entityWithXMLTag(xmlTag) && !xmlTagIsCompound) { // System.out.println ("Mapping: "+xmlMapping); // System.out.println ("modelEntity: "+modelEntity); // System.out.println ("xmlMapping.entityWithXMLTag(xmlTag): "+xmlMapping.entityWithXMLTag(xmlTag)); throw new InvalidModelException("Entity matching '" + anObject.getClass().getName() + "' does not contain tag " + xmlTag); } else { return buildNewElementFrom(anObject, xmlTag, modelEntity, aDocument); } } /** * Internally used during coding process.<br> * Build and returns new element given an object <code>anObject</code>, an XML document <code>aDocument</code> and a model entity * <code>aModelEntity</code> * * @param anObject * an <code>Object</code> value * @param aModelEntity * a <code>ModelEntity</code> value * @param aDocument * a <code>Document</code> value * @return an <code>Element</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation * @throws DuplicateSerializationIdentifierException * if two different objects uses the same serialization identifier */ protected Element buildNewElementFrom(Object anObject, String xmlTag, ModelEntity aModelEntity, Document aDocument) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException { Element returnedElement; ModelProperty modelProperty; boolean primitiveProperty; KeyValueProperty keyValueProperty = null; // Debugging.debug ("buildNewElementFrom(Object,ModelEntity,Document) // called with entity "+aModelEntity.getName()+" and object "+anObject); if (anObject == null) { return null; } if (!(anObject instanceof XMLSerializable)) { throw new InvalidObjectSpecificationException("This object is not XML-serializable, object=" + anObject); } // Is this object already serialized ? Object reference = alreadySerialized.get(anObject); // Integer reference = (Integer) alreadySerialized.get(anObject); if (reference == null) { if (_serializationHandler != null) { _serializationHandler.objectWillBeSerialized((XMLSerializable) anObject); } // First time i see this object // Put this object in alreadySerialized objects if (implementsCustomIdMappingScheme()) { try { reference = xmlMapping.mapId.getIdentifierAsStringForObject((XMLSerializable) anObject, stringEncoder); } catch (NoMapIdEntryException e) { reference = getNextReference(); } } else { reference = getNextReference(); } if (serializationIdentifierForObject.get(reference) != null && serializationIdentifierForObject.get(reference) != anObject) { throw new DuplicateSerializationIdentifierException(reference, serializationIdentifierForObject.get(reference), anObject, aModelEntity, xmlTag); } alreadySerialized.put(anObject, reference); serializationIdentifierForObject.put(reference, anObject); // and then continue to serialize // Debugging.debug ("This object has not been serialized until now // "+anObject); returnedElement = new Element(xmlTag); if (xmlMapping.handlesReferences()) { returnedElement.setAttribute(XMLMapping.idLabel, reference.toString()); } if (aModelEntity.implementsGenericTypingKVProperty()) { String classNameValue = KeyValueDecoder.valueForKey(anObject, aModelEntity.getGenericTypingKVProperty(), stringEncoder); // System.out.println("On essaie de faire "+XMLMapping.genericTypingStoredIn+"="+classNameValue); returnedElement.setAttribute(XMLMapping.genericTypingClassName, classNameValue); } else { if (anObject.getClass() != aModelEntity.getRelatedClass()) { returnedElement.setAttribute(XMLMapping.classNameLabel, anObject.getClass().getName()); } } for (Enumeration en = aModelEntity.getModelProperties(); en.hasMoreElements();) { modelProperty = (ModelProperty) en.nextElement(); // Debugging.debug ("Now working on "+modelProperty.getName()); // First, get the key-value property keyValueProperty = modelProperty.getKeyValueProperty(); if (keyValueProperty instanceof SingleKeyValueProperty) { SingleKeyValueProperty singleKeyValueProperty = (SingleKeyValueProperty) keyValueProperty; primitiveProperty = singleKeyValueProperty.classIsPrimitive(stringEncoder); // Check if default value should be ignored in serialization if (modelProperty.getIgnoreDefaultValue() != null && modelProperty.getIgnoreDefaultValue().equals( KeyValueDecoder.valueForKey(anObject, singleKeyValueProperty, stringEncoder))) { // System.out.println("Ignore default value "+modelProperty.getIgnoreDefaultValue()); } // Check if property is a text property attribute else if (modelProperty.getIsText()) { returnedElement.addContent(buildTextNodeFrom(anObject, singleKeyValueProperty, modelProperty, aDocument)); } // Check if property is an attribute else if (modelProperty.getIsAttribute()) { Attribute attr = buildAttributeNodeFrom(anObject, singleKeyValueProperty, modelProperty, aDocument); if (attr != null) { returnedElement.setAttribute(attr); } } else { // Property seem to match a unique element if (primitiveProperty) { Element newElement = new Element(modelProperty.getDefaultXmlTag()); String value = KeyValueDecoder.valueForKey(anObject, singleKeyValueProperty, stringEncoder); if (value != null) { Text textValue = new Text(value); newElement.addContent(textValue); returnedElement.addContent(newElement); } } else { Object newObject = KeyValueDecoder.objectForKey(anObject, keyValueProperty); if (newObject != null) { returnedElement.addContent(buildNewElementFrom(newObject, aDocument, modelProperty)); } } } } else if (keyValueProperty instanceof VectorKeyValueProperty) { List<?> values = KeyValueDecoder.vectorForKey(anObject, (VectorKeyValueProperty) keyValueProperty); if (values != null && values.size() > 0) { for (Object o : values) { returnedElement.addContent(buildNewElementFrom(o, aDocument, modelProperty)); } } } else if (keyValueProperty instanceof ArrayKeyValueProperty) { Object[] values = KeyValueDecoder.arrayForKey(anObject, (ArrayKeyValueProperty) keyValueProperty); if (values != null && values.length > 0) { for (int i = 0; i < values.length; i++) { returnedElement.addContent(buildNewElementFrom(values[i], aDocument, modelProperty)); } } } else if (keyValueProperty instanceof PropertiesKeyValueProperty) { if (modelProperty.isProperties()) { Map<?, ?> values = KeyValueDecoder.hashtableForKey(anObject, (PropertiesKeyValueProperty) keyValueProperty); if (values != null && values.size() > 0) { Element propertiesElement = new Element(modelProperty.getDefaultXmlTag()); for (Entry<?, ?> e : values.entrySet()) { Object keyAsObject = e.getKey(); if (!(keyAsObject instanceof String)) { throw new InvalidDataException("Properties keys must be only String values"); } String key = (String) keyAsObject; Object value = e.getValue(); Element valueElement = new Element(key); if (value instanceof PropertiesKeyValueProperty.UndecodableProperty) { // In this case, class matching property is not loaded, and thus // Object is not instanciated. But, we must keep serialized version Text textValue = new Text(((PropertiesKeyValueProperty.UndecodableProperty) value).value); valueElement.addContent(textValue); valueElement.setAttribute(XMLMapping.classNameLabel, ((PropertiesKeyValueProperty.UndecodableProperty) value).className); } else { String valueAsString = stringEncoder._encodeObject(value); if (valueAsString != null) { Text textValue = new Text(valueAsString); valueElement.addContent(textValue); } valueElement.setAttribute(XMLMapping.classNameLabel, value.getClass().getName()); } propertiesElement.addContent(valueElement); } returnedElement.addContent(propertiesElement); } } else if (modelProperty.isSafeProperties()) { Map<?, ?> values = KeyValueDecoder.hashtableForKey(anObject, (PropertiesKeyValueProperty) keyValueProperty); if (values != null && values.size() > 0) { Element propertiesElement = new Element(modelProperty.getDefaultXmlTag()); for (Entry<?, ?> e : values.entrySet()) { Object keyAsObject = e.getKey(); if (!(keyAsObject instanceof String)) { throw new InvalidDataException("Properties keys must be instance of String"); } String key = (String) keyAsObject; Object value = e.getValue(); Element valueElement = new Element(XMLMapping.entryLabel); valueElement.setAttribute(XMLMapping.keyLabel, key); String valueAsText = null; String classNameLabel = null; if (value instanceof PropertiesKeyValueProperty.UndecodableProperty) { // In this case, class matching property is not loaded, and thus // Object is not instanciated. But, we must keep serialized version valueAsText = ((PropertiesKeyValueProperty.UndecodableProperty) value).value; classNameLabel = ((PropertiesKeyValueProperty.UndecodableProperty) value).className; } else if (value != null) { valueAsText = stringEncoder._encodeObject(value); classNameLabel = value.getClass().getName(); } if (valueAsText != null) { valueElement.setAttribute(XMLMapping.classNameLabel, classNameLabel); valueElement.setAttribute(XMLMapping.valueLabel, valueAsText); } propertiesElement.addContent(valueElement); } returnedElement.addContent(propertiesElement); } } else if (modelProperty.isUnmappedAttributes()) { Map<?, ?> values = KeyValueDecoder.hashtableForKey(anObject, (HashtableKeyValueProperty) keyValueProperty); if (values != null) { for (Entry<?, ?> e : values.entrySet()) { Object keyAsObject = e.getKey(); if (!(keyAsObject instanceof String)) { throw new InvalidDataException("Properties keys must be only String values"); } String key = (String) keyAsObject; String valueAsString; Object value = e.getValue(); if (value instanceof String) { valueAsString = (String) value; } else { valueAsString = stringEncoder._encodeObject(value); } returnedElement.setAttribute(key, valueAsString); } } } } else if (keyValueProperty instanceof HashtableKeyValueProperty) { Map<?, ?> values = KeyValueDecoder.hashtableForKey(anObject, (HashtableKeyValueProperty) keyValueProperty); if (values != null && values.size() > 0) { if (modelProperty.getKeyToUse() == null) { for (Entry<?, ?> e : values.entrySet()) { Object key = e.getKey(); Object value = e.getValue(); Element valueElement = buildNewElementFrom(value, aDocument, modelProperty); if (key instanceof String) { valueElement.setAttribute(XMLMapping.keyLabel, (String) key); } else { Element keyElement = new Element(XMLMapping.keyLabel); Element keyValueElement = buildNewElementFrom(key, null, aDocument); keyElement.addContent(keyValueElement); valueElement.addContent(keyElement); } returnedElement.addContent(valueElement); } } else { for (Entry<?, ?> e : values.entrySet()) { Object key = e.getKey(); Object value = e.getValue(); Object expectedKey = KeyValueDecoder.objectForKey(value, modelProperty.getKeyToUse()); if (key.equals(expectedKey)) { // This is the good key, nice ! Element valueElement = buildNewElementFrom(value, aDocument, modelProperty); returnedElement.addContent(valueElement); } else { /*System.out.println("Object="+anObject); System.out.println("values="+values.hashCode()+" "+values); for (Enumeration e2 = values.keys(); e2.hasMoreElements();) { Object key2 = e2.nextElement(); Object value2 = values.get(key2); System.out.println("k="+key2+" v="+value2); } */ throw new InvalidDataException("Strange key found: does not match property specification " + modelProperty.getKeyToUse() + " Found key: " + key + " of " + key.getClass().getName() + " Expected key: " + expectedKey + " of " + expectedKey.getClass().getName() + " current modelProperty is " + modelProperty.getName()); } } } } } } // end of for () if (_serializationHandler != null) { _serializationHandler.objectHasBeenSerialized((XMLSerializable) anObject); } } else { // This object was already serialized somewhere, only put an idref // Debugging.debug ("This object has already been serialized // somewhere "+anObject); if (!xmlMapping.handlesReferences()) { throw new InvalidObjectSpecificationException("Loop detected in data structure while 'handlesReferences' in XML model " + "has been disabled."); } else { returnedElement = new Element(xmlTag); returnedElement.setAttribute(XMLMapping.idrefLabel, reference.toString()); } } return returnedElement; } /** * Internally used during coding process.<br> * Build and returns new text node given an object <code>anObject</code>, an XML document <code>aDocument</code>, a model property * <code>aModelProperty</code> and a field <code>aField</code> * * @param anObject * an <code>Object</code> value * @param aField * a <code>Field</code> value * @param aModelProperty * a <code>ModelProperty</code> value * @param aDocument * a <code>Document</code> value * @return a <code>Text</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation */ protected Text buildTextNodeFrom(Object anObject, SingleKeyValueProperty aKeyValueProperty, ModelProperty aModelProperty, Document aDocument) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException { Text returnedTextNode; // Debugging.debug // ("buildTextNodeFrom(Object,Field,ModelProperty,Document) called with // property "+aModelProperty.getName()); String textValue = KeyValueDecoder.valueForKey(anObject, aKeyValueProperty, stringEncoder); returnedTextNode = new Text(textValue); return returnedTextNode; } private static final String ILLEGAL_XML_CHARS = "[\\x00-\\x08\\x0B\\x0C\\x0E\\x0F]"; private static final Pattern ILLEGAL_XML_CHARS_PATTERN = Pattern.compile(ILLEGAL_XML_CHARS); /** * Internally used during coding process.<br> * Build and returns new attribute node given an object <code>anObject</code>, an XML document <code>aDocument</code>, a model property * <code>aModelProperty</code> and a field <code>aField</code> * * @param anObject * an <code>Object</code> value * @param aField * a <code>Field</code> value * @param aModelProperty * a <code>ModelProperty</code> value * @param aDocument * a <code>Document</code> value * @return an <code>Attr</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation */ protected Attribute buildAttributeNodeFrom(Object anObject, SingleKeyValueProperty aKeyValueProperty, ModelProperty aModelProperty, Document aDocument) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException { // Debugging.debug // ("buildAttributeNodeFrom(Object,Field,ModelProperty,Document) called // with property "+aModelProperty.getName()); String value = KeyValueDecoder.valueForKey(anObject, aKeyValueProperty, stringEncoder); if (value != null) { return new Attribute(aModelProperty.getDefaultXmlTag(), removeUnacceptableChars(value)); // return new Attribute(aModelProperty.getDefaultXmlTag(),value); } else { return null; } } public static String removeUnacceptableChars(String value) { return ILLEGAL_XML_CHARS_PATTERN.matcher(value).replaceAll(" "); } private Integer getNextReference() { nextReference++; return new Integer(nextReference); } public boolean implementsCustomIdMappingScheme() { return xmlMapping.implementsCustomIdMappingScheme(); } protected class OrderedElementReferenceList { protected OrderedElementReference first; protected OrderedElementReference last; protected int size; // Keys are Element and values are OrderedElementReference private Hashtable<Element, OrderedElementReference> elementReferences; public OrderedElementReferenceList() { first = null; last = null; size = 0; elementReferences = new Hashtable<Element, OrderedElementReference>(); } protected void delete() { OrderedElementReference previous = null; for (Enumeration en = elements(); en.hasMoreElements();) { OrderedElementReference ref = (OrderedElementReference) en.nextElement(); ref.previous = null; ref.element = null; ref.objectReference = null; if (previous != null) { previous.next = null; } previous = ref; } if (previous != null) { previous.next = null; } } public OrderedElementReference initEntry() { OrderedElementReference returned = new OrderedElementReference(); appendElement(returned); return returned; } public void updateEntry(Element element, OrderedElementReference reference, ObjectReference ref) { reference.element = element; reference.objectReference = ref; elementReferences.put(element, reference); } public void swap(Element element1, Element element2) { OrderedElementReference startRef1 = elementReferences.get(element1); OrderedElementReference endRef1 = lastDescendantElement(startRef1); OrderedElementReference startRef2 = elementReferences.get(element2); OrderedElementReference endRef2 = lastDescendantElement(startRef2); // System.out.println("swap indexes ("+startRef1.index+"-"+endRef1.index+") and ("+startRef2.index+"-"+endRef2.index+")"); OrderedElementReference pRef1 = startRef1.previous; OrderedElementReference nRef1 = endRef1.next; OrderedElementReference pRef2 = startRef2.previous; OrderedElementReference nRef2 = endRef2.next; startRef1.previous = pRef2; endRef1.next = nRef2; startRef2.previous = pRef1; endRef2.next = nRef1; if (pRef1 != null) { pRef1.next = startRef2; } if (nRef1 != null) { nRef1.previous = endRef2; } if (pRef2 != null) { pRef2.next = startRef1; } if (nRef2 != null) { nRef2.previous = endRef1; } if (first == startRef1) { first = startRef2; } if (first == startRef2) { first = startRef1; } if (last == endRef1) { last = endRef2; } if (last == endRef2) { last = endRef1; } } private OrderedElementReference lastDescendantElement(OrderedElementReference ref) { OrderedElementReference current = ref; int i = 0; while (current != null) { OrderedElementReference next = current.next; if (next == null || !ref.element.isAncestor(next.element)) { return current; } current = next; i++; } return null; } private void appendElement(OrderedElementReference e) { e.index = size; if (first == null) { first = e; } if (last != null) { last.next = e; } e.previous = last; last = e; size++; } public Enumeration elements() { return new OrderedElementReferenceListEnumeration(); } protected class OrderedElementReferenceListEnumeration implements Enumeration { private OrderedElementReference current; protected OrderedElementReferenceListEnumeration() { current = first; } @Override public boolean hasMoreElements() { return current != null; } @Override public Object nextElement() { Object returned = current; if (current != null) { current = current.next; } return returned; } } protected class OrderedElementReference { protected OrderedElementReference previous; protected OrderedElementReference next; protected Element element; protected ObjectReference objectReference; protected int index; protected boolean isPrimary() { return objectReference.isFullyDescribed(element); } } } protected class ObjectReference { private int id = -1; private String customIdMappingValue = null; protected XMLSerializable serializedObject; protected ElementReference primaryElement; protected Vector<ElementReference> referenceElements; protected ObjectReference(XMLSerializable anObject, ModelProperty aProperty, Element anElement) { super(); serializedObject = anObject; referenceElements = new Vector<ElementReference>(); addElementReference(new ElementReference(aProperty, anElement)); if (implementsCustomIdMappingScheme()) { try { customIdMappingValue = xmlMapping.mapId.getIdentifierAsStringForObject(anObject, stringEncoder); } catch (NoMapIdEntryException e) { id = idForElement(anElement); } } else { id = idForElement(anElement); } } protected void delete() { serializedObject = null; primaryElement.delete(); for (Enumeration<ElementReference> en = referenceElements.elements(); en.hasMoreElements();) { ElementReference next = en.nextElement(); next.delete(); } primaryElement = null; referenceElements.clear(); referenceElements = null; } protected void notifyNewElementReference(ModelProperty aProperty, Element anElement) { addElementReference(new ElementReference(aProperty, anElement)); } protected int getId() { return id; } protected void changeId(int newId) { // System.out.println("changeId() to "+newId+" for "+primaryElement.element); if (primaryElement != null && primaryElement.element != null) { changeIdForElement(newId, primaryElement.element); } for (Enumeration en = referenceElements.elements(); en.hasMoreElements();) { ElementReference next = (ElementReference) en.nextElement(); if (next.element != null) { changeIdForElement(newId, next.element); } } } protected void changeIdForElement(int newId, Element element) { if (element.getAttribute(XMLMapping.idLabel) != null) { element.setAttribute(XMLMapping.idLabel, StringEncoder.encodeInteger(newId)); } else if (element.getAttribute(XMLMapping.idrefLabel) != null) { element.setAttribute(XMLMapping.idrefLabel, StringEncoder.encodeInteger(newId)); } } private void addElementReference(ElementReference elementReference) { if (isFullyDescribed(elementReference.element)) { // System.out.println("object: "+serializedObject.getClass().getName()+"/"+serializedObject.hashCode()+" PRIMARY "+outputter.outputString(elementReference.element)); primaryElement = elementReference; } else { // System.out.println("object: "+serializedObject.getClass().getName()+"/"+serializedObject.hashCode()+" "+outputter.outputString(elementReference.element)); referenceElements.add(elementReference); } } protected boolean isFullyDescribed(Element element) { return element.getAttribute("id") != null; } private boolean done = false; protected boolean postProcess() { if (done) { return true; } if (primaryElement.property.isPrimary()) { // That's OK done = true; return true; } else { // It might be NOK for (Enumeration en = referenceElements.elements(); en.hasMoreElements();) { ElementReference next = (ElementReference) en.nextElement(); if (next.property.isPrimary()) { return setAsNewPrimaryElement(next); } } done = true; return true; } } private boolean setAsNewPrimaryElement(ElementReference newElementReference) { // System.out.println("Need to exchange "+primaryElement.element+" and "+newElementReference.element); if (exchange(primaryElement, newElementReference)) { referenceElements.remove(newElementReference); referenceElements.add(primaryElement); primaryElement = newElementReference; done = true; return true; } else { return false; } } private boolean exchange(ElementReference ref1, ElementReference ref2) { Element element1 = ref1.element; Element element2 = ref2.element; Element father1 = element1.getParentElement(); Element father2 = element2.getParentElement(); if (isAncestorOf(element1, element2)) { // In this case, do nothing and try later (in another loop) return false; } else if (isAncestorOf(element2, element1)) { // In this case, do nothing and try later (in another loop) return false; } else { int index1 = father1.indexOf(element1); father1.removeContent(index1); int index2 = father2.indexOf(element2); father2.removeContent(index2); father2.addContent(index2, element1); father1.addContent(index1, element2); if (!ref1.xmlTag.equals(ref2.xmlTag)) { // System.out.println("Exchange names "+ref1.xmlTag+" and "+ref2.xmlTag); element1.setName(ref2.xmlTag); element2.setName(ref1.xmlTag); } if (xmlMapping.serializationMode == XMLMapping.ORDERED_PSEUDO_TREE) { orderedElementReferenceList.swap(element1, element2); } return true; } } private int idForElement(Element el) { int returned = StringEncoder.decodeAsInteger(el.getAttributeValue(XMLMapping.idLabel)); if (returned == -1) { returned = StringEncoder.decodeAsInteger(el.getAttributeValue(XMLMapping.idrefLabel)); } return returned; } private boolean isAncestorOf(Element e1, Element e2) { return e1.isAncestor(e2); } protected class ElementReference { protected ModelProperty property; protected String xmlTag; protected Element element; protected ElementReference(ModelProperty aProperty, Element anElement) { super(); property = aProperty; element = anElement; xmlTag = anElement.getName(); } protected void delete() { property = null; xmlTag = null; element = null; } } } private void postProcess(Element rootElement) { if (xmlMapping.serializationMode == XMLMapping.DEEP_FIRST) { return; } else if (xmlMapping.serializationMode == XMLMapping.PSEUDO_TREE || xmlMapping.serializationMode == XMLMapping.ORDERED_PSEUDO_TREE) { int requiredSwaps = objectReferences.size(); while (requiredSwaps > 0) { // System.out.println("Still "+requiredSwaps+" post processing"); int newRequiredSwaps = 0; for (Enumeration en = objectReferences.elements(); en.hasMoreElements();) { ObjectReference next = (ObjectReference) en.nextElement(); if (!next.postProcess()) { newRequiredSwaps++; } } if (newRequiredSwaps == requiredSwaps) { requiredSwaps = 0; // To avoid infinite loop } else { requiredSwaps = newRequiredSwaps; } } if (xmlMapping.serializationMode == XMLMapping.ORDERED_PSEUDO_TREE && !implementsCustomIdMappingScheme()) { int i = 0; int newIndex = 1; Enumeration en = orderedElementReferenceList.elements(); for (; en.hasMoreElements(); i++) { OrderedElementReferenceList.OrderedElementReference next = (OrderedElementReferenceList.OrderedElementReference) en .nextElement(); // System.out.println(">: "+i+((next.isPrimary())?" * ":" ")+next.index+" "+next.element); if (next.isPrimary()) { next.objectReference.changeId(++newIndex); } } } } } }