use std::borrow::Cow;
use std::io::Write;
use convert_case::{Case, Casing as _};
use typeshare_model::Language;
use typeshare_model::decorator;
use typeshare_model::decorator::DecoratorSet;
use typeshare_model::prelude::*;
use crate::config::HeaderComment;
use crate::config::JavaConfig;
use crate::config::JavaSerializerOptions;
use crate::error::AssertJavaIdentifierError;
use crate::error::FormatSpecialTypeError;
use crate::error::WriteConstError;
use crate::error::WriteDecoratorError;
use crate::error::WriteEnumError;
use crate::util::indented_writer::IndentedWriter;
#[derive(Debug)]
pub struct Java {
config: JavaConfig,
}
impl Language<'_> for Java {
type Config = JavaConfig;
const NAME: &'static str = "java";
fn new_from_config(config: Self::Config) -> anyhow::Result<Self> {
Ok(Self { config })
}
fn mapped_type(&self, type_name: &TypeName) -> Option<Cow<'_, str>> {
self.config
.type_mappings
.get(type_name.as_str())
.map(Cow::from)
}
fn output_filename_for_crate(&self, crate_name: &CrateName) -> String {
crate_name.as_str().to_case(Case::Pascal) + ".java"
}
fn format_special_type(
&self,
special_ty: &SpecialRustType,
generic_context: &[TypeName],
) -> anyhow::Result<String> {
Ok(match special_ty {
SpecialRustType::Vec(rtype) => {
format!(
"java.util.ArrayList<{}>",
self.format_type(rtype, generic_context)?
)
}
SpecialRustType::Array(rtype, _) => {
format!("{}[]", self.format_type(rtype, generic_context)?)
}
SpecialRustType::Slice(rtype) => {
format!("{}[]", self.format_type(rtype, generic_context)?)
}
SpecialRustType::HashMap(rtype1, rtype2) => {
format!(
"java.util.HashMap<{}, {}>",
self.format_type(rtype1, generic_context)?,
self.format_type(rtype2, generic_context)?
)
}
SpecialRustType::Option(rtype) => self.format_type(rtype, generic_context)?,
SpecialRustType::Unit => "Void".into(),
SpecialRustType::String => "String".into(),
SpecialRustType::Char => "Character".into(),
SpecialRustType::I8 => "Byte".into(),
SpecialRustType::I16 => "Short".into(),
SpecialRustType::ISize | SpecialRustType::I32 => "Integer".into(),
SpecialRustType::I54 | SpecialRustType::I64 => "Long".into(),
SpecialRustType::U8 => "Short".into(),
SpecialRustType::U16 => "Integer".into(),
SpecialRustType::USize | SpecialRustType::U32 => "Long".into(),
SpecialRustType::U53 | SpecialRustType::U64 => "java.math.BigInteger".into(),
SpecialRustType::Bool => "Boolean".into(),
SpecialRustType::F32 => "Float".into(),
SpecialRustType::F64 => "Double".into(),
_ => {
return Err(
FormatSpecialTypeError::UnsupportedSpecialType(special_ty.clone()).into(),
);
}
})
}
fn begin_file(&self, w: &mut impl Write, mode: FilesMode<&CrateName>) -> anyhow::Result<()> {
match &self.config.header_comment {
HeaderComment::None => {}
HeaderComment::Default => {
writeln!(w, "/**")?;
writeln!(
w,
" * Generated by typeshare-java {}",
env!("CARGO_PKG_VERSION")
)?;
writeln!(w, " */")?;
writeln!(w)?;
}
HeaderComment::Custom { comment } => {
writeln!(w, "/**")?;
for comment in comment.split("\n") {
writeln!(w, " * {comment}")?;
}
writeln!(w, " */")?;
writeln!(w)?;
}
}
if let Some(package) = &self.config.package {
if let FilesMode::Multi(crate_name) = mode {
writeln!(
w,
"package {}.{};",
package,
crate_name.as_str().to_case(Case::Pascal)
)?;
} else {
writeln!(w, "package {package};")?;
}
writeln!(w)?;
}
match (self.config.namespace_class, mode) {
(true, FilesMode::Multi(crate_name)) => {
writeln!(
w,
"public class {} {{",
crate_name.as_str().to_case(Case::Pascal),
)?;
writeln!(w)?;
}
(true, FilesMode::Single) => {
writeln!(w, "public class Namespace {{",)?;
writeln!(w)?;
}
_ => {}
}
Ok(())
}
fn write_imports<'a, Crates, Types>(
&self,
writer: &mut impl Write,
_crate_name: &CrateName,
imports: Crates,
) -> anyhow::Result<()>
where
Crates: IntoIterator<Item = (&'a CrateName, Types)>,
Types: IntoIterator<Item = &'a TypeName>,
{
for (path, ty) in imports {
for t in ty {
writeln!(
writer,
"import {}.{path}.{t};",
self.config
.package
.as_ref()
.map(|package| format!("{package}."))
.unwrap_or_default()
)?;
}
}
writeln!(writer).map_err(|err| err.into())
}
fn end_file(&self, w: &mut impl Write, _mode: FilesMode<&CrateName>) -> anyhow::Result<()> {
if self.config.namespace_class {
writeln!(w, "}}")?;
}
Ok(())
}
fn write_type_alias(&self, _w: &mut impl Write, _t: &RustTypeAlias) -> anyhow::Result<()> {
todo!("type aliases are not supported yet")
}
fn write_struct(&self, w: &mut impl Write, rs: &RustStruct) -> anyhow::Result<()> {
let mut indented_writer = IndentedWriter::new(
w,
self.config.indent.char(),
if self.config.namespace_class {
self.config.indent.size()
} else {
0
},
);
self.write_multiline_comment(&mut indented_writer, 0, &rs.comments)?;
write!(
indented_writer,
"public record {}{}{}(",
self.config.prefix.as_ref().unwrap_or(&String::default()),
rs.id.renamed,
if !rs.generic_types.is_empty() {
format!("<{}>", rs.generic_types.join(", "))
} else {
"".to_string()
}
)?;
if let Some((last, elements)) = rs.fields.split_last() {
writeln!(indented_writer)?;
for f in elements.iter() {
self.write_element(&mut indented_writer, f, rs.generic_types.as_slice())?;
writeln!(indented_writer, ",")?;
}
self.write_element(&mut indented_writer, last, rs.generic_types.as_slice())?;
writeln!(indented_writer)?;
}
writeln!(indented_writer, r") {{}}")?;
writeln!(indented_writer)?;
Ok(())
}
fn write_enum(&self, w: &mut impl Write, e: &RustEnum) -> anyhow::Result<()> {
if self.config.serializer == JavaSerializerOptions::Gson {
self.write_struct_types_for_enum_variants_gson(w, e)?;
}
let mut indented_writer = IndentedWriter::new(
w,
self.config.indent.char(),
if self.config.namespace_class {
self.config.indent.size()
} else {
0
},
);
self.write_multiline_comment(&mut indented_writer, 0, &e.shared().comments)?;
self.write_annotations(&mut indented_writer, &e.shared().decorators)?;
match e {
RustEnum::Unit {
shared,
unit_variants,
} => self.write_unit_enum(&mut indented_writer, shared, unit_variants)?,
RustEnum::Algebraic {
shared,
tag_key,
content_key,
variants,
} => self.write_algebraic_enum(
&mut indented_writer,
shared,
tag_key,
content_key,
variants,
)?,
}
writeln!(w)?;
Ok(())
}
fn write_const(&self, w: &mut impl Write, c: &RustConst) -> anyhow::Result<()> {
if !self.config.namespace_class {
todo!("constants are currently only supported with the namespace class option");
}
let mut indented_writer = self.config.indent.to_indented_writer(w);
let literal = match c.expr {
RustConstExpr::Int(int) => int.to_string(),
_ => return Err(WriteConstError::UnsupportedConstExpression(c.expr.clone()).into()),
};
writeln!(
indented_writer,
"static {} {} = {literal};",
self.format_type(&c.ty, &[])?,
self.santitize_itentifier(c.id.renamed.as_str()),
)?;
writeln!(indented_writer)?;
Ok(())
}
}
impl Java {
fn indent<S: AsRef<str>>(&self, line: S, indent: usize) -> String {
self.config.indent.indent(line, indent)
}
fn indent_whitespace(&self, indent: usize) -> String {
self.indent("", indent)
}
#[inline]
fn is_java_letter(&self, c: char) -> bool {
c.is_ascii_alphabetic() || c == '_' || c == '$'
}
#[inline]
fn is_java_letter_or_number(&self, c: char) -> bool {
self.is_java_letter(c) || c.is_ascii_digit()
}
#[inline]
fn is_java_reserved_keyword(&self, name: &str) -> bool {
matches!(
name,
"abstract"
| "continue"
| "for"
| "new"
| "switch"
| "assert"
| "default"
| "if"
| "package"
| "synchronized"
| "boolean"
| "do"
| "goto"
| "private"
| "this"
| "break"
| "double"
| "implements"
| "protected"
| "throw"
| "byte"
| "else"
| "import"
| "public"
| "throws"
| "case"
| "enum"
| "instanceof"
| "return"
| "transient"
| "catch"
| "extends"
| "int"
| "short"
| "try"
| "char"
| "final"
| "interface"
| "static"
| "void"
| "class"
| "finally"
| "long"
| "strictfp"
| "volatile"
| "const"
| "float"
| "native"
| "super"
| "while"
| "_"
)
}
#[inline]
fn is_java_boolean_literal(&self, name: &str) -> bool {
matches!(name, "true" | "false")
}
#[inline]
fn is_java_null_literal(&self, name: &str) -> bool {
matches!(name, "null")
}
fn santitize_itentifier(&self, name: &str) -> String {
let mut chars = name.chars();
let first_char = chars
.next()
.map(|c| if self.is_java_letter(c) { c } else { '_' });
let rest: String = chars
.filter_map(|c| match c {
'-' => Some('_'),
c if self.is_java_letter_or_number(c) => Some(c),
_ => None,
})
.collect();
let name: String = first_char.into_iter().chain(rest.chars()).collect();
if self.is_java_reserved_keyword(&name)
|| self.is_java_boolean_literal(&name)
|| self.is_java_null_literal(&name)
{
format!("_{name}")
} else {
name
}
}
fn assert_java_identifier<T: AsRef<str>>(&self, name: T) -> anyhow::Result<T> {
let mut chars = name.as_ref().chars();
match chars.next() {
Some(char) if self.is_java_letter(char) => {}
Some(char) => {
return Err(AssertJavaIdentifierError::InvalidCharacter {
name: name.as_ref().to_string(),
char,
}
.into());
}
None => return Err(AssertJavaIdentifierError::EmptyString.into()),
}
for char in chars {
if !self.is_java_letter_or_number(char) {
return Err(AssertJavaIdentifierError::InvalidCharacter {
name: name.as_ref().to_string(),
char,
}
.into());
}
}
Ok(name)
}
fn make_anonymous_struct_name_for_enum(
&self,
enum_shared: &RustEnumShared,
ty: &TypeName,
) -> String {
self.santitize_itentifier(&format!("{}{}Inner", enum_shared.id.renamed, ty))
}
fn write_element(
&self,
w: &mut impl Write,
f: &RustField,
generic_types: &[TypeName],
) -> anyhow::Result<()> {
self.write_multiline_comment(w, 1, &f.comments)?;
let ty = self.format_type(&f.ty, generic_types)?;
write!(
w,
"{}{} {}",
self.indent_whitespace(1),
ty,
self.santitize_itentifier(f.id.renamed.as_str()),
)
.map_err(|err| err.into())
}
fn write_unit_enum(
&self,
w: &mut impl Write,
shared: &RustEnumShared,
unit_variants: &[RustEnumVariantShared],
) -> anyhow::Result<()> {
if !shared.generic_types.is_empty() {
todo!("generic types on unit enums are not supported yet");
}
writeln!(
w,
"public enum {}{} {{",
self.config.prefix.as_ref().unwrap_or(&String::default()),
self.santitize_itentifier(shared.id.renamed.as_str()),
)?;
if let Some((last_variant, variants)) = unit_variants.split_last() {
for variant in variants {
self.write_multiline_comment(w, 1, &variant.comments)?;
writeln!(
w,
"{}{},",
self.indent_whitespace(1),
self.santitize_itentifier(variant.id.renamed.as_str()),
)?;
}
self.write_multiline_comment(w, 1, &last_variant.comments)?;
writeln!(
w,
"{}{}",
self.indent_whitespace(1),
self.santitize_itentifier(last_variant.id.renamed.as_str()),
)?;
}
writeln!(w, "}}")?;
Ok(())
}
fn write_algebraic_enum(
&self,
w: &mut impl Write,
shared: &RustEnumShared,
tag_key: &str,
content_key: &str,
variants: &[RustEnumVariant],
) -> anyhow::Result<()> {
match self.config.serializer {
JavaSerializerOptions::None => {
Err(WriteEnumError::SerializerRequiredForAlgebraicEnums {
name: shared.id.original.to_string(),
}
.into())
}
JavaSerializerOptions::Gson => {
self.write_algebraic_enum_gson(w, shared, tag_key, content_key, variants)
}
}
}
fn write_struct_types_for_enum_variants_gson(
&self,
w: &mut impl Write,
e: &RustEnum,
) -> anyhow::Result<()> {
self.write_struct_types_for_enum_variants(w, e, &|ty| {
self.make_anonymous_struct_name_for_enum(e.shared(), ty)
})
}
fn write_algebraic_enum_gson(
&self,
w: &mut impl Write,
shared: &RustEnumShared,
tag_key: &str,
content_key: &str,
variants: &[RustEnumVariant],
) -> anyhow::Result<()> {
if !shared.generic_types.is_empty() {
todo!("generic types on unit enums are not supported yet");
}
let java_enum_identifier = self.santitize_itentifier(&format!(
"{}{}",
self.config.prefix.as_ref().unwrap_or(&String::default()),
shared.id.renamed.as_str(),
));
let java_enum_adapter_identifier = format!("_{java_enum_identifier}Adapter");
writeln!(
w,
"@com.google.gson.annotations.JsonAdapter({java_enum_adapter_identifier}.class)",
)?;
writeln!(w, "public sealed interface {java_enum_identifier}")?;
let (variant_last, variants_rest) = variants
.split_last()
.expect("algebraic enum should have at least one variant");
writeln!(w, "{}permits", self.indent_whitespace(1))?;
for variant in variants_rest {
writeln!(
w,
"{}{}.{},",
self.indent_whitespace(2),
java_enum_identifier,
self.santitize_itentifier(variant.shared().id.renamed.as_str()),
)?;
}
writeln!(
w,
"{}{}.{} {{",
self.indent_whitespace(2),
java_enum_identifier,
self.santitize_itentifier(variant_last.shared().id.renamed.as_str()),
)?;
writeln!(w)?;
let mut indented_writer = self.config.indent.to_indented_writer(w);
for variant in variants {
self.write_multiline_comment(&mut indented_writer, 0, &variant.shared().comments)?;
match variant {
RustEnumVariant::Unit(shared) => self.write_algebraic_enum_unit_variant_gson(
&mut indented_writer,
shared,
&java_enum_identifier,
&java_enum_adapter_identifier,
)?,
RustEnumVariant::Tuple {
ty: variant_ty,
shared: variant_shared,
} => {
self.write_algebraic_enum_newtype_tuple_variant_gson(
&mut indented_writer,
shared,
variant_shared,
content_key,
variant_ty,
&java_enum_identifier,
&java_enum_adapter_identifier,
)?;
}
RustEnumVariant::AnonymousStruct {
fields: _vairant_fields,
shared: variant_shared,
} => {
self.write_algebraic_enum_anonymous_struct_variant_gson(
&mut indented_writer,
shared,
variant_shared,
content_key,
&java_enum_identifier,
&java_enum_adapter_identifier,
)?;
}
_ => return Err(WriteEnumError::UnsupportedEnumVariant(variant.clone()).into()),
}
writeln!(indented_writer)?;
}
writeln!(w, "}}")?;
writeln!(w)?;
self.write_algebraic_enum_adapter_gson(
w,
shared,
tag_key,
content_key,
variants,
&java_enum_identifier,
&java_enum_adapter_identifier,
)?;
Ok(())
}
fn write_algebraic_enum_unit_variant_gson(
&self,
w: &mut impl Write,
variant_shared: &RustEnumVariantShared,
java_enum_identifier: &str,
java_enum_adapter_identifier: &str,
) -> anyhow::Result<()> {
writeln!(
w,
"@com.google.gson.annotations.JsonAdapter({java_enum_adapter_identifier}.class)",
)?;
writeln!(
w,
"public record {}() implements {java_enum_identifier} {{}}",
self.santitize_itentifier(variant_shared.id.renamed.as_str()),
)?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn write_algebraic_enum_newtype_tuple_variant_gson(
&self,
w: &mut impl Write,
enum_shared: &RustEnumShared,
variant_shared: &RustEnumVariantShared,
content_key: &str,
ty: &RustType,
java_enum_identifier: &str,
java_enum_adapter_identifier: &str,
) -> anyhow::Result<()> {
let ty = self.format_type(ty, enum_shared.generic_types.as_ref())?;
writeln!(
w,
"@com.google.gson.annotations.JsonAdapter({java_enum_adapter_identifier}.class)",
)?;
writeln!(
w,
"public record {}({} {}) implements {} {{}}",
self.santitize_itentifier(variant_shared.id.renamed.as_str()),
ty,
self.assert_java_identifier(content_key)?,
java_enum_identifier,
)?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn write_algebraic_enum_anonymous_struct_variant_gson(
&self,
w: &mut impl Write,
enum_shared: &RustEnumShared,
variant_shared: &RustEnumVariantShared,
content_key: &str,
java_enum_identifier: &str,
java_enum_adapter_identifier: &str,
) -> anyhow::Result<()> {
let ty_inner =
self.make_anonymous_struct_name_for_enum(enum_shared, &variant_shared.id.original);
writeln!(
w,
"@com.google.gson.annotations.JsonAdapter({java_enum_adapter_identifier}.class)",
)?;
writeln!(
w,
"public record {}({} {}) implements {} {{}}",
self.santitize_itentifier(variant_shared.id.renamed.as_str()),
ty_inner,
self.assert_java_identifier(content_key)?,
java_enum_identifier,
)?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn write_algebraic_enum_adapter_gson(
&self,
w: &mut impl Write,
shared: &RustEnumShared,
tag_key: &str,
content_key: &str,
variants: &[RustEnumVariant],
java_enum_identifier: &str,
java_enum_adapter_identifier: &str,
) -> anyhow::Result<()> {
writeln!(
w,
"private final class {java_enum_adapter_identifier} extends com.google.gson.TypeAdapter<{java_enum_identifier}> {{",
)?;
writeln!(w)?;
writeln!(
w,
"{}private static com.google.gson.Gson gson = new com.google.gson.Gson();",
self.indent_whitespace(1),
)?;
writeln!(w)?;
let mut indented_writer = self.config.indent.to_indented_writer(w);
self.write_algebraic_enum_adapter_write_method_gson(
&mut indented_writer,
tag_key,
content_key,
variants,
java_enum_identifier,
)?;
writeln!(indented_writer)?;
self.write_algebraic_enum_adapter_read_method_gson(
&mut indented_writer,
shared,
tag_key,
content_key,
variants,
java_enum_identifier,
)?;
writeln!(indented_writer)?;
writeln!(w, "}}")?;
Ok(())
}
fn write_algebraic_enum_adapter_write_method_gson(
&self,
w: &mut impl Write,
tag_key: &str,
content_key: &str,
variants: &[RustEnumVariant],
java_enum_identifier: &str,
) -> anyhow::Result<()> {
writeln!(w, "@Override")?;
writeln!(w, "public void write(")?;
writeln!(
w,
"{}com.google.gson.stream.JsonWriter out,",
self.indent_whitespace(1),
)?;
writeln!(
w,
"{}{java_enum_identifier} value",
self.indent_whitespace(1),
)?;
writeln!(w, ") throws java.io.IOException {{")?;
let mut indented_writer = self.config.indent.to_indented_writer(w);
for variant in variants {
writeln!(
indented_writer,
"if (value instanceof {java_enum_identifier}.{}) {{",
self.santitize_itentifier(variant.shared().id.renamed.as_str()),
)?;
indented_writer.indent();
match variant {
RustEnumVariant::Unit(variant_shared) => {
writeln!(indented_writer, "out.beginObject();")?;
writeln!(indented_writer, "out.name(\"{tag_key}\");")?;
writeln!(
indented_writer,
"out.value(\"{}\");",
variant_shared.id.renamed
)?;
writeln!(indented_writer, "out.endObject();")?;
writeln!(indented_writer, "return;")?;
}
RustEnumVariant::Tuple {
shared: variant_shared,
..
}
| RustEnumVariant::AnonymousStruct {
shared: variant_shared,
..
} => {
writeln!(
indented_writer,
"var content = (({java_enum_identifier}.{}) value).{}();",
self.santitize_itentifier(variant_shared.id.renamed.as_str()),
self.assert_java_identifier(content_key)?,
)?;
writeln!(indented_writer, "out.beginObject();")?;
writeln!(indented_writer, "out.name(\"{tag_key}\");")?;
writeln!(
indented_writer,
"out.value(\"{}\");",
variant_shared.id.renamed
)?;
writeln!(indented_writer, "out.name(\"{content_key}\");")?;
writeln!(
indented_writer,
"gson.toJson(gson.toJsonTree(content), out);"
)?;
writeln!(indented_writer, "out.endObject();")?;
writeln!(indented_writer, "return;")?;
}
_ => return Err(WriteEnumError::UnsupportedEnumVariant(variant.clone()).into()),
}
indented_writer.dedent();
writeln!(indented_writer, "}}")?;
writeln!(indented_writer)?;
}
writeln!(
indented_writer,
"throw new RuntimeException(\"unreachable!\");"
)?;
writeln!(w, "}}")?;
Ok(())
}
fn write_algebraic_enum_adapter_read_method_gson(
&self,
w: &mut impl Write,
enum_shared: &RustEnumShared,
tag_key: &str,
content_key: &str,
variants: &[RustEnumVariant],
java_enum_identifier: &str,
) -> anyhow::Result<()> {
writeln!(w, "@Override")?;
writeln!(w, "public {java_enum_identifier} read(")?;
writeln!(
w,
"{}com.google.gson.stream.JsonReader in",
self.indent_whitespace(1),
)?;
writeln!(w, ") throws java.io.IOException {{")?;
let mut indented_writer = self.config.indent.to_indented_writer(w);
writeln!(
indented_writer,
"JsonObject jsonObject = gson.fromJson(in, JsonObject.class);"
)?;
writeln!(
indented_writer,
"JsonElement tagElement = jsonObject.get(\"{tag_key}\");",
)?;
writeln!(indented_writer)?;
writeln!(indented_writer, "if (tagElement == null) {{")?;
writeln!(
indented_writer,
"{}throw new java.io.IOException(\"Missing '{tag_key}' field for {java_enum_identifier}\");",
self.indent_whitespace(1),
)?;
writeln!(indented_writer, "}}")?;
writeln!(indented_writer)?;
writeln!(indented_writer, "if (!tagElement.isJsonPrimitive()) {{")?;
writeln!(
indented_writer,
"{}throw new java.io.IOException(\"Invalid '{tag_key}' field for {java_enum_identifier}\");",
self.indent_whitespace(1),
)?;
writeln!(indented_writer, "}}")?;
writeln!(indented_writer)?;
writeln!(indented_writer, "String tag = tagElement.getAsString();")?;
writeln!(indented_writer)?;
writeln!(indented_writer, "return switch (tag) {{")?;
indented_writer.indent();
for variant in variants {
match variant {
RustEnumVariant::Unit(variant_shared) => {
writeln!(
indented_writer,
"case \"{}\" -> new {java_enum_identifier}.{}();",
variant_shared.id.renamed,
self.santitize_itentifier(variant_shared.id.renamed.as_str()),
)?;
}
RustEnumVariant::Tuple {
ty,
shared: variant_shared,
} => {
writeln!(
indented_writer,
"case \"{}\" -> {{",
variant_shared.id.renamed
)?;
indented_writer.indent();
writeln!(
indented_writer,
"JsonElement contentElement = jsonObject.get(\"{content_key}\");",
)?;
match ty {
RustType::Special(SpecialRustType::Option(_)) => {}
_ => {
writeln!(indented_writer, "if (contentElement == null) {{")?;
writeln!(
indented_writer,
"\tthrow new java.io.IOException(\"'{}' variant missing '{}'\");",
variant_shared.id.renamed, content_key,
)?;
writeln!(indented_writer, "}}")?;
}
}
if !enum_shared.generic_types.is_empty() {
todo!("algebraic enums with generic type parameters are not yet supported");
}
let java_ty = self.format_type(ty, &[])?;
match ty {
RustType::Special(SpecialRustType::Vec(_))
| RustType::Special(SpecialRustType::Array(_, _))
| RustType::Special(SpecialRustType::Slice(_))
| RustType::Special(SpecialRustType::HashMap(_, _)) => {
writeln!(
indented_writer,
"com.google.gson.reflect.TypeToken<{java_ty}> contentType = new com.google.gson.reflect.TypeToken<{java_ty}>() {{}};"
)?;
writeln!(
indented_writer,
"{java_ty} content = gson.fromJson(contentElement, contentType);",
)?
}
RustType::Special(SpecialRustType::Unit) => {
todo!("algebraic enum variants with unit values are not supported yet")
}
RustType::Generic { .. } => {
writeln!(
indented_writer,
"com.google.gson.reflect.TypeToken<{java_ty}> contentType = new com.google.gson.reflect.TypeToken<{java_ty}>() {{}};"
)?;
writeln!(
indented_writer,
"{java_ty} content = gson.fromJson(contentElement, contentType);",
)?
}
_ => writeln!(
indented_writer,
"{java_ty} content = gson.fromJson(contentElement, {java_ty}.class);",
)?,
}
writeln!(
indented_writer,
"yield new {java_enum_identifier}.{}(content);",
self.santitize_itentifier(variant_shared.id.renamed.as_str()),
)?;
indented_writer.dedent();
writeln!(indented_writer, "}}")?;
}
RustEnumVariant::AnonymousStruct {
shared: variant_shared,
..
} => {
writeln!(
indented_writer,
"case \"{}\" -> {{",
variant_shared.id.renamed
)?;
indented_writer.indent();
writeln!(
indented_writer,
"JsonElement contentElement = jsonObject.get(\"{content_key}\");",
)?;
writeln!(indented_writer, "if (contentElement == null) {{")?;
writeln!(
indented_writer,
"\tthrow new java.io.IOException(\"'{}' variant missing '{}'\");",
variant_shared.id.renamed, content_key,
)?;
writeln!(indented_writer, "}}")?;
let ty_inner = self.make_anonymous_struct_name_for_enum(
enum_shared,
&variant_shared.id.original,
);
writeln!(
indented_writer,
"{ty_inner} content = gson.fromJson(contentElement, {ty_inner}.class);",
)?;
writeln!(
indented_writer,
"yield new {java_enum_identifier}.{}(content);",
self.santitize_itentifier(variant_shared.id.renamed.as_str()),
)?;
indented_writer.dedent();
writeln!(indented_writer, "}}")?;
}
_ => return Err(WriteEnumError::UnsupportedEnumVariant(variant.clone()).into()),
}
}
writeln!(
indented_writer,
"default -> throw new java.io.IOException(\"Unknown variant: \" + tag);",
)?;
indented_writer.dedent();
writeln!(indented_writer, "}};")?;
writeln!(w, "}}")?;
Ok(())
}
fn write_annotations(
&self,
w: &mut impl Write,
decorator_set: &DecoratorSet,
) -> anyhow::Result<()> {
for decorator_value in decorator_set.get_all(Self::NAME) {
if let decorator::Value::Nested(decorator_set) = decorator_value {
let annotations = decorator_set.get_all("annotations");
self.write_java_annotations(w, annotations)?;
}
}
Ok(())
}
fn write_java_annotations(
&self,
w: &mut impl Write,
annotations: &[decorator::Value],
) -> anyhow::Result<()> {
for annotation in annotations {
match annotation {
decorator::Value::String(annotations) => {
for annotation in annotations
.split("\n")
.map(str::trim)
.filter(|str| !str.is_empty())
{
writeln!(w, "{annotation}")?;
}
}
_ => return Err(WriteDecoratorError::InvalidAnnotation(annotation.clone()).into()),
}
}
Ok(())
}
fn write_multiline_comment_line(
&self,
w: &mut impl Write,
indent: usize,
comment: &str,
) -> std::io::Result<()> {
writeln!(w, "{} * {}", self.indent_whitespace(indent), comment,)?;
Ok(())
}
fn write_multiline_comment(
&self,
w: &mut impl Write,
indent: usize,
comment_lines: &[String],
) -> std::io::Result<()> {
if comment_lines.is_empty() {
return Ok(());
}
writeln!(w, "{}/**", self.indent_whitespace(indent))?;
comment_lines
.iter()
.try_for_each(|comment| self.write_multiline_comment_line(w, indent, comment))?;
writeln!(w, "{} */", self.indent_whitespace(indent))?;
Ok(())
}
}