musikr: add metadata builders
This commit is contained in:
parent
1cb2f5026f
commit
6385928150
12 changed files with 327 additions and 5 deletions
7
musikr/src/main/jni/Cargo.lock
generated
7
musikr/src/main/jni/Cargo.lock
generated
|
@ -8,6 +8,12 @@ version = "1.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.21.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
|
@ -193,6 +199,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
name = "metadatajni"
|
name = "metadatajni"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
"cxx",
|
"cxx",
|
||||||
"cxx-build",
|
"cxx-build",
|
||||||
"jni",
|
"jni",
|
||||||
|
|
|
@ -8,6 +8,7 @@ name = "metadatajni"
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bytemuck = "1.21.0"
|
||||||
cxx = "1.0.137"
|
cxx = "1.0.137"
|
||||||
jni = "0.21.1"
|
jni = "0.21.1"
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,10 @@ namespace taglib_shim {
|
||||||
return dynamic_cast<const TagLib::ID3v2::AttachedPictureFrame*>(frame);
|
return dynamic_cast<const TagLib::ID3v2::AttachedPictureFrame*>(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<TagLib::ByteVector> Frame_id(const TagLib::ID3v2::Frame& frame) {
|
||||||
|
return std::make_unique<TagLib::ByteVector>(frame.frameID());
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<TagLib::ByteVector> AttachedPictureFrame_picture(const TagLib::ID3v2::AttachedPictureFrame& frame) {
|
std::unique_ptr<TagLib::ByteVector> AttachedPictureFrame_picture(const TagLib::ID3v2::AttachedPictureFrame& frame) {
|
||||||
return std::make_unique<TagLib::ByteVector>(frame.picture());
|
return std::make_unique<TagLib::ByteVector>(frame.picture());
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ namespace taglib_shim {
|
||||||
std::unique_ptr<std::vector<FramePointer>> FrameList_to_vector(const TagLib::ID3v2::FrameList& list);
|
std::unique_ptr<std::vector<FramePointer>> FrameList_to_vector(const TagLib::ID3v2::FrameList& list);
|
||||||
|
|
||||||
// Frame type checking and casting
|
// Frame type checking and casting
|
||||||
|
std::unique_ptr<TagLib::ByteVector> Frame_id(const TagLib::ID3v2::Frame &frame);
|
||||||
const TagLib::ID3v2::TextIdentificationFrame* Frame_asTextIdentification(const TagLib::ID3v2::Frame* frame);
|
const TagLib::ID3v2::TextIdentificationFrame* Frame_asTextIdentification(const TagLib::ID3v2::Frame* frame);
|
||||||
const TagLib::ID3v2::UserTextIdentificationFrame* Frame_asUserTextIdentification(const TagLib::ID3v2::Frame* frame);
|
const TagLib::ID3v2::UserTextIdentificationFrame* Frame_asUserTextIdentification(const TagLib::ID3v2::Frame* frame);
|
||||||
const TagLib::ID3v2::AttachedPictureFrame* Frame_asAttachedPicture(const TagLib::ID3v2::Frame* frame);
|
const TagLib::ID3v2::AttachedPictureFrame* Frame_asAttachedPicture(const TagLib::ID3v2::Frame* frame);
|
||||||
|
|
214
musikr/src/main/jni/src/jbuilder.rs
Normal file
214
musikr/src/main/jni/src/jbuilder.rs
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
use jni::{objects::{JByteArray, JClass, JMap, JObject, JString, JValueGen}, sys::jlong, JNIEnv};
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
use crate::taglib::{
|
||||||
|
id3v1, id3v2, xiph, mp4, tk, audioproperties,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::tagmap::JTagMap;
|
||||||
|
|
||||||
|
pub struct JMetadataBuilder<'local, 'file_ref> {
|
||||||
|
env: Rc<RefCell<JNIEnv<'local>>>,
|
||||||
|
id3v2: JTagMap<'local>,
|
||||||
|
xiph: JTagMap<'local>,
|
||||||
|
mp4: JTagMap<'local>,
|
||||||
|
cover: Option<Vec<u8>>,
|
||||||
|
properties: Option<audioproperties::AudioProperties<'file_ref>>,
|
||||||
|
mime_type: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'local, 'file_ref> JMetadataBuilder<'local, 'file_ref> {
|
||||||
|
pub fn new(env: Rc<RefCell<JNIEnv<'local>>>) -> Self {
|
||||||
|
Self {
|
||||||
|
id3v2: JTagMap::new(env.clone()),
|
||||||
|
xiph: JTagMap::new(env.clone()),
|
||||||
|
mp4: JTagMap::new(env.clone()),
|
||||||
|
env,
|
||||||
|
cover: None,
|
||||||
|
properties: None,
|
||||||
|
mime_type: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_mime_type(&mut self, mime_type: impl Into<String>) {
|
||||||
|
self.mime_type = Some(mime_type.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_id3v1(&mut self, tag: &id3v1::ID3v1Tag) {
|
||||||
|
if let Some(title) = tag.title() {
|
||||||
|
self.id3v2.add_id("TIT2", title.to_string());
|
||||||
|
}
|
||||||
|
if let Some(artist) = tag.artist() {
|
||||||
|
self.id3v2.add_id("TPE1", artist.to_string());
|
||||||
|
}
|
||||||
|
if let Some(album) = tag.album() {
|
||||||
|
self.id3v2.add_id("TALB", album.to_string());
|
||||||
|
}
|
||||||
|
self.id3v2.add_id("TRCK", tag.track().to_string());
|
||||||
|
self.id3v2.add_id("TYER", tag.year().to_string());
|
||||||
|
|
||||||
|
let genre = tag.genre_index();
|
||||||
|
if genre != 255 {
|
||||||
|
self.id3v2.add_id("TCON", genre.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_id3v2(&mut self, tag: &id3v2::ID3v2Tag) {
|
||||||
|
let mut first_pic = None;
|
||||||
|
let mut front_cover_pic = None;
|
||||||
|
|
||||||
|
if let Some(frames) = tag.frames() {
|
||||||
|
for mut frame in frames.to_vec() {
|
||||||
|
if let Some(text_frame) = frame.as_text_identification() {
|
||||||
|
if let Some(field_list) = text_frame.field_list() {
|
||||||
|
let values: Vec<String> = field_list.to_vec()
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect();
|
||||||
|
self.id3v2.add_id_list(frame.id().to_string_lossy(), values);
|
||||||
|
}
|
||||||
|
} else if let Some(user_text_frame) = frame.as_user_text_identification() {
|
||||||
|
if let Some(values) = user_text_frame.values() {
|
||||||
|
let mut values = values.to_vec();
|
||||||
|
if !values.is_empty() {
|
||||||
|
let description = values.remove(0);
|
||||||
|
for value in values {
|
||||||
|
self.id3v2.add_combined(
|
||||||
|
frame.id().to_string_lossy(),
|
||||||
|
description.clone(),
|
||||||
|
value.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(picture_frame) = frame.as_attached_picture() {
|
||||||
|
if first_pic.is_none() {
|
||||||
|
first_pic = picture_frame.picture().map(|p| p.to_vec());
|
||||||
|
}
|
||||||
|
// TODO: Check for front cover type when bindings are available
|
||||||
|
if front_cover_pic.is_none() {
|
||||||
|
front_cover_pic = picture_frame.picture().map(|p| p.to_vec());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer front cover, fall back to first picture
|
||||||
|
self.cover = front_cover_pic.or(first_pic);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_xiph(&mut self, tag: &xiph::XiphComment) {
|
||||||
|
for (key, values) in tag.field_list_map().to_hashmap() {
|
||||||
|
let values: Vec<String> = values.to_vec().into_iter().map(|s| s.to_string()).collect();
|
||||||
|
self.xiph.add_id_list(key.to_uppercase(), values);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle FLAC pictures when bindings are available
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_mp4(&mut self, tag: &mp4::MP4Tag) {
|
||||||
|
let map = tag.item_map().to_hashmap();
|
||||||
|
|
||||||
|
for (key, item) in map {
|
||||||
|
if key == "covr" {
|
||||||
|
if let Some(mp4::MP4Data::CoverArtList(cover_list)) = item.data() {
|
||||||
|
let covers = cover_list.to_vec();
|
||||||
|
// Prefer PNG/JPEG covers
|
||||||
|
let preferred_cover = covers.iter().find(|c| {
|
||||||
|
matches!(c.format(), mp4::CoverArtFormat::PNG | mp4::CoverArtFormat::JPEG)
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(cover) = preferred_cover {
|
||||||
|
self.cover = Some(cover.data().to_vec());
|
||||||
|
} else if let Some(first_cover) = covers.first() {
|
||||||
|
self.cover = Some(first_cover.data().to_vec());
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(data) = item.data() {
|
||||||
|
match data {
|
||||||
|
mp4::MP4Data::StringList(list) => {
|
||||||
|
let values: Vec<String> = list.to_vec().into_iter().map(|s| s.to_string()).collect();
|
||||||
|
if key.starts_with("----") {
|
||||||
|
if let Some(split_idx) = key.find(':') {
|
||||||
|
let (atom_name, atom_desc) = key.split_at(split_idx);
|
||||||
|
let atom_desc = &atom_desc[1..]; // Skip the colon
|
||||||
|
for value in values {
|
||||||
|
self.mp4.add_combined(atom_name, atom_desc, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.mp4.add_id_list(key, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mp4::MP4Data::Int(v) => self.mp4.add_id(key, v.to_string()),
|
||||||
|
mp4::MP4Data::UInt(v) => self.mp4.add_id(key, v.to_string()),
|
||||||
|
mp4::MP4Data::LongLong(v) => self.mp4.add_id(key, v.to_string()),
|
||||||
|
mp4::MP4Data::IntPair(pair) => {
|
||||||
|
if let Some((first, second)) = pair.to_tuple() {
|
||||||
|
self.mp4.add_id(key, format!("{}/{}", first, second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_properties(&mut self, properties: audioproperties::AudioProperties<'file_ref>) {
|
||||||
|
self.properties = Some(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(&self) -> JObject {
|
||||||
|
// Create Properties object
|
||||||
|
let properties_class = self.env.borrow_mut().find_class("org/oxycblt/musikr/metadata/Properties").unwrap();
|
||||||
|
let properties = if let Some(props) = &self.properties {
|
||||||
|
let mime_type = self.mime_type.as_deref().unwrap_or("").to_string();
|
||||||
|
let j_mime_type = self.env.borrow().new_string(mime_type).unwrap();
|
||||||
|
|
||||||
|
self.env.borrow_mut().new_object(
|
||||||
|
properties_class,
|
||||||
|
"(Ljava/lang/String;JII)V",
|
||||||
|
&[
|
||||||
|
JValueGen::from(&j_mime_type),
|
||||||
|
(props.length_in_milliseconds() as jlong).into(),
|
||||||
|
(props.bitrate() as i32).into(),
|
||||||
|
(props.sample_rate() as i32).into(),
|
||||||
|
],
|
||||||
|
).unwrap()
|
||||||
|
} else {
|
||||||
|
let empty_mime = self.env.borrow().new_string("").unwrap();
|
||||||
|
self.env.borrow_mut().new_object(
|
||||||
|
properties_class,
|
||||||
|
"(Ljava/lang/String;JII)V",
|
||||||
|
&[JValueGen::from(&empty_mime), 0i64.into(), 0i32.into(), 0i32.into()],
|
||||||
|
).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create cover byte array if present
|
||||||
|
let cover_array = if let Some(cover_data) = &self.cover {
|
||||||
|
let array = self.env.borrow().new_byte_array(cover_data.len() as i32).unwrap();
|
||||||
|
self.env.borrow().set_byte_array_region(&array, 0, bytemuck::cast_slice(cover_data)).unwrap();
|
||||||
|
array.into()
|
||||||
|
} else {
|
||||||
|
JObject::null()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create Metadata object
|
||||||
|
let metadata_class = self.env.borrow_mut().find_class("org/oxycblt/musikr/metadata/Metadata").unwrap();
|
||||||
|
self.env.borrow_mut().new_object(
|
||||||
|
metadata_class,
|
||||||
|
"(Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;[BLorg/oxycblt/musikr/metadata/Properties;)V",
|
||||||
|
&[
|
||||||
|
JValueGen::from(&self.id3v2.get_object()),
|
||||||
|
JValueGen::from(&self.xiph.get_object()),
|
||||||
|
JValueGen::from(&self.mp4.get_object()),
|
||||||
|
JValueGen::from(&cover_array),
|
||||||
|
JValueGen::from(&properties),
|
||||||
|
],
|
||||||
|
).unwrap()
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,9 +6,12 @@ use std::rc::Rc;
|
||||||
|
|
||||||
mod jstream;
|
mod jstream;
|
||||||
mod taglib;
|
mod taglib;
|
||||||
|
mod tagmap;
|
||||||
|
mod jbuilder;
|
||||||
|
|
||||||
use jstream::JInputStream;
|
use jstream::JInputStream;
|
||||||
use taglib::file_ref::FileRef;
|
use taglib::file_ref::FileRef;
|
||||||
|
use jbuilder::JMetadataBuilder;
|
||||||
|
|
||||||
type SharedEnv<'local> = Rc<RefCell<JNIEnv<'local>>>;
|
type SharedEnv<'local> = Rc<RefCell<JNIEnv<'local>>>;
|
||||||
|
|
||||||
|
|
|
@ -171,6 +171,8 @@ mod bridge_impl {
|
||||||
#[cxx_name = "Frame"]
|
#[cxx_name = "Frame"]
|
||||||
type CPPID3v2Frame;
|
type CPPID3v2Frame;
|
||||||
#[namespace = "taglib_shim"]
|
#[namespace = "taglib_shim"]
|
||||||
|
fn Frame_id(frame: &CPPID3v2Frame) -> UniquePtr<CPPByteVector>;
|
||||||
|
#[namespace = "taglib_shim"]
|
||||||
unsafe fn Frame_asTextIdentification(
|
unsafe fn Frame_asTextIdentification(
|
||||||
frame: *const CPPID3v2Frame,
|
frame: *const CPPID3v2Frame,
|
||||||
) -> *const CPPID3v2TextIdentificationFrame;
|
) -> *const CPPID3v2TextIdentificationFrame;
|
||||||
|
|
|
@ -2,7 +2,7 @@ use super::bridge::{
|
||||||
self, CPPID3v2AttachedPictureFrame, CPPID3v2Frame, CPPID3v2FrameList, CPPID3v2Tag,
|
self, CPPID3v2AttachedPictureFrame, CPPID3v2Frame, CPPID3v2FrameList, CPPID3v2Tag,
|
||||||
CPPID3v2TextIdentificationFrame, CPPID3v2UserTextIdentificationFrame, CPPStringList, CPPByteVector,
|
CPPID3v2TextIdentificationFrame, CPPID3v2UserTextIdentificationFrame, CPPStringList, CPPByteVector,
|
||||||
};
|
};
|
||||||
use super::tk::{ByteVector, StringList, OwnedByteVector, OwnedStringList};
|
use super::tk::{self, ByteVector, StringList, OwnedByteVector, OwnedStringList};
|
||||||
use super::this::{OwnedThis, RefThisMut, RefThis, This};
|
use super::this::{OwnedThis, RefThisMut, RefThis, This};
|
||||||
|
|
||||||
pub struct ID3v2Tag<'file_ref> {
|
pub struct ID3v2Tag<'file_ref> {
|
||||||
|
@ -53,6 +53,12 @@ impl<'file_ref> Frame<'file_ref> {
|
||||||
Self { this }
|
Self { this }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> tk::OwnedByteVector<'file_ref> {
|
||||||
|
let id = bridge::Frame_id(self.this.as_ref());
|
||||||
|
let this = unsafe { OwnedThis::new(id).unwrap() };
|
||||||
|
ByteVector::new(this)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn as_text_identification(&mut self) -> Option<TextIdentificationFrame<'file_ref>> {
|
pub fn as_text_identification(&mut self) -> Option<TextIdentificationFrame<'file_ref>> {
|
||||||
let frame = unsafe { bridge::Frame_asTextIdentification(self.this.ptr()) };
|
let frame = unsafe { bridge::Frame_asTextIdentification(self.this.ptr()) };
|
||||||
let frame_ref = unsafe { frame.as_ref() };
|
let frame_ref = unsafe { frame.as_ref() };
|
||||||
|
|
|
@ -54,6 +54,7 @@ impl<'io_stream> DynIOStream<'io_stream> {
|
||||||
pub fn write(&mut self, data: &[u8]) {
|
pub fn write(&mut self, data: &[u8]) {
|
||||||
self.0.write_all(data).unwrap();
|
self.0.write_all(data).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn seek(&mut self, offset: i64, whence: i32) {
|
pub fn seek(&mut self, offset: i64, whence: i32) {
|
||||||
let pos = match whence {
|
let pos = match whence {
|
||||||
0 => SeekFrom::Start(offset as u64),
|
0 => SeekFrom::Start(offset as u64),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
mod bridge;
|
pub mod bridge;
|
||||||
mod this;
|
pub mod this;
|
||||||
pub mod iostream;
|
pub mod iostream;
|
||||||
pub mod file_ref;
|
pub mod file_ref;
|
||||||
pub mod file;
|
pub mod file;
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::marker::PhantomData;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::{ffi::CStr, string::ToString};
|
use std::{ffi::CStr, string::ToString};
|
||||||
|
|
||||||
pub(super) struct String<'file_ref, T: This<'file_ref, CPPString>> {
|
pub struct String<'file_ref, T: This<'file_ref, CPPString>> {
|
||||||
_data: PhantomData<&'file_ref ()>,
|
_data: PhantomData<&'file_ref ()>,
|
||||||
this: T,
|
this: T,
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ impl<'file_ref, T: This<'file_ref, CPPString>> ToString for String<'file_ref, T>
|
||||||
pub type OwnedString<'file_ref> = String<'file_ref, OwnedThis<'file_ref, CPPString>>;
|
pub type OwnedString<'file_ref> = String<'file_ref, OwnedThis<'file_ref, CPPString>>;
|
||||||
pub type RefString<'file_ref> = String<'file_ref, RefThis<'file_ref, CPPString>>;
|
pub type RefString<'file_ref> = String<'file_ref, RefThis<'file_ref, CPPString>>;
|
||||||
pub type RefStringMut<'file_ref> = String<'file_ref, RefThisMut<'file_ref, CPPString>>;
|
pub type RefStringMut<'file_ref> = String<'file_ref, RefThisMut<'file_ref, CPPString>>;
|
||||||
pub(super) struct StringList<'file_ref, T: This<'file_ref, CPPStringList>> {
|
pub struct StringList<'file_ref, T: This<'file_ref, CPPStringList>> {
|
||||||
_data: PhantomData<&'file_ref ()>,
|
_data: PhantomData<&'file_ref ()>,
|
||||||
this: T,
|
this: T,
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,15 @@ impl<'file_ref, T: This<'file_ref, CPPByteVector>> ByteVector<'file_ref, T> {
|
||||||
std::slice::from_raw_parts(data, size).to_vec()
|
std::slice::from_raw_parts(data, size).to_vec()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_string_lossy(&self) -> std::string::String {
|
||||||
|
let this = self.this.as_ref();
|
||||||
|
let size = this.size().try_into().unwrap();
|
||||||
|
let data = this.data();
|
||||||
|
let data: *const u8 = data as *const u8;
|
||||||
|
let slice = unsafe { std::slice::from_raw_parts(data, size) };
|
||||||
|
std::string::String::from_utf8_lossy(slice).to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type OwnedByteVector<'file_ref> = ByteVector<'file_ref, OwnedThis<'file_ref, CPPByteVector>>;
|
pub type OwnedByteVector<'file_ref> = ByteVector<'file_ref, OwnedThis<'file_ref, CPPByteVector>>;
|
||||||
|
|
74
musikr/src/main/jni/src/tagmap.rs
Normal file
74
musikr/src/main/jni/src/tagmap.rs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use jni::{objects::{JObject, JValueGen}, JNIEnv};
|
||||||
|
|
||||||
|
pub struct JTagMap<'local> {
|
||||||
|
env: Rc<RefCell<JNIEnv<'local>>>,
|
||||||
|
map: HashMap<String, Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'local> JTagMap<'local> {
|
||||||
|
pub fn new(env: Rc<RefCell<JNIEnv<'local>>>) -> Self {
|
||||||
|
Self {
|
||||||
|
env,
|
||||||
|
map: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_id(&mut self, id: impl Into<String>, value: impl Into<String>) {
|
||||||
|
let id = id.into();
|
||||||
|
let value = value.into();
|
||||||
|
self.map.entry(id).or_default().push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_id_list(&mut self, id: impl Into<String>, values: Vec<String>) {
|
||||||
|
let id = id.into();
|
||||||
|
self.map.entry(id).or_default().extend(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_combined(&mut self, id: impl Into<String>, description: impl Into<String>, value: impl Into<String>) {
|
||||||
|
let id = id.into();
|
||||||
|
let description = description.into();
|
||||||
|
let value = value.into();
|
||||||
|
let combined_key = format!("{}:{}", id, description);
|
||||||
|
self.map.entry(combined_key).or_default().push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_object(&self) -> JObject {
|
||||||
|
let map_class = self.env.borrow_mut().find_class("java/util/HashMap").unwrap();
|
||||||
|
let map = self.env.borrow_mut().new_object(&map_class, "()V", &[]).unwrap();
|
||||||
|
|
||||||
|
for (key, values) in &self.map {
|
||||||
|
let j_key = self.env.borrow().new_string(key).unwrap();
|
||||||
|
let j_values: Vec<JObject> = values
|
||||||
|
.iter()
|
||||||
|
.map(|v| self.env.borrow().new_string(v).unwrap().into())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Create ArrayList for values
|
||||||
|
let array_list_class = self.env.borrow_mut().find_class("java/util/ArrayList").unwrap();
|
||||||
|
let array_list = self.env.borrow_mut().new_object(array_list_class, "()V", &[]).unwrap();
|
||||||
|
|
||||||
|
for value in j_values {
|
||||||
|
self.env.borrow_mut()
|
||||||
|
.call_method(
|
||||||
|
&array_list,
|
||||||
|
"add",
|
||||||
|
"(Ljava/lang/Object;)Z",
|
||||||
|
&[JValueGen::from(&value)]
|
||||||
|
).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.env.borrow_mut()
|
||||||
|
.call_method(
|
||||||
|
&map,
|
||||||
|
"put",
|
||||||
|
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
|
||||||
|
&[JValueGen::from(&j_key), JValueGen::from(&array_list)]
|
||||||
|
).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
map
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue