Skip to content

Commit 169bbdc

Browse files
committed
feat(core): Add TypeSerializer for Optional
(including primitive specializations) Closes #288
1 parent 09777bb commit 169bbdc

3 files changed

Lines changed: 237 additions & 0 deletions

File tree

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/*
2+
* Configurate
3+
* Copyright (C) zml and Configurate contributors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.spongepowered.configurate.serialize;
18+
19+
import io.leangen.geantyref.TypeToken;
20+
import org.checkerframework.checker.nullness.qual.Nullable;
21+
import org.spongepowered.configurate.ConfigurationNode;
22+
import org.spongepowered.configurate.ConfigurationOptions;
23+
24+
import java.lang.reflect.AnnotatedParameterizedType;
25+
import java.lang.reflect.AnnotatedType;
26+
import java.lang.reflect.Type;
27+
import java.util.Optional;
28+
import java.util.OptionalDouble;
29+
import java.util.OptionalInt;
30+
import java.util.OptionalLong;
31+
32+
@SuppressWarnings("checkstyle:IllegalType") // for Optional
33+
final class OptionalSerializer implements TypeSerializer.Annotated<Optional<?>> {
34+
35+
static final TypeToken<Optional<?>> TYPE = new TypeToken<Optional<?>>() {};
36+
static final TypeSerializer<Optional<?>> INSTANCE = new OptionalSerializer();
37+
38+
private OptionalSerializer() {
39+
}
40+
41+
private static AnnotatedType extractParameter(final AnnotatedType optional) throws SerializationException {
42+
if (!(optional instanceof AnnotatedParameterizedType)) {
43+
throw new SerializationException(optional, "Required type parameters on annotated type");
44+
}
45+
46+
return ((AnnotatedParameterizedType) optional).getAnnotatedActualTypeArguments()[0];
47+
}
48+
49+
@Override
50+
public Optional<?> deserialize(final AnnotatedType type, final ConfigurationNode node) throws SerializationException {
51+
if (node.empty()) {
52+
return Optional.empty();
53+
}
54+
return Optional.ofNullable(node.get(extractParameter(type)));
55+
}
56+
57+
@Override
58+
public void serialize(final AnnotatedType type, final @Nullable Optional<?> obj, final ConfigurationNode node) throws SerializationException {
59+
if (obj == null || !obj.isPresent()) {
60+
node.set(null);
61+
return;
62+
}
63+
64+
node.set(extractParameter(type), obj.get());
65+
}
66+
67+
@Override
68+
public Optional<?> emptyValue(final AnnotatedType specificType, final ConfigurationOptions options) {
69+
return Optional.empty();
70+
}
71+
72+
static final class OfInt implements TypeSerializer<OptionalInt> {
73+
74+
static final Class<OptionalInt> TYPE = OptionalInt.class;
75+
static final TypeSerializer<OptionalInt> INSTANCE = new OfInt();
76+
77+
private OfInt() {
78+
}
79+
80+
@Override
81+
public OptionalInt deserialize(final Type type, final ConfigurationNode node) throws SerializationException {
82+
if (node.empty()) {
83+
return OptionalInt.empty();
84+
}
85+
return OptionalInt.of(node.require(int.class));
86+
}
87+
88+
@Override
89+
public void serialize(final Type type, final @Nullable OptionalInt obj, final ConfigurationNode node) throws SerializationException {
90+
if (obj == null || !obj.isPresent()) {
91+
node.set(null);
92+
return;
93+
}
94+
95+
node.set(int.class, obj.getAsInt());
96+
}
97+
98+
@Override
99+
public OptionalInt emptyValue(final AnnotatedType specificType, final ConfigurationOptions options) {
100+
return OptionalInt.empty();
101+
}
102+
103+
}
104+
105+
static final class OfLong implements TypeSerializer<OptionalLong> {
106+
107+
static final Class<OptionalLong> TYPE = OptionalLong.class;
108+
static final TypeSerializer<OptionalLong> INSTANCE = new OfLong();
109+
110+
private OfLong() {
111+
}
112+
113+
@Override
114+
public OptionalLong deserialize(final Type type, final ConfigurationNode node) throws SerializationException {
115+
if (node.empty()) {
116+
return OptionalLong.empty();
117+
}
118+
return OptionalLong.of(node.require(long.class));
119+
}
120+
121+
@Override
122+
public void serialize(final Type type, final @Nullable OptionalLong obj, final ConfigurationNode node) throws SerializationException {
123+
if (obj == null || !obj.isPresent()) {
124+
node.set(null);
125+
return;
126+
}
127+
128+
node.set(long.class, obj.getAsLong());
129+
}
130+
131+
@Override
132+
public OptionalLong emptyValue(final AnnotatedType specificType, final ConfigurationOptions options) {
133+
return OptionalLong.empty();
134+
}
135+
136+
}
137+
138+
static final class OfDouble implements TypeSerializer<OptionalDouble> {
139+
140+
static final Class<OptionalDouble> TYPE = OptionalDouble.class;
141+
static final TypeSerializer<OptionalDouble> INSTANCE = new OfDouble();
142+
143+
private OfDouble() {
144+
}
145+
146+
@Override
147+
public OptionalDouble deserialize(final Type type, final ConfigurationNode node) throws SerializationException {
148+
if (node.empty()) {
149+
return OptionalDouble.empty();
150+
}
151+
return OptionalDouble.of(node.require(double.class));
152+
}
153+
154+
@Override
155+
public void serialize(final Type type, final @Nullable OptionalDouble obj, final ConfigurationNode node) throws SerializationException {
156+
if (obj == null || !obj.isPresent()) {
157+
node.set(null);
158+
return;
159+
}
160+
161+
node.set(double.class, obj.getAsDouble());
162+
}
163+
164+
@Override
165+
public OptionalDouble emptyValue(final AnnotatedType specificType, final ConfigurationOptions options) {
166+
return OptionalDouble.empty();
167+
}
168+
169+
}
170+
171+
}

core/src/main/java/org/spongepowered/configurate/serialize/TypeSerializerCollection.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ public final class TypeSerializerCollection {
7979
.register(ConfigurationNodeSerializer.TYPE, new ConfigurationNodeSerializer())
8080
.register(PathSerializer.TYPE, PathSerializer.INSTANCE)
8181
.registerExact(FileSerializer.TYPE, FileSerializer.INSTANCE)
82+
.register(OptionalSerializer.TYPE, OptionalSerializer.INSTANCE)
83+
.registerExact(OptionalSerializer.OfInt.TYPE, OptionalSerializer.OfInt.INSTANCE)
84+
.registerExact(OptionalSerializer.OfLong.TYPE, OptionalSerializer.OfLong.INSTANCE)
85+
.registerExact(OptionalSerializer.OfDouble.TYPE, OptionalSerializer.OfDouble.INSTANCE)
8286
.build();
8387
}
8488

core/src/test/java/org/spongepowered/configurate/serialize/TypeSerializersTest.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,15 @@
5353
import java.util.HashMap;
5454
import java.util.List;
5555
import java.util.Map;
56+
import java.util.Optional;
57+
import java.util.OptionalDouble;
58+
import java.util.OptionalInt;
59+
import java.util.OptionalLong;
5660
import java.util.Set;
5761
import java.util.UUID;
5862
import java.util.regex.Pattern;
5963

64+
@SuppressWarnings("checkstyle:IllegalType") // for Optional
6065
class TypeSerializersTest {
6166

6267
private <T> TypeSerializer<T> serializer(final TypeToken<T> type) {
@@ -678,4 +683,61 @@ void testPatternFlags() throws SerializationException {
678683
.isEqualTo(Pattern.CASE_INSENSITIVE);
679684
}
680685

686+
@Test
687+
void testOptional() throws SerializationException {
688+
final TypeToken<Optional<String>> type = new TypeToken<Optional<String>>() {};
689+
final TypeSerializer<Optional<String>> serializer = this.serializer(type);
690+
691+
final Optional<String> shouldBeEmpty = serializer.deserialize(type.getAnnotatedType(), BasicConfigurationNode.root());
692+
assertThat(shouldBeEmpty)
693+
.isEmpty();
694+
695+
final Optional<String> present = serializer.deserialize(type.getAnnotatedType(), BasicConfigurationNode.root().raw("hello world"));
696+
assertThat(present)
697+
.isNotEmpty()
698+
.hasValue("hello world");
699+
}
700+
701+
@Test
702+
void testOptionalInt() throws SerializationException {
703+
final TypeSerializer<OptionalInt> serializer = this.serializer(OptionalInt.class);
704+
705+
final OptionalInt shouldBeEmpty = serializer.deserialize(OptionalInt.class, BasicConfigurationNode.root());
706+
assertThat(shouldBeEmpty)
707+
.isEmpty();
708+
709+
final OptionalInt present = serializer.deserialize(OptionalInt.class, BasicConfigurationNode.root().raw(13));
710+
assertThat(present)
711+
.isNotEmpty()
712+
.hasValue(13);
713+
}
714+
715+
@Test
716+
void testOptionalLong() throws SerializationException {
717+
final TypeSerializer<OptionalLong> serializer = this.serializer(OptionalLong.class);
718+
719+
final OptionalLong shouldBeEmpty = serializer.deserialize(OptionalLong.class, BasicConfigurationNode.root());
720+
assertThat(shouldBeEmpty)
721+
.isEmpty();
722+
723+
final OptionalLong present = serializer.deserialize(OptionalLong.class, BasicConfigurationNode.root().raw(450000));
724+
assertThat(present)
725+
.isNotEmpty()
726+
.hasValue(450000L);
727+
}
728+
729+
@Test
730+
void testOptionalDouble() throws SerializationException {
731+
final TypeSerializer<OptionalDouble> serializer = this.serializer(OptionalDouble.class);
732+
733+
final OptionalDouble shouldBeEmpty = serializer.deserialize(OptionalDouble.class, BasicConfigurationNode.root());
734+
assertThat(shouldBeEmpty)
735+
.isEmpty();
736+
737+
final OptionalDouble present = serializer.deserialize(OptionalDouble.class, BasicConfigurationNode.root().raw(5.83e10));
738+
assertThat(present)
739+
.isNotEmpty()
740+
.hasValue(5.83e10d);
741+
}
742+
681743
}

0 commit comments

Comments
 (0)