All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.simpleframework.xml.load.Composite Maven / Gradle / Ivy

/*
 * Composite.java July 2006
 *
 * Copyright (C) 2006, Niall Gallagher 
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * This library 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General 
 * Public License along with this library; if not, write to the 
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
 * Boston, MA  02111-1307  USA
 */

package org.simpleframework.xml.load;

import org.simpleframework.xml.stream.InputNode;
import org.simpleframework.xml.stream.NodeMap;
import org.simpleframework.xml.stream.OutputNode;
import org.simpleframework.xml.stream.Position;

/**
 * The Composite object is used to perform serialization
 * of objects that contain XML annotation. Composite objects are objects
 * that are not primitive and contain references to serializable fields.
 * This Converter will visit each field within the object
 * and deserialize or serialize that field depending on the requested
 * action. If a required field is not present when deserializing from
 * an XML element this terminates the deserialization reports the error.
 * 
 * 
 *    <element name="test" class="some.package.Type">
 *       <text>string value</text>
 *       <integer>1234</integer>
 *    </element>
 * 
 * 
* To deserialize the above XML source this will attempt to match the * attribute name with an Attribute annotation from the * XML schema class, which is specified as "some.package.Type". This * type must also contain Element annotations for the * "text" and "integer" elements. *

* Serialization requires that contacts marked as required must have * values that are not null. This ensures that the serialized object * can be deserialized at a later stage using the same class schema. * If a required value is null the serialization terminates an an * exception is thrown. * * @author Niall Gallagher */ class Composite implements Converter { /** * This factory creates instances of the deserialized object. */ private final ObjectFactory factory; /** * This is the source object for the instance of serialization. */ private final Source root; /** * This is the type that this composite produces instances of. */ private final Class type; /** * Constructor for the Composite object. This creates * a converter object capable of serializing and deserializing root * objects labeled with XML annotations. The XML schema class must * be given to the instance in order to perform deserialization. * * @param root the source object used to perform serialization * @param type this is the XML schema class to use */ public Composite(Source root, Class type) { this.factory = new ObjectFactory(root, type); this.root = root; this.type = type; } /** * This read method performs deserialization of the XML * schema class type by traversing the contacts and instantiating them * using details from the provided XML element. Because this will * convert a non-primitive value it delegates to other converters to * perform deserialization of lists and primitives. *

* If any of the required contacts are not present within the provided * XML element this will terminate deserialization and throw an * exception. The annotation missing is reported in the exception. * * @param node the XML element contact values are deserialized from * * @return this returns the fully deserialized object graph */ public Object read(InputNode node) throws Exception { Type type = factory.getInstance(node); Object source = type.getInstance(); if(!type.isReference()) { return read(node, source); } return source; } /** * This read method performs deserialization of the XML * schema class type by traversing the contacts and instantiating them * using details from the provided XML element. Because this will * convert a non-primitive value it delegates to other converters to * perform deserialization of lists and primitives. *

* If any of the required contacts are not present within the provided * XML element this will terminate deserialization and throw an * exception. The annotation missing is reported in the exception. * * @param node the XML element contact values are deserialized from * @param source the object whose contacts are to be deserialized * * @return this returns the fully deserialized object graph */ public Object read(InputNode node, Object source) throws Exception { Schema schema = root.getSchema(source); read(node, source, schema); schema.validate(source); schema.commit(source); return readResolve(node, source, schema); } /** * The replace method is used to determine if there is * a replacement method which can be used to substitute the object * deserialized. The replace method is used when an object wishes * to provide a substitute within the deserialized object graph. * This acts as an equivelant to the Java Object Serialization * readResolve method for the object deserialization. * * @param node the XML element object provided as a replacement * @param source this is the source object that is deserialized * @param schema this object visits the objects contacts * * @return this returns a replacement for the deserialized object */ private Object readResolve(InputNode node, Object source, Schema schema) throws Exception { Position line = node.getPosition(); if(source != null) { Object value = schema.resolve(source); Class real = value.getClass(); if(!type.isAssignableFrom(real)) { throw new ElementException("Type %s does not match %s at %s", real, type, line); } return value; } return source; } /** * This read method performs deserialization of the XML * schema class type by traversing the contacts and instantiating them * using details from the provided XML element. Because this will * convert a non-primitive value it delegates to other converters to * perform deserialization of lists and primitives. *

* If any of the required contacts are not present within the provided * XML element this will terminate deserialization and throw an * exception. The annotation missing is reported in the exception. * * @param node the XML element contact values are deserialized from * @param source ths object whose contacts are to be deserialized * @param schema this object visits the objects contacts */ private void read(InputNode node, Object source, Schema schema) throws Exception { readText(node, source, schema); readAttributes(node, source, schema); readElements(node, source, schema); } /** * This read method is used to read the attributes from * the provided XML element. This will iterate over all attributes * within the element and convert those attributes as primitives to * contact values within the source object. *

* Once all attributes within the XML element have been evaluated * the Schema is checked to ensure that there are no * required contacts annotated with the Attribute that * remain. If any required attribute remains an exception is thrown. * * @param node this is the XML element to be evaluated * @param source the source object which will be deserialized * @param schema this is used to visit the attribute contacts * * @throws Exception thrown if any required attributes remain */ private void readAttributes(InputNode node, Object source, Schema schema) throws Exception { NodeMap list = node.getAttributes(); LabelMap map = schema.getAttributes(); for(String name : list) { readAttribute(node.getAttribute(name), source, map); } validate(node, map, source); } /** * This read method is used to read the elements from * the provided XML element. This will iterate over all elements * within the element and convert those elements to primitives or * composite objects depending on the contact annotation. *

* Once all elements within the XML element have been evaluated * the Schema is checked to ensure that there are no * required contacts annotated with the Element that * remain. If any required element remains an exception is thrown. * * @param node this is the XML element to be evaluated * @param source the source object which will be deserialized * @param schema this is used to visit the element contacts * * @throws Exception thrown if any required elements remain */ private void readElements(InputNode node, Object source, Schema schema) throws Exception { LabelMap map = schema.getElements(); while(true) { InputNode child = node.getNext(); if(child == null) { break; } readElement(child, source, map); } validate(node, map, source); } /** * This read method is used to read the text value * from the XML element node specified. This will check the class * schema to determine if a Text annotation was * specified. If one was specified then the text within the XML * element input node is used to populate the contact value. * * @param node this is the XML element to acquire the text from * @param source the source object which will be deserialized * @param schema this is used to visit the element contacts * * @throws Exception thrown if a required text value was null */ private void readText(InputNode node, Object source, Schema schema) throws Exception { Label label = schema.getText(); if(label != null) { read(node, source, label); } } /** * This read method is used to perform deserialization * of the provided node object using a delegate converter. This is * typically another Composite converter, or if the * node is an attribute a Primitive converter. When * the delegate converter has completed the deserialized value is * assigned to the contact. * * @param node this is the node that contains the contact value * @param source the source object to assign the contact value to * @param map this is the map that contains the label objects * * @throws Exception thrown if the the label object does not exist */ private void readAttribute(InputNode node, Object source, LabelMap map) throws Exception { Position line = node.getPosition(); String name = node.getName(); Label label = map.take(name); if(label == null) { if(map.isStrict()) { throw new AttributeException("Attribute '%s' does not exist at %s", name, line); } } else { read(node, source, label); } } /** * This read method is used to perform deserialization * of the provided node object using a delegate converter. This is * typically another Composite converter, or if the * node is an attribute a Primitive converter. When * the delegate converter has completed the deserialized value is * assigned to the contact. * * @param node this is the node that contains the contact value * @param source the source object to assign the contact value to * @param map this is the map that contains the label objects * * @throws Exception thrown if the the label object does not exist */ private void readElement(InputNode node, Object source, LabelMap map) throws Exception { Position line = node.getPosition(); String name = node.getName(); Label label = map.take(name); if(label == null) { if(map.isStrict()) { throw new ElementException("Element '%s' does not exist at %s", name, line); } else { node.skip(); } } else { read(node, source, label); } } /** * This read method is used to perform deserialization * of the provided node object using a delegate converter. This is * typically another Composite converter, or if the * node is an attribute a Primitive converter. When * the delegate converter has completed the deserialized value is * assigned to the contact. * * @param node this is the node that contains the contact value * @param source the source object to assign the contact value to * @param label this is the label used to create the converter * * @throws Exception thrown if the contact could not be deserialized */ private void read(InputNode node, Object source, Label label) throws Exception { Converter reader = label.getConverter(root); Contact contact = label.getContact(); Object object = reader.read(node); if(object == null) { Position line = node.getPosition(); Class type = source.getClass(); if(label.isRequired()) { throw new ValueRequiredException("Empty value for %s in %s at %s", label, type, line); } } else if(object != label.getEmpty()) { contact.set(source, object); } } /** * This method checks to see if there are any Label * objects remaining in the provided map that are required. This is * used when deserialization is performed to ensure the the XML * element deserialized contains sufficient details to satisfy the * XML schema class annotations. If there is a required label that * remains it is reported within the exception thrown. * * @param map this is the map to check for remaining labels * @param source this is the object that has been deserialized * * @throws Exception thrown if an XML property was not declared */ private void validate(InputNode node, LabelMap map, Object source) throws Exception { Position line = node.getPosition(); Class type = source.getClass(); for(Label label : map) { if(label.isRequired()) { throw new ValueRequiredException("Unable to satisfy %s for %s at %s", label, type, line); } } } /** * This write method is used to perform serialization of * the given source object. Serialization is performed by appending * elements and attributes from the source object to the provided XML * element object. How the objects contacts are serialized is * determined by the XML schema class that the source object is an * instance of. If a required contact is null an exception is thrown. * * @param source this is the source object to be serialized * @param node the XML element the object is to be serialized to * * @throws Exception thrown if there is a serialization problem */ public void write(OutputNode node, Object source) throws Exception { Schema schema = root.getSchema(source); try { schema.persist(source); write(node, source, schema); } finally { schema.complete(source); } } /** * This write method is used to perform serialization of * the given source object. Serialization is performed by appending * elements and attributes from the source object to the provided XML * element object. How the objects contacts are serialized is * determined by the XML schema class that the source object is an * instance of. If a required contact is null an exception is thrown. * * @param source this is the source object to be serialized * @param node the XML element the object is to be serialized to * @param schema this is used to track the referenced contacts * * @throws Exception thrown if there is a serialization problem */ private void write(OutputNode node, Object source, Schema schema) throws Exception { writeAttributes(node, source, schema); writeElements(node, source, schema); writeText(node, source, schema); } /** * This write method is used to write all the attribute contacts from * the provided source object to the XML element. This visits all * the contacts marked with the Attribute annotation in * the source object. All annotated contacts are written as attributes * to the XML element. This will throw an exception if a required * contact within the source object is null. * * @param source this is the source object to be serialized * @param node this is the XML element to write attributes to * @param schema this is used to track the referenced attributes * * @throws Exception thrown if there is a serialization problem */ private void writeAttributes(OutputNode node, Object source, Schema schema) throws Exception { LabelMap attributes = schema.getAttributes(); for(Label label : attributes) { Contact contact = label.getContact(); Object value = contact.get(source); if(value == null) { value = label.getEmpty(); } if(value == null && label.isRequired()) { throw new AttributeException("Value for %s is null", label); } writeAttribute(node, value, label); } } /** * This write method is used to write all the element contacts from * the provided source object to the XML element. This visits all * the contacts marked with the Element annotation in * the source object. All annotated contacts are written as children * to the XML element. This will throw an exception if a required * contact within the source object is null. * * @param source this is the source object to be serialized * @param node this is the XML element to write elements to * @param schema this is used to track the referenced elements * * @throws Exception thrown if there is a serialization problem */ private void writeElements(OutputNode node, Object source, Schema schema) throws Exception { LabelMap elements = schema.getElements(); for(Label label : elements) { Contact contact = label.getContact(); Object value = contact.get(source); if(value == null && label.isRequired()) { throw new ElementException("Value for %s is null", label); } Object replace = writeReplace(value); if(replace != null) { writeElement(node, replace, label); } } } /** * The replace method is used to replace an object * before it is serialized. This is used so that an object can give * a substitute to be written to the XML document in the event that * the actual object is not suitable or desired for serialization. * This acts as an equivelant to the Java Object Serialization * writeReplace method for the object serialization. * * @param source this is the source object that is to be replaced * * @return this returns the object to use as a replacement value * * @throws Exception if the replacement object is not suitable */ private Object writeReplace(Object source) throws Exception { if(source != null) { Schema schema = root.getSchema(source); return schema.replace(source); } return source; } /** * This write method is used to write the text contact from the * provided source object to the XML element. This takes the text * value from the source object and writes it to the single contact * marked with the Text annotation. If the value is * null and the contact value is required an exception is thrown. * * @param source this is the source object to be serialized * @param node this is the XML element to write text value to * @param schema this is used to track the referenced elements * * @throws Exception thrown if there is a serialization problem */ private void writeText(OutputNode node, Object source, Schema schema) throws Exception { Label label = schema.getText(); if(label != null) { Contact contact = label.getContact(); Object value = contact.get(source); if(value == null) { value = label.getEmpty(); } if(value == null && label.isRequired()) { throw new TextException("Value for %s is null", label); } writeText(node, value, label); } } /** * This write method is used to set the value of the provided object * as an attribute to the XML element. This will acquire the string * value of the object using toString only if the * object provided is not an enumerated type. If the object is an * enumerated type then the Enum.name method is used. * * @param value this is the value to be set as an attribute * @param node this is the XML element to write the attribute to * @param label the label that contains the contact details * * @throws Exception thrown if there is a serialization problem */ private void writeAttribute(OutputNode node, Object value, Label label) throws Exception { if(value != null) { String name = label.getName(); String text = factory.getText(value); node.setAttribute(name, text); } } /** * This write method is used to append the provided object as an * element to the given XML element object. This will recursively * write the contacts from the provided object as elements. This is * done using the Converter acquired from the contact * label. If the type of the contact value is not of the same * type as the XML schema class a "class" attribute is appended. * * @param value this is the value to be set as an element * @param node this is the XML element to write the element to * @param label the label that contains the contact details * * @throws Exception thrown if there is a serialization problem */ private void writeElement(OutputNode node, Object value, Label label) throws Exception { if(value != null) { String name = label.getName(); OutputNode next = node.getChild(name); Class type = label.getType(); if(label.isInline() || !isOverridden(next, value, type)) { Converter convert = label.getConverter(root); boolean data = label.isData(); next.setData(data); convert.write(next, value); } } } /** * This write method is used to set the value of the provided object * as the text for the XML element. This will acquire the string * value of the object using toString only if the * object provided is not an enumerated type. If the object is an * enumerated type then the Enum.name method is used. * * @param value this is the value to set as the XML element text * @param node this is the XML element to write the text value to * @param label the label that contains the contact details * * @throws Exception thrown if there is a serialization problem */ private void writeText(OutputNode node, Object value, Label label) throws Exception { if(value != null) { String text = factory.getText(value); boolean data = label.isData(); node.setData(data); node.setValue(text); } } /** * This is used to determine whether the specified value has been * overrideen by the strategy. If the item has been overridden * then no more serialization is require for that value, this is * effectivly telling the serialization process to stop writing. * * @param node the node that a potential override is written to * @param value this is the object instance to be serialized * @param type this is the type of the object to be serialized * * @return returns true if the strategy overrides the object */ private boolean isOverridden(OutputNode node, Object value, Class type) throws Exception{ return factory.setOverride(type, value, node); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy