perf[orm]: map key codec

This commit is contained in:
godotg
2024-06-14 17:49:04 +08:00
parent 5446f8e452
commit 6b6e653635
5 changed files with 144 additions and 123 deletions
@@ -0,0 +1,64 @@
package com.zfoo.orm.codec;
import org.bson.*;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import java.util.HashMap;
import java.util.Map;
/**
* map解析器
*
* @Authorlqh
* @Date2024/6/14 10:32
*/
public class IntMapCodec<V> implements Codec<Map<Integer, V>> {
private final Class<Map<Integer, V>> encoderClass;
private final Codec<Integer> keyCodec;
private final Codec<V> valueCodec;
IntMapCodec(final Class<Map<Integer, V>> encoderClass, final Codec<Integer> keyCodec, final Codec<V> valueCodec) {
this.encoderClass = encoderClass;
this.keyCodec = keyCodec;
this.valueCodec = valueCodec;
}
@Override
public void encode(final BsonWriter writer, final Map<Integer, V> map, final EncoderContext encoderContext) {
try (var dummyWriter = new BsonDocumentWriter(new BsonDocument())) {
dummyWriter.writeStartDocument();
writer.writeStartDocument();
for (final Map.Entry<Integer, V> entry : map.entrySet()) {
writer.writeName(entry.getKey().toString());
valueCodec.encode(writer, entry.getValue(), encoderContext);
}
dummyWriter.writeEndDocument();
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
writer.writeEndDocument();
}
@Override
public Map<Integer, V> decode(final BsonReader reader, final DecoderContext context) {
reader.readStartDocument();
Map<Integer, V> map = new HashMap<>();
while (!BsonType.END_OF_DOCUMENT.equals(reader.readBsonType())) {
int key = java.lang.Integer.parseInt(reader.readName());
V value = BsonType.NULL.equals(reader.getCurrentBsonType()) ? null : valueCodec.decode(reader, context);
map.put(key, value);
}
reader.readEndDocument();
return map;
}
@Override
public Class<Map<Integer, V>> getEncoderClass() {
return encoderClass;
}
}
@@ -0,0 +1,64 @@
package com.zfoo.orm.codec;
import org.bson.*;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import java.util.HashMap;
import java.util.Map;
/**
* map解析器
*
* @Authorlqh
* @Date2024/6/14 10:32
*/
public class LongMapCodec<V> implements Codec<Map<Long, V>> {
private final Class<Map<Long, V>> encoderClass;
private final Codec<Long> keyCodec;
private final Codec<V> valueCodec;
LongMapCodec(final Class<Map<Long, V>> encoderClass, final Codec<Long> keyCodec, final Codec<V> valueCodec) {
this.encoderClass = encoderClass;
this.keyCodec = keyCodec;
this.valueCodec = valueCodec;
}
@Override
public void encode(final BsonWriter writer, final Map<Long, V> map, final EncoderContext encoderContext) {
try (var dummyWriter = new BsonDocumentWriter(new BsonDocument())) {
dummyWriter.writeStartDocument();
writer.writeStartDocument();
for (final Map.Entry<Long, V> entry : map.entrySet()) {
writer.writeName(entry.getKey().toString());
valueCodec.encode(writer, entry.getValue(), encoderContext);
}
dummyWriter.writeEndDocument();
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
writer.writeEndDocument();
}
@Override
public Map<Long, V> decode(final BsonReader reader, final DecoderContext context) {
reader.readStartDocument();
Map<Long, V> map = new HashMap<>();
while (!BsonType.END_OF_DOCUMENT.equals(reader.readBsonType())) {
long key = Long.parseLong(reader.readName());
V value = BsonType.NULL.equals(reader.getCurrentBsonType()) ? null : valueCodec.decode(reader, context);
map.put(key, value);
}
reader.readEndDocument();
return map;
}
@Override
public Class<Map<Long, V>> getEncoderClass() {
return encoderClass;
}
}
@@ -1,117 +0,0 @@
package com.zfoo.orm.codec;
import org.bson.*;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import org.bson.codecs.configuration.CodecConfigurationException;
import org.bson.json.JsonReader;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* map解析器
* @Authorlqh
* @Date2024/6/14 10:32
*/
public class MapCodec<K, T> implements Codec<Map<K, T>> {
private static final Logger LOGGER = Logger.getLogger(MapCodec.class.getName());
private final Class<Map<K, T>> encoderClass;
private final Codec<K> keyCodec;
private final Codec<T> valueCodec;
MapCodec(final Class<Map<K, T>> encoderClass, final Codec<K> keyCodec, final Codec<T> valueCodec,
Map<Class<?>, Class<? extends PropertyEditor>> map) {
this.encoderClass = encoderClass;
this.keyCodec = keyCodec;
this.valueCodec = valueCodec;
map.forEach(PropertyEditorManager::registerEditor);
}
MapCodec(final Class<Map<K, T>> encoderClass, final Codec<K> keyCodec, final Codec<T> valueCodec) {
this.encoderClass = encoderClass;
this.keyCodec = keyCodec;
this.valueCodec = valueCodec;
}
@Override
public void encode(final BsonWriter writer, final Map<K, T> map, final EncoderContext encoderContext) {
try (var dummyWriter = new BsonDocumentWriter(new BsonDocument())) {
dummyWriter.writeStartDocument();
writer.writeStartDocument();
for (final Map.Entry<K, T> entry : map.entrySet()) {
PropertyEditor editor = PropertyEditorManager.findEditor(keyCodec.getEncoderClass());
if (editor != null) {
LOGGER.fine("Found PropertyEditor for class: " + keyCodec.getEncoderClass().getName());
editor.setValue(entry.getKey());
writer.writeName(editor.getAsText());
} else {
String dummyId = UUID.randomUUID().toString();
dummyWriter.writeName(dummyId);
keyCodec.encode(dummyWriter, entry.getKey(), encoderContext);
writer.writeName(dummyWriter.getDocument().asDocument().get(dummyId).asString().getValue());
}
valueCodec.encode(writer, entry.getValue(), encoderContext);
}
dummyWriter.writeEndDocument();
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Failed to encode map: " + map, e);
throw new IllegalArgumentException(e);
}
writer.writeEndDocument();
}
@Override
@SuppressWarnings("unchecked")
public Map<K, T> decode(final BsonReader reader, final DecoderContext context) {
reader.readStartDocument();
Map<K, T> map = getInstance();
while (!BsonType.END_OF_DOCUMENT.equals(reader.readBsonType())) {
K key;
PropertyEditor editor = PropertyEditorManager.findEditor(keyCodec.getEncoderClass());
if (editor != null) {
LOGGER.fine("Found PropertyEditor for class: " + keyCodec.getEncoderClass().getName());
editor.setAsText(reader.readName());
key = (K) editor.getValue();
} else {
var dummyReader = new JsonReader(String.format("\"key\": \"%s\"", reader.readName()));
key = keyCodec.decode(dummyReader, context);
}
map.put(key,
(!BsonType.NULL.equals(reader.getCurrentBsonType()))
? valueCodec.decode(reader, context)
: null);
}
reader.readEndDocument();
return map;
}
@Override
public Class<Map<K, T>> getEncoderClass() {
return encoderClass;
}
private Map<K, T> getInstance() {
if (encoderClass.isInterface()) {
return new HashMap<>();
}
try {
return encoderClass.getDeclaredConstructor().newInstance();
} catch (final Exception e) {
throw new CodecConfigurationException(e.getMessage(), e);
}
}
}
@@ -8,7 +8,6 @@ import org.bson.codecs.pojo.TypeWithTypeParameters;
import java.util.Map;
/**
*
* @Authorlqh
* @Date2024/6/14 10:23
*/
@@ -16,11 +15,18 @@ public class MapCodecProvider implements PropertyCodecProvider {
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public <T> Codec<T> get(final TypeWithTypeParameters<T> type, final PropertyCodecRegistry registry) {
if (Map.class.isAssignableFrom(type.getType()) && type.getTypeParameters().size() == 2) {
return new MapCodec(
type.getType(),
registry.get(type.getTypeParameters().get(0)),
registry.get(type.getTypeParameters().get(1)));
if (!Map.class.isAssignableFrom(type.getType())) {
return null;
}
var typeParameters = type.getTypeParameters();
if (type.getTypeParameters().size() != 2) {
return null;
}
if (typeParameters.get(0).getType() == Integer.class) {
return new IntMapCodec(type.getType(), registry.get(type.getTypeParameters().get(0)), registry.get(type.getTypeParameters().get(1)));
}
if (typeParameters.get(0).getType() == Long.class) {
return new LongMapCodec(type.getType(), registry.get(type.getTypeParameters().get(0)), registry.get(type.getTypeParameters().get(1)));
}
return null;
}
@@ -85,6 +85,10 @@ public class MapTest {
OrmContext.getAccessor().insert(entity);
var myEntity = OrmContext.getAccessor().load(1, MapEntity.class);
Assert.assertEquals(entity.getBagMap(), myEntity.getBagMap());
Assert.assertEquals(entity.getBaseMap(), myEntity.getBaseMap());
Assert.assertEquals(entity.getIntStringMap(), myEntity.getIntStringMap());
Assert.assertEquals(entity.getIntBagMap(), myEntity.getIntBagMap());
Assert.assertEquals(entity, myEntity);
}
}