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