org.joda.beans.ser.bin.JodaBeanBinWriter 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.bin;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.joda.beans.Bean;
import org.joda.beans.ImmutableBean;
import org.joda.beans.ser.JodaBeanSer;
/**
* Provides the ability for a Joda-Bean to be written to a binary format.
*
* This class is immutable and may be used from multiple threads.
*
*
Standard format
* The binary format is based on MessagePack v2.0.
* Each bean is output as a map using the property name.
*
* Most simple types, defined by Joda-Convert, are output as MessagePack strings.
* However, MessagePack nil, boolean, float, integral and bin types are also used
* for null, byte[] and the Java numeric primitive types (excluding char).
*
* Beans are output using MessagePack maps where the key is the property name.
* Collections are output using MessagePack maps or arrays.
* Multisets are output as a map of value to count.
*
* If a collection contains a collection then addition meta-type information is
* written to aid with deserialization.
* At this level, the data read back may not be identical to that written.
*
* Where necessary, the Java type is sent using an 'ext' entity.
* Three 'ext' types are used, one each for beans, meta-type and simple.
* The class name is passed as the 'ext' data.
* The 'ext' value is sent as an additional key-value pair for beans, with the
* 'ext' as the key and 'nil' as the value. Where the additional type information
* is not about a bean, a tuple is written using a size 1 map where the key is the
* 'ext' data and the value is the data being annotated.
*
* 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.
*
*
Referencing format
* The referencing format is based on the standard format.
* As a more complex format, it is intended to be consumed only by Joda-Beans
* (whereas the standard format could be consumed by any consumer using MsgPack).
* Thus this format is not fully documented and may change over time.
*
* The referencing format only supports serialization of instances of {@code ImmutableBean}
* and other basic types. If any mutable beans are encountered during traversal an exception will be thrown.
*
* An initial pass of the bean is used to build up a map of unique immutable beans
* and unique immutable instances of other classes (based on an equality check).
* Then the class and property names for each bean class is serialized up front as a map of class name to list of
* property names, along with class information for any class where type information would be required when parsing
* and is not available on the metabean for the enclosing bean object.
*
* Each unique immutable bean is output as a list of each property value using the fixed
* property order previously serialized. Subsequent instances of unique objects (defined by an
* equality check) are replaced by references to the first serialized instance.
*
* The Java type names are sent using an 'ext' entity.
* Five 'ext' types are used, one each for beans, meta-type and simple, reference keys and reference lookups.
* The class name is passed as the 'ext' data.
* The 'ext' value is sent as the first item in an array of property values for beans, an integer referring to the
* location in the initial class mapping.
* Where the additional type information is not about a bean, a tuple is written using a size 1 map where the key is
* the 'ext' data and the value is the data being annotated.
*
* For references, when an object will be referred back to it is written as a map of size one with 'ext' as the key
* and the object that should be referred to as the value.
* When that same object is referred back to it is written as 'ext' with the data from the initial 'ext'.
*/
public class JodaBeanBinWriter {
/**
* Settings.
*/
private final JodaBeanSer settings;
/**
* Whether to use referencing.
*/
private final boolean referencing;
//-----------------------------------------------------------------------
/**
* Creates an instance.
*
* @param settings the settings to use, not null
*/
public JodaBeanBinWriter(JodaBeanSer settings) {
this(settings, false);
}
/**
* Creates an instance.
*
* @param settings the settings to use, not null
* @param referencing whether to use referencing
*/
public JodaBeanBinWriter(JodaBeanSer settings, boolean referencing) {
if (settings == null) {
throw new NullPointerException("settings");
}
this.settings = settings;
this.referencing = referencing;
}
//-----------------------------------------------------------------------
/**
* Writes the bean to an array of bytes.
*
* The type of the bean will be set in the message.
*
* @param bean the bean to output, not null
* @return the binary data, not null
*/
public byte[] write(Bean bean) {
return write(bean, true);
}
/**
* Writes the bean to an array of bytes.
*
* @param bean the bean to output, not null
* @param rootType true to output the root type
* @return the binary data, not null
*/
public byte[] write(Bean bean, boolean rootType) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
try {
write(bean, rootType, baos);
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
return baos.toByteArray();
}
/**
* Writes the bean to the {@code OutputStream}.
*
* The type of the bean will be set in the message.
*
* @param bean the bean to output, not null
* @param output the output stream, not null
* @throws IOException if an error occurs
*/
public void write(Bean bean, OutputStream output) throws IOException {
write(bean, true, output);
}
/**
* Writes the bean to the {@code OutputStream}.
*
* @param bean the bean to output, not null
* @param rootType true to output the root type
* @param output the output stream, not null
* @throws IOException if an error occurs
*/
public void write(Bean bean, boolean rootType, OutputStream output) throws IOException {
if (bean == null) {
throw new NullPointerException("bean");
}
if (output == null) {
throw new NullPointerException("output");
}
if (referencing) {
if (!(bean instanceof ImmutableBean)) {
throw new IllegalArgumentException(
"Referencing binary format can only write ImmutableBean instances: " + bean.getClass().getName());
}
new JodaBeanReferencingBinWriter(settings, output).write((ImmutableBean) bean);
} else {
new JodaBeanStandardBinWriter(settings, output).write(bean, rootType);
}
}
}