typeshare_java/
language.rs

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