rdc/targets/java/
cg_data_enum.rs

1use 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}