Skip to content

Commit b21627b

Browse files
committed
Ability to set custom map type
1 parent cc44f07 commit b21627b

File tree

4 files changed

+140
-45
lines changed

4 files changed

+140
-45
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (C) 2023 Elytrium
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package net.elytrium.serializer.annotations;
19+
20+
import java.lang.annotation.Documented;
21+
import java.lang.annotation.ElementType;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
import java.util.Map;
26+
27+
/**
28+
* Changes map type
29+
*/
30+
@SuppressWarnings("rawtypes")
31+
@Documented
32+
@Retention(RetentionPolicy.RUNTIME)
33+
@Target({ElementType.FIELD})
34+
public @interface MapType {
35+
36+
/**
37+
* @return Class that will be initialized when the Map field is read. (e.g. HashMap.class)
38+
*/
39+
Class<? extends Map> value();
40+
}

src/main/java/net/elytrium/serializer/language/reader/AbstractReader.java

Lines changed: 86 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package net.elytrium.serializer.language.reader;
1919

20+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
2021
import java.io.BufferedReader;
2122
import java.io.IOException;
2223
import java.lang.reflect.Constructor;
@@ -29,6 +30,7 @@
2930
import java.util.Collection;
3031
import java.util.Deque;
3132
import java.util.HashSet;
33+
import java.util.LinkedHashMap;
3234
import java.util.List;
3335
import java.util.Map;
3436
import java.util.Queue;
@@ -38,6 +40,7 @@
3840
import javax.annotation.Nullable;
3941
import net.elytrium.serializer.SerializerConfig;
4042
import net.elytrium.serializer.annotations.CollectionType;
43+
import net.elytrium.serializer.annotations.MapType;
4144
import net.elytrium.serializer.annotations.Serializer;
4245
import net.elytrium.serializer.custom.ClassSerializer;
4346
import net.elytrium.serializer.exceptions.ReflectionException;
@@ -203,42 +206,9 @@ public Object readByType(@Nullable Field owner, @Nullable Object holder, Type ty
203206
} else if (type instanceof ParameterizedType parameterizedType) {
204207
Class<?> clazz = (Class<?>) parameterizedType.getRawType();
205208
if (Map.class.isAssignableFrom(clazz)) {
206-
return this.readMap(owner,
207-
GenericUtils.getParameterType(Map.class, parameterizedType, 0),
208-
GenericUtils.getParameterType(Map.class, parameterizedType, 1));
209+
return this.readMapByType(owner, parameterizedType);
209210
} else if (Collection.class.isAssignableFrom(clazz)) {
210-
Type collectionEntryType = GenericUtils.getParameterType(Collection.class, parameterizedType, 0);
211-
if (owner != null) {
212-
CollectionType collectionType = owner.getAnnotation(CollectionType.class);
213-
if (collectionType != null) {
214-
try {
215-
//noinspection unchecked
216-
return this.readCollection(owner,
217-
(Collection<Object>) collectionType.value().getDeclaredConstructor().newInstance(),
218-
collectionEntryType);
219-
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
220-
throw new SerializableReadException(e);
221-
}
222-
} else {
223-
try {
224-
//noinspection unchecked
225-
return this.readCollection(owner,
226-
(Collection<Object>) owner.getType().getDeclaredConstructor().newInstance(),
227-
collectionEntryType);
228-
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
229-
throw new SerializableReadException(e);
230-
} catch (NoSuchMethodException e) {
231-
// Ignoring NoSuchMethod.
232-
}
233-
}
234-
}
235-
if (Set.class.isAssignableFrom(clazz)) {
236-
return this.readSet(collectionEntryType);
237-
} else if (Queue.class.isAssignableFrom(clazz)) {
238-
return this.readDeque(collectionEntryType);
239-
} else {
240-
return this.readList(collectionEntryType);
241-
}
211+
return this.readCollectionByType(owner, parameterizedType, clazz);
242212
} else {
243213
return this.readGuessingType(owner);
244214
}
@@ -286,6 +256,72 @@ public Object readByType(@Nullable Field owner, @Nullable Object holder, Type ty
286256
}
287257
}
288258

259+
@SuppressFBWarnings("NP_LOAD_OF_KNOWN_NULL_VALUE")
260+
private Collection<Object> readCollectionByType(Field owner, ParameterizedType parameterizedType, Class<?> clazz) {
261+
Type collectionEntryType = GenericUtils.getParameterType(Collection.class, parameterizedType, 0);
262+
if (owner != null) {
263+
CollectionType collectionType = owner.getAnnotation(CollectionType.class);
264+
if (collectionType != null) {
265+
try {
266+
//noinspection unchecked
267+
return this.readCollection(owner,
268+
(Collection<Object>) collectionType.value().getDeclaredConstructor().newInstance(),
269+
collectionEntryType);
270+
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
271+
throw new SerializableReadException(e);
272+
}
273+
} else {
274+
try {
275+
//noinspection unchecked
276+
return this.readCollection(owner,
277+
(Collection<Object>) owner.getType().getDeclaredConstructor().newInstance(), collectionEntryType);
278+
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
279+
throw new SerializableReadException(e);
280+
} catch (NoSuchMethodException e) {
281+
// Ignoring NoSuchMethod.
282+
}
283+
}
284+
}
285+
286+
if (Set.class.isAssignableFrom(clazz)) {
287+
return this.readSet(owner, collectionEntryType);
288+
} else if (Queue.class.isAssignableFrom(clazz)) {
289+
return this.readDeque(owner, collectionEntryType);
290+
} else {
291+
return this.readList(owner, collectionEntryType);
292+
}
293+
}
294+
295+
@SuppressFBWarnings("NP_LOAD_OF_KNOWN_NULL_VALUE")
296+
private Map<Object, Object> readMapByType(Field owner, ParameterizedType parameterizedType) {
297+
Type mapKeyType = GenericUtils.getParameterType(Map.class, parameterizedType, 0);
298+
Type mapValueType = GenericUtils.getParameterType(Map.class, parameterizedType, 1);
299+
if (owner != null) {
300+
MapType mapType = owner.getAnnotation(MapType.class);
301+
if (mapType != null) {
302+
try {
303+
//noinspection unchecked
304+
return this.readMap(owner,
305+
(Map<Object, Object>) mapType.value().getDeclaredConstructor().newInstance(), mapKeyType, mapValueType);
306+
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
307+
throw new SerializableReadException(e);
308+
}
309+
} else {
310+
try {
311+
//noinspection unchecked
312+
return this.readMap(owner,
313+
(Map<Object, Object>) owner.getType().getDeclaredConstructor().newInstance(), mapKeyType, mapValueType);
314+
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
315+
throw new SerializableReadException(e);
316+
} catch (NoSuchMethodException e) {
317+
// Ignoring NoSuchMethod.
318+
}
319+
}
320+
}
321+
322+
return this.readMap(owner, mapKeyType, mapValueType);
323+
}
324+
289325
public Object readGuessingType() {
290326
return this.readGuessingType(null);
291327
}
@@ -297,10 +333,22 @@ public Map<Object, Object> readMap(@Nullable Field owner) {
297333
}
298334

299335
public Map<Object, Object> readMap(Type keyType, Type valueType) {
300-
return this.readMap(null, keyType, valueType);
336+
return this.readMap((Field) null, keyType, valueType);
301337
}
302-
303-
public abstract Map<Object, Object> readMap(@Nullable Field owner, Type keyType, Type valueType);
338+
339+
public Map<Object, Object> readMap(@Nullable Field owner, Type keyType, Type valueType) {
340+
return this.readMap(owner, new LinkedHashMap<>(), keyType, valueType);
341+
}
342+
343+
public <C extends Map<Object, Object>> C readMap(@Nullable Field owner, C result) {
344+
return this.readMap(owner, result, Object.class, Object.class);
345+
}
346+
347+
public <C extends Map<Object, Object>> C readMap(C result, Type keyType, Type valueType) {
348+
return this.readMap(null, result, keyType, valueType);
349+
}
350+
351+
public abstract <C extends Map<Object, Object>> C readMap(@Nullable Field owner, C result, Type keyType, Type valueType);
304352

305353
public <C extends Collection<Object>> C readCollection(@Nullable Field owner, C result) {
306354
return this.readCollection(owner, result, Object.class);

src/main/java/net/elytrium/serializer/language/reader/YamlReader.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -250,15 +250,15 @@ public Object readGuessingType(@Nullable Field owner) {
250250
}
251251

252252
@Override
253-
public Map<Object, Object> readMap(@Nullable Field owner, Type keyType, Type valueType) {
253+
public <C extends Map<Object, Object>> C readMap(@Nullable Field owner, C result, Type keyType, Type valueType) {
254254
boolean startOfFile = this.startOfFile;
255255
synchronized (this) {
256256
char marker = this.readRawIgnoreEmpty();
257257
if (startOfFile) {
258258
this.setTempRestoreNewLine();
259259
}
260260

261-
return this.readMapByMarker(owner, keyType, valueType, marker);
261+
return this.readMapByMarker(owner, result, keyType, valueType, marker);
262262
}
263263
}
264264

@@ -453,9 +453,12 @@ private <C extends Collection<Object>> C readCollectionByMarker(@Nullable Field
453453
return result;
454454
}
455455

456+
private Map<Object, Object> readMapByMarker(@Nullable Field owner, char marker) {
457+
return this.readMapByMarker(owner, new LinkedHashMap<>(), Object.class, Object.class, marker);
458+
}
459+
456460
@SuppressFBWarnings("SA_FIELD_SELF_COMPARISON")
457-
private Map<Object, Object> readMapByMarker(@Nullable Field owner, Type keyType, Type valueType, char marker) {
458-
Map<Object, Object> result = new LinkedHashMap<>();
461+
private <C extends Map<Object, Object>> C readMapByMarker(@Nullable Field owner, C result, Type keyType, Type valueType, char marker) {
459462
if (this.skipComments(owner, marker, false)) {
460463
marker = AbstractReader.NEW_LINE;
461464
}
@@ -590,7 +593,7 @@ private Object readGuessingTypeByMarker(@Nullable Field owner, char marker) {
590593
case AbstractReader.NEW_LINE -> {
591594
char nextMarker = this.readRawIgnoreEmpty();
592595
this.setReuseBuffer();
593-
yield nextMarker == '-' ? this.readListByMarker(owner, marker) : this.readMapByMarker(owner, Object.class, Object.class, marker);
596+
yield nextMarker == '-' ? this.readListByMarker(owner, marker) : this.readMapByMarker(owner, marker);
594597
}
595598
case '-' -> {
596599
this.setReuseBuffer();
@@ -612,7 +615,7 @@ private Object readGuessingTypeByMarker(@Nullable Field owner, char marker) {
612615
yield this.readListByMarker(owner, AbstractReader.NEW_LINE);
613616
}
614617
case '[' -> this.readListByMarker(owner, marker);
615-
case '{' -> this.readMapByMarker(owner, Object.class, Object.class, marker);
618+
case '{' -> this.readMapByMarker(owner, marker);
616619
case '"', '\'', '>', '|' -> this.readStringFromMarker(owner, marker, false);
617620
default -> {
618621
if (this.isNullSkippedByMarker(marker)) {
@@ -628,7 +631,7 @@ private Object readGuessingTypeByMarker(@Nullable Field owner, char marker) {
628631
if (string.endsWith(":") || string.endsWith(": ") || string.contains(": ")) {
629632
this.unsetSeek();
630633
this.unsetTempRestoreNewLine();
631-
yield this.readMapByMarker(owner, Object.class, Object.class, AbstractReader.NEW_LINE);
634+
yield this.readMapByMarker(owner, AbstractReader.NEW_LINE);
632635
} else {
633636
this.clearSeek();
634637
try {

src/test/java/net/elytrium/serializer/SerializerTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.Collection;
3333
import java.util.Date;
3434
import java.util.Deque;
35+
import java.util.HashMap;
3536
import java.util.HashSet;
3637
import java.util.LinkedHashMap;
3738
import java.util.List;
@@ -40,6 +41,7 @@
4041
import net.elytrium.serializer.annotations.Comment;
4142
import net.elytrium.serializer.annotations.CommentValue;
4243
import net.elytrium.serializer.annotations.Final;
44+
import net.elytrium.serializer.annotations.MapType;
4345
import net.elytrium.serializer.annotations.NewLine;
4446
import net.elytrium.serializer.annotations.OverrideNameStyle;
4547
import net.elytrium.serializer.annotations.RegisterPlaceholders;
@@ -137,6 +139,7 @@ void testConfig() throws IOException {
137139
Assertions.assertEquals(3, settings.listOfString2ObjectMap.size());
138140
Assertions.assertEquals(3, settings.chaosMapList.size());
139141
Assertions.assertEquals(2, settings.chaosMap.size());
142+
Assertions.assertEquals(HashMap.class, settings.int2StringMap.getClass());
140143

141144
settings.int2StringMap.forEach((key, value) -> {
142145
Assertions.assertEquals(Integer.class, key.getClass());
@@ -326,6 +329,7 @@ public String deserialize(String from) {
326329
@RegisterPlaceholders(value = {"PLACEHOLDER", "another-placeholder"}, wrapWithBraces = false)
327330
public String anotherStringWithPlaceholders = "PLACEHOLDER another-placeholder";
328331

332+
@MapType(HashMap.class)
329333
public Map<Integer, String> int2StringMap = SerializerTest.map(1, "v1", 15555, "v2", 44, "v3");
330334

331335
public final List<Object> objectListWithMaps = Arrays.asList(

0 commit comments

Comments
 (0)