Skip to content

Instantly share code, notes, and snippets.

@trueinsider
Created February 23, 2016 01:00
Show Gist options
  • Save trueinsider/78a7abc8dc0d4ec5810a to your computer and use it in GitHub Desktop.
Save trueinsider/78a7abc8dc0d4ec5810a to your computer and use it in GitHub Desktop.
package database;
import com.esotericsoftware.reflectasm.ConstructorAccess;
import com.esotericsoftware.reflectasm.FieldAccess;
import org.bson.*;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import static util.Throw.sneaky;
/**
* Created by insider on 17.06.2015.
*/
public class ObjectCodec<T> implements Codec<T> {
private final Class<T> entityClass;
private final Function<BsonReader, T> entityReader;
private final BiConsumer<BsonWriter, T> entityWriter;
private Object readNullableValue(BsonReader reader, Function<BsonReader, ?> wrapped) {
if (reader.getCurrentBsonType() == BsonType.NULL) {
reader.readNull();
return null;
}
return wrapped.apply(reader);
}
private byte readByte(BsonReader reader) {
return (byte) reader.readInt32();
}
private short readShort(BsonReader reader) {
return (short) reader.readInt32();
}
private float readFloat(BsonReader reader) {
return (float) reader.readDouble();
}
private <R> Function<BsonReader, Object> getArrayReader(Class<R[]> type) {
return reader -> {
reader.readStartArray();
Class<?> elementType = type.getComponentType();
Function<BsonReader, Object> elementReader = getTypeReader(elementType);
List<Object> elements = new ArrayList<>();
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
Object value = elementReader.apply(reader);
elements.add(value);
}
reader.readEndArray();
int size = elements.size();
Object array = Array.newInstance(elementType, size);
BiConsumer<Integer, Object> setByIndex;
if (elementType == boolean.class) {
setByIndex = (index, value) -> Array.setBoolean(array, index, (boolean) value);
} else if (elementType == byte.class) {
setByIndex = (index, value) -> Array.setByte(array, index, (byte) value);
} else if (elementType == short.class) {
setByIndex = (index, value) -> Array.setShort(array, index, (short) value);
} else if (elementType == int.class) {
setByIndex = (index, value) -> Array.setInt(array, index, (int) value);
} else if (elementType == long.class) {
setByIndex = (index, value) -> Array.setLong(array, index, (long) value);
} else if (elementType == float.class) {
setByIndex = (index, value) -> Array.setFloat(array, index, (float) value);
} else if (elementType == double.class) {
setByIndex = (index, value) -> Array.setDouble(array, index, (double) value);
} else {
setByIndex = (index, value) -> Array.set(array, index, value);
}
for (int i = 0; i < size; i++) {
Object value = elements.get(i);
setByIndex.accept(i, value);
}
return array;
};
}
private <R> Function<BsonReader, Object> getTypeReader(Class<R> type) {
if (type == boolean.class) {
return BsonReader::readBoolean;
} else if (type == Boolean.class) {
return reader -> readNullableValue(reader, BsonReader::readBoolean);
} else if (type == byte.class) {
return this::readByte;
} else if (type == Byte.class) {
return reader -> readNullableValue(reader, this::readByte);
} else if (type == short.class) {
return this::readShort;
} else if (type == Short.class) {
return reader -> readNullableValue(reader, this::readShort);
} else if (type == int.class) {
return BsonReader::readInt32;
} else if (type == Integer.class) {
return reader -> readNullableValue(reader, BsonReader::readInt32);
} else if (type == long.class) {
return BsonReader::readInt64;
} else if (type == Long.class) {
return reader -> readNullableValue(reader, BsonReader::readInt64);
} else if (type == float.class) {
return this::readFloat;
} else if (type == Float.class) {
return reader -> readNullableValue(reader, this::readFloat);
} else if (type == double.class) {
return BsonReader::readDouble;
} else if (type == Double.class) {
return reader -> readNullableValue(reader, BsonReader::readDouble);
} else if (type == String.class) {
return reader -> readNullableValue(reader, BsonReader::readString);
} else if (type.isArray()) {
return reader -> readNullableValue(reader, getArrayReader((Class<Object[]>) type));
} else {
return reader -> readNullableValue(reader, getClassReader(type));
}
}
private <R> Function<BsonReader, R> getClassReader(Class<R> entityClass) {
ConstructorAccess<R> constructorAccess = ConstructorAccess.get(entityClass);
Map<String, BiConsumer<R, Object>> fieldSetters = new HashMap<>();
Map<String, Function<BsonReader, Object>> fieldReaders = new HashMap<>();
FieldAccess fieldAccess = FieldAccess.get(entityClass);
String[] fieldNames = fieldAccess.getFieldNames();
Class<?>[] fieldTypes = fieldAccess.getFieldTypes();
for (int i = 0; i < fieldNames.length; i++) {
String name = fieldNames[i];
int index = fieldAccess.getIndex(name);
fieldSetters.put(name, (instance, value) -> fieldAccess.set(instance, index, value));
Class<?> type = fieldTypes[i];
fieldReaders.put(name, getTypeReader(type));
}
return reader -> {
R instance = constructorAccess.newInstance();
reader.readStartDocument();
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
String name = reader.readName();
Function<BsonReader, Object> valueReader = fieldReaders.get(name);
Object value = valueReader.apply(reader);
BiConsumer<R, Object> setter = fieldSetters.get(name);
setter.accept(instance, value);
}
reader.readEndDocument();
return instance;
};
}
private void writeNullableValue(BsonWriter writer, Object value, Runnable wrapped) {
if (value == null) {
writer.writeNull();
} else {
wrapped.run();
}
}
private <R> BiConsumer<BsonWriter, Object> getArrayWriter(Class<R> type) {
return (writer, value) -> {
writer.writeStartArray();
Class<?> elementType = type.getComponentType();
BiConsumer<BsonWriter, Object> elementWriter = getTypeWriter(elementType);
Function<Integer, Object> getByIndex;
if (elementType == boolean.class) {
getByIndex = index -> Array.getBoolean(value, index);
} else if (elementType == byte.class) {
getByIndex = index -> Array.getByte(value, index);
} else if (elementType == short.class) {
getByIndex = index -> Array.getShort(value, index);
} else if (elementType == int.class) {
getByIndex = index -> Array.getInt(value, index);
} else if (elementType == long.class) {
getByIndex = index -> Array.getLong(value, index);
} else if (elementType == float.class) {
getByIndex = index -> Array.getFloat(value, index);
} else if (elementType == double.class) {
getByIndex = index -> Array.getDouble(value, index);
} else {
getByIndex = index -> Array.get(value, index);
}
int length = Array.getLength(value);
for (int i = 0; i < length; i++) {
elementWriter.accept(writer, getByIndex.apply(i));
}
writer.writeEndArray();
};
}
@SuppressWarnings("unchecked")
public <R> BiConsumer<BsonWriter, Object> getTypeWriter(Class<R> type) {
if (type == boolean.class) {
return (writer, value) -> writer.writeBoolean((boolean) value);
} else if (type == Boolean.class) {
return (writer, value) -> writeNullableValue(writer, value, () -> writer.writeBoolean((Boolean) value));
} else if (type == byte.class) {
return (writer, value) -> writer.writeInt32((byte) value);
} else if (type == Byte.class) {
return (writer, value) -> writeNullableValue(writer, value, () -> writer.writeInt32((Byte) value));
} else if (type == short.class) {
return (writer, value) -> writer.writeInt32((short) value);
} else if (type == Short.class) {
return (writer, value) -> writeNullableValue(writer, value, () -> writer.writeInt32((Short) value));
} else if (type == int.class) {
return (writer, value) -> writer.writeInt32((int) value);
} else if (type == Integer.class) {
return (writer, value) -> writeNullableValue(writer, value, () -> writer.writeInt32((Integer) value));
} else if (type == long.class) {
return (writer, value) -> writer.writeInt64((long) value);
} else if (type == Long.class) {
return (writer, value) -> writeNullableValue(writer, value, () -> writer.writeInt64((Long) value));
} else if (type == float.class) {
return (writer, value) -> writer.writeDouble((float) value);
} else if (type == Float.class) {
return (writer, value) -> writeNullableValue(writer, value, () -> writer.writeDouble((Float) value));
} else if (type == double.class) {
return (writer, value) -> writer.writeDouble((double) value);
} else if (type == Double.class) {
return (writer, value) -> writeNullableValue(writer, value, () -> writer.writeDouble((Double) value));
} else if (type == String.class) {
return (writer, value) -> writeNullableValue(writer, value, () -> writer.writeString((String) value));
} else if (type.isArray()) {
return (writer, value) -> writeNullableValue(writer, value, () -> getArrayWriter(type).accept(writer, value));
} else {
return (writer, value) -> writeNullableValue(writer, value, () -> getClassWriter(type).accept(writer, (R) value));
}
}
public <R> BiConsumer<BsonWriter, R> getClassWriter(Class<R> entityClass) {
Map<String, Function<R, Object>> fieldGetters = new LinkedHashMap<>();
Map<String, BiConsumer<BsonWriter, Object>> fieldWriters = new LinkedHashMap<>();
FieldAccess fieldAccess = FieldAccess.get(entityClass);
String[] fieldNames = fieldAccess.getFieldNames();
Class<?>[] fieldTypes = fieldAccess.getFieldTypes();
for (int i = 0; i < fieldNames.length; i++) {
String name = fieldNames[i];
try {
Field field = entityClass.getDeclaredField(name);
if ((field.getModifiers() & Modifier.TRANSIENT) == Modifier.TRANSIENT) {
continue;
}
} catch (NoSuchFieldException e) {
throw sneaky(e);
}
int index = fieldAccess.getIndex(name);
fieldGetters.put(name, instance -> fieldAccess.get(instance, index));
Class<?> type = fieldTypes[i];
fieldWriters.put(name, getTypeWriter(type));
}
return (writer, instance) -> {
writer.writeStartDocument();
for (Map.Entry<String, BiConsumer<BsonWriter, Object>> entry : fieldWriters.entrySet()) {
String name = entry.getKey();
writer.writeName(name);
BiConsumer<BsonWriter, Object> fieldWriter = entry.getValue();
Object value = fieldGetters.get(name).apply(instance);
fieldWriter.accept(writer, value);
}
writer.writeEndDocument();
};
}
public ObjectCodec(Class<T> entityClass) {
this.entityClass = entityClass;
entityReader = getClassReader(entityClass);
entityWriter = getClassWriter(entityClass);
}
@Override
public void encode(BsonWriter writer, T instance, EncoderContext encoderContext) {
entityWriter.accept(writer, instance);
}
@Override
public T decode(BsonReader reader, DecoderContext decoderContext) {
return entityReader.apply(reader);
}
@Override
public Class<T> getEncoderClass() {
return entityClass;
}
}
@trueinsider
Copy link
Author

Use like this:
MongoClients.create().getDatabase("database").getCollection("collection", SomeClass.class).withCodecRegistry(CodecRegistries.fromCodecs(new ObjectCodec<>(SomeClass.class)))

@gdrte
Copy link

gdrte commented Mar 26, 2016

This doesn't work, if fields are declared private. So the expectation is always work with mutable classes?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment