org.joda.beans.ser.xml.JodaBeanXmlWriter Maven / Gradle / Ivy
/*
* Copyright 2001-present Stephen Colebourne
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.joda.beans.ser.xml;
import static org.joda.beans.ser.xml.JodaBeanXml.BEAN;
import static org.joda.beans.ser.xml.JodaBeanXml.COL;
import static org.joda.beans.ser.xml.JodaBeanXml.COLS;
import static org.joda.beans.ser.xml.JodaBeanXml.COUNT;
import static org.joda.beans.ser.xml.JodaBeanXml.ENTRY;
import static org.joda.beans.ser.xml.JodaBeanXml.ITEM;
import static org.joda.beans.ser.xml.JodaBeanXml.KEY;
import static org.joda.beans.ser.xml.JodaBeanXml.METATYPE;
import static org.joda.beans.ser.xml.JodaBeanXml.NULL;
import static org.joda.beans.ser.xml.JodaBeanXml.ROW;
import static org.joda.beans.ser.xml.JodaBeanXml.ROWS;
import static org.joda.beans.ser.xml.JodaBeanXml.TYPE;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.joda.beans.Bean;
import org.joda.beans.MetaProperty;
import org.joda.beans.ser.JodaBeanSer;
import org.joda.beans.ser.SerCategory;
import org.joda.beans.ser.SerIterator;
import org.joda.beans.ser.SerOptional;
import org.joda.beans.ser.SerTypeMapper;
import org.joda.convert.StringConverter;
/**
* Provides the ability for a Joda-Bean to be written to XML.
*
* This class contains mutable state and cannot be used from multiple threads.
* A new instance must be created for each message.
*
* The XML consists of a root level 'bean' element with a 'type' attribute.
* At each subsequent level, a bean is output using the property name.
* Where necessary, the 'type' attribute is used to clarify a type.
*
* Simple types, defined by Joda-Convert, are output as strings.
* Beans are output recursively within the parent property element.
* Collections are output using 'item' elements within the property element.
* The 'item' elements will use 'key' for map keys, 'count' for multiset counts
* and 'null=true' for null entries. Note that map keys must be simple types.
*
* If a collection contains a collection then more information is written.
* A 'metatype' attribute is added to define the high level type, such as List.
* At this level, the data read back may not be identical to that written.
*
* Type names are shortened by the package of the root type if possible.
* Certain basic types are also handled, such as String, Integer, File and URI.
*/
public class JodaBeanXmlWriter {
/**
* The settings to use.
*/
private final JodaBeanSer settings;
/**
* The string builder, unused if writing to an {@code Appendable}.
*/
private final StringBuilder builder;
/**
* The location to output to.
*/
private Appendable output;
/**
* The root bean.
*/
private Bean rootBean;
/**
* The base package including the trailing dot.
*/
private String basePackage;
/**
* The known types.
*/
private Map, String> knownTypes = new HashMap<>();
/**
* Creates an instance.
*
* @param settings the settings to use, not null
*/
public JodaBeanXmlWriter(JodaBeanSer settings) {
this(settings, new StringBuilder(1024));
}
/**
* Creates an instance.
*
* @param settings the settings to use, not null
* @param builder the builder to output to, not null
*/
public JodaBeanXmlWriter(JodaBeanSer settings, StringBuilder builder) {
this.settings = settings;
this.builder = builder;
}
//-----------------------------------------------------------------------
/**
* Writes the bean to a string.
*
* The type of the bean will be set in the message.
*
* @param bean the bean to output, not null
* @return the XML, not null
*/
public String write(Bean bean) {
return write(bean, true);
}
/**
* Writes the bean to a string.
*
* @param bean the bean to output, not null
* @param rootType true to output the root type
* @return the XML, not null
*/
public String write(Bean bean, boolean rootType) {
return writeToBuilder(bean, rootType).toString();
}
/**
* Writes the bean to the {@code StringBuilder}.
*
* The type of the bean will be set in the message.
*
* @param bean the bean to output, not null
* @return the builder, not null
* @deprecated Use {@link #write(Bean, Appendable)}
*/
@Deprecated
public StringBuilder writeToBuilder(Bean bean) {
return writeToBuilder(bean, true);
}
/**
* Writes the bean to the {@code StringBuilder}.
*
* @param bean the bean to output, not null
* @param rootType true to output the root type
* @return the builder, not null
* @deprecated Use {@link #write(Bean, boolean, Appendable)}
*/
@Deprecated
public StringBuilder writeToBuilder(Bean bean, boolean rootType) {
try {
write(bean, rootType, this.builder);
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
return builder;
}
/**
* Writes the bean to the {@code Appendable}.
*
* The type of the bean will be set in the message.
*
* @param bean the bean to output, not null
* @param output the output appendable, not null
* @throws IOException if an error occurs
*/
public void write(Bean bean, Appendable output) throws IOException {
write(bean, true, output);
}
/**
* Writes the bean to the {@code Appendable}.
*
* @param bean the bean to output, not null
* @param rootType true to output the root type
* @param output the output appendable, not null
* @throws IOException if an error occurs
*/
public void write(Bean bean, boolean rootType, Appendable output) throws IOException {
if (bean == null) {
throw new NullPointerException("bean");
}
this.output = output;
this.rootBean = bean;
this.basePackage = (rootType ? bean.getClass().getPackage().getName() + "." : null);
String type = rootBean.getClass().getName();
writeHeader();
output.append('<').append(BEAN);
if (rootType) {
appendAttribute(output, TYPE, type);
}
output.append('>').append(settings.getNewLine());
writeBean(rootBean, settings.getIndent());
output.append('<').append('/').append(BEAN).append('>').append(settings.getNewLine());
}
private void writeHeader() throws IOException {
output.append("").append(settings.getNewLine());
}
//-----------------------------------------------------------------------
private boolean willWriteBean(Bean bean) {
for (MetaProperty> prop : bean.metaBean().metaPropertyIterable()) {
if (prop.style().isSerializable() || (prop.style().isDerived() && settings.isIncludeDerived())) {
return true;
}
}
return false;
}
private void writeBean(Bean bean, String currentIndent) throws IOException {
for (MetaProperty> prop : bean.metaBean().metaPropertyIterable()) {
if (prop.style().isSerializable() || (prop.style().isDerived() && settings.isIncludeDerived())) {
Object value = SerOptional.extractValue(prop, bean);
if (value != null) {
String propName = prop.name();
Class> propType = SerOptional.extractType(prop, bean.getClass());
if (value instanceof Bean) {
if (settings.getConverter().isConvertible(value.getClass())) {
writeSimple(currentIndent, propName, new StringBuilder(), propType, value);
} else {
writeBean(currentIndent, propName, new StringBuilder(), propType, (Bean) value);
}
} else {
SerIterator itemIterator = settings.getIteratorFactory().create(value, prop, bean.getClass());
if (itemIterator != null) {
writeElements(currentIndent, propName, new StringBuilder(), itemIterator);
} else {
writeSimple(currentIndent, propName, new StringBuilder(), propType, value);
}
}
}
}
}
}
//-----------------------------------------------------------------------
private void writeBean(String currentIndent, String tagName, StringBuilder attrs, Class> propType, Bean value) throws IOException {
if (value == null) {
throw new IllegalArgumentException("Bean cannot be null");
}
output.append(currentIndent).append('<').append(tagName).append(attrs);
if (value.getClass() != propType) {
String typeStr = SerTypeMapper.encodeType(value.getClass(), settings, basePackage, knownTypes);
appendAttribute(output, TYPE, typeStr);
}
if (willWriteBean(value)) {
output.append('>').append(settings.getNewLine());
writeBean(value, currentIndent + settings.getIndent());
output.append(currentIndent).append('<').append('/').append(tagName).append('>').append(settings.getNewLine());
} else {
output.append('/').append('>').append(settings.getNewLine());
}
}
//-----------------------------------------------------------------------
private void writeElements(String currentIndent, String tagName, StringBuilder attrs, SerIterator itemIterator) throws IOException {
if (itemIterator.metaTypeRequired()) {
appendAttribute(attrs, METATYPE, itemIterator.metaTypeName());
}
if (itemIterator.category() == SerCategory.GRID) {
appendAttribute(attrs, ROWS, Integer.toString(itemIterator.dimensionSize(0)));
appendAttribute(attrs, COLS, Integer.toString(itemIterator.dimensionSize(1)));
}
if (itemIterator.size() == 0) {
output.append(currentIndent).append('<').append(tagName).append(attrs).append('/').append('>').append(settings.getNewLine());
} else {
output.append(currentIndent).append('<').append(tagName).append(attrs).append('>').append(settings.getNewLine());
writeElements(currentIndent + settings.getIndent(), itemIterator);
output.append(currentIndent).append('<').append('/').append(tagName).append('>').append(settings.getNewLine());
}
}
private void writeElements(String currentIndent, SerIterator itemIterator) throws IOException {
// find converter once for performance, and before checking if key is bean
StringConverter