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}