pub(crate) fn erase_generics(lang_type: &str) -> &str {
match lang_type.find('<') {
Some(index) => lang_type[..index].trim_end(),
None => lang_type,
}
}
pub(crate) fn java_class_literal(java_type: &str) -> String {
format!("{}.class", erase_generics(java_type))
}
pub(crate) fn kotlin_class_literal(kotlin_type: &str) -> String {
format!("{}::class.java", erase_generics(kotlin_type))
}
fn is_kotlin_primitive(kotlin_type: &str) -> bool {
matches!(
kotlin_type,
"Boolean" | "Byte" | "Short" | "Int" | "Long" | "Float" | "Double" | "Char"
)
}
pub(crate) fn kotlin_boxed_class_literal(kotlin_type: &str) -> String {
if is_kotlin_primitive(kotlin_type) {
format!("{kotlin_type}::class.javaObjectType")
} else {
kotlin_class_literal(kotlin_type)
}
}
pub(crate) fn java_named_getter(java_type: &str) -> Option<&'static str> {
match java_type {
"boolean" | "Boolean" => Some("getBoolean"),
"byte" | "Byte" => Some("getByte"),
"short" | "Short" => Some("getShort"),
"int" | "Integer" => Some("getInt"),
"long" | "Long" => Some("getLong"),
"float" | "Float" => Some("getFloat"),
"double" | "Double" => Some("getDouble"),
"String" => Some("getString"),
"byte[]" => Some("getBytes"),
_ if java_type.contains("BigDecimal") => Some("getBigDecimal"),
_ => None,
}
}
pub(crate) fn kotlin_named_getter(kotlin_type: &str) -> Option<&'static str> {
match kotlin_type {
"Boolean" => Some("getBoolean"),
"Byte" => Some("getByte"),
"Short" => Some("getShort"),
"Int" => Some("getInt"),
"Long" => Some("getLong"),
"Float" => Some("getFloat"),
"Double" => Some("getDouble"),
"String" => Some("getString"),
"ByteArray" => Some("getBytes"),
_ if kotlin_type.contains("BigDecimal") => Some("getBigDecimal"),
_ => None,
}
}
pub(crate) fn java_jdbc_read_call(column: &str, java_type: &str) -> String {
match java_named_getter(java_type) {
Some(getter) => format!("rs.{getter}(\"{column}\")"),
None => format!("rs.getObject(\"{column}\", {})", java_class_literal(java_type)),
}
}
pub(crate) fn kotlin_jdbc_read_call(column: &str, kotlin_type: &str) -> String {
match kotlin_named_getter(kotlin_type) {
Some(getter) => format!("rs.{getter}(\"{column}\")"),
None => format!("rs.getObject(\"{column}\", {})", kotlin_class_literal(kotlin_type)),
}
}
pub(crate) fn is_enum_column(neutral_type: &str) -> bool {
neutral_type.starts_with("enum::")
}
pub(crate) fn array_element_neutral_type(neutral_type: &str) -> Option<&str> {
neutral_type.strip_prefix("array<")?.strip_suffix('>')
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn erase_generics_strips_type_arguments_and_leaves_plain_types_alone() {
assert_eq!(erase_generics("List<String>"), "List");
assert_eq!(erase_generics("java.util.Map<String, Integer>"), "java.util.Map");
assert_eq!(erase_generics("java.util.UUID"), "java.util.UUID");
assert_eq!(erase_generics("byte[]"), "byte[]");
}
#[test]
fn class_literals_are_valid_for_qualified_array_and_generic_types() {
assert_eq!(java_class_literal("TortureAddress"), "TortureAddress.class");
assert_eq!(java_class_literal("java.util.UUID"), "java.util.UUID.class");
assert_eq!(java_class_literal("byte[]"), "byte[].class");
assert_eq!(java_class_literal("List<String>"), "List.class");
assert_eq!(kotlin_class_literal("TortureAddress"), "TortureAddress::class.java");
assert_eq!(kotlin_class_literal("java.util.UUID"), "java.util.UUID::class.java");
}
#[test]
fn kotlin_primitive_backed_types_use_the_boxed_class_object() {
assert_eq!(kotlin_boxed_class_literal("Int"), "Int::class.javaObjectType");
assert_eq!(kotlin_boxed_class_literal("Boolean"), "Boolean::class.javaObjectType");
assert_eq!(kotlin_boxed_class_literal("String"), "String::class.java");
assert_eq!(kotlin_boxed_class_literal("ByteArray"), "ByteArray::class.java");
}
#[test]
fn types_without_a_named_getter_read_through_a_class_literal() {
assert_eq!(java_named_getter("java.util.UUID"), None);
assert_eq!(java_named_getter("TortureAddress"), None);
assert_eq!(
java_jdbc_read_call("home_address", "TortureAddress"),
"rs.getObject(\"home_address\", TortureAddress.class)"
);
assert_eq!(
kotlin_jdbc_read_call("external_id", "java.util.UUID"),
"rs.getObject(\"external_id\", java.util.UUID::class.java)"
);
}
#[test]
fn array_element_neutral_type_extracts_the_inner_type() {
assert_eq!(array_element_neutral_type("array<int32>"), Some("int32"));
assert_eq!(
array_element_neutral_type("array<enum::widget_status>"),
Some("enum::widget_status")
);
assert_eq!(array_element_neutral_type("int32"), None);
assert_eq!(array_element_neutral_type("range<int32>"), None);
}
#[test]
fn named_getters_are_used_where_jdbc_has_one() {
assert_eq!(java_jdbc_read_call("id", "int"), "rs.getInt(\"id\")");
assert_eq!(java_jdbc_read_call("payload", "byte[]"), "rs.getBytes(\"payload\")");
assert_eq!(kotlin_jdbc_read_call("id", "Int"), "rs.getInt(\"id\")");
assert_eq!(
kotlin_jdbc_read_call("amount", "java.math.BigDecimal"),
"rs.getBigDecimal(\"amount\")"
);
}
}