blob: fc5bdc73adc51eb9df86cb02c0fafda3db2b4431 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use crate::Result;
use jni::JNIEnv;
use jni::objects::JObject;
use jni::objects::JString;
use jni::objects::{JByteArray, JMap};
use jni::sys::jlong;
use opendal::{Error, ErrorKind};
use std::collections::HashMap;
use std::ops::Bound;
pub(crate) fn usize_to_jlong(n: Option<usize>) -> jlong {
// usize is always >= 0, so we can use -1 to identify the empty value.
n.map_or(-1, |v| v as jlong)
}
pub(crate) fn jmap_to_hashmap(
env: &mut JNIEnv,
params: &JObject,
) -> Result<HashMap<String, String>> {
let map = JMap::from_env(env, params)?;
let mut iter = map.iter(env)?;
let mut result: HashMap<String, String> = HashMap::new();
while let Some(e) = iter.next(env)? {
let k = JString::from(e.0);
let v = JString::from(e.1);
result.insert(env.get_string(&k)?.into(), env.get_string(&v)?.into());
}
Ok(result)
}
pub(crate) fn hashmap_to_jmap<'a>(
env: &mut JNIEnv<'a>,
map: &HashMap<String, String>,
) -> Result<JObject<'a>> {
let map_object = env.new_object("java/util/HashMap", "()V", &[])?;
let jmap = env.get_map(&map_object)?;
for (k, v) in map {
let key = env.new_string(k)?;
let value = env.new_string(v)?;
jmap.put(env, &key, &value)?;
}
Ok(map_object)
}
pub(crate) fn string_to_jstring<'a>(env: &mut JNIEnv<'a>, s: Option<&str>) -> Result<JObject<'a>> {
s.map_or_else(
|| Ok(JObject::null()),
|v| Ok(env.new_string(v.to_string())?.into()),
)
}
pub(crate) fn read_bool_field(env: &mut JNIEnv<'_>, obj: &JObject, field: &str) -> Result<bool> {
Ok(env.get_field(obj, field, "Z")?.z()?)
}
pub(crate) fn read_int64_field(env: &mut JNIEnv<'_>, obj: &JObject, field: &str) -> Result<i64> {
Ok(env.get_field(obj, field, "J")?.j()?)
}
pub(crate) fn read_int_field(env: &mut JNIEnv<'_>, obj: &JObject, field: &str) -> Result<i32> {
Ok(env.get_field(obj, field, "I")?.i()?)
}
pub(crate) fn read_string_field(
env: &mut JNIEnv<'_>,
obj: &JObject,
field: &str,
) -> Result<Option<String>> {
let result = env.get_field(obj, field, "Ljava/lang/String;")?.l()?;
if result.is_null() {
Ok(None)
} else {
Ok(Some(jstring_to_string(env, &JString::from(result))?))
}
}
pub(crate) fn read_map_field(
env: &mut JNIEnv<'_>,
obj: &JObject,
field: &str,
) -> Result<Option<HashMap<String, String>>> {
let result = env.get_field(obj, field, "Ljava/util/Map;")?.l()?;
if result.is_null() {
Ok(None)
} else {
Ok(Some(jmap_to_hashmap(env, &result)?))
}
}
pub(crate) fn read_jlong_field_to_usize(
env: &mut JNIEnv,
options: &JObject,
field_name: &str,
) -> Result<Option<usize>> {
match read_int64_field(env, options, field_name)? {
-1 => Ok(None),
v if v > 0 => Ok(Some(v as usize)),
v => Err(Error::new(
ErrorKind::Unexpected,
format!("{field_name} must be positive, instead got: {v}"),
)
.into()),
}
}
pub(crate) fn read_instant_field_to_timestamp(
env: &mut JNIEnv<'_>,
obj: &JObject,
field: &str,
) -> Result<Option<opendal::raw::Timestamp>> {
let result = env.get_field(obj, field, "Ljava/time/Instant;")?.l()?;
if result.is_null() {
return Ok(None);
}
let epoch_second = env
.call_method(&result, "getEpochSecond", "()J", &[])?
.j()?;
let nano = env.call_method(&result, "getNano", "()I", &[])?.i()?;
match opendal::raw::Timestamp::new(epoch_second, nano) {
Ok(ts) => Ok(Some(ts)),
Err(err) => Err(Error::new(
ErrorKind::Unexpected,
format!("invalid timestamp: seconds={epoch_second}, nanos={nano}"),
)
.set_source(err)
.into()),
}
}
pub(crate) fn offset_length_to_range(offset: i64, length: i64) -> Result<(Bound<u64>, Bound<u64>)> {
let offset = u64::try_from(offset)
.map_err(|_| Error::new(ErrorKind::RangeNotSatisfied, "offset must be non-negative"))?;
match length {
-1 => Ok((Bound::Included(offset), Bound::Unbounded)),
_ => match u64::try_from(length) {
Ok(length) => match offset.checked_add(length) {
Some(end) => Ok((Bound::Included(offset), Bound::Excluded(end))),
None => Err(Error::new(
ErrorKind::RangeNotSatisfied,
"offset + length causes overflow",
)
.into()),
},
Err(_) => {
Err(Error::new(ErrorKind::RangeNotSatisfied, "length must be non-negative").into())
}
},
}
}
pub(crate) fn bytes_to_jbytearray<'a>(
env: &mut JNIEnv<'a>,
bytes: impl AsRef<[u8]>,
) -> Result<JByteArray<'a>> {
let bytes = bytes.as_ref();
let res = env.byte_array_from_slice(bytes)?;
Ok(res)
}
/// # Safety
///
/// The caller must guarantee that the Object passed in is an instance
/// of `java.lang.String`, passing in anything else will lead to undefined behavior.
pub(crate) fn jstring_to_string(env: &mut JNIEnv, s: &JString) -> Result<String> {
let res = unsafe { env.get_string_unchecked(s)? };
Ok(res.into())
}