mod modifier;
mod utils;
pub use self::modifier::Modifier;
pub use self::utils::BlockComment;
use crate as genco;
use crate::{quote, Cons, Formatter, Lang, LangItem};
use std::collections::{BTreeSet, HashMap};
use std::fmt;
pub type Tokens<'el> = crate::Tokens<'el, Java>;
impl_type_basics!(Java, TypeEnum<'a>, TypeTrait, TypeBox, TypeArgs, {Primitive, Void, Type, Optional, Local});
pub trait TypeTrait: 'static + fmt::Debug + LangItem<Java> {
fn as_enum(&self) -> TypeEnum<'_>;
fn name(&self) -> &str;
fn package(&self) -> Option<&str> {
None
}
fn arguments(&self) -> Option<&[TypeBox]> {
None
}
fn type_imports(&self, _: &mut BTreeSet<(Cons<'static>, Cons<'static>)>) {}
}
static JAVA_LANG: &'static str = "java.lang";
static SEP: &'static str = ".";
pub const SHORT: Primitive = Primitive {
primitive: "short",
boxed: "Short",
};
pub const INTEGER: Primitive = Primitive {
primitive: "int",
boxed: "Integer",
};
pub const LONG: Primitive = Primitive {
primitive: "long",
boxed: "Long",
};
pub const FLOAT: Primitive = Primitive {
primitive: "float",
boxed: "Float",
};
pub const DOUBLE: Primitive = Primitive {
primitive: "double",
boxed: "Double",
};
pub const CHAR: Primitive = Primitive {
primitive: "char",
boxed: "Character",
};
pub const BOOLEAN: Primitive = Primitive {
primitive: "boolean",
boxed: "Boolean",
};
pub const BYTE: Primitive = Primitive {
primitive: "byte",
boxed: "Byte",
};
pub const VOID: Void = Void(());
#[derive(Debug)]
pub struct Config {
package: Option<Cons<'static>>,
imported: HashMap<String, String>,
}
impl Config {
pub fn with_package(self, package: impl Into<Cons<'static>>) -> Self {
Self {
package: Some(package.into()),
..self
}
}
}
impl Default for Config {
fn default() -> Self {
Self {
package: Default::default(),
imported: Default::default(),
}
}
}
#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct Type {
package: Cons<'static>,
name: Cons<'static>,
path: Vec<Cons<'static>>,
arguments: Vec<TypeBox>,
}
impl Type {
pub fn path<P: Into<Cons<'static>>>(self, part: P) -> Self {
let mut path = self.path;
path.push(part.into());
Self {
package: self.package,
name: self.name,
path: path,
arguments: vec![],
}
}
pub fn with_arguments(self, args: impl TypeArgs) -> Self {
Self {
package: self.package,
name: self.name,
path: self.path,
arguments: args.into_args(),
}
}
pub fn as_raw(self) -> Self {
Self {
package: self.package,
name: self.name,
path: self.path,
arguments: vec![],
}
}
pub fn is_generic(&self) -> bool {
!self.arguments.is_empty()
}
}
impl LangItem<Java> for Type {
fn format(&self, out: &mut Formatter, config: &mut Config, level: usize) -> fmt::Result {
{
let file_package = config.package.as_ref().map(|p| p.as_ref());
let imported = config.imported.get(self.name.as_ref()).map(String::as_str);
let pkg = Some(self.package.as_ref());
if self.package.as_ref() != JAVA_LANG && imported != pkg && file_package != pkg {
out.write_str(self.package.as_ref())?;
out.write_str(SEP)?;
}
}
{
out.write_str(self.name.as_ref())?;
let mut it = self.path.iter();
while let Some(n) = it.next() {
out.write_str(".")?;
out.write_str(n.as_ref())?;
}
}
if !self.arguments.is_empty() {
out.write_str("<")?;
let mut it = self.arguments.iter().peekable();
while let Some(argument) = it.next() {
argument.format(out, config, level + 1usize)?;
if it.peek().is_some() {
out.write_str(", ")?;
}
}
out.write_str(">")?;
}
Ok(())
}
fn as_import(&self) -> Option<&dyn TypeTrait> {
Some(self)
}
}
impl TypeTrait for Type {
fn as_enum(&self) -> TypeEnum<'_> {
TypeEnum::Type(self)
}
fn name(&self) -> &str {
&*self.name
}
fn package(&self) -> Option<&str> {
Some(&*self.package)
}
fn arguments(&self) -> Option<&[TypeBox]> {
Some(&self.arguments)
}
fn type_imports(&self, modules: &mut BTreeSet<(Cons<'static>, Cons<'static>)>) {
for argument in &self.arguments {
if let TypeEnum::Type(ty) = argument.as_enum() {
ty.type_imports(modules);
}
}
modules.insert((self.package.clone(), self.name.clone()));
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Void(());
impl TypeTrait for Void {
fn as_enum(&self) -> TypeEnum<'_> {
TypeEnum::Void(self)
}
fn name(&self) -> &str {
"void"
}
}
impl LangItem<Java> for Void {
fn format(&self, out: &mut Formatter, _: &mut Config, level: usize) -> fmt::Result {
if level > 0 {
out.write_str("Void")
} else {
out.write_str("void")
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Primitive {
boxed: &'static str,
primitive: &'static str,
}
impl Primitive {
pub fn into_boxed(self) -> Type {
Type {
package: Cons::Borrowed(JAVA_LANG),
name: Cons::Borrowed(self.boxed),
path: vec![],
arguments: vec![],
}
}
}
impl LangItem<Java> for Primitive {
fn format(&self, out: &mut Formatter, _: &mut Config, level: usize) -> fmt::Result {
if level > 0 {
out.write_str(self.boxed)
} else {
out.write_str(self.primitive)
}
}
}
impl TypeTrait for Primitive {
fn name(&self) -> &str {
self.primitive
}
fn as_enum(&self) -> TypeEnum<'_> {
TypeEnum::Primitive(self)
}
fn package(&self) -> Option<&str> {
Some(JAVA_LANG)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Local {
name: Cons<'static>,
}
impl TypeTrait for Local {
fn as_enum(&self) -> TypeEnum<'_> {
TypeEnum::Local(self)
}
fn name(&self) -> &str {
&*self.name
}
}
impl LangItem<Java> for Local {
fn format(&self, out: &mut Formatter, _: &mut Config, _: usize) -> fmt::Result {
out.write_str(&*self.name)
}
fn as_import(&self) -> Option<&dyn TypeTrait> {
Some(self)
}
}
#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct Optional {
pub value: TypeBox,
pub field: TypeBox,
}
impl TypeTrait for Optional {
fn as_enum(&self) -> TypeEnum<'_> {
TypeEnum::Optional(self)
}
fn name(&self) -> &str {
self.value.name()
}
fn package(&self) -> Option<&str> {
self.value.package()
}
fn arguments(&self) -> Option<&[TypeBox]> {
self.value.arguments()
}
fn type_imports(&self, modules: &mut BTreeSet<(Cons<'static>, Cons<'static>)>) {
self.value.type_imports(modules);
}
}
impl Optional {
pub fn as_field(self) -> TypeBox {
self.field.clone()
}
pub fn as_value(self) -> TypeBox {
self.value.clone()
}
}
impl LangItem<Java> for Optional {
fn format(&self, out: &mut Formatter, config: &mut Config, level: usize) -> fmt::Result {
self.field.format(out, config, level)
}
fn as_import(&self) -> Option<&dyn TypeTrait> {
Some(self)
}
}
pub struct Java(());
impl Java {
fn imports<'el>(tokens: &Tokens<'el>, config: &mut Config) -> Option<Tokens<'el>> {
let mut modules = BTreeSet::new();
let file_package = config.package.as_ref().map(|p| p.as_ref());
for custom in tokens.walk_custom() {
if let Some(ty) = custom.as_import() {
ty.type_imports(&mut modules);
}
}
if modules.is_empty() {
return None;
}
let mut out = Tokens::new();
for (package, name) in modules {
if config.imported.contains_key(&*name) {
continue;
}
if &*package == JAVA_LANG {
continue;
}
if Some(&*package) == file_package.as_deref() {
continue;
}
out.push(quote!(import #(package)#(SEP)#(name);));
config
.imported
.insert(name.to_string(), package.to_string());
}
Some(out)
}
}
impl Lang for Java {
type Config = Config;
type Import = dyn TypeTrait;
fn quote_string(out: &mut Formatter, input: &str) -> fmt::Result {
use std::fmt::Write as _;
out.write_char('"')?;
for c in input.chars() {
match c {
'\t' => out.write_str("\\t")?,
'\u{0007}' => out.write_str("\\b")?,
'\n' => out.write_str("\\n")?,
'\r' => out.write_str("\\r")?,
'\u{0014}' => out.write_str("\\f")?,
'\'' => out.write_str("\\'")?,
'"' => out.write_str("\\\"")?,
'\\' => out.write_str("\\\\")?,
c => out.write_char(c)?,
}
}
out.write_char('"')?;
Ok(())
}
fn write_file(
tokens: Tokens<'_>,
out: &mut Formatter,
config: &mut Self::Config,
level: usize,
) -> fmt::Result {
let mut toks = Tokens::new();
if let Some(ref package) = config.package {
toks.push(toks!["package ", package.clone(), ";"]);
toks.line_spacing();
}
if let Some(imports) = Self::imports(&tokens, config) {
toks.push(imports);
toks.line_spacing();
}
toks.extend(tokens);
toks.format(out, config, level)
}
}
pub fn imported<P: Into<Cons<'static>>, N: Into<Cons<'static>>>(package: P, name: N) -> Type {
Type {
package: package.into(),
name: name.into(),
path: vec![],
arguments: vec![],
}
}
pub fn local<'el, N: Into<Cons<'static>>>(name: N) -> Local {
Local { name: name.into() }
}
pub fn optional<'el, I: Into<TypeBox>, F: Into<TypeBox>>(value: I, field: F) -> Optional {
Optional {
value: value.into(),
field: field.into(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate as genco;
use crate::{quote, Java, Quoted, Tokens};
#[test]
fn test_string() {
let mut toks: Tokens<Java> = Tokens::new();
toks.append("hello \n world".quoted());
assert_eq!("\"hello \\n world\"", toks.to_string().unwrap().as_str());
}
#[test]
fn test_imported() {
let integer = imported("java.lang", "Integer");
let a = imported("java.io", "A");
let b = imported("java.io", "B");
let ob = imported("java.util", "B");
let ob_a = ob.clone().with_arguments(a.clone());
let toks = quote!(#integer #a #b #ob #ob_a);
assert_eq!(
Ok("import java.io.A;\nimport java.io.B;\n\nInteger A B java.util.B java.util.B<A>\n",),
toks.to_file_string().as_ref().map(|s| s.as_str())
);
}
}