Skip to content

Commit

Permalink
Add support for EnumMaps
Browse files Browse the repository at this point in the history
  • Loading branch information
TBlueF committed Mar 12, 2024
1 parent 7cf3f05 commit a72bcb1
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 38 deletions.
48 changes: 48 additions & 0 deletions src/main/java/de/bluecolored/bluenbt/BlueNBT.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ public <T> TypeSerializer<T> getTypeSerializer(TypeToken<T> type) {
TypeSerializer<T> serializer = (TypeSerializer<T>) typeSerializerMap.get(type);

if (serializer == null) {
FutureTypeSerializer<T> future = new FutureTypeSerializer<>();
typeSerializerMap.put(type, future); // set future before creation of new serializers to avoid recursive creation

for (int i = serializerFactories.size() - 1; i >= 0; i--) {
TypeSerializerFactory factory = serializerFactories.get(i);
serializer = factory.create(type, this).orElse(null);
Expand All @@ -132,6 +135,7 @@ public <T> TypeSerializer<T> getTypeSerializer(TypeToken<T> type) {
if (serializer == null)
serializer = DefaultSerializerFactory.INSTANCE.createFor(type, this);

future.complete(serializer);
typeSerializerMap.put(type, serializer);
}

Expand All @@ -143,6 +147,9 @@ public <T> TypeDeserializer<T> getTypeDeserializer(TypeToken<T> type) {
TypeDeserializer<T> deserializer = (TypeDeserializer<T>) typeDeserializerMap.get(type);

if (deserializer == null) {
FutureTypeDeserializer<T> future = new FutureTypeDeserializer<>();
typeDeserializerMap.put(type, future); // set future before creation of new deserializers to avoid recursive creation

for (int i = deserializerFactories.size() - 1; i >= 0; i--) {
TypeDeserializerFactory factory = deserializerFactories.get(i);
deserializer = factory.create(type, this).orElse(null);
Expand All @@ -152,6 +159,7 @@ public <T> TypeDeserializer<T> getTypeDeserializer(TypeToken<T> type) {
if (deserializer == null)
deserializer = DefaultDeserializerFactory.INSTANCE.createFor(type, this);

future.complete(deserializer);
typeDeserializerMap.put(type, deserializer);
}

Expand Down Expand Up @@ -212,4 +220,44 @@ public <T> ObjectConstructor<T> createObjectConstructor(TypeToken<T> type) {
return constructorConstructor.get(type);
}

private static class FutureTypeSerializer<T> implements TypeSerializer<T> {

private TypeSerializer<T> value;

public void complete(TypeSerializer<T> value) {
if (this.value != null) throw new IllegalStateException("FutureTypeSerializer already completed!");
this.value = Objects.requireNonNull(value);
}

@Override
public void write(T value, NBTWriter writer) throws IOException {
if (this.value == null) throw new IllegalStateException("FutureTypeSerializer not completed!");
this.value.write(value, writer);
}

@Override
public TagType type() {
if (this.value == null) throw new IllegalStateException("FutureTypeSerializer is not ready!");
return this.value.type();
}

}

private static class FutureTypeDeserializer<T> implements TypeDeserializer<T> {

private TypeDeserializer<T> value;

public void complete(TypeDeserializer<T> value) {
if (this.value != null) throw new IllegalStateException("FutureTypeDeserializer already completed!");
this.value = Objects.requireNonNull(value);
}

@Override
public T read(NBTReader reader) throws IOException {
if (this.value == null) throw new IllegalStateException("FutureTypeDeserializer is not ready!");
return this.value.read(reader);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ public <T> Optional<TypeDeserializer<T>> create(TypeToken<T> type, BlueNBT blueN
}

public <T> TypeDeserializer<T> createFor(TypeToken<T> type, BlueNBT blueNBT) {
return new DefaultAdapter<>(type, blueNBT.createObjectConstructor(type), blueNBT);
try {
return new DefaultAdapter<>(type, blueNBT.createObjectConstructor(type), blueNBT);
} catch (Exception ex) {
throw new RuntimeException("Failed to create Default-TypeSerializer for type: " + type, ex);
}
}

static class DefaultAdapter<T> implements TypeDeserializer<T> {
Expand Down
50 changes: 39 additions & 11 deletions src/main/java/de/bluecolored/bluenbt/adapter/MapAdapterFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@

import java.io.IOException;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.EnumMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

public class MapAdapterFactory implements TypeAdapterFactory {

Expand All @@ -48,32 +51,56 @@ public <T> Optional<TypeAdapter<T>> create(TypeToken<T> typeToken, BlueNBT blueN

Type[] keyAndValueTypes = TypeUtil.getMapKeyAndValueTypes(type, rawType);

// only String keys are supported
if (!String.class.equals(keyAndValueTypes[0])) return Optional.empty();
Function<?, String> toStringFunction;
Function<String, ?> fromStringFunction;
Class<?> keyType = TypeUtil.getRawType(keyAndValueTypes[0]);
if (String.class.equals(keyType)) {
toStringFunction = Function.identity();
fromStringFunction = Function.identity();
} else if (Enum.class.isAssignableFrom(keyType)) {
toStringFunction = (Function<Enum<?>, String>) Enum::name;
//noinspection unchecked, rawtypes
fromStringFunction = (Function<String, Enum>) name -> Enum.valueOf((Class<? extends Enum>) keyAndValueTypes[0], name);
} else {
// key-type not supported
return Optional.empty();
}

TypeToken<?> valueType = TypeToken.get(keyAndValueTypes[1]);
TypeSerializer<?> elementTypeSerializer = blueNBT.getTypeSerializer(valueType);
TypeDeserializer<?> elementTypeDeserializer = blueNBT.getTypeDeserializer(valueType);

ObjectConstructor<T> constructor = blueNBT.createObjectConstructor(typeToken);

@SuppressWarnings({"unchecked", "rawtypes"})
TypeAdapter<T> result = new MapAdapter(elementTypeSerializer, elementTypeDeserializer, constructor);
TypeAdapter<T> result = new MapAdapter(
toStringFunction,
fromStringFunction,
elementTypeSerializer,
elementTypeDeserializer,
constructor
);
return Optional.of(result);
}

@RequiredArgsConstructor
static class MapAdapter<E> implements TypeAdapter<Map<String, E>> {
static class MapAdapter<K, E> implements TypeAdapter<Map<K, E>> {

private final Function<K, String> toStringFunction;
private final Function<String, K> fromStringFunction;

private final TypeSerializer<E> typeSerializer;
private final TypeDeserializer<E> typeDeserializer;
private final ObjectConstructor<? extends Map<String, E>> constructor;

private final ObjectConstructor<? extends Map<K, E>> constructor;

@Override
public Map<String, E> read(NBTReader reader) throws IOException {
Map<String, E> map = constructor.construct();
public Map<K, E> read(NBTReader reader) throws IOException {
Map<K, E> map = constructor.construct();
reader.beginCompound();
while (reader.hasNext()) {
String key = reader.name();
String keyString = reader.name();
K key = fromStringFunction.apply(keyString);
E instance = typeDeserializer.read(reader);
map.put(key, instance);
}
Expand All @@ -82,10 +109,11 @@ public Map<String, E> read(NBTReader reader) throws IOException {
}

@Override
public void write(Map<String, E> value, NBTWriter writer) throws IOException {
public void write(Map<K, E> value, NBTWriter writer) throws IOException {
writer.beginCompound();
for (Map.Entry<String, E> entry : value.entrySet()) {
writer.name(entry.getKey());
for (Map.Entry<K, E> entry : value.entrySet()) {
String keyString = toStringFunction.apply(entry.getKey());
writer.name(keyString);
typeSerializer.write(entry.getValue(), writer);
}
writer.endCompound();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,7 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap;
Expand Down Expand Up @@ -149,18 +137,23 @@ private <T> ObjectConstructor<T> newDefaultImplementationConstructor(
}

if (Map.class.isAssignableFrom(rawType)) {

if (ConcurrentNavigableMap.class.isAssignableFrom(rawType)) {
return () -> (T) new ConcurrentSkipListMap<>();
} else if (ConcurrentMap.class.isAssignableFrom(rawType)) {
return () -> (T) new ConcurrentHashMap<>();
} else if (SortedMap.class.isAssignableFrom(rawType)) {
return () -> (T) new TreeMap<>();
} else if (type instanceof ParameterizedType && !(String.class.isAssignableFrom(
TypeToken.get(((ParameterizedType) type).getActualTypeArguments()[0]).getRawType()))) {
return () -> (T) new LinkedHashMap<>();
} else {
return () -> (T) new LinkedTreeMap<String, Object>();
} else if (type instanceof ParameterizedType) {
Class<?> keyType = TypeToken.get(((ParameterizedType) type).getActualTypeArguments()[0]).getRawType();
if (EnumMap.class.isAssignableFrom(rawType)) {
return () -> (T) new EnumMap(keyType);
} else if (String.class.isAssignableFrom(keyType)) {
return () -> (T) new LinkedTreeMap<String, Object>();
}
}

return () -> (T) new LinkedHashMap<>();
}

return null;
Expand All @@ -179,7 +172,7 @@ public T construct() {
return (T) newInstance;
} catch (Exception e) {
throw new RuntimeException(("Unable to invoke no-args constructor for " + type + ". "
+ "Register an InstanceCreator with Gson for this type may fix this problem."), e);
+ "Register an InstanceCreator with BlueNBT for this type may fix this problem."), e);
}
}
};
Expand Down
39 changes: 32 additions & 7 deletions src/test/java/de/bluecolored/bluenbt/BlueNBTTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,11 @@
import net.querz.nbt.mca.CompressionType;
import org.junit.jupiter.api.Test;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.zip.GZIPInputStream;

import static org.junit.jupiter.api.Assertions.assertEquals;
Expand Down Expand Up @@ -131,6 +126,30 @@ public void testArrays() throws IOException {

}

@Test
public void testEnumMap() throws IOException {
EnumMap<TestEnum, String> testMap = new EnumMap<>(TestEnum.class);
testMap.put(TestEnum.SOME_TEST, "someTestValue");
testMap.put(TestEnum.TEST1, "test1Value");
testMap.put(TestEnum.ABC, "abcValue");

BlueNBT blueNBT = new BlueNBT();

byte[] data;
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
blueNBT.write(testMap, out, new TypeToken<>() {});
data = out.toByteArray();
}

EnumMap<TestEnum, String> resultMap;
try (ByteArrayInputStream in = new ByteArrayInputStream(data)) {
resultMap = blueNBT.read(in, new TypeToken<>() {});
}

assertEquals(testMap, resultMap);

}

@SuppressWarnings({"resource", "SameParameterValue"})
private InputStream loadMcaFileChunk(int chunkX, int chunkZ) throws IOException {
Path regionFile = Files.createTempFile(null, null);
Expand Down Expand Up @@ -163,6 +182,12 @@ private InputStream loadMcaFileChunk(int chunkX, int chunkZ) throws IOException
return compressionType.decompress(new FileInputStream(raf.getFD()));
}

private enum TestEnum {
TEST1,
SOME_TEST,
ABC
}

@SuppressWarnings({"unused", "MismatchedReadAndWriteOfArray"})
private static class PlayerData {

Expand Down

0 comments on commit a72bcb1

Please sign in to comment.