typeshare_java/
language.rs

1use std::io::Write;
2
3use convert_case::{Case, Casing as _};
4use typeshare_model::Language;
5use typeshare_model::decorator;
6use typeshare_model::decorator::DecoratorSet;
7use typeshare_model::prelude::*;
8
9use crate::config::HeaderComment;
10use crate::config::JavaConfig;
11use crate::config::JavaSerializerOptions;
12use crate::error::AssertJavaIdentifierError;
13use crate::error::FormatSpecialTypeError;
14use crate::error::WriteDecoratorError;
15use crate::error::WriteEnumError;
16use crate::util::indented_writer::IndentedWriter;
17
18#[derive(Debug)]
19pub struct Java {
20    config: JavaConfig,
21}
22
23impl Language<'_> for Java {
24    type Config = JavaConfig;
25
26    const NAME: &'static str = "java";
27
28    fn new_from_config(config: Self::Config) -> anyhow::Result<Self> {
29        Ok(Self { config })
30    }
31
32    fn output_filename_for_crate(&self, crate_name: &CrateName) -> String {
33        crate_name.as_str().to_case(Case::Pascal) + ".java"
34    }
35
36    fn format_special_type(
37        &self,
38        special_ty: &SpecialRustType,
39        generic_context: &[TypeName],
40    ) -> anyhow::Result<String> {
41        Ok(match special_ty {
42            SpecialRustType::Vec(rtype) => {
43                format!(
44                    "java.util.ArrayList<{}>",
45                    self.format_type(rtype, generic_context)?
46                )
47            }
48            SpecialRustType::Array(rtype, _) => {
49                format!("{}[]", self.format_type(rtype, generic_context)?)
50            }
51            SpecialRustType::Slice(rtype) => {
52                format!("{}[]", self.format_type(rtype, generic_context)?)
53            }
54            SpecialRustType::HashMap(rtype1, rtype2) => {
55                format!(
56                    "java.util.HashMap<{}, {}>",
57                    self.format_type(rtype1, generic_context)?,
58                    self.format_type(rtype2, generic_context)?
59                )
60            }
61            SpecialRustType::Option(rtype) => self.format_type(rtype, generic_context)?,
62            SpecialRustType::Unit => "Void".into(),
63            // https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-IntegralType
64            // Char in Java is 16 bits long, so we need to use String
65            SpecialRustType::String => "String".into(),
66            SpecialRustType::Char => "Character".into(),
67            SpecialRustType::I8 => "Byte".into(),
68            SpecialRustType::I16 => "Short".into(),
69            SpecialRustType::ISize | SpecialRustType::I32 => "Integer".into(),
70            SpecialRustType::I54 | SpecialRustType::I64 => "Long".into(),
71            // byte in Java is signed, so we need to use short to represent all possible values
72            SpecialRustType::U8 => "Short".into(),
73            // short in Java is signed, so we need to use int to represent all possible values
74            SpecialRustType::U16 => "Integer".into(),
75            // ing in Java is signed, so we need to use long to represent all possible values
76            SpecialRustType::USize | SpecialRustType::U32 => "Long".into(),
77            // long in Java is signed, so we need to use BigInteger to represent all possible values
78            SpecialRustType::U53 | SpecialRustType::U64 => "java.math.BigInteger".into(),
79            // https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-PrimitiveType
80            SpecialRustType::Bool => "Boolean".into(),
81            // https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-FloatingPointType
82            SpecialRustType::F32 => "Float".into(),
83            SpecialRustType::F64 => "Double".into(),
84            _ => {
85                return Err(
86                    FormatSpecialTypeError::UnsupportedSpecialType(special_ty.clone()).into(),
87                );
88            }
89        })
90    }
91
92    fn begin_file(&self, w: &mut impl Write, mode: FilesMode<&CrateName>) -> anyhow::Result<()> {
93        match &self.config.header_comment {
94            HeaderComment::None => {}
95            HeaderComment::Default => {
96                writeln!(w, "/**")?;
97                writeln!(
98                    w,
99                    " * Generated by typeshare-java {}",
100                    env!("CARGO_PKG_VERSION")
101                )?;
102                writeln!(w, " */")?;
103                writeln!(w)?;
104            }
105            HeaderComment::Custom { comment } => {
106                writeln!(w, "/**")?;
107                for comment in comment.split("\n") {
108                    writeln!(w, " * {comment}")?;
109                }
110                writeln!(w, " */")?;
111                writeln!(w)?;
112            }
113        }
114
115        if let Some(package) = &self.config.package {
116            if let FilesMode::Multi(crate_name) = mode {
117                writeln!(
118                    w,
119                    "package {}.{};",
120                    package,
121                    crate_name.as_str().to_case(Case::Pascal)
122                )?;
123            } else {
124                writeln!(w, "package {package};")?;
125            }
126            writeln!(w)?;
127        }
128
129        match (self.config.namespace_class, mode) {
130            (true, FilesMode::Multi(crate_name)) => {
131                writeln!(
132                    w,
133                    "public class {} {{",
134                    crate_name.as_str().to_case(Case::Pascal),
135                )?;
136                writeln!(w)?;
137            }
138            (true, FilesMode::Single) => {
139                writeln!(w, "public class Namespace {{",)?;
140                writeln!(w)?;
141            }
142            _ => {}
143        }
144
145        Ok(())
146    }
147
148    fn write_imports<'a, Crates, Types>(
149        &self,
150        writer: &mut impl Write,
151        _crate_name: &CrateName,
152        imports: Crates,
153    ) -> anyhow::Result<()>
154    where
155        Crates: IntoIterator<Item = (&'a CrateName, Types)>,
156        Types: IntoIterator<Item = &'a TypeName>,
157    {
158        for (path, ty) in imports {
159            for t in ty {
160                writeln!(
161                    writer,
162                    "import {}.{path}.{t};",
163                    self.config
164                        .package
165                        .as_ref()
166                        .map(|package| format!("{package}."))
167                        .unwrap_or_default()
168                )?;
169            }
170        }
171        writeln!(writer).map_err(|err| err.into())
172    }
173
174    fn end_file(&self, w: &mut impl Write, _mode: FilesMode<&CrateName>) -> anyhow::Result<()> {
175        if self.config.namespace_class {
176            writeln!(w, "}}")?;
177        }
178
179        Ok(())
180    }
181
182    fn write_type_alias(&self, _w: &mut impl Write, _t: &RustTypeAlias) -> anyhow::Result<()> {
183        todo!("type aliases are not supported yet")
184    }
185
186    fn write_struct(&self, w: &mut impl Write, rs: &RustStruct) -> anyhow::Result<()> {
187        let mut indented_writer = IndentedWriter::new(
188            w,
189            self.config.indent.char(),
190            if self.config.namespace_class {
191                self.config.indent.size()
192            } else {
193                0
194            },
195        );
196
197        self.write_multiline_comment(&mut indented_writer, 0, &rs.comments)?;
198
199        write!(
200            indented_writer,
201            "public record {}{}{}(",
202            self.config.prefix.as_ref().unwrap_or(&String::default()),
203            rs.id.renamed,
204            if !rs.generic_types.is_empty() {
205                format!("<{}>", rs.generic_types.join(", "))
206            } else {
207                "".to_string()
208            }
209        )?;
210
211        if let Some((last, elements)) = rs.fields.split_last() {
212            writeln!(indented_writer)?;
213            for f in elements.iter() {
214                self.write_element(&mut indented_writer, f, rs.generic_types.as_slice())?;
215                writeln!(indented_writer, ",")?;
216            }
217            self.write_element(&mut indented_writer, last, rs.generic_types.as_slice())?;
218            writeln!(indented_writer)?;
219        }
220
221        writeln!(indented_writer, r") {{}}")?;
222        writeln!(indented_writer)?;
223
224        Ok(())
225    }
226
227    fn write_enum(&self, w: &mut impl Write, e: &RustEnum) -> anyhow::Result<()> {
228        if self.config.serializer == JavaSerializerOptions::Gson {
229            self.write_struct_types_for_enum_variants_gson(w, e)?;
230        }
231
232        let mut indented_writer = IndentedWriter::new(
233            w,
234            self.config.indent.char(),
235            if self.config.namespace_class {
236                self.config.indent.size()
237            } else {
238                0
239            },
240        );
241
242        self.write_multiline_comment(&mut indented_writer, 0, &e.shared().comments)?;
243        self.write_annotations(&mut indented_writer, &e.shared().decorators)?;
244
245        match e {
246            RustEnum::Unit {
247                shared,
248                unit_variants,
249            } => self.write_unit_enum(&mut indented_writer, shared, unit_variants)?,
250            RustEnum::Algebraic {
251                shared,
252                tag_key,
253                content_key,
254                variants,
255            } => self.write_algebraic_enum(
256                &mut indented_writer,
257                shared,
258                tag_key,
259                content_key,
260                variants,
261            )?,
262        }
263
264        writeln!(w)?;
265
266        Ok(())
267    }
268
269    fn write_const(&self, _w: &mut impl Write, _c: &RustConst) -> anyhow::Result<()> {
270        todo!("constants are not supported yet")
271    }
272}
273
274impl Java {
275    fn indent<S: AsRef<str>>(&self, line: S, indent: usize) -> String {
276        self.config.indent.indent(line, indent)
277    }
278
279    fn indent_whitespace(&self, indent: usize) -> String {
280        self.indent("", indent)
281    }
282
283    #[inline]
284    fn is_java_letter(&self, c: char) -> bool {
285        // https://docs.oracle.com/javase/specs/jls/se23/html/jls-3.html#jls-JavaLetter
286        c.is_ascii_alphabetic() || c == '_' || c == '$'
287    }
288
289    #[inline]
290    fn is_java_letter_or_number(&self, c: char) -> bool {
291        // https://docs.oracle.com/javase/specs/jls/se23/html/jls-3.html#jls-JavaLetterOrDigit
292        self.is_java_letter(c) || c.is_ascii_digit()
293    }
294
295    #[inline]
296    fn is_java_reserved_keyword(&self, name: &str) -> bool {
297        // https://docs.oracle.com/javase/specs/jls/se23/html/jls-3.html#jls-ReservedKeyword
298        matches!(
299            name,
300            "abstract"
301                | "continue"
302                | "for"
303                | "new"
304                | "switch"
305                | "assert"
306                | "default"
307                | "if"
308                | "package"
309                | "synchronized"
310                | "boolean"
311                | "do"
312                | "goto"
313                | "private"
314                | "this"
315                | "break"
316                | "double"
317                | "implements"
318                | "protected"
319                | "throw"
320                | "byte"
321                | "else"
322                | "import"
323                | "public"
324                | "throws"
325                | "case"
326                | "enum"
327                | "instanceof"
328                | "return"
329                | "transient"
330                | "catch"
331                | "extends"
332                | "int"
333                | "short"
334                | "try"
335                | "char"
336                | "final"
337                | "interface"
338                | "static"
339                | "void"
340                | "class"
341                | "finally"
342                | "long"
343                | "strictfp"
344                | "volatile"
345                | "const"
346                | "float"
347                | "native"
348                | "super"
349                | "while"
350                | "_"
351        )
352    }
353
354    #[inline]
355    fn is_java_boolean_literal(&self, name: &str) -> bool {
356        // https://docs.oracle.com/javase/specs/jls/se23/html/jls-3.html#jls-BooleanLiteral
357        matches!(name, "true" | "false")
358    }
359
360    #[inline]
361    fn is_java_null_literal(&self, name: &str) -> bool {
362        // https://docs.oracle.com/javase/specs/jls/se23/html/jls-3.html#jls-NullLiteral
363        matches!(name, "null")
364    }
365
366    fn santitize_itentifier(&self, name: &str) -> String {
367        // https://docs.oracle.com/javase/specs/jls/se23/html/jls-3.html#jls-Identifier
368        let mut chars = name.chars();
369
370        // Ensure the first character is valid "JavaLetter"
371        let first_char = chars
372            .next()
373            .map(|c| if self.is_java_letter(c) { c } else { '_' });
374
375        // Ensure each remaining characters is a valid "JavaLetterOrDigit"
376        let rest: String = chars
377            .filter_map(|c| match c {
378                '-' => Some('_'),
379                c if self.is_java_letter_or_number(c) => Some(c),
380                _ => None,
381            })
382            .collect();
383
384        // Combine and return the sanitized identifier
385        let name: String = first_char.into_iter().chain(rest.chars()).collect();
386
387        if self.is_java_reserved_keyword(&name)
388            || self.is_java_boolean_literal(&name)
389            || self.is_java_null_literal(&name)
390        {
391            format!("_{name}")
392        } else {
393            name
394        }
395    }
396
397    fn assert_java_identifier<T: AsRef<str>>(&self, name: T) -> anyhow::Result<T> {
398        let mut chars = name.as_ref().chars();
399
400        // Ensure the first character is valid "JavaLetter"
401        match chars.next() {
402            Some(char) if self.is_java_letter(char) => {}
403            Some(char) => {
404                return Err(AssertJavaIdentifierError::InvalidCharacter {
405                    name: name.as_ref().to_string(),
406                    char,
407                }
408                .into());
409            }
410            None => return Err(AssertJavaIdentifierError::EmptyString.into()),
411        }
412
413        // Ensure each remaining characters is a valid "JavaLetterOrDigit"
414        for char in chars {
415            if !self.is_java_letter_or_number(char) {
416                return Err(AssertJavaIdentifierError::InvalidCharacter {
417                    name: name.as_ref().to_string(),
418                    char,
419                }
420                .into());
421            }
422        }
423
424        Ok(name)
425    }
426
427    fn make_anonymous_struct_name_for_enum(
428        &self,
429        enum_shared: &RustEnumShared,
430        ty: &TypeName,
431    ) -> String {
432        self.santitize_itentifier(&format!("{}{}Inner", enum_shared.id.renamed, ty))
433    }
434
435    fn write_element(
436        &self,
437        w: &mut impl Write,
438        f: &RustField,
439        generic_types: &[TypeName],
440    ) -> anyhow::Result<()> {
441        self.write_multiline_comment(w, 1, &f.comments)?;
442        let ty = self.format_type(&f.ty, generic_types)?;
443        write!(
444            w,
445            "{}{} {}",
446            self.indent_whitespace(1),
447            ty,
448            self.santitize_itentifier(f.id.renamed.as_str()),
449        )
450        .map_err(|err| err.into())
451    }
452
453    fn write_unit_enum(
454        &self,
455        w: &mut impl Write,
456        shared: &RustEnumShared,
457        unit_variants: &[RustEnumVariantShared],
458    ) -> anyhow::Result<()> {
459        if !shared.generic_types.is_empty() {
460            todo!("generic types on unit enums are not supported yet");
461        }
462
463        writeln!(
464            w,
465            "public enum {}{} {{",
466            self.config.prefix.as_ref().unwrap_or(&String::default()),
467            self.santitize_itentifier(shared.id.renamed.as_str()),
468        )?;
469
470        if let Some((last_variant, variants)) = unit_variants.split_last() {
471            for variant in variants {
472                self.write_multiline_comment(w, 1, &variant.comments)?;
473                writeln!(
474                    w,
475                    "{}{},",
476                    self.indent_whitespace(1),
477                    self.santitize_itentifier(variant.id.renamed.as_str()),
478                )?;
479            }
480            self.write_multiline_comment(w, 1, &last_variant.comments)?;
481            writeln!(
482                w,
483                "{}{}",
484                self.indent_whitespace(1),
485                self.santitize_itentifier(last_variant.id.renamed.as_str()),
486            )?;
487        }
488
489        writeln!(w, "}}")?;
490
491        Ok(())
492    }
493
494    fn write_algebraic_enum(
495        &self,
496        w: &mut impl Write,
497        shared: &RustEnumShared,
498        tag_key: &str,
499        content_key: &str,
500        variants: &[RustEnumVariant],
501    ) -> anyhow::Result<()> {
502        match self.config.serializer {
503            JavaSerializerOptions::None => {
504                Err(WriteEnumError::SerializerRequiredForAlgebraicEnums {
505                    name: shared.id.original.to_string(),
506                }
507                .into())
508            }
509            JavaSerializerOptions::Gson => {
510                self.write_algebraic_enum_gson(w, shared, tag_key, content_key, variants)
511            }
512        }
513    }
514
515    fn write_struct_types_for_enum_variants_gson(
516        &self,
517        w: &mut impl Write,
518        e: &RustEnum,
519    ) -> anyhow::Result<()> {
520        self.write_struct_types_for_enum_variants(w, e, &|ty| {
521            self.make_anonymous_struct_name_for_enum(e.shared(), ty)
522        })
523    }
524
525    fn write_algebraic_enum_gson(
526        &self,
527        w: &mut impl Write,
528        shared: &RustEnumShared,
529        tag_key: &str,
530        content_key: &str,
531        variants: &[RustEnumVariant],
532    ) -> anyhow::Result<()> {
533        if !shared.generic_types.is_empty() {
534            todo!("generic types on unit enums are not supported yet");
535        }
536
537        let java_enum_identifier = self.santitize_itentifier(&format!(
538            "{}{}",
539            self.config.prefix.as_ref().unwrap_or(&String::default()),
540            shared.id.renamed.as_str(),
541        ));
542
543        let java_enum_adapter_identifier = format!("_{java_enum_identifier}Adapter");
544
545        writeln!(
546            w,
547            "@com.google.gson.annotations.JsonAdapter({java_enum_adapter_identifier}.class)",
548        )?;
549        writeln!(w, "public sealed interface {java_enum_identifier}")?;
550
551        let (variant_last, variants_rest) = variants
552            .split_last()
553            .expect("algebraic enum should have at least one variant");
554
555        writeln!(w, "{}permits", self.indent_whitespace(1))?;
556        for variant in variants_rest {
557            writeln!(
558                w,
559                "{}{}.{},",
560                self.indent_whitespace(2),
561                java_enum_identifier,
562                self.santitize_itentifier(variant.shared().id.renamed.as_str()),
563            )?;
564        }
565        writeln!(
566            w,
567            "{}{}.{} {{",
568            self.indent_whitespace(2),
569            java_enum_identifier,
570            self.santitize_itentifier(variant_last.shared().id.renamed.as_str()),
571        )?;
572        writeln!(w)?;
573
574        let mut indented_writer = self.config.indent.to_indented_writer(w);
575
576        for variant in variants {
577            self.write_multiline_comment(&mut indented_writer, 0, &variant.shared().comments)?;
578
579            match variant {
580                RustEnumVariant::Unit(shared) => self.write_algebraic_enum_unit_variant_gson(
581                    &mut indented_writer,
582                    shared,
583                    &java_enum_identifier,
584                    &java_enum_adapter_identifier,
585                )?,
586                RustEnumVariant::Tuple {
587                    ty: variant_ty,
588                    shared: variant_shared,
589                } => {
590                    self.write_algebraic_enum_newtype_tuple_variant_gson(
591                        &mut indented_writer,
592                        shared,
593                        variant_shared,
594                        content_key,
595                        variant_ty,
596                        &java_enum_identifier,
597                        &java_enum_adapter_identifier,
598                    )?;
599                }
600                RustEnumVariant::AnonymousStruct {
601                    fields: _vairant_fields,
602                    shared: variant_shared,
603                } => {
604                    self.write_algebraic_enum_anonymous_struct_variant_gson(
605                        &mut indented_writer,
606                        shared,
607                        variant_shared,
608                        content_key,
609                        &java_enum_identifier,
610                        &java_enum_adapter_identifier,
611                    )?;
612                }
613                _ => return Err(WriteEnumError::UnsupportedEnumVariant(variant.clone()).into()),
614            }
615
616            writeln!(indented_writer)?;
617        }
618
619        writeln!(w, "}}")?;
620        writeln!(w)?;
621
622        self.write_algebraic_enum_adapter_gson(
623            w,
624            shared,
625            tag_key,
626            content_key,
627            variants,
628            &java_enum_identifier,
629            &java_enum_adapter_identifier,
630        )?;
631
632        Ok(())
633    }
634
635    fn write_algebraic_enum_unit_variant_gson(
636        &self,
637        w: &mut impl Write,
638        variant_shared: &RustEnumVariantShared,
639        java_enum_identifier: &str,
640        java_enum_adapter_identifier: &str,
641    ) -> anyhow::Result<()> {
642        writeln!(
643            w,
644            "@com.google.gson.annotations.JsonAdapter({java_enum_adapter_identifier}.class)",
645        )?;
646        writeln!(
647            w,
648            "public record {}() implements {java_enum_identifier} {{}}",
649            self.santitize_itentifier(variant_shared.id.renamed.as_str()),
650        )?;
651        Ok(())
652    }
653
654    #[allow(clippy::too_many_arguments)]
655    fn write_algebraic_enum_newtype_tuple_variant_gson(
656        &self,
657        w: &mut impl Write,
658        enum_shared: &RustEnumShared,
659        variant_shared: &RustEnumVariantShared,
660        content_key: &str,
661        ty: &RustType,
662        java_enum_identifier: &str,
663        java_enum_adapter_identifier: &str,
664    ) -> anyhow::Result<()> {
665        let ty = self.format_type(ty, enum_shared.generic_types.as_ref())?;
666        writeln!(
667            w,
668            "@com.google.gson.annotations.JsonAdapter({java_enum_adapter_identifier}.class)",
669        )?;
670        writeln!(
671            w,
672            "public record {}({} {}) implements {} {{}}",
673            self.santitize_itentifier(variant_shared.id.renamed.as_str()),
674            ty,
675            self.assert_java_identifier(content_key)?,
676            java_enum_identifier,
677        )?;
678        Ok(())
679    }
680
681    #[allow(clippy::too_many_arguments)]
682    fn write_algebraic_enum_anonymous_struct_variant_gson(
683        &self,
684        w: &mut impl Write,
685        enum_shared: &RustEnumShared,
686        variant_shared: &RustEnumVariantShared,
687        content_key: &str,
688        java_enum_identifier: &str,
689        java_enum_adapter_identifier: &str,
690    ) -> anyhow::Result<()> {
691        let ty_inner =
692            self.make_anonymous_struct_name_for_enum(enum_shared, &variant_shared.id.original);
693        writeln!(
694            w,
695            "@com.google.gson.annotations.JsonAdapter({java_enum_adapter_identifier}.class)",
696        )?;
697        writeln!(
698            w,
699            "public record {}({} {}) implements {} {{}}",
700            self.santitize_itentifier(variant_shared.id.renamed.as_str()),
701            ty_inner,
702            self.assert_java_identifier(content_key)?,
703            java_enum_identifier,
704        )?;
705        Ok(())
706    }
707
708    #[allow(clippy::too_many_arguments)]
709    fn write_algebraic_enum_adapter_gson(
710        &self,
711        w: &mut impl Write,
712        shared: &RustEnumShared,
713        tag_key: &str,
714        content_key: &str,
715        variants: &[RustEnumVariant],
716        java_enum_identifier: &str,
717        java_enum_adapter_identifier: &str,
718    ) -> anyhow::Result<()> {
719        writeln!(
720            w,
721            "private final class {java_enum_adapter_identifier} extends com.google.gson.TypeAdapter<{java_enum_identifier}> {{",
722        )?;
723        writeln!(w)?;
724        writeln!(
725            w,
726            "{}private static com.google.gson.Gson gson = new com.google.gson.Gson();",
727            self.indent_whitespace(1),
728        )?;
729        writeln!(w)?;
730
731        let mut indented_writer = self.config.indent.to_indented_writer(w);
732
733        self.write_algebraic_enum_adapter_write_method_gson(
734            &mut indented_writer,
735            tag_key,
736            content_key,
737            variants,
738            java_enum_identifier,
739        )?;
740        writeln!(indented_writer)?;
741
742        self.write_algebraic_enum_adapter_read_method_gson(
743            &mut indented_writer,
744            shared,
745            tag_key,
746            content_key,
747            variants,
748            java_enum_identifier,
749        )?;
750        writeln!(indented_writer)?;
751
752        writeln!(w, "}}")?;
753
754        Ok(())
755    }
756
757    fn write_algebraic_enum_adapter_write_method_gson(
758        &self,
759        w: &mut impl Write,
760        tag_key: &str,
761        content_key: &str,
762        variants: &[RustEnumVariant],
763        java_enum_identifier: &str,
764    ) -> anyhow::Result<()> {
765        writeln!(w, "@Override")?;
766        writeln!(w, "public void write(")?;
767        writeln!(
768            w,
769            "{}com.google.gson.stream.JsonWriter out,",
770            self.indent_whitespace(1),
771        )?;
772        writeln!(
773            w,
774            "{}{java_enum_identifier} value",
775            self.indent_whitespace(1),
776        )?;
777        writeln!(w, ") throws java.io.IOException {{")?;
778
779        let mut indented_writer = self.config.indent.to_indented_writer(w);
780
781        for variant in variants {
782            writeln!(
783                indented_writer,
784                "if (value instanceof {java_enum_identifier}.{}) {{",
785                self.santitize_itentifier(variant.shared().id.renamed.as_str()),
786            )?;
787            indented_writer.indent();
788            match variant {
789                RustEnumVariant::Unit(variant_shared) => {
790                    writeln!(indented_writer, "out.beginObject();")?;
791                    writeln!(indented_writer, "out.name(\"{tag_key}\");")?;
792                    writeln!(
793                        indented_writer,
794                        "out.value(\"{}\");",
795                        variant_shared.id.renamed
796                    )?;
797                    writeln!(indented_writer, "out.endObject();")?;
798                    writeln!(indented_writer, "return;")?;
799                }
800                RustEnumVariant::Tuple {
801                    shared: variant_shared,
802                    ..
803                }
804                | RustEnumVariant::AnonymousStruct {
805                    shared: variant_shared,
806                    ..
807                } => {
808                    writeln!(
809                        indented_writer,
810                        "var content = (({java_enum_identifier}.{}) value).{}();",
811                        self.santitize_itentifier(variant_shared.id.renamed.as_str()),
812                        self.assert_java_identifier(content_key)?,
813                    )?;
814                    writeln!(indented_writer, "out.beginObject();")?;
815                    writeln!(indented_writer, "out.name(\"{tag_key}\");")?;
816                    writeln!(
817                        indented_writer,
818                        "out.value(\"{}\");",
819                        variant_shared.id.renamed
820                    )?;
821                    writeln!(indented_writer, "out.name(\"{content_key}\");")?;
822                    writeln!(
823                        indented_writer,
824                        "gson.toJson(gson.toJsonTree(content), out);"
825                    )?;
826                    writeln!(indented_writer, "out.endObject();")?;
827                    writeln!(indented_writer, "return;")?;
828                }
829                _ => return Err(WriteEnumError::UnsupportedEnumVariant(variant.clone()).into()),
830            }
831            indented_writer.dedent();
832            writeln!(indented_writer, "}}")?;
833            writeln!(indented_writer)?;
834        }
835
836        writeln!(
837            indented_writer,
838            "throw new RuntimeException(\"unreachable!\");"
839        )?;
840        writeln!(w, "}}")?;
841
842        Ok(())
843    }
844
845    fn write_algebraic_enum_adapter_read_method_gson(
846        &self,
847        w: &mut impl Write,
848        enum_shared: &RustEnumShared,
849        tag_key: &str,
850        content_key: &str,
851        variants: &[RustEnumVariant],
852        java_enum_identifier: &str,
853    ) -> anyhow::Result<()> {
854        writeln!(w, "@Override")?;
855        writeln!(w, "public {java_enum_identifier} read(")?;
856        writeln!(
857            w,
858            "{}com.google.gson.stream.JsonReader in",
859            self.indent_whitespace(1),
860        )?;
861        writeln!(w, ") throws java.io.IOException {{")?;
862
863        let mut indented_writer = self.config.indent.to_indented_writer(w);
864
865        writeln!(
866            indented_writer,
867            "JsonObject jsonObject = gson.fromJson(in, JsonObject.class);"
868        )?;
869        writeln!(
870            indented_writer,
871            "JsonElement tagElement = jsonObject.get(\"{tag_key}\");",
872        )?;
873        writeln!(indented_writer)?;
874        writeln!(indented_writer, "if (tagElement == null) {{")?;
875        writeln!(
876            indented_writer,
877            "{}throw new java.io.IOException(\"Missing '{tag_key}' field for {java_enum_identifier}\");",
878            self.indent_whitespace(1),
879        )?;
880        writeln!(indented_writer, "}}")?;
881        writeln!(indented_writer)?;
882        writeln!(indented_writer, "if (!tagElement.isJsonPrimitive()) {{")?;
883        writeln!(
884            indented_writer,
885            "{}throw new java.io.IOException(\"Invalid '{tag_key}' field for {java_enum_identifier}\");",
886            self.indent_whitespace(1),
887        )?;
888        writeln!(indented_writer, "}}")?;
889        writeln!(indented_writer)?;
890        writeln!(indented_writer, "String tag = tagElement.getAsString();")?;
891        writeln!(indented_writer)?;
892        writeln!(indented_writer, "return switch (tag) {{")?;
893
894        indented_writer.indent();
895
896        for variant in variants {
897            match variant {
898                RustEnumVariant::Unit(variant_shared) => {
899                    writeln!(
900                        indented_writer,
901                        "case \"{}\" -> new {java_enum_identifier}.{}();",
902                        variant_shared.id.renamed,
903                        self.santitize_itentifier(variant_shared.id.renamed.as_str()),
904                    )?;
905                }
906                RustEnumVariant::Tuple {
907                    ty,
908                    shared: variant_shared,
909                } => {
910                    writeln!(
911                        indented_writer,
912                        "case \"{}\" -> {{",
913                        variant_shared.id.renamed
914                    )?;
915                    indented_writer.indent();
916                    writeln!(
917                        indented_writer,
918                        "JsonElement contentElement = jsonObject.get(\"{content_key}\");",
919                    )?;
920                    match ty {
921                        // For Option types, there's no need to check for null values
922                        RustType::Special(SpecialRustType::Option(_)) => {}
923                        // For all other types, ensure the content is not null (or missing)
924                        _ => {
925                            writeln!(indented_writer, "if (contentElement == null) {{")?;
926                            writeln!(
927                                indented_writer,
928                                "\tthrow new java.io.IOException(\"'{}' variant missing '{}'\");",
929                                variant_shared.id.renamed, content_key,
930                            )?;
931                            writeln!(indented_writer, "}}")?;
932                        }
933                    }
934                    if !enum_shared.generic_types.is_empty() {
935                        // EXPLANATION: The Gson TypeToken<T> constructor cannot accept types T<...K> where T is generic
936                        //              over types ...K. Such types can be represented using TypeToken.getParametarized(T.class, K.class, ...)
937                        //              but this requires some extra logic to be implemented here.
938                        // REFERENCE: https://github.com/google/gson/blob/259c477cecaea8e73cd19e5207ba63edc04157da/gson/src/main/java/com/google/gson/reflect/TypeToken.java#L40-L44
939                        todo!("algebraic enums with generic type parameters are not yet supported");
940                    }
941                    let java_ty = self.format_type(ty, &[])?;
942                    match ty {
943                        RustType::Special(SpecialRustType::Vec(_))
944                        | RustType::Special(SpecialRustType::Array(_, _))
945                        | RustType::Special(SpecialRustType::Slice(_))
946                        | RustType::Special(SpecialRustType::HashMap(_, _)) => {
947                            writeln!(
948                                indented_writer,
949                                "var contentType = new com.google.gson.reflect.TypeToken<{java_ty}>() {{}};"
950                            )?;
951                            writeln!(
952                                indented_writer,
953                                "{java_ty} content = gson.fromJson(contentElement, contentType);",
954                            )?
955                        }
956                        RustType::Special(SpecialRustType::Unit) => {
957                            todo!("algebraic enum variants with unit values are not supported yet")
958                        }
959                        _ => writeln!(
960                            indented_writer,
961                            "{java_ty} content = gson.fromJson(contentElement, {java_ty}.class);",
962                        )?,
963                    }
964                    writeln!(
965                        indented_writer,
966                        "yield new {java_enum_identifier}.{}(content);",
967                        self.santitize_itentifier(variant_shared.id.renamed.as_str()),
968                    )?;
969                    indented_writer.dedent();
970                    writeln!(indented_writer, "}}")?;
971                }
972                RustEnumVariant::AnonymousStruct {
973                    shared: variant_shared,
974                    ..
975                } => {
976                    writeln!(
977                        indented_writer,
978                        "case \"{}\" -> {{",
979                        variant_shared.id.renamed
980                    )?;
981                    indented_writer.indent();
982                    writeln!(
983                        indented_writer,
984                        "JsonElement contentElement = jsonObject.get(\"{content_key}\");",
985                    )?;
986                    writeln!(indented_writer, "if (contentElement == null) {{")?;
987                    writeln!(
988                        indented_writer,
989                        "\tthrow new java.io.IOException(\"'{}' variant missing '{}'\");",
990                        variant_shared.id.renamed, content_key,
991                    )?;
992                    writeln!(indented_writer, "}}")?;
993                    let ty_inner = self.make_anonymous_struct_name_for_enum(
994                        enum_shared,
995                        &variant_shared.id.original,
996                    );
997                    writeln!(
998                        indented_writer,
999                        "{ty_inner} content = gson.fromJson(contentElement, {ty_inner}.class);",
1000                    )?;
1001                    writeln!(
1002                        indented_writer,
1003                        "yield new {java_enum_identifier}.{}(content);",
1004                        self.santitize_itentifier(variant_shared.id.renamed.as_str()),
1005                    )?;
1006                    indented_writer.dedent();
1007                    writeln!(indented_writer, "}}")?;
1008                }
1009                _ => return Err(WriteEnumError::UnsupportedEnumVariant(variant.clone()).into()),
1010            }
1011        }
1012
1013        writeln!(
1014            indented_writer,
1015            "default -> throw new java.io.IOException(\"Unknown variant: \" + tag);",
1016        )?;
1017
1018        indented_writer.dedent();
1019        writeln!(indented_writer, "}};")?;
1020        writeln!(w, "}}")?;
1021
1022        Ok(())
1023    }
1024
1025    fn write_annotations(
1026        &self,
1027        w: &mut impl Write,
1028        decorator_set: &DecoratorSet,
1029    ) -> anyhow::Result<()> {
1030        for decorator_value in decorator_set.get_all(Self::NAME) {
1031            if let decorator::Value::Nested(decorator_set) = decorator_value {
1032                let annotations = decorator_set.get_all("annotations");
1033                self.write_java_annotations(w, annotations)?;
1034            }
1035        }
1036
1037        Ok(())
1038    }
1039
1040    fn write_java_annotations(
1041        &self,
1042        w: &mut impl Write,
1043        annotations: &[decorator::Value],
1044    ) -> anyhow::Result<()> {
1045        for annotation in annotations {
1046            match annotation {
1047                decorator::Value::String(annotations) => {
1048                    for annotation in annotations
1049                        .split("\n")
1050                        .map(str::trim)
1051                        .filter(|str| !str.is_empty())
1052                    {
1053                        writeln!(w, "{annotation}")?;
1054                    }
1055                }
1056                _ => return Err(WriteDecoratorError::InvalidAnnotation(annotation.clone()).into()),
1057            }
1058        }
1059
1060        Ok(())
1061    }
1062
1063    fn write_multiline_comment_line(
1064        &self,
1065        w: &mut impl Write,
1066        indent: usize,
1067        comment: &str,
1068    ) -> std::io::Result<()> {
1069        writeln!(w, "{} * {}", self.indent_whitespace(indent), comment,)?;
1070        Ok(())
1071    }
1072
1073    fn write_multiline_comment(
1074        &self,
1075        w: &mut impl Write,
1076        indent: usize,
1077        comment_lines: &[String],
1078    ) -> std::io::Result<()> {
1079        if comment_lines.is_empty() {
1080            return Ok(());
1081        }
1082
1083        writeln!(w, "{}/**", self.indent_whitespace(indent))?;
1084        comment_lines
1085            .iter()
1086            .try_for_each(|comment| self.write_multiline_comment_line(w, indent, comment))?;
1087        writeln!(w, "{} */", self.indent_whitespace(indent))?;
1088
1089        Ok(())
1090    }
1091}