Skip to content

Commit

Permalink
Adjust serialization
Browse files Browse the repository at this point in the history
* Support `Iterable` collection type in standard binary and simple JSON
* Support null map keys in binary formats
* Update docs
  • Loading branch information
jodastephen committed Dec 20, 2024
1 parent 2d8b08f commit 3e68413
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 154 deletions.
16 changes: 16 additions & 0 deletions src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@
Mostly compatible with v2.x.
Deprecated methods have been removed.
</action>
<action dev="jodastephen" type="add" issue="445">
Add a new binary format.
This change adds a third binary format, with a focus on size and performance.
In testing it produces binary files similar or slightly smaller than the referencing binary format,
but with faster serialization and deserialization. This solves a performance problem with the referencing
format whereby it tried to deduplicate all beans, even those with very large hash codes.
As part of this change, callers should now explicitly select which binary format they want using `JodaBeanBinFormat`.
</action>
<action dev="jodastephen" type="add" issue="446">
Potentially incompatible change:
The standard binary and simple JSON formats now handle `Iterable` as a collection type.
</action>
<action dev="jodastephen" type="add" issue="446">
Potentially incompatible change:
The standard and referencing binary formats now support null as a map key.
</action>
<action dev="jodastephen" type="add" issue="232">
Potentially incompatible change:
Manual equals, hashCode and toString methods must now be located *before* the autogenerated block.
Expand Down
3 changes: 0 additions & 3 deletions src/main/java/org/joda/beans/ser/SerIteratorFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -585,9 +585,6 @@ public SerIterator iterator() {

@Override
public void add(Object key, Object column, Object value, int count) {
if (key == null) {
throw new IllegalArgumentException("Missing key");
}
if (count != 1) {
throw new IllegalArgumentException("Unexpected count");
}
Expand Down
6 changes: 1 addition & 5 deletions src/main/java/org/joda/beans/ser/bin/AbstractBinWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,7 @@ void writeMap(SerIterator itemIterator) throws IOException {
output.writeMapHeader(itemIterator.size());
while (itemIterator.hasNext()) {
itemIterator.next();
var key = itemIterator.key();
if (key == null) {
throw new IllegalArgumentException("Unable to write map key as it cannot be null");
}
writeObject(itemIterator.keyType(), key, null);
writeObject(itemIterator.keyType(), itemIterator.key(), null);
writeObject(itemIterator.valueType(), itemIterator.value(), itemIterator);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,15 +464,15 @@ BinHandler<?> createHandler(Class<?> type) {
return (BinHandler<Object[]>) BaseBinHandlers::writeArray;
}
}
if (Map.class.isAssignableFrom(type)) {
return (BinHandler<Map<?, ?>>) BaseBinHandlers::writeMap;
}
if (Collection.class.isAssignableFrom(type)) {
return (BinHandler<Collection<?>>) BaseBinHandlers::writeCollection;
}
if (Iterable.class.isAssignableFrom(type)) {
return (BinHandler<Iterable<?>>) BaseBinHandlers::writeIterable;
}
if (Map.class.isAssignableFrom(type)) {
return (BinHandler<Map<?, ?>>) BaseBinHandlers::writeMap;
}
return (writer, declaredType, propertyName, value) -> writer.writeSimple(propertyName, value);
}

Expand Down Expand Up @@ -570,7 +570,7 @@ private static String metaTypeArrayName(Class<?> valueType) {
return valueType.getName() + "[]";
}

// writes a collection, with meta type information if necessary
// writes an iterable, with meta type information if necessary
private static void writeIterable(
JodaBeanPackedBinWriter writer,
ResolvedType declaredType,
Expand Down Expand Up @@ -770,6 +770,8 @@ private static void writeBiMap(
// was actually meant to be an ImmutableMap
if ((declaredType.getRawType() != Map.class && declaredType.getRawType() != ImmutableMap.class) || biMap.size() >= 2) {
writer.output.writeTypeReference(BeanPack.TYPE_CODE_BIMAP);
} else if (!Map.class.isAssignableFrom(declaredType.getRawType())) {
writer.output.writeTypeReference(BeanPack.TYPE_CODE_MAP);
}
}
// write content
Expand Down
82 changes: 59 additions & 23 deletions src/main/java/org/joda/beans/ser/bin/JodaBeanStandardBinWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.Spliterator;
import java.util.stream.StreamSupport;

import org.joda.beans.Bean;
import org.joda.beans.ResolvedType;
Expand All @@ -32,6 +34,8 @@
import org.joda.collect.grid.Grid;
import org.joda.collect.grid.ImmutableGrid;

import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
Expand Down Expand Up @@ -332,6 +336,9 @@ private static final BaseBinHandlers getInstance() {

// creates the handler, called from ClassValue.computeValue()
BinHandler<?> createHandler(Class<?> type) {
if (type == Optional.class) {
return OptionalBinHandler.INSTANCE;
}
if (type.isArray()) {
var componentType = type.getComponentType();
if (componentType.isPrimitive()) {
Expand All @@ -344,14 +351,14 @@ BinHandler<?> createHandler(Class<?> type) {
return (CollectionBinHandler<Object[]>) BaseBinHandlers::writeArray;
}
}
if (Collection.class.isAssignableFrom(type)) {
return (CollectionBinHandler<Collection<?>>) BaseBinHandlers::writeCollection;
}
if (Map.class.isAssignableFrom(type)) {
return (CollectionBinHandler<Map<?, ?>>) BaseBinHandlers::writeMap;
}
if (type == Optional.class) {
return OptionalBinHandler.INSTANCE;
if (Collection.class.isAssignableFrom(type)) {
return (CollectionBinHandler<Collection<?>>) BaseBinHandlers::writeCollection;
}
if (Iterable.class.isAssignableFrom(type)) {
return (CollectionBinHandler<Iterable<?>>) BaseBinHandlers::writeIterable;
}
return JodaBeanStandardBinWriter::writeSimple;
}
Expand Down Expand Up @@ -401,17 +408,31 @@ private static void writePrimitiveArray(
}

// determines the meta type name to use
private static String metaTypeArrayName(Class<?> arrayType) {
if (arrayType.isArray()) {
return metaTypeArrayName(arrayType.getComponentType()) + "[]";
private static String metaTypeArrayName(Class<?> valueType) {
if (valueType.isArray()) {
return metaTypeArrayName(valueType.getComponentType()) + "[]";
}
if (arrayType == Object.class) {
if (valueType == Object.class) {
return "Object[]";
}
if (arrayType == String.class) {
if (valueType == String.class) {
return "String[]";
}
return arrayType.getName() + "[]";
return valueType.getName() + "[]";
}

// writes an iterable, with meta type information if necessary
private static void writeIterable(
JodaBeanStandardBinWriter writer,
ResolvedType declaredType,
String propertyName,
Iterable<?> iterable) throws IOException {

// convert to a list, which is necessary as there is no size() on Iterable
// this ensures that the generics of the iterable are retained
var list = StreamSupport.stream(iterable::spliterator, Spliterator.ORDERED, false).toList();
var adjustedType = ResolvedType.of(List.class, declaredType.getArguments());
writeCollection(writer, adjustedType, propertyName, list);
}

// writes a collection, with meta type information if necessary
Expand Down Expand Up @@ -463,19 +484,10 @@ static <K, V> void writeMapEntries(
var valueType = toWeakenedType(declaredType.getArgumentOrDefault(1));
writer.output.writeMapHeader(mapEntries.size());
for (var entry : mapEntries) {
var key = entry.getKey();
if (key == null) {
throw invalidNullMapKey(propertyName);
}
writer.writeObject(keyType, "", entry.getKey());
writer.writeObject(valueType, "", entry.getValue());
}
}

private static IllegalArgumentException invalidNullMapKey(String propertyName) throws IOException {
return new IllegalArgumentException(
"Unable to write property '" + propertyName + "', map key must not be null");
}
}

//-------------------------------------------------------------------------
Expand All @@ -492,6 +504,9 @@ BinHandler<?> createHandler(Class<?> type) {
if (Table.class.isAssignableFrom(type)) {
return (CollectionBinHandler<Table<?, ?, ?>>) GuavaBinHandlers::writeTable;
}
if (BiMap.class.isAssignableFrom(type)) {
return (CollectionBinHandler<BiMap<?, ?>>) GuavaBinHandlers::writeBiMap;
}
if (com.google.common.base.Optional.class.isAssignableFrom(type)) {
return GuavaOptionalBinHandler.INSTANCE;
}
Expand All @@ -513,7 +528,7 @@ private static void writeMultimap(
} else if (!Multimap.class.isAssignableFrom(declaredType.getRawType())) {
writeMetaPropertyReference(writer, "Multimap");
}
// write content
// write content, using a map with repeated keys
writeMapEntries(writer, declaredType, propertyName, mmap.entries());
}

Expand All @@ -528,7 +543,7 @@ private static void writeMultiset(
if (!Multiset.class.isAssignableFrom(declaredType.getRawType())) {
writeMetaPropertyReference(writer, "Multiset");
}
// write content
// write content, using a map of value to count
var valueType = toWeakenedType(declaredType.getArgumentOrDefault(0));
var entrySet = mset.entrySet();
writer.output.writeMapHeader(entrySet.size());
Expand All @@ -549,7 +564,7 @@ private static void writeTable(
if (!Table.class.isAssignableFrom(declaredType.getRawType())) {
writeMetaPropertyReference(writer, "Table");
}
// write content
// write content, using an array of cells
var rowType = toWeakenedType(declaredType.getArgumentOrDefault(0));
var columnType = toWeakenedType(declaredType.getArgumentOrDefault(1));
var valueType = toWeakenedType(declaredType.getArgumentOrDefault(2));
Expand All @@ -561,6 +576,27 @@ private static void writeTable(
writer.writeObject(valueType, "", cell.getValue());
}
}

// writes a BiMap, with meta type information if necessary
private static void writeBiMap(
JodaBeanStandardBinWriter writer,
ResolvedType declaredType,
String propertyName,
BiMap<?, ?> biMap) throws IOException {

// write actual type
if (!BiMap.class.isAssignableFrom(declaredType.getRawType())) {
// hack around Guava annoyance by assuming that size 0 and 1 ImmutableBiMap
// was actually meant to be an ImmutableMap
if ((declaredType.getRawType() != Map.class && declaredType.getRawType() != ImmutableMap.class) || biMap.size() >= 2) {
writeMetaPropertyReference(writer, "BiMap");
} else if (!Map.class.isAssignableFrom(declaredType.getRawType())) {
writeMetaPropertyReference(writer, "Map");
}
}
// write content
writeMapEntries(writer, declaredType, propertyName, biMap.entrySet());
}
}

//-------------------------------------------------------------------------
Expand Down
Loading

0 comments on commit 3e68413

Please sign in to comment.