org.joda.beans.ser.bin.AbstractBinWriter 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.IOException;
import java.io.OutputStream;
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;
/**
* Provides the ability for a Joda-Bean to be written to both the standard and referencing binary formats.
*
* This class contains mutable state and cannot be used from multiple threads.
* A new instance must be created for each message.
*/
abstract class AbstractBinWriter {
/**
* The settings to use.
*/
final JodaBeanSer settings; // CSIGNORE
/**
* The output stream.
*/
final MsgPackOutput output; // CSIGNORE
/**
* The base package including the trailing dot.
*/
private String basePackage;
/**
* The known types.
*/
private Map, String> knownTypes = new HashMap<>();
// creates an instance
AbstractBinWriter(JodaBeanSer settings, OutputStream output) {
this.settings = settings;
this.output = new MsgPackOutput(output);
}
//-----------------------------------------------------------------------
void writeRootBean(Bean bean, boolean rootTypeFlag) throws IOException {
writeBean(bean, bean.getClass(), rootTypeFlag ? RootType.ROOT_WITH_TYPE : RootType.ROOT_WITHOUT_TYPE);
}
void writeBean(Bean bean, Class> declaredType, RootType rootTypeFlag) throws IOException {
int count = bean.metaBean().metaPropertyCount();
MetaProperty>[] props = new MetaProperty>[count];
Object[] values = new Object[count];
int size = 0;
for (MetaProperty> prop : bean.metaBean().metaPropertyIterable()) {
if (settings.isSerialized(prop)) {
Object value = SerOptional.extractValue(prop, bean);
if (value != null) {
props[size] = prop;
values[size++] = value;
}
}
}
if (rootTypeFlag == RootType.ROOT_WITH_TYPE || (rootTypeFlag == RootType.NOT_ROOT && bean.getClass() != declaredType)) {
String type = SerTypeMapper.encodeType(bean.getClass(), settings, basePackage, knownTypes);
if (rootTypeFlag == RootType.ROOT_WITH_TYPE) {
basePackage = bean.getClass().getPackage().getName() + ".";
}
output.writeMapHeader(size + 1);
output.writeExtensionString(MsgPack.JODA_TYPE_BEAN, type);
output.writeNil();
} else {
output.writeMapHeader(size);
}
for (int i = 0; i < size; i++) {
MetaProperty> prop = props[i];
Object value = values[i];
output.writeString(prop.name());
Class> propType = SerOptional.extractType(prop, bean.getClass());
if (value instanceof Bean) {
if (settings.getConverter().isConvertible(value.getClass())) {
writeSimple(propType, value);
} else {
writeBean((Bean) value, propType, RootType.NOT_ROOT);
}
} else {
SerIterator itemIterator = settings.getIteratorFactory().create(value, prop, bean.getClass());
if (itemIterator != null) {
writeElements(itemIterator);
} else {
writeSimple(propType, value);
}
}
}
}
//-----------------------------------------------------------------------
void writeMetaPropertyReference(String metaTypeName) throws IOException {
output.writeExtensionString(MsgPack.JODA_TYPE_META, metaTypeName);
}
void writeElements(SerIterator itemIterator) throws IOException {
if (itemIterator.metaTypeRequired()) {
output.writeMapHeader(1);
writeMetaPropertyReference(itemIterator.metaTypeName());
}
if (itemIterator.category() == SerCategory.MAP) {
writeMap(itemIterator);
} else if (itemIterator.category() == SerCategory.COUNTED) {
writeCounted(itemIterator);
} else if (itemIterator.category() == SerCategory.TABLE) {
writeTable(itemIterator);
} else if (itemIterator.category() == SerCategory.GRID) {
writeGrid(itemIterator);
} else {
writeArray(itemIterator);
}
}
void writeArray(SerIterator itemIterator) throws IOException {
output.writeArrayHeader(itemIterator.size());
while (itemIterator.hasNext()) {
itemIterator.next();
writeObject(itemIterator.valueType(), itemIterator.value(), itemIterator);
}
}
void writeMap(SerIterator itemIterator) throws IOException {
output.writeMapHeader(itemIterator.size());
while (itemIterator.hasNext()) {
itemIterator.next();
Object key = itemIterator.key();
if (key == null) {
throw new IllegalArgumentException("Unable to write map key as it cannot be null: " + key);
}
writeObject(itemIterator.keyType(), key, null);
writeObject(itemIterator.valueType(), itemIterator.value(), itemIterator);
}
}
void writeTable(SerIterator itemIterator) throws IOException {
output.writeArrayHeader(itemIterator.size());
while (itemIterator.hasNext()) {
itemIterator.next();
output.writeArrayHeader(3);
writeObject(itemIterator.keyType(), itemIterator.key(), null);
writeObject(itemIterator.columnType(), itemIterator.column(), null);
writeObject(itemIterator.valueType(), itemIterator.value(), itemIterator);
}
}
void writeGrid(SerIterator itemIterator) throws IOException {
int rows = itemIterator.dimensionSize(0);
int columns = itemIterator.dimensionSize(1);
int totalSize = rows * columns;
if (itemIterator.size() < (totalSize / 4)) {
// sparse
output.writeArrayHeader(itemIterator.size() + 2);
output.writeInt(rows);
output.writeInt(columns);
while (itemIterator.hasNext()) {
itemIterator.next();
output.writeArrayHeader(3);
output.writeInt((Integer) itemIterator.key());
output.writeInt((Integer) itemIterator.column());
writeObject(itemIterator.valueType(), itemIterator.value(), itemIterator);
}
} else {
// dense
output.writeArrayHeader(totalSize + 2);
output.writeInt(rows);
output.writeInt(columns);
for (int row = 0; row < rows; row++) {
for (int column = 0; column < columns; column++) {
writeObject(itemIterator.valueType(), itemIterator.value(row, column), itemIterator);
}
}
}
}
void writeCounted(SerIterator itemIterator) throws IOException {
output.writeMapHeader(itemIterator.size());
while (itemIterator.hasNext()) {
itemIterator.next();
writeObject(itemIterator.valueType(), itemIterator.value(), itemIterator);
output.writeInt(itemIterator.count());
}
}
void writeObject(Class> declaredType, Object obj, SerIterator parentIterator) throws IOException {
if (obj == null) {
output.writeNil();
} else if (settings.getConverter().isConvertible(obj.getClass())) {
writeSimple(declaredType, obj);
} else if (obj instanceof Bean) {
writeBean((Bean) obj, declaredType, RootType.NOT_ROOT);
} else if (parentIterator != null) {
SerIterator childIterator = settings.getIteratorFactory().createChild(obj, parentIterator);
if (childIterator != null) {
writeElements(childIterator);
} else {
writeSimple(declaredType, obj);
}
} else {
writeSimple(declaredType, obj);
}
}
//-----------------------------------------------------------------------
void writeSimple(Class> declaredType, Object value) throws IOException {
// simple types have no need to write a type object
Class> realType = value.getClass();
if (realType == Integer.class) {
output.writeInt(((Integer) value).intValue());
return;
} else if (realType == Double.class) {
output.writeDouble(((Double) value).doubleValue());
return;
} else if (realType == Float.class) {
output.writeFloat(((Float) value).floatValue());
return;
} else if (realType == Boolean.class) {
output.writeBoolean(((Boolean) value).booleanValue());
return;
}
// handle no declared type and subclasses
Class> effectiveType = getAndSerializeEffectiveTypeIfRequired(value, declaredType);
// long/short/byte only processed now to ensure that a distinction can be made between Integer and Long
if (realType == Long.class) {
output.writeLong(((Long) value).longValue());
return;
} else if (realType == Short.class) {
output.writeInt(((Short) value).shortValue());
return;
} else if (realType == Byte.class) {
output.writeInt(((Byte) value).byteValue());
return;
} else if (realType == byte[].class) {
output.writeBytes((byte[]) value);
return;
}
// write as a string
try {
writeObjectAsString(value, effectiveType);
} catch (RuntimeException ex) {
throw new IllegalArgumentException("Unable to convert type " + effectiveType.getName() + " declared as " + declaredType.getName(), ex);
}
}
// called when serializing an object in {@link #writeSimple(Class, Object)}, to get the effective type of the
// object and if necessary to serialize the class information
// needs to handle no declared type and subclass instances
Class> getAndSerializeEffectiveTypeIfRequired(Object value, Class> declaredType) throws IOException {
Class> realType = value.getClass();
Class> effectiveType = declaredType;
if (declaredType == Object.class) {
if (realType != String.class) {
effectiveType = settings.getConverter().findTypedConverter(realType).getEffectiveType();
output.writeMapHeader(1);
String type = SerTypeMapper.encodeType(effectiveType, settings, basePackage, knownTypes);
output.writeExtensionString(MsgPack.JODA_TYPE_DATA, type);
} else {
effectiveType = realType;
}
} else if (settings.getConverter().isConvertible(declaredType) == false) {
effectiveType = settings.getConverter().findTypedConverter(realType).getEffectiveType();
output.writeMapHeader(1);
String type = SerTypeMapper.encodeType(effectiveType, settings, basePackage, knownTypes);
output.writeExtensionString(MsgPack.JODA_TYPE_DATA, type);
}
return effectiveType;
}
// writes a value as a string
// called after discerning that the value is not a simple type
void writeObjectAsString(Object value, Class> effectiveType) throws IOException {
String converted = settings.getConverter().convertToString(effectiveType, value);
if (converted == null) {
throw new IllegalArgumentException("Unable to write because converter returned a null string: " + value);
}
output.writeString(converted);
}
//-----------------------------------------------------------------------
enum RootType {
ROOT_WITH_TYPE,
ROOT_WITHOUT_TYPE,
NOT_ROOT,
}
}