1use convert_case::{Case, Casing as _};
2use itertools::Itertools as _;
3use typeshare_model::Language;
4use typeshare_model::prelude::*;
5
6use crate::config::HeaderComment;
7use crate::config::JavaConfig;
8use crate::error::FormatSpecialTypeError;
9
10#[derive(Debug)]
11pub struct Java {
12 config: JavaConfig,
13}
14
15impl<'config> Language<'config> for Java {
16 type Config = JavaConfig;
17
18 const NAME: &'static str = "java";
19
20 fn new_from_config(config: Self::Config) -> anyhow::Result<Self> {
21 Ok(Self { config })
22 }
23
24 fn output_filename_for_crate(&self, crate_name: &CrateName) -> String {
25 crate_name.as_str().to_case(Case::Pascal) + ".java"
26 }
27
28 fn format_special_type(
29 &self,
30 special_ty: &SpecialRustType,
31 generic_context: &[TypeName],
32 ) -> anyhow::Result<String> {
33 Ok(match special_ty {
34 SpecialRustType::Vec(rtype) => {
35 format!(
36 "java.util.ArrayList<{}>",
37 self.format_type(rtype, generic_context)?
38 )
39 }
40 SpecialRustType::Array(rtype, _) => {
41 format!("{}[]", self.format_type(rtype, generic_context)?)
42 }
43 SpecialRustType::Slice(rtype) => {
44 format!("{}[]", self.format_type(rtype, generic_context)?)
45 }
46 SpecialRustType::HashMap(rtype1, rtype2) => {
47 format!(
48 "java.util.HashMap<{}, {}>",
49 self.format_type(rtype1, generic_context)?,
50 self.format_type(rtype2, generic_context)?
51 )
52 }
53 SpecialRustType::Option(rtype) => self.format_type(rtype, generic_context)?,
54 SpecialRustType::Unit => "Void".into(),
55 SpecialRustType::String | SpecialRustType::Char => "String".into(),
58 SpecialRustType::I8 => "byte".into(),
59 SpecialRustType::I16 => "short".into(),
60 SpecialRustType::ISize | SpecialRustType::I32 => "int".into(),
61 SpecialRustType::I54 | SpecialRustType::I64 => "long".into(),
62 SpecialRustType::U8 => "short".into(),
64 SpecialRustType::U16 => "int".into(),
66 SpecialRustType::USize | SpecialRustType::U32 => "long".into(),
68 SpecialRustType::U53 | SpecialRustType::U64 => "java.math.BigInteger".into(),
70 SpecialRustType::Bool => "boolean".into(),
72 SpecialRustType::F32 => "float".into(),
74 SpecialRustType::F64 => "double".into(),
75 _ => {
76 return Err(
77 FormatSpecialTypeError::UnsupportedSpecialType(special_ty.clone()).into(),
78 );
79 }
80 })
81 }
82
83 fn begin_file(
84 &self,
85 w: &mut impl std::io::Write,
86 mode: FilesMode<&CrateName>,
87 ) -> anyhow::Result<()> {
88 match &self.config.header_comment {
89 HeaderComment::None => {}
90 HeaderComment::Default => {
91 writeln!(w, "/**")?;
92 writeln!(
93 w,
94 " * Generated by typeshare-java {}",
95 env!("CARGO_PKG_VERSION")
96 )?;
97 writeln!(w, " */")?;
98 writeln!(w)?;
99 }
100 HeaderComment::Custom { comment } => {
101 writeln!(w, "/**")?;
102 for comment in comment.split("\n") {
103 writeln!(w, " * {}", comment)?;
104 }
105 writeln!(w, " */")?;
106 writeln!(w)?;
107 }
108 }
109
110 if let Some(package) = &self.config.package {
111 if let FilesMode::Multi(crate_name) = mode {
112 writeln!(w, "package {}.{};", package, crate_name.as_str())?;
113 } else {
114 writeln!(w, "package {};", package)?;
115 }
116 writeln!(w)?;
117 }
118
119 Ok(())
120 }
121
122 fn write_imports<'a, Crates, Types>(
123 &self,
124 writer: &mut impl std::io::Write,
125 _crate_name: &CrateName,
126 imports: Crates,
127 ) -> anyhow::Result<()>
128 where
129 Crates: IntoIterator<Item = (&'a CrateName, Types)>,
130 Types: IntoIterator<Item = &'a TypeName>,
131 {
132 for (path, ty) in imports {
133 for t in ty {
134 writeln!(
135 writer,
136 "import {}.{path}.{t};",
137 self.config
138 .package
139 .as_ref()
140 .map(|package| format!("{package}."))
141 .unwrap_or_default()
142 )?;
143 }
144 }
145 writeln!(writer).map_err(|err| err.into())
146 }
147
148 fn write_type_alias(
149 &self,
150 _w: &mut impl std::io::Write,
151 _t: &RustTypeAlias,
152 ) -> anyhow::Result<()> {
153 todo!("type aliases are not supported yet")
154 }
155
156 fn write_struct(&self, w: &mut impl std::io::Write, rs: &RustStruct) -> anyhow::Result<()> {
157 self.write_comments(w, 0, &rs.comments)?;
158
159 write!(
160 w,
161 "public record {}{}{}(",
162 self.config.prefix.as_ref().unwrap_or(&String::default()),
163 rs.id.renamed,
164 (!rs.generic_types.is_empty())
165 .then(|| format!("<{}>", rs.generic_types.join(", ")))
166 .unwrap_or_default()
167 )?;
168
169 if let Some((last, elements)) = rs.fields.split_last() {
170 writeln!(w)?;
171 for f in elements.iter() {
172 self.write_element(w, f, rs.generic_types.as_slice())?;
173 writeln!(w, ",")?;
174 }
175 self.write_element(w, last, rs.generic_types.as_slice())?;
176 writeln!(w)?;
177 }
178
179 writeln!(w, r") {{}}")?;
180
181 writeln!(w)?;
182
183 Ok(())
184 }
185
186 fn write_enum(&self, w: &mut impl std::io::Write, e: &RustEnum) -> anyhow::Result<()> {
187 self.write_comments(w, 0, &e.shared().comments)?;
190
191 match e {
192 RustEnum::Unit {
193 shared,
194 unit_variants,
195 } => self.write_unit_enum(w, shared, unit_variants),
196 RustEnum::Algebraic { .. } => todo!("algebraic enums are not supported yet"),
197 }
198 }
199
200 fn write_const(&self, _w: &mut impl std::io::Write, _c: &RustConst) -> anyhow::Result<()> {
201 todo!("constants are not supported yet")
202 }
203}
204
205impl Java {
206 #[inline]
207 fn is_java_letter(&self, c: char) -> bool {
208 c.is_ascii_alphabetic() || c == '_' || c == '$'
210 }
211
212 #[inline]
213 fn is_java_letter_or_number(&self, c: char) -> bool {
214 self.is_java_letter(c) || c.is_ascii_digit()
216 }
217
218 #[inline]
219 fn is_java_reserved_keyword(&self, name: &str) -> bool {
220 matches!(
222 name,
223 "abstract"
224 | "continue"
225 | "for"
226 | "new"
227 | "switch"
228 | "assert"
229 | "default"
230 | "if"
231 | "package"
232 | "synchronized"
233 | "boolean"
234 | "do"
235 | "goto"
236 | "private"
237 | "this"
238 | "break"
239 | "double"
240 | "implements"
241 | "protected"
242 | "throw"
243 | "byte"
244 | "else"
245 | "import"
246 | "public"
247 | "throws"
248 | "case"
249 | "enum"
250 | "instanceof"
251 | "return"
252 | "transient"
253 | "catch"
254 | "extends"
255 | "int"
256 | "short"
257 | "try"
258 | "char"
259 | "final"
260 | "interface"
261 | "static"
262 | "void"
263 | "class"
264 | "finally"
265 | "long"
266 | "strictfp"
267 | "volatile"
268 | "const"
269 | "float"
270 | "native"
271 | "super"
272 | "while"
273 | "_"
274 )
275 }
276
277 #[inline]
278 fn is_java_boolean_literal(&self, name: &str) -> bool {
279 matches!(name, "true" | "false")
281 }
282
283 #[inline]
284 fn is_java_null_literal(&self, name: &str) -> bool {
285 matches!(name, "null")
287 }
288
289 fn santitize_itentifier(&self, name: &str) -> String {
290 let mut chars = name.chars();
292
293 let first_char = chars
295 .next()
296 .map(|c| if self.is_java_letter(c) { c } else { '_' });
297
298 let rest: String = chars
300 .filter_map(|c| match c {
301 '-' => Some('_'),
302 c if self.is_java_letter_or_number(c) => Some(c),
303 _ => None,
304 })
305 .collect();
306
307 let name: String = first_char.into_iter().chain(rest.chars()).collect();
309
310 if self.is_java_reserved_keyword(&name)
311 || self.is_java_boolean_literal(&name)
312 || self.is_java_null_literal(&name)
313 {
314 format!("_{name}")
315 } else {
316 name
317 }
318 }
319
320 fn write_element(
321 &self,
322 w: &mut impl std::io::Write,
323 f: &RustField,
324 generic_types: &[TypeName],
325 ) -> anyhow::Result<()> {
326 self.write_comments(w, 1, &f.comments)?;
327 let ty = self.format_type(&f.ty, generic_types)?;
328 write!(
329 w,
330 "\t{} {}",
331 ty,
332 self.santitize_itentifier(&f.id.renamed.as_str()),
333 )
334 .map_err(|err| err.into())
335 }
336
337 fn write_unit_enum(
338 &self,
339 w: &mut impl std::io::Write,
340 shared: &RustEnumShared,
341 unit_variants: &[RustEnumVariantShared],
342 ) -> anyhow::Result<()> {
343 writeln!(
344 w,
345 "public enum {}{} {{",
346 self.config.prefix.as_ref().unwrap_or(&String::default()),
347 &shared.id.renamed
348 )?;
349
350 if let Some((last_variant, variants)) = unit_variants.split_last() {
351 for variant in variants {
352 self.write_comments(w, 1, &variant.comments)?;
353 writeln!(
354 w,
355 "\t{},",
356 self.santitize_itentifier(&variant.id.renamed.as_str()),
357 )?;
358 }
359 self.write_comments(w, 1, &last_variant.comments)?;
360 writeln!(
361 w,
362 "\t{}",
363 self.santitize_itentifier(&last_variant.id.renamed.as_str()),
364 )?;
365 }
366
367 writeln!(w, "}}")?;
368
369 Ok(())
370 }
371
372 fn write_comment(
373 &self,
374 w: &mut impl std::io::Write,
375 indent: usize,
376 comment: &str,
377 ) -> std::io::Result<()> {
378 writeln!(w, "{}/// {}", "\t".repeat(indent), comment)?;
379 Ok(())
380 }
381
382 fn write_comments(
383 &self,
384 w: &mut impl std::io::Write,
385 indent: usize,
386 comments: &[String],
387 ) -> std::io::Result<()> {
388 comments
389 .iter()
390 .try_for_each(|comment| self.write_comment(w, indent, comment))
391 }
392
393 fn indent(&self, str: impl AsRef<str>, count: usize) -> String {
394 let indentation = " ".repeat(count);
395 str.as_ref()
396 .split('\n')
397 .map(|line| {
398 if line.is_empty() {
399 line.to_string()
400 } else {
401 format!("{indentation}{line}")
402 }
403 })
404 .join("\n")
405 }
406}