typeshare_java/
language.rs

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