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 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 SpecialRustType::U8 => "Short".into(),
82 SpecialRustType::U16 => "Integer".into(),
84 SpecialRustType::USize | SpecialRustType::U32 => "Long".into(),
86 SpecialRustType::U53 | SpecialRustType::U64 => "java.math.BigInteger".into(),
88 SpecialRustType::Bool => "Boolean".into(),
90 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 c.is_ascii_alphabetic() || c == '_' || c == '$'
311 }
312
313 #[inline]
314 fn is_java_letter_or_number(&self, c: char) -> bool {
315 self.is_java_letter(c) || c.is_ascii_digit()
317 }
318
319 #[inline]
320 fn is_java_reserved_keyword(&self, name: &str) -> bool {
321 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 matches!(name, "true" | "false")
382 }
383
384 #[inline]
385 fn is_java_null_literal(&self, name: &str) -> bool {
386 matches!(name, "null")
388 }
389
390 fn santitize_itentifier(&self, name: &str) -> String {
391 let mut chars = name.chars();
393
394 let first_char = chars
396 .next()
397 .map(|c| if self.is_java_letter(c) { c } else { '_' });
398
399 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 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 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 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 RustType::Special(SpecialRustType::Option(_)) => {}
947 _ => {
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 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 "var 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 _ => writeln!(
984 indented_writer,
985 "{java_ty} content = gson.fromJson(contentElement, {java_ty}.class);",
986 )?,
987 }
988 writeln!(
989 indented_writer,
990 "yield new {java_enum_identifier}.{}(content);",
991 self.santitize_itentifier(variant_shared.id.renamed.as_str()),
992 )?;
993 indented_writer.dedent();
994 writeln!(indented_writer, "}}")?;
995 }
996 RustEnumVariant::AnonymousStruct {
997 shared: variant_shared,
998 ..
999 } => {
1000 writeln!(
1001 indented_writer,
1002 "case \"{}\" -> {{",
1003 variant_shared.id.renamed
1004 )?;
1005 indented_writer.indent();
1006 writeln!(
1007 indented_writer,
1008 "JsonElement contentElement = jsonObject.get(\"{content_key}\");",
1009 )?;
1010 writeln!(indented_writer, "if (contentElement == null) {{")?;
1011 writeln!(
1012 indented_writer,
1013 "\tthrow new java.io.IOException(\"'{}' variant missing '{}'\");",
1014 variant_shared.id.renamed, content_key,
1015 )?;
1016 writeln!(indented_writer, "}}")?;
1017 let ty_inner = self.make_anonymous_struct_name_for_enum(
1018 enum_shared,
1019 &variant_shared.id.original,
1020 );
1021 writeln!(
1022 indented_writer,
1023 "{ty_inner} content = gson.fromJson(contentElement, {ty_inner}.class);",
1024 )?;
1025 writeln!(
1026 indented_writer,
1027 "yield new {java_enum_identifier}.{}(content);",
1028 self.santitize_itentifier(variant_shared.id.renamed.as_str()),
1029 )?;
1030 indented_writer.dedent();
1031 writeln!(indented_writer, "}}")?;
1032 }
1033 _ => return Err(WriteEnumError::UnsupportedEnumVariant(variant.clone()).into()),
1034 }
1035 }
1036
1037 writeln!(
1038 indented_writer,
1039 "default -> throw new java.io.IOException(\"Unknown variant: \" + tag);",
1040 )?;
1041
1042 indented_writer.dedent();
1043 writeln!(indented_writer, "}};")?;
1044 writeln!(w, "}}")?;
1045
1046 Ok(())
1047 }
1048
1049 fn write_annotations(
1050 &self,
1051 w: &mut impl Write,
1052 decorator_set: &DecoratorSet,
1053 ) -> anyhow::Result<()> {
1054 for decorator_value in decorator_set.get_all(Self::NAME) {
1055 if let decorator::Value::Nested(decorator_set) = decorator_value {
1056 let annotations = decorator_set.get_all("annotations");
1057 self.write_java_annotations(w, annotations)?;
1058 }
1059 }
1060
1061 Ok(())
1062 }
1063
1064 fn write_java_annotations(
1065 &self,
1066 w: &mut impl Write,
1067 annotations: &[decorator::Value],
1068 ) -> anyhow::Result<()> {
1069 for annotation in annotations {
1070 match annotation {
1071 decorator::Value::String(annotations) => {
1072 for annotation in annotations
1073 .split("\n")
1074 .map(str::trim)
1075 .filter(|str| !str.is_empty())
1076 {
1077 writeln!(w, "{annotation}")?;
1078 }
1079 }
1080 _ => return Err(WriteDecoratorError::InvalidAnnotation(annotation.clone()).into()),
1081 }
1082 }
1083
1084 Ok(())
1085 }
1086
1087 fn write_multiline_comment_line(
1088 &self,
1089 w: &mut impl Write,
1090 indent: usize,
1091 comment: &str,
1092 ) -> std::io::Result<()> {
1093 writeln!(w, "{} * {}", self.indent_whitespace(indent), comment,)?;
1094 Ok(())
1095 }
1096
1097 fn write_multiline_comment(
1098 &self,
1099 w: &mut impl Write,
1100 indent: usize,
1101 comment_lines: &[String],
1102 ) -> std::io::Result<()> {
1103 if comment_lines.is_empty() {
1104 return Ok(());
1105 }
1106
1107 writeln!(w, "{}/**", self.indent_whitespace(indent))?;
1108 comment_lines
1109 .iter()
1110 .try_for_each(|comment| self.write_multiline_comment_line(w, indent, comment))?;
1111 writeln!(w, "{} */", self.indent_whitespace(indent))?;
1112
1113 Ok(())
1114 }
1115}