use crate::type_map::{java_boxed_type, java_type};
use ahash::AHashSet;
use alef_codegen::naming::to_class_name;
use alef_core::hash::{self, CommentStyle};
use alef_core::ir::{DefaultValue, EnumDef, PrimitiveType, TypeDef, TypeRef};
use heck::{ToLowerCamelCase, ToSnakeCase};
use std::fmt::Write;
use super::helpers::{
RECORD_LINE_WRAP_THRESHOLD, emit_javadoc, escape_javadoc_line, format_optional_value, is_tuple_field_name,
java_apply_rename_all, safe_java_field_name,
};
pub(crate) fn gen_record_type(
package: &str,
typ: &TypeDef,
complex_enums: &AHashSet<String>,
lang_rename_all: &str,
has_visitor_pattern: bool,
) -> String {
struct FieldEntry {
decl: String,
doc: String,
}
let mut field_entries: Vec<FieldEntry> = Vec::with_capacity(typ.fields.len());
let mut fields_joined = String::with_capacity(typ.fields.len().saturating_mul(42));
for (i, f) in typ.fields.iter().enumerate() {
let is_complex = matches!(&f.ty, TypeRef::Named(n) if complex_enums.contains(n.as_str()));
let is_visitor_field = has_visitor_pattern && typ.name == "ConversionOptions" && f.name == "visitor";
let ftype = if is_visitor_field {
"Visitor".to_string()
} else if is_complex {
"Object".to_string()
} else if f.optional {
java_boxed_type(&f.ty).to_string()
} else {
java_type(&f.ty).to_string()
};
let jname = safe_java_field_name(&f.name);
let needs_non_null = !f.optional && matches!(&f.ty, TypeRef::Vec(_));
let has_json_property = lang_rename_all == "camelCase" && f.name.contains('_');
let has_nullable = f.optional;
let mut decl = String::new();
if is_visitor_field {
decl.push_str("@JsonIgnore ");
}
let nullable_at_leading_pos = has_nullable && !ftype.contains('.');
if nullable_at_leading_pos {
decl.push_str("@Nullable ");
}
if needs_non_null {
decl.push_str("@JsonInclude(JsonInclude.Include.NON_NULL) ");
}
if has_json_property && !is_visitor_field {
decl.push_str(&format!("@JsonProperty(\"{}\") ", f.name));
}
if has_nullable && !nullable_at_leading_pos {
if let Some(idx) = ftype.rfind('.') {
let (pkg, simple) = ftype.split_at(idx);
let simple = simple.trim_start_matches('.');
decl.push_str(&format!("{pkg}.@Nullable {simple} {jname}"));
} else {
decl.push_str(&format!("@Nullable {ftype} {jname}"));
}
} else {
decl.push_str(&format!("{} {}", ftype, jname));
}
if i > 0 {
fields_joined.push_str(", ");
}
fields_joined.push_str(&decl);
field_entries.push(FieldEntry {
decl,
doc: f.doc.clone(),
});
}
let single_line_len = "public record ".len() + typ.name.len() + 1 + fields_joined.len() + ") { }".len();
let mut record_block = String::new();
emit_javadoc(&mut record_block, &typ.doc, "");
writeln!(record_block, "@SuppressWarnings(\"checkstyle:LineLength\")").ok();
if typ.has_serde {
writeln!(record_block, "@JsonInclude(JsonInclude.Include.NON_ABSENT)").ok();
}
if typ.has_default {
writeln!(record_block, "@JsonDeserialize(builder = {}Builder.class)", typ.name).ok();
}
if single_line_len > RECORD_LINE_WRAP_THRESHOLD && field_entries.len() > 1 {
writeln!(record_block, "public record {}(", typ.name).ok();
for (i, entry) in field_entries.iter().enumerate() {
let comma = if i < field_entries.len() - 1 { "," } else { "" };
if !entry.doc.is_empty() {
let doc_summary = escape_javadoc_line(entry.doc.lines().next().unwrap_or("").trim());
writeln!(record_block, " /** {doc_summary} */").ok();
}
let line_with_indent_and_comma = format!(" {}{}", entry.decl, comma);
if line_with_indent_and_comma.len() > 120 {
let mut anno_lines = Vec::new();
let mut rest_of_decl = entry.decl.as_str();
loop {
let trimmed = rest_of_decl.trim_start();
if trimmed.starts_with('@') {
if let Some(paren_pos) = trimmed.find('(') {
let mut paren_count = 1;
let after_paren = &trimmed[paren_pos + 1..];
let mut end_pos = paren_pos + 1;
for ch in after_paren.chars() {
if ch == '(' {
paren_count += 1;
} else if ch == ')' {
paren_count -= 1;
if paren_count == 0 {
break;
}
}
end_pos += 1;
}
let anno = trimmed[..end_pos + 1].trim_end();
anno_lines.push(anno.to_string());
rest_of_decl = &trimmed[end_pos + 1..];
} else {
let space_pos = trimmed.find(' ').unwrap_or(trimmed.len());
anno_lines.push(trimmed[..space_pos].to_string());
rest_of_decl = &trimmed[space_pos..];
}
} else {
break;
}
}
for anno in anno_lines {
writeln!(record_block, " {}", anno).ok();
}
let rest_trimmed = rest_of_decl.trim();
let final_line = format!(" {}{}", rest_trimmed, comma);
if final_line.len() > 120 && rest_trimmed.contains(' ') {
if let Some(space_idx) = rest_trimmed.rfind(' ') {
let field_type = &rest_trimmed[..space_idx];
let field_name = &rest_trimmed[space_idx + 1..];
writeln!(record_block, " {}", field_type).ok();
writeln!(record_block, " {}{}", field_name, comma).ok();
} else {
writeln!(record_block, " {}{}", rest_trimmed, comma).ok();
}
} else {
writeln!(record_block, " {}{}", rest_trimmed, comma).ok();
}
} else {
writeln!(record_block, " {}{}", entry.decl, comma).ok();
}
}
writeln!(record_block, ") {{").ok();
} else {
writeln!(record_block, "public record {}({}) {{", typ.name, fields_joined).ok();
}
if typ.has_default {
writeln!(record_block, " public static {}Builder builder() {{", typ.name).ok();
writeln!(record_block, " return new {}Builder();", typ.name).ok();
writeln!(record_block, " }}").ok();
}
let compact_ctor_lines: Vec<String> = typ
.fields
.iter()
.filter(|f| !f.optional)
.filter_map(|f| {
let jname = safe_java_field_name(&f.name);
match &f.typed_default {
Some(DefaultValue::IntLiteral(n)) if *n != 0 => {
let suffix = if matches!(f.ty, TypeRef::Duration) { "L" } else { "" };
Some(format!(" if ({jname} == 0) {jname} = {n}{suffix};"))
}
_ => None,
}
})
.collect();
if !compact_ctor_lines.is_empty() {
writeln!(record_block, " public {}{{", typ.name).ok();
for line in &compact_ctor_lines {
writeln!(record_block, "{line}").ok();
}
writeln!(record_block, " }}").ok();
}
writeln!(record_block, "}}").ok();
let needs_json_property = fields_joined.contains("@JsonProperty(");
let needs_json_include = fields_joined.contains("@JsonInclude(") || record_block.contains("@JsonInclude(");
let needs_json_deserialize = record_block.contains("@JsonDeserialize(");
let needs_json_ignore = fields_joined.contains("@JsonIgnore");
let needs_nullable = fields_joined.contains("@Nullable");
let _needs_transient = fields_joined.contains("@Transient");
let needs_optional = fields_joined.contains("Optional<");
let mut out = String::with_capacity(record_block.len() + 512);
out.push_str(&hash::header(CommentStyle::DoubleSlash));
writeln!(out, "package {};", package).ok();
writeln!(out).ok();
if fields_joined.contains("List<") {
writeln!(out, "import java.util.List;").ok();
}
if fields_joined.contains("Map<") {
writeln!(out, "import java.util.Map;").ok();
}
if needs_optional {
writeln!(out, "import java.util.Optional;").ok();
}
if needs_json_property {
writeln!(out, "import com.fasterxml.jackson.annotation.JsonProperty;").ok();
}
if needs_json_include {
writeln!(out, "import com.fasterxml.jackson.annotation.JsonInclude;").ok();
}
if needs_json_deserialize {
writeln!(out, "import com.fasterxml.jackson.databind.annotation.JsonDeserialize;").ok();
}
if needs_json_ignore {
writeln!(out, "import com.fasterxml.jackson.annotation.JsonIgnore;").ok();
}
if needs_nullable {
writeln!(out, "import org.jspecify.annotations.Nullable;").ok();
}
writeln!(out).ok();
write!(out, "{}", record_block).ok();
out
}
pub(crate) fn gen_enum_class(package: &str, enum_def: &EnumDef) -> String {
let has_data_variants = enum_def.variants.iter().any(|v| !v.fields.is_empty());
if enum_def.serde_tag.is_some() && has_data_variants {
return gen_java_tagged_union(package, enum_def);
}
let mut out = String::with_capacity(1024);
out.push_str(&hash::header(CommentStyle::DoubleSlash));
writeln!(out, "package {};", package).ok();
writeln!(out).ok();
writeln!(out, "import com.fasterxml.jackson.annotation.JsonCreator;").ok();
writeln!(out, "import com.fasterxml.jackson.annotation.JsonValue;").ok();
writeln!(out).ok();
emit_javadoc(&mut out, &enum_def.doc, "");
writeln!(out, "@SuppressWarnings(\"checkstyle:LineLength\")").ok();
writeln!(out, "public enum {} {{", enum_def.name).ok();
for (i, variant) in enum_def.variants.iter().enumerate() {
let comma = if i < enum_def.variants.len() - 1 { "," } else { ";" };
let json_name = variant
.serde_rename
.clone()
.unwrap_or_else(|| java_apply_rename_all(&variant.name, enum_def.serde_rename_all.as_deref()));
if !variant.doc.is_empty() {
let doc_summary = escape_javadoc_line(variant.doc.lines().next().unwrap_or("").trim());
if doc_summary.len() + 11 > 80 {
writeln!(out, " /**").ok();
writeln!(out, " * {doc_summary}").ok();
writeln!(out, " */").ok();
} else {
writeln!(out, " /** {doc_summary} */").ok();
}
}
writeln!(out, " {}(\"{}\"){}", variant.name, json_name, comma).ok();
}
writeln!(out).ok();
writeln!(out, " /** The string value. */").ok();
writeln!(out, " private final String value;").ok();
writeln!(out).ok();
writeln!(out, " {}(final String value) {{", enum_def.name).ok();
writeln!(out, " this.value = value;").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(out, " /** Returns the string value. */").ok();
writeln!(out, " @JsonValue").ok();
writeln!(out, " public String getValue() {{").ok();
writeln!(out, " return value;").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(out, " /** Creates an instance from a string value. */").ok();
writeln!(out, " @JsonCreator").ok();
writeln!(
out,
" public static {} fromValue(final String value) {{",
enum_def.name
)
.ok();
writeln!(out, " for ({} e : values()) {{", enum_def.name).ok();
writeln!(out, " if (e.value.equalsIgnoreCase(value)) {{").ok();
writeln!(out, " return e;").ok();
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
writeln!(
out,
" throw new IllegalArgumentException(\"Unknown value: \" + value);"
)
.ok();
writeln!(out, " }}").ok();
writeln!(out, "}}").ok();
out
}
pub(crate) fn gen_java_tagged_union(package: &str, enum_def: &EnumDef) -> String {
let tag_field = enum_def.serde_tag.as_deref().unwrap_or("type");
let variant_names: std::collections::HashSet<&str> = enum_def.variants.iter().map(|v| v.name.as_str()).collect();
let optional_type = if variant_names.contains("Optional") {
"java.util.Optional"
} else {
"Optional"
};
let needs_json_property = enum_def
.variants
.iter()
.any(|v| v.fields.iter().any(|f| !is_tuple_field_name(&f.name)));
let has_data_variants = enum_def
.variants
.iter()
.any(|v| !v.fields.is_empty() && is_tuple_field_name(&v.fields[0].name));
let mut out = String::with_capacity(2048);
out.push_str(&hash::header(CommentStyle::DoubleSlash));
writeln!(out, "package {};", package).ok();
writeln!(out).ok();
if needs_json_property {
writeln!(out, "import com.fasterxml.jackson.annotation.JsonProperty;").ok();
}
writeln!(out, "import com.fasterxml.jackson.annotation.JsonSubTypes;").ok();
writeln!(out, "import com.fasterxml.jackson.annotation.JsonTypeInfo;").ok();
let needs_list = !variant_names.contains("List")
&& enum_def
.variants
.iter()
.any(|v| v.fields.iter().any(|f| matches!(&f.ty, TypeRef::Vec(_))));
let needs_map = !variant_names.contains("Map")
&& enum_def
.variants
.iter()
.any(|v| v.fields.iter().any(|f| matches!(&f.ty, TypeRef::Map(_, _))));
let needs_optional =
!variant_names.contains("Optional") && enum_def.variants.iter().any(|v| v.fields.iter().any(|f| f.optional));
if needs_list {
writeln!(out, "import java.util.List;").ok();
}
if needs_map {
writeln!(out, "import java.util.Map;").ok();
}
if needs_optional {
writeln!(out, "import java.util.Optional;").ok();
}
if has_data_variants {
writeln!(out, "import org.jspecify.annotations.Nullable;").ok();
}
writeln!(out).ok();
emit_javadoc(&mut out, &enum_def.doc, "");
writeln!(out, "@SuppressWarnings(\"checkstyle:LineLength\")").ok();
writeln!(
out,
"@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = \"{tag_field}\", visible = false)"
)
.ok();
writeln!(out, "@JsonSubTypes({{").ok();
for (i, variant) in enum_def.variants.iter().enumerate() {
let discriminator = variant
.serde_rename
.clone()
.unwrap_or_else(|| java_apply_rename_all(&variant.name, enum_def.serde_rename_all.as_deref()));
let comma = if i < enum_def.variants.len() - 1 { "," } else { "" };
writeln!(
out,
" @JsonSubTypes.Type(value = {}.{}.class, name = \"{}\"){}",
enum_def.name, variant.name, discriminator, comma
)
.ok();
}
writeln!(out, "}})").ok();
writeln!(
out,
"@com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)"
)
.ok();
writeln!(out, "public sealed interface {} {{", enum_def.name).ok();
for variant in &enum_def.variants {
writeln!(out).ok();
if variant.fields.is_empty() {
if !variant.doc.is_empty() {
let doc_summary = escape_javadoc_line(variant.doc.lines().next().unwrap_or("").trim());
writeln!(out, " /** {doc_summary} */").ok();
}
writeln!(out, " record {}() implements {} {{", variant.name, enum_def.name).ok();
writeln!(out, " }}").ok();
} else {
let field_parts: Vec<String> = variant
.fields
.iter()
.map(|f| {
let ftype = if f.optional {
let inner = java_boxed_type(&f.ty);
let inner_str = inner.as_ref();
let inner_qualified = if inner_str.starts_with("List<") && variant_names.contains("List") {
inner_str.replacen("List<", "java.util.List<", 1)
} else if inner_str.starts_with("Map<") && variant_names.contains("Map") {
inner_str.replacen("Map<", "java.util.Map<", 1)
} else {
inner_str.to_string()
};
format!("{optional_type}<{inner_qualified}>")
} else {
let t = java_type(&f.ty);
let t_str = t.as_ref();
if t_str.starts_with("List<") && variant_names.contains("List") {
t_str.replacen("List<", "java.util.List<", 1)
} else if t_str.starts_with("Map<") && variant_names.contains("Map") {
t_str.replacen("Map<", "java.util.Map<", 1)
} else {
t_str.to_string()
}
};
if is_tuple_field_name(&f.name) {
format!("{ftype} value")
} else {
let json_name = f.name.trim_start_matches('_');
let jname = safe_java_field_name(json_name);
format!("@JsonProperty(\"{json_name}\") {ftype} {jname}")
}
})
.collect();
let fields_joined: String = field_parts.join(", ");
let single_len = " record ".len()
+ variant.name.len()
+ 1
+ fields_joined.len()
+ ") implements ".len()
+ enum_def.name.len()
+ " { }".len();
if !variant.doc.is_empty() {
let doc_summary = escape_javadoc_line(variant.doc.lines().next().unwrap_or("").trim());
writeln!(out, " /** {doc_summary} */").ok();
}
if single_len > RECORD_LINE_WRAP_THRESHOLD && field_parts.len() > 1 {
writeln!(out, " record {}(", variant.name).ok();
for (i, fp) in field_parts.iter().enumerate() {
let comma = if i < field_parts.len() - 1 { "," } else { "" };
writeln!(out, " {}{}", fp, comma).ok();
}
writeln!(out, " ) implements {} {{", enum_def.name).ok();
writeln!(out, " }}").ok();
} else {
writeln!(
out,
" record {}({}) implements {} {{ }}",
variant.name, fields_joined, enum_def.name
)
.ok();
}
}
}
if has_data_variants {
writeln!(out).ok();
for variant in &enum_def.variants {
if variant.fields.is_empty() || !is_tuple_field_name(&variant.fields[0].name) {
continue;
}
let method_name = variant.name.to_lower_camel_case();
let return_type = java_boxed_type(&variant.fields[0].ty);
let variant_name = &variant.name;
writeln!(
out,
" /** Returns the {variant_name} data if this is a {variant_name} variant, otherwise null. */"
)
.ok();
writeln!(out, " default @Nullable {return_type} {method_name}() {{").ok();
writeln!(
out,
" return this instanceof {variant_name} e ? e.value() : null;"
)
.ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
}
}
writeln!(out, "}}").ok();
out
}
pub(crate) fn gen_opaque_handle_class(package: &str, typ: &TypeDef, prefix: &str) -> String {
let mut out = String::with_capacity(1024);
let class_name = &typ.name;
let type_snake = class_name.to_snake_case();
out.push_str(&hash::header(CommentStyle::DoubleSlash));
writeln!(out, "package {};", package).ok();
writeln!(out).ok();
writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
writeln!(out).ok();
emit_javadoc(&mut out, &typ.doc, "");
writeln!(out, "@SuppressWarnings(\"checkstyle:LineLength\")").ok();
writeln!(out, "public class {} implements AutoCloseable {{", class_name).ok();
writeln!(out, " private final MemorySegment handle;").ok();
writeln!(out).ok();
writeln!(out, " {}(MemorySegment handle) {{", class_name).ok();
writeln!(out, " this.handle = handle;").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(out, " MemorySegment handle() {{").ok();
writeln!(out, " return this.handle;").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(out, " @Override").ok();
writeln!(out, " public void close() {{").ok();
writeln!(
out,
" if (handle != null && !handle.equals(MemorySegment.NULL)) {{"
)
.ok();
writeln!(out, " try {{").ok();
writeln!(
out,
" NativeLib.{}_{}_FREE.invoke(handle);",
prefix.to_uppercase(),
type_snake.to_uppercase()
)
.ok();
writeln!(out, " }} catch (Throwable e) {{").ok();
writeln!(
out,
" throw new RuntimeException(\"Failed to free {}: \" + e.getMessage(), e);",
class_name
)
.ok();
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
writeln!(out, "}}").ok();
out
}
pub(crate) fn gen_builder_class(package: &str, typ: &TypeDef, has_visitor_pattern: bool) -> String {
let mut body = String::with_capacity(2048);
emit_javadoc(&mut body, &typ.doc, "");
writeln!(body, "@SuppressWarnings(\"checkstyle:LineLength\")").ok();
writeln!(body, "@JsonPOJOBuilder(withPrefix = \"with\")").ok();
writeln!(body, "public class {}Builder {{", typ.name).ok();
writeln!(body).ok();
for field in &typ.fields {
let field_name = safe_java_field_name(&field.name);
if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
|| field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
{
continue;
}
let is_visitor_field = has_visitor_pattern && typ.name == "ConversionOptions" && field.name == "visitor";
let field_type = if is_visitor_field {
"Optional<Visitor>".to_string()
} else if field.optional {
format!("Optional<{}>", java_boxed_type(&field.ty))
} else if matches!(field.ty, TypeRef::Duration) {
java_boxed_type(&field.ty).to_string()
} else {
java_type(&field.ty).to_string()
};
let default_value = if is_visitor_field {
"Optional.empty()".to_string()
} else if field.optional {
if let Some(default) = &field.default {
format_optional_value(&field.ty, default)
} else {
"Optional.empty()".to_string()
}
} else {
if let Some(default) = &field.default {
default.clone()
} else {
match &field.ty {
TypeRef::String | TypeRef::Char | TypeRef::Path => {
match &field.typed_default {
Some(DefaultValue::StringLiteral(s)) => {
let escaped = s
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t");
format!("\"{escaped}\"")
}
_ => "\"\"".to_string(),
}
}
TypeRef::Json => "null".to_string(),
TypeRef::Bytes => "new byte[0]".to_string(),
TypeRef::Primitive(p) => match p {
PrimitiveType::Bool => {
match &field.typed_default {
Some(DefaultValue::BoolLiteral(b)) => b.to_string(),
_ => "false".to_string(),
}
}
PrimitiveType::F32 => "0.0f".to_string(),
PrimitiveType::F64 => "0.0".to_string(),
_ => "0".to_string(),
},
TypeRef::Vec(_) => "List.of()".to_string(),
TypeRef::Map(_, _) => "Map.of()".to_string(),
TypeRef::Optional(_) => "Optional.empty()".to_string(),
TypeRef::Duration => "null".to_string(),
_ => "null".to_string(),
}
}
};
writeln!(body, " private {} {} = {};", field_type, field_name, default_value).ok();
}
writeln!(body).ok();
for field in &typ.fields {
if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
|| field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
{
continue;
}
let field_name = safe_java_field_name(&field.name);
let field_name_pascal = to_class_name(&field.name);
let is_visitor_field = has_visitor_pattern && typ.name == "ConversionOptions" && field.name == "visitor";
let field_type = if is_visitor_field {
"Visitor".to_string()
} else if field.optional {
format!("Optional<{}>", java_boxed_type(&field.ty))
} else if matches!(field.ty, TypeRef::Duration) {
java_boxed_type(&field.ty).to_string()
} else {
java_type(&field.ty).to_string()
};
writeln!(body, " /** Sets the {} field. */", field_name).ok();
writeln!(
body,
" public {}Builder with{}(final {} value) {{",
typ.name, field_name_pascal, field_type
)
.ok();
if is_visitor_field {
writeln!(body, " this.{} = Optional.ofNullable(value);", field_name).ok();
} else {
writeln!(body, " this.{} = value;", field_name).ok();
}
writeln!(body, " return this;").ok();
writeln!(body, " }}").ok();
writeln!(body).ok();
}
writeln!(body, " /** Builds the {} instance. */", typ.name).ok();
writeln!(body, " public {} build() {{", typ.name).ok();
writeln!(body, " return new {}(", typ.name).ok();
let non_tuple_fields: Vec<_> = typ
.fields
.iter()
.filter(|f| {
!(f.name.starts_with('_') && f.name[1..].chars().all(|c| c.is_ascii_digit())
|| f.name.chars().next().is_none_or(|c| c.is_ascii_digit()))
})
.collect();
for (i, field) in non_tuple_fields.iter().enumerate() {
let field_name = safe_java_field_name(&field.name);
let comma = if i < non_tuple_fields.len() - 1 { "," } else { "" };
let is_visitor_field = has_visitor_pattern && typ.name == "ConversionOptions" && field.name == "visitor";
if field.optional || is_visitor_field {
writeln!(body, " {}.orElse(null){}", field_name, comma).ok();
} else {
writeln!(body, " {}{}", field_name, comma).ok();
}
}
writeln!(body, " );").ok();
writeln!(body, " }}").ok();
writeln!(body, "}}").ok();
let mut out = String::with_capacity(body.len() + 512);
out.push_str(&hash::header(CommentStyle::DoubleSlash));
writeln!(out, "package {};", package).ok();
writeln!(out).ok();
if body.contains("List<") {
writeln!(out, "import java.util.List;").ok();
}
if body.contains("Map<") {
writeln!(out, "import java.util.Map;").ok();
}
if body.contains("Optional<") {
writeln!(out, "import java.util.Optional;").ok();
}
if body.contains("@JsonPOJOBuilder") {
writeln!(out, "import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;").ok();
}
writeln!(out).ok();
out.push_str(&body);
out
}