rdc/targets/java/
cg_data_enum.rs1use crate::errors::Error;
2use crate::ir::{DataEnum, DataEnumObjectField, DataEnumStyle, DataEnumVariant, Type};
3use crate::quote_iter;
4use crate::targets::java::cg_data_enum::external::{
5 generate_external_deserializer, generate_external_serializer,
6};
7use crate::targets::java::cg_utils::Compact;
8use crate::targets::java::JavaClass;
9use genco::prelude::*;
10use genco::quote;
11
12mod external;
13
14pub fn generate_enum_data_class(de: &DataEnum) -> Result<JavaClass, Error> {
15 let class_name = de.name().as_pascal_case();
16 let class_name_str = class_name.as_str();
17
18 let serializer_code = match de.style() {
19 DataEnumStyle::External => generate_external_serializer(de),
20 };
21 let deserializer_code = match de.style() {
22 DataEnumStyle::External => generate_external_deserializer(de),
23 };
24 let fields_code = generate_fields_code(de);
25 let variants_enum = generate_variants_enum(de);
26 let tokens: java::Tokens = quote!(
27 import com.fasterxml.jackson.annotation.JsonIgnore;
28 import com.fasterxml.jackson.annotation.JsonInclude;
29 import com.fasterxml.jackson.annotation.JsonProperty;
30 import com.fasterxml.jackson.core.*;
31 import com.fasterxml.jackson.databind.DeserializationContext;
32 import com.fasterxml.jackson.databind.JsonNode;
33 import com.fasterxml.jackson.databind.SerializerProvider;
34 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
35 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
36 import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
37 import com.fasterxml.jackson.databind.node.ObjectNode;
38 import com.fasterxml.jackson.databind.ser.std.StdSerializer;
39 import com.fasterxml.jackson.core.type.TypeReference;
40
41 import java.io.IOException;
42
43 @JsonInclude(JsonInclude.Include.NON_NULL)
44 @JsonSerialize(using = $class_name_str.Serializer.class)
45 @JsonDeserialize(using = $class_name_str.Deserializer.class)
46 public class $class_name_str {
47 @JsonIgnore
48 private final Variant variant;
49
50 @JsonIgnore
51 private final Object value;
52
53 private $class_name_str(Variant variant, Object value) {
54 this.variant = variant;
55 this.value = value;
56 }
57
58 $fields_code
59
60 $serializer_code
61
62 $deserializer_code
63
64 $variants_enum
65
66 Variant getVariant() {
67 return variant;
68 }
69 }
70 );
71
72 JavaClass::from_tokens(class_name, tokens)
73}
74
75fn generate_variants_enum(de: &DataEnum) -> java::Tokens {
76 let variants = de
77 .variants()
78 .iter()
79 .map(|v: &DataEnumVariant| {
80 let name = v.name().as_upper_snake_case();
81 quote!($name)
82 })
83 .collect::<Vec<java::Tokens>>();
84 let variants_enum = quote!(
85 public enum Variant {
86 $(for v in variants join (,) => $v)
87 }
88 );
89 variants_enum
90}
91
92fn generate_fields_code(de: &DataEnum) -> java::Tokens {
93 let contents: Vec<java::Tokens> = de
94 .variants()
95 .iter()
96 .map(|v| generate_field_code(de, v))
97 .collect();
98
99 quote!(
100 $(for c in contents => $c)
101 )
102}
103
104fn generate_field_code(de: &DataEnum, v: &DataEnumVariant) -> java::Tokens {
105 let class_name = &de.name().as_pascal_case();
106 let name = v.name();
107 let variant_enum_name = &name.as_upper_snake_case();
108 let of_method_name = &format!("of{}", name.as_pascal_case());
109 let is_method_name = &format!("is{}", name.as_pascal_case());
110 let variant_specific_code: java::Tokens = match v {
111 DataEnumVariant::Unit { .. } => {
112 quote!(
113 public static $class_name $of_method_name() {
114 return new $class_name(Variant.$variant_enum_name, null);
115 }
116 )
117 }
118 DataEnumVariant::Object { fields, .. } => {
119 let sub_class_name = &name.as_pascal_case();
120 let field_names = &fields
121 .iter()
122 .map(|f: &DataEnumObjectField| f.name().as_camel_case())
123 .collect::<Vec<String>>();
124 let field_declarations = &fields
125 .iter()
126 .map(|f: &DataEnumObjectField| {
127 let field_name = f.name().as_camel_case();
128 let field_type = f.field_type().type_name();
129 format!("{field_type} {field_name}")
130 })
131 .collect::<Vec<String>>();
132 let field_args = &fields
133 .iter()
134 .map(|f: &DataEnumObjectField| {
135 let field_name = f.name().as_camel_case();
136 let field_type = f.field_type().type_name();
137 let json_name = f.json_name();
138 quote!(
139 @JsonProperty($[str]($[const](json_name))) $field_type $field_name
140 )
141 })
142 .collect::<Vec<java::Tokens>>();
143 let getters = &fields
144 .iter()
145 .map(|f: &DataEnumObjectField| {
146 let field_name = f.name().as_camel_case();
147 let getter_name = &format!("get{}", f.name().as_pascal_case());
148 let field_type = f.field_type().type_name();
149 let json_name = f.json_name();
150 quote!(
151 @JsonProperty($[str]($[const](json_name)))
152 public $field_type $getter_name() {
153 return $field_name;
154 }
155 )
156 })
157 .collect::<Vec<java::Tokens>>();
158 let main_getter_name = &format!("get{}", name.as_pascal_case());
159 let enum_field_name = &name.as_upper_snake_case();
160 quote!(
161 public static $class_name $of_method_name($sub_class_name value) {
162 return new $class_name(Variant.$variant_enum_name, value);
163 }
164
165 public $sub_class_name $main_getter_name() {
166 if (variant != Variant.$enum_field_name) {
167 throw new IllegalStateException("Invalid variant: " + variant);
168 }
169 return ($sub_class_name) value;
170 }
171
172 public static class $sub_class_name {
173 $(for fd in field_declarations => private final $fd;)
174
175 public $sub_class_name($(for fd in field_args join (,) => $fd)) {
176 $(for f in field_names => this.$f = $f;)
177 }
178
179 $(for g in getters => $g)
180 }
181 )
182 }
183 DataEnumVariant::Tuple { fields, .. } => {
184 let mut counter = 0;
185 let args = fields
186 .iter()
187 .map(|t: &Type| {
188 let arg_name = format!("arg{counter}");
189 let type_name = t.type_name();
190 counter += 1;
191 quote!($type_name $arg_name)
192 })
193 .collect::<Vec<java::Tokens>>();
194
195 let mut counter = 0;
196 let objects = fields
197 .iter()
198 .map(|_t: &Type| {
199 let arg_name = format!("arg{counter}");
200 counter += 1;
201 quote!($arg_name)
202 })
203 .collect::<Vec<java::Tokens>>();
204
205 let getter_numbering = fields.len() != 1;
206 let mut counter = 0;
207 let variant_enum_name = &name.as_upper_snake_case();
208 let getters = quote_iter!(fields.iter() => |f: &Type| {
209 let type_name = f.type_name();
210 let getter_name = if getter_numbering {
211 format!("get{}{}", name.as_pascal_case(), counter)
212 } else {
213 format!("get{}", name.as_pascal_case())
214 };
215 let getter = quote!(
216 @SuppressWarnings("unchecked")
217 public $type_name $getter_name() {
218 if (variant != Variant.$variant_enum_name) {
219 throw new IllegalStateException("Invalid variant: " + variant);
220 }
221 return ($type_name) ((Object[]) value)[$counter];
222 }
223 );
224 counter += 1;
225 getter
226 });
227
228 quote!(
229 public static $class_name $of_method_name($(for a in args join (,) => $a)) {
230 return new $class_name(Variant.$variant_enum_name, new Object[] {
231 $(for o in objects join (,) => $o)
232 });
233 }
234
235 $getters
236 )
237 }
238 };
239 quote!(
240 $variant_specific_code
241
242 public boolean $is_method_name() {
243 return variant == Variant.$variant_enum_name;
244 }
245 )
246}
247
248#[cfg(test)]
249mod tests {
250 use crate as rdc;
251 use crate::targets::java::tests::run_java;
252 use crate::targets::java::JavaClass;
253 use crate::{rdc_java, RDCType, RDC};
254 use genco::quote;
255 use serde::{Deserialize, Serialize};
256 use std::fmt::Debug;
257
258 #[derive(RDC, Serialize, Deserialize, PartialEq, Debug)]
259 enum TestEnum<T>
260 where
261 T: RDCType + PartialEq + Debug,
262 {
263 Csv(String),
264 #[serde(rename = "JSON")]
265 Json(i32),
266 #[serde(rename = "XML")]
267 Xml(f64, i32),
268 #[serde(rename = "YAML")]
269 Yaml(T),
270 Other {
271 #[serde(rename = "other")]
272 name: String,
273 },
274 Unit,
275 List(Vec<i32>),
276 Nested(Box<TestEnum<T>>),
277 }
278
279 #[test]
280 fn enum_derive_test() {
281 let mut classes = rdc_java!(TestEnum<i32>).unwrap();
282 for class in &classes {
283 println!("{}", class.code());
284 }
285 classes.push(JavaClass::from_tokens("Main".to_string(), quote!(
286 import com.fasterxml.jackson.databind.ObjectMapper;
287 import com.fasterxml.jackson.core.type.TypeReference;
288 public class Main {
289 public static void main(String[] args) throws Exception {
290 var objectMapper = new ObjectMapper();
291 String input = Utils.input();
292 TestEnumInteger b = objectMapper.readValue(input, new TypeReference<TestEnumInteger>() {});
293 if (b.isOther()) {
294 var other = b.getOther();
295 assert other.getName().equals("test");
296 }
297 if (b.isYaml()) {
298 var yaml = b.getYaml();
299 assert yaml == 42;
300 }
301 if (b.isXml()) {
302 assert b.getXml0() == 3.13467;
303 assert b.getXml1() == 57;
304 }
305 if (b.isNested()) {
306 var nested = b.getNested();
307 assert nested.isUnit();
308 }
309 String serialized = objectMapper.writeValueAsString(b);
310 TestEnumInteger b2 = objectMapper.readValue(serialized, new TypeReference<TestEnumInteger>() {});
311 String serialized2 = objectMapper.writeValueAsString(b2);
312 System.out.print(serialized2);
313 }
314 }
315 )).unwrap());
316 let enums = vec![
317 TestEnum::Csv("test".to_string()),
318 TestEnum::Json(42),
319 TestEnum::Xml(3.13467, 57),
320 TestEnum::Yaml(42),
321 TestEnum::Other {
322 name: "test".to_string(),
323 },
324 TestEnum::Unit,
325 TestEnum::Nested(Box::new(TestEnum::Unit)),
326 ];
327 for e in enums {
328 let serialized = serde_json::to_string(&e).unwrap();
329 let processed = run_java(&classes, serialized.as_str()).unwrap();
330 let deserialized: TestEnum<i32> = serde_json::from_str(&processed).unwrap();
331 assert_eq!(e, deserialized);
332 }
333 }
334}