genco/lang/java/
mod.rs

1//! Specialization for Java code generation.
2//!
3//! # String Quoting in Java
4//!
5//! Since Java uses UTF-16 internally, string quoting for high unicode
6//! characters is done through surrogate pairs, as seen with the 😊 below.
7//!
8//! ```rust
9//! use genco::prelude::*;
10//!
11//! let toks: java::Tokens = quote!("start π 😊 \n \x7f end");
12//! assert_eq!("\"start \\u03c0 \\ud83d\\ude0a \\n \\u007f end\"", toks.to_string()?);
13//! # Ok::<_, genco::fmt::Error>(())
14//! ```
15
16mod block_comment;
17pub use self::block_comment::BlockComment;
18
19use core::fmt::Write as _;
20
21use alloc::collections::{BTreeMap, BTreeSet};
22use alloc::string::{String, ToString};
23
24use crate as genco;
25use crate::fmt;
26use crate::tokens::ItemStr;
27use crate::{quote, quote_in};
28
29/// Tokens container specialized for Java.
30pub type Tokens = crate::Tokens<Java>;
31
32impl_lang! {
33    /// Language specialization for Java.
34    pub Java {
35        type Config = Config;
36        type Format = Format;
37        type Item = Import;
38
39        fn write_quoted(out: &mut fmt::Formatter<'_>, input: &str) -> fmt::Result {
40            // From: https://docs.oracle.com/javase/tutorial/java/data/characters.html
41
42            for c in input.chars() {
43                match c {
44                    '\t' => out.write_str("\\t")?,
45                    '\u{0008}' => out.write_str("\\b")?,
46                    '\n' => out.write_str("\\n")?,
47                    '\r' => out.write_str("\\r")?,
48                    '\u{0014}' => out.write_str("\\f")?,
49                    '\'' => out.write_str("\\'")?,
50                    '"' => out.write_str("\\\"")?,
51                    '\\' => out.write_str("\\\\")?,
52                    ' ' => out.write_char(' ')?,
53                    c if c.is_ascii() && !c.is_control() => out.write_char(c)?,
54                    c => {
55                        for c in c.encode_utf16(&mut [0u16; 2]) {
56                            write!(out, "\\u{c:04x}")?;
57                        }
58                    }
59                }
60            }
61
62            Ok(())
63        }
64
65        fn format_file(
66            tokens: &Tokens,
67            out: &mut fmt::Formatter<'_>,
68            config: &Self::Config,
69        ) -> fmt::Result {
70            let mut header = Tokens::new();
71
72            if let Some(ref package) = config.package {
73                quote_in!(header => package $package;);
74                header.line();
75            }
76
77            let mut format = Format::default();
78            Self::imports(&mut header, tokens, config, &mut format.imported);
79            header.format(out, config, &format)?;
80            tokens.format(out, config, &format)?;
81            Ok(())
82        }
83    }
84
85    Import(Import) {
86        fn format(&self, out: &mut fmt::Formatter<'_>, config: &Config, format: &Format) -> fmt::Result {
87            let file_package = config.package.as_ref().map(|p| p.as_ref());
88            let imported = format.imported.get(self.name.as_ref()).map(String::as_str);
89            let pkg = Some(self.package.as_ref());
90
91            if &*self.package != JAVA_LANG && imported != pkg && file_package != pkg {
92                out.write_str(self.package.as_ref())?;
93                out.write_str(SEP)?;
94            }
95
96            out.write_str(&self.name)?;
97            Ok(())
98        }
99    }
100}
101
102const JAVA_LANG: &str = "java.lang";
103const SEP: &str = ".";
104
105/// Formtat state for Java.
106#[derive(Debug, Default)]
107pub struct Format {
108    /// Types which has been imported into the local namespace.
109    imported: BTreeMap<String, String>,
110}
111
112/// Configuration for Java.
113#[derive(Debug, Default)]
114pub struct Config {
115    /// Package to use.
116    package: Option<ItemStr>,
117}
118
119impl Config {
120    /// Configure package to use for the file generated.
121    ///
122    /// # Examples
123    ///
124    /// ```
125    /// use genco::prelude::*;
126    /// use genco::fmt;
127    ///
128    /// let optional = java::import("java.util", "Optional");
129    ///
130    /// let toks = quote!($optional);
131    ///
132    /// let config = java::Config::default().with_package("java.util");
133    /// let fmt = fmt::Config::from_lang::<Java>();
134    ///
135    /// let mut w = fmt::VecWriter::new();
136    ///
137    /// toks.format_file(&mut w.as_formatter(&fmt), &config)?;
138    ///
139    /// assert_eq!(
140    ///     vec![
141    ///         "package java.util;",
142    ///         "",
143    ///         "Optional",
144    ///     ],
145    ///     w.into_vec(),
146    /// );
147    /// # Ok::<_, genco::fmt::Error>(())
148    /// ```
149    pub fn with_package<P>(self, package: P) -> Self
150    where
151        P: Into<ItemStr>,
152    {
153        Self {
154            package: Some(package.into()),
155        }
156    }
157}
158
159/// The import of a Java type `import java.util.Optional;`.
160///
161/// Created through the [import()] function.
162#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
163pub struct Import {
164    /// Package of the class.
165    package: ItemStr,
166    /// Name  of class.
167    name: ItemStr,
168}
169
170impl Java {
171    fn imports(
172        out: &mut Tokens,
173        tokens: &Tokens,
174        config: &Config,
175        imported: &mut BTreeMap<String, String>,
176    ) {
177        let mut modules = BTreeSet::new();
178
179        let file_package = config.package.as_ref().map(|p| p.as_ref());
180
181        for import in tokens.iter_lang() {
182            modules.insert((import.package.clone(), import.name.clone()));
183        }
184
185        if modules.is_empty() {
186            return;
187        }
188
189        for (package, name) in modules {
190            if imported.contains_key(&*name) {
191                continue;
192            }
193
194            if &*package == JAVA_LANG {
195                continue;
196            }
197
198            if Some(&*package) == file_package {
199                continue;
200            }
201
202            out.append(quote!(import $(package.clone())$(SEP)$(name.clone());));
203            out.push();
204
205            imported.insert(name.to_string(), package.to_string());
206        }
207
208        out.line();
209    }
210}
211
212/// The import of a Java type `import java.util.Optional;`.
213///
214/// # Examples
215///
216/// ```
217/// use genco::prelude::*;
218///
219/// let integer = java::import("java.lang", "Integer");
220/// let a = java::import("java.io", "A");
221///
222/// let toks = quote! {
223///     $integer
224///     $a
225/// };
226///
227/// assert_eq!(
228///     vec![
229///         "import java.io.A;",
230///         "",
231///         "Integer",
232///         "A",
233///     ],
234///     toks.to_file_vec()?
235/// );
236/// # Ok::<_, genco::fmt::Error>(())
237/// ```
238pub fn import<P, N>(package: P, name: N) -> Import
239where
240    P: Into<ItemStr>,
241    N: Into<ItemStr>,
242{
243    Import {
244        package: package.into(),
245        name: name.into(),
246    }
247}
248
249/// Format a block comment, starting with `/**`, and ending in `*/`.
250///
251/// # Examples
252///
253/// ```
254/// use genco::prelude::*;
255/// use std::iter;
256///
257/// let toks = quote! {
258///     $(java::block_comment(vec!["first line", "second line"]))
259///     $(java::block_comment(iter::empty::<&str>()))
260///     $(java::block_comment(vec!["third line"]))
261/// };
262///
263/// assert_eq!(
264///     vec![
265///         "/**",
266///         " * first line",
267///         " * second line",
268///         " */",
269///         "/**",
270///         " * third line",
271///         " */",
272///     ],
273///     toks.to_file_vec()?
274/// );
275/// # Ok::<_, genco::fmt::Error>(())
276/// ```
277pub fn block_comment<T>(comment: T) -> BlockComment<T>
278where
279    T: IntoIterator,
280    T::Item: Into<ItemStr>,
281{
282    BlockComment(comment)
283}