typeshare_java/
language.rs

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            // https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-IntegralType
58            // Char in Java is 16 bits long, so we need to use String
59            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            // byte in Java is signed, so we need to use short to represent all possible values
65            SpecialRustType::U8 => "short".into(),
66            // short in Java is signed, so we need to use int to represent all possible values
67            SpecialRustType::U16 => "int".into(),
68            // ing in Java is signed, so we need to use long to represent all possible values
69            SpecialRustType::USize | SpecialRustType::U32 => "long".into(),
70            // long in Java is signed, so we need to use BigInteger to represent all possible values
71            SpecialRustType::U53 | SpecialRustType::U64 => "java.math.BigInteger".into(),
72            // https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-PrimitiveType
73            SpecialRustType::Bool => "boolean".into(),
74            // https://docs.oracle.com/javase/specs/jls/se23/html/jls-4.html#jls-FloatingPointType
75            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        // TODO: Generate named types for any anonymous struct variants of this enum
216
217        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        // https://docs.oracle.com/javase/specs/jls/se23/html/jls-3.html#jls-JavaLetter
240        c.is_ascii_alphabetic() || c == '_' || c == '$'
241    }
242
243    #[inline]
244    fn is_java_letter_or_number(&self, c: char) -> bool {
245        // https://docs.oracle.com/javase/specs/jls/se23/html/jls-3.html#jls-JavaLetterOrDigit
246        self.is_java_letter(c) || c.is_ascii_digit()
247    }
248
249    #[inline]
250    fn is_java_reserved_keyword(&self, name: &str) -> bool {
251        // https://docs.oracle.com/javase/specs/jls/se23/html/jls-3.html#jls-ReservedKeyword
252        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        // https://docs.oracle.com/javase/specs/jls/se23/html/jls-3.html#jls-BooleanLiteral
311        matches!(name, "true" | "false")
312    }
313
314    #[inline]
315    fn is_java_null_literal(&self, name: &str) -> bool {
316        // https://docs.oracle.com/javase/specs/jls/se23/html/jls-3.html#jls-NullLiteral
317        matches!(name, "null")
318    }
319
320    fn santitize_itentifier(&self, name: &str) -> String {
321        // https://docs.oracle.com/javase/specs/jls/se23/html/jls-3.html#jls-Identifier
322        let mut chars = name.chars();
323
324        // Ensure the first character is valid "JavaLetter"
325        let first_char = chars
326            .next()
327            .map(|c| if self.is_java_letter(c) { c } else { '_' });
328
329        // Ensure each remaining characters is a valid "JavaLetterOrDigit"
330        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        // Combine and return the sanitized identifier
339        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}