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}