Skip to content

Commit 596f580

Browse files
committed
Add Notice JSON serialize/deserialize API with round-trip test
1 parent 9ffe125 commit 596f580

6 files changed

Lines changed: 193 additions & 1 deletion

File tree

buildSrc/src/main/kotlin/Versions.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ object Versions {
1313
const val VELOCITY_API = "3.4.0"
1414

1515
const val JETBRAINS_ANNOTATIONS = "26.1.0"
16+
const val GSON = "2.13.2"
1617

1718

1819
}

multification-core/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ plugins {
99
dependencies {
1010
compileOnlyApi("net.kyori:adventure-api:${Versions.ADVENTURE_API}")
1111
api("org.jetbrains:annotations:${Versions.JETBRAINS_ANNOTATIONS}")
12+
implementation("com.google.code.gson:gson:${Versions.GSON}")
1213

1314
testImplementation("net.kyori:adventure-api:${Versions.ADVENTURE_API}")
14-
}
15+
}

multification-core/src/com/eternalcode/multification/Multification.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.eternalcode.multification.viewer.ViewerProvider;
1010
import com.eternalcode.multification.adventure.AudienceConverter;
1111
import com.eternalcode.multification.executor.AsyncExecutor;
12+
import com.eternalcode.multification.notice.Notice;
1213
import com.eternalcode.multification.notice.NoticeBroadcast;
1314
import com.eternalcode.multification.notice.NoticeBroadcastImpl;
1415
import com.eternalcode.multification.notice.provider.NoticeProvider;
@@ -126,6 +127,14 @@ public void all(NoticeProvider<TRANSLATION> extractor, Formatter... formatters)
126127
.send();
127128
}
128129

130+
public String serialize(Notice notice) {
131+
return notice.serialize(this.noticeRegistry);
132+
}
133+
134+
public Notice deserialize(String raw) {
135+
return Notice.deserialize(raw, this.noticeRegistry);
136+
}
137+
129138
@ApiStatus.Internal
130139
public NoticeResolverRegistry getNoticeRegistry() {
131140
return noticeRegistry;

multification-core/src/com/eternalcode/multification/notice/Notice.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.eternalcode.multification.notice;
22

33
import com.eternalcode.multification.notice.resolver.NoticeContent;
4+
import com.eternalcode.multification.notice.resolver.NoticeResolverDefaults;
5+
import com.eternalcode.multification.notice.resolver.NoticeResolverRegistry;
46
import com.eternalcode.multification.notice.resolver.actionbar.ActionbarContent;
57
import com.eternalcode.multification.notice.resolver.bossbar.BossBarContent;
68
import com.eternalcode.multification.notice.resolver.chat.ChatContent;
@@ -25,6 +27,8 @@
2527

2628
public class Notice {
2729

30+
private static final NoticeResolverRegistry DEFAULT_NOTICE_REGISTRY = NoticeResolverDefaults.createRegistry();
31+
2832
private final Map<NoticeKey<?>, NoticePart<?>> parts = new LinkedHashMap<>();
2933

3034
protected Notice(Map<NoticeKey<?>, NoticePart<?>> parts) {
@@ -149,6 +153,22 @@ public static Notice empty() {
149153
return new Notice(Collections.emptyMap());
150154
}
151155

156+
public String serialize() {
157+
return serialize(DEFAULT_NOTICE_REGISTRY);
158+
}
159+
160+
public String serialize(NoticeResolverRegistry noticeRegistry) {
161+
return NoticeJsonCodec.serialize(this, noticeRegistry);
162+
}
163+
164+
public static Notice deserialize(String raw) {
165+
return deserialize(raw, DEFAULT_NOTICE_REGISTRY);
166+
}
167+
168+
public static Notice deserialize(String raw, NoticeResolverRegistry noticeRegistry) {
169+
return NoticeJsonCodec.deserialize(raw, noticeRegistry);
170+
}
171+
152172
public static Builder builder() {
153173
return new Builder();
154174
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package com.eternalcode.multification.notice;
2+
3+
import com.eternalcode.multification.notice.resolver.NoticeDeserializeResult;
4+
import com.eternalcode.multification.notice.resolver.NoticeContent;
5+
import com.eternalcode.multification.notice.resolver.NoticeResolverRegistry;
6+
import com.eternalcode.multification.notice.resolver.NoticeSerdesResult;
7+
import com.google.gson.JsonArray;
8+
import com.google.gson.JsonElement;
9+
import com.google.gson.JsonNull;
10+
import com.google.gson.JsonObject;
11+
import com.google.gson.JsonParseException;
12+
import com.google.gson.JsonParser;
13+
import com.google.gson.JsonPrimitive;
14+
import java.util.ArrayList;
15+
import java.util.LinkedHashMap;
16+
import java.util.List;
17+
import java.util.Map;
18+
import java.util.Optional;
19+
20+
final class NoticeJsonCodec {
21+
22+
private NoticeJsonCodec() {
23+
}
24+
25+
static String serialize(Notice notice, NoticeResolverRegistry noticeRegistry) {
26+
JsonObject root = new JsonObject();
27+
28+
for (NoticePart<?> part : notice.parts()) {
29+
NoticeSerdesResult serializedPart = noticeRegistry.serialize(part);
30+
root.add(part.noticeKey().key(), toJsonElement(serializedPart));
31+
}
32+
33+
return root.toString();
34+
}
35+
36+
static Notice deserialize(String raw, NoticeResolverRegistry noticeRegistry) {
37+
JsonElement parsed = JsonParser.parseString(raw);
38+
if (!parsed.isJsonObject()) {
39+
throw new IllegalArgumentException("Notice JSON must be an object");
40+
}
41+
42+
Notice.Builder builder = Notice.builder();
43+
JsonObject root = parsed.getAsJsonObject();
44+
45+
for (Map.Entry<String, JsonElement> entry : root.entrySet()) {
46+
String key = entry.getKey();
47+
NoticeSerdesResult result = toSerdesResult(key, entry.getValue());
48+
Optional<NoticeDeserializeResult<?>> deserialized = noticeRegistry.deserialize(key, result);
49+
50+
if (deserialized.isEmpty()) {
51+
throw new IllegalArgumentException("Unsupported notice key: " + key);
52+
}
53+
54+
withPart(builder, deserialized.get());
55+
}
56+
57+
return builder.build();
58+
}
59+
60+
private static JsonElement toJsonElement(NoticeSerdesResult result) {
61+
if (result instanceof NoticeSerdesResult.Single single) {
62+
return new JsonPrimitive(single.element());
63+
}
64+
65+
if (result instanceof NoticeSerdesResult.Multiple multiple) {
66+
JsonArray array = new JsonArray();
67+
for (String element : multiple.elements()) {
68+
array.add(element);
69+
}
70+
return array;
71+
}
72+
73+
if (result instanceof NoticeSerdesResult.Section section) {
74+
JsonObject object = new JsonObject();
75+
for (Map.Entry<String, String> sectionEntry : section.elements().entrySet()) {
76+
object.addProperty(sectionEntry.getKey(), sectionEntry.getValue());
77+
}
78+
return object;
79+
}
80+
81+
if (result instanceof NoticeSerdesResult.Empty) {
82+
return JsonNull.INSTANCE;
83+
}
84+
85+
throw new IllegalArgumentException("Unsupported result type: " + result.getClass().getName());
86+
}
87+
88+
private static NoticeSerdesResult toSerdesResult(String key, JsonElement element) {
89+
if (element.isJsonNull()) {
90+
return new NoticeSerdesResult.Empty();
91+
}
92+
93+
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) {
94+
return new NoticeSerdesResult.Single(element.getAsString());
95+
}
96+
97+
if (element.isJsonArray()) {
98+
return new NoticeSerdesResult.Multiple(toStringList(key, element.getAsJsonArray()));
99+
}
100+
101+
if (element.isJsonObject()) {
102+
return new NoticeSerdesResult.Section(toStringMap(key, element.getAsJsonObject()));
103+
}
104+
105+
throw new JsonParseException("Unsupported JSON value for key '" + key + "'");
106+
}
107+
108+
private static List<String> toStringList(String key, JsonArray jsonArray) {
109+
List<String> values = new ArrayList<>();
110+
for (JsonElement jsonElement : jsonArray) {
111+
if (!jsonElement.isJsonPrimitive() || !jsonElement.getAsJsonPrimitive().isString()) {
112+
throw new JsonParseException("All array elements for key '" + key + "' must be strings");
113+
}
114+
values.add(jsonElement.getAsString());
115+
}
116+
return values;
117+
}
118+
119+
private static Map<String, String> toStringMap(String key, JsonObject jsonObject) {
120+
Map<String, String> values = new LinkedHashMap<>();
121+
for (Map.Entry<String, JsonElement> jsonEntry : jsonObject.entrySet()) {
122+
JsonElement jsonElement = jsonEntry.getValue();
123+
if (!jsonElement.isJsonPrimitive() || !jsonElement.getAsJsonPrimitive().isString()) {
124+
throw new JsonParseException("All object values for key '" + key + "' must be strings");
125+
}
126+
values.put(jsonEntry.getKey(), jsonElement.getAsString());
127+
}
128+
return values;
129+
}
130+
131+
private static <T extends NoticeContent> void withPart(
132+
Notice.Builder builder,
133+
NoticeDeserializeResult<T> noticeResult
134+
) {
135+
builder.withPart(noticeResult.noticeKey(), noticeResult.content());
136+
}
137+
138+
}

multification-core/test/com/eternalcode/multification/MultificationTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.eternalcode.multification.viewer.ViewerProvider;
66
import com.eternalcode.multification.adventure.AudienceConverter;
77
import com.eternalcode.multification.notice.Notice;
8+
import java.time.Duration;
89
import com.eternalcode.multification.shared.Replacer;
910
import java.util.ArrayList;
1011
import java.util.Collection;
@@ -13,6 +14,7 @@
1314
import java.util.Map;
1415
import java.util.UUID;
1516
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
17+
import static org.junit.jupiter.api.Assertions.assertEquals;
1618
import org.jetbrains.annotations.NotNull;
1719
import org.junit.jupiter.api.DisplayName;
1820
import org.junit.jupiter.api.Test;
@@ -141,4 +143,25 @@ void test() {
141143
.containsExactly(DEFAULT_MESSAGE, OTHER_MESSAGE, "-INNER- -GLOBAL-");
142144
}
143145

146+
@Test
147+
@DisplayName("Should serialize and deserialize notice as json")
148+
void shouldSerializeAndDeserializeNoticeAsJson() {
149+
MyMultification multification = new MyMultification();
150+
Notice notice = Notice.builder()
151+
.chat("line-1", "line-2")
152+
.actionBar("action")
153+
.title("title", "subtitle")
154+
.times(Duration.ofSeconds(1), Duration.ofSeconds(2), Duration.ofSeconds(3))
155+
.build();
156+
157+
String raw = multification.serialize(notice);
158+
Notice restored = multification.deserialize(raw);
159+
160+
assertThat(raw)
161+
.contains("\"chat\"")
162+
.contains("\"actionbar\"")
163+
.contains("\"times\"");
164+
assertEquals(notice.parts(), restored.parts());
165+
}
166+
144167
}

0 commit comments

Comments
 (0)