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