Created
February 23, 2016 01:00
-
-
Save trueinsider/78a7abc8dc0d4ec5810a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
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
Use like this:
MongoClients.create().getDatabase("database").getCollection("collection", SomeClass.class).withCodecRegistry(CodecRegistries.fromCodecs(new ObjectCodec<>(SomeClass.class)))