Skip to content

Commit

Permalink
Add Read.getObjectType (#11)
Browse files Browse the repository at this point in the history
Problem: sometimes you have an object ID but need to know what type of
object the ID points at in order to determine what to do with it but
there is no way of getting this information from automerge.

Solution: add Read.getObjectType which returns the type of the object
ID, or `Optional.empty` if the object is not in the document.
  • Loading branch information
alexjg authored Jan 11, 2024
1 parent d92e28a commit 28addf7
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 3 deletions.
4 changes: 4 additions & 0 deletions lib/src/main/java/org/automerge/AutomergeSys.java
Original file line number Diff line number Diff line change
Expand Up @@ -335,4 +335,8 @@ public static native long lookupCursorIndexInDoc(DocPointer doc, ObjectId obj, C

public static native long lookupCursorIndexInTx(TransactionPointer tx, ObjectId obj, Cursor cursor,
Optional<ChangeHash[]> heads);

public static native Optional<ObjectType> getObjectTypeInDoc(DocPointer doc, ObjectId obj);

public static native Optional<ObjectType> getObjectTypeInTx(TransactionPointer tx, ObjectId obj);
}
9 changes: 9 additions & 0 deletions lib/src/main/java/org/automerge/Document.java
Original file line number Diff line number Diff line change
Expand Up @@ -714,4 +714,13 @@ public synchronized long lookupCursorIndex(ObjectId obj, Cursor cursor, ChangeHa
return AutomergeSys.lookupCursorIndexInDoc(this.pointer.get(), obj, cursor, Optional.of(heads));
}
}

@Override
public synchronized Optional<ObjectType> getObjectType(ObjectId obj) {
if (this.transactionPtr.isPresent()) {
return AutomergeSys.getObjectTypeInTx(this.transactionPtr.get(), obj);
} else {
return AutomergeSys.getObjectTypeInDoc(this.pointer.get(), obj);
}
}
}
11 changes: 11 additions & 0 deletions lib/src/main/java/org/automerge/Read.java
Original file line number Diff line number Diff line change
Expand Up @@ -408,4 +408,15 @@ public interface Read {
* object
*/
public long lookupCursorIndex(ObjectId obj, Cursor cursor, ChangeHash[] heads);

/**
* Get the object type of the object given by obj
*
* @param obj
* - The ID of the object to get the type of
*
* @return The type of the object or Optional.empty if the object does not exist
* in this document
*/
public Optional<ObjectType> getObjectType(ObjectId obj);
}
5 changes: 5 additions & 0 deletions lib/src/main/java/org/automerge/TransactionImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -378,4 +378,9 @@ public synchronized long lookupCursorIndex(ObjectId obj, Cursor cursor, ChangeHa
return AutomergeSys.lookupCursorIndexInTx(this.pointer.get(), obj, cursor, Optional.of(heads));
}

@Override
public synchronized Optional<ObjectType> getObjectType(ObjectId obj) {
return AutomergeSys.getObjectTypeInTx(this.pointer.get(), obj);
}

}
44 changes: 44 additions & 0 deletions lib/src/test/java/org/automerge/TestGetObjType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.automerge;

import java.util.Optional;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class TestGetObjType {

@Test
public void testGetObjType() {
Document doc = new Document();
ObjectId map;
ObjectId list;
ObjectId text;
try (Transaction tx = doc.startTransaction()) {
map = tx.set(ObjectId.ROOT, "map", ObjectType.MAP);
list = tx.set(ObjectId.ROOT, "list", ObjectType.LIST);
text = tx.set(ObjectId.ROOT, "text", ObjectType.TEXT);
tx.commit();
}

// make an object ID from a different document
Document otherDoc = new Document();
ObjectId missingObj;
try (Transaction tx = otherDoc.startTransaction()) {
missingObj = tx.set(ObjectId.ROOT, "other", ObjectType.MAP);
tx.commit();
}

Assertions.assertEquals(Optional.of(ObjectType.MAP), doc.getObjectType(map));
Assertions.assertEquals(Optional.of(ObjectType.LIST), doc.getObjectType(list));
Assertions.assertEquals(Optional.of(ObjectType.TEXT), doc.getObjectType(text));
Assertions.assertEquals(Optional.empty(), doc.getObjectType(missingObj));

// now the same tests but in a transaction
try (Transaction tx = doc.startTransaction()) {
Assertions.assertEquals(Optional.of(ObjectType.MAP), tx.getObjectType(map));
Assertions.assertEquals(Optional.of(ObjectType.LIST), tx.getObjectType(list));
Assertions.assertEquals(Optional.of(ObjectType.TEXT), tx.getObjectType(text));
Assertions.assertEquals(Optional.empty(), tx.getObjectType(missingObj));
}
}

}
3 changes: 1 addition & 2 deletions rust/src/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ impl Cursor {
.map_err(errors::FromRaw::GetByteArray)?;
let bytes =
std::slice::from_raw_parts(arr.as_ptr() as *const u8, arr.size().unwrap() as usize);
let cursor: automerge::Cursor =
bytes.try_into().map_err(errors::FromRaw::Invalid)?;
let cursor: automerge::Cursor = bytes.try_into().map_err(errors::FromRaw::Invalid)?;
Ok(Self(cursor))
}
}
Expand Down
20 changes: 20 additions & 0 deletions rust/src/obj_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ pub(crate) enum JavaObjType {
Text,
}

pub(crate) const CLASSNAME: &str = am_classname!("ObjectType");

// The ordinal of the various types in the `org.automerge.jni.ObjectType` enum
const MAP_ORDINAL: i32 = 0;
const LIST_ORDINAL: i32 = 1;
const TEXT_ORDINAL: i32 = 2;

const MAP_FIELD_NAME: &str = "MAP";
const LIST_FIELD_NAME: &str = "LIST";
const TEXT_FIELD_NAME: &str = "TEXT";

impl JavaObjType {
/// Convert a `jobject` referring to an instance of org.automerge.jni.ObjectType to a
/// `JavaObjType`
Expand Down Expand Up @@ -42,6 +48,20 @@ impl JavaObjType {
other => Err(FromJavaError::UnknownOrdinal(other)),
}
}

/// Convert a `JavaObjType` to a `JOBject`
pub(crate) unsafe fn to_java_enum<'a>(
&'_ self,
env: jni::JNIEnv<'a>,
) -> Result<JObject<'a>, jni::errors::Error> {
let field_name = match self {
Self::Map => MAP_FIELD_NAME,
Self::List => LIST_FIELD_NAME,
Self::Text => TEXT_FIELD_NAME,
};
let field = env.get_static_field(CLASSNAME, field_name, format!("L{};", CLASSNAME))?;
field.l()
}
}

impl From<JavaObjType> for am::ObjType {
Expand Down
20 changes: 19 additions & 1 deletion rust/src/read_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::interop::{changehash_to_jobject, heads_from_jobject, CHANGEHASH_CLASS
use crate::java_option::{make_empty_option, make_optional};
use crate::mark::mark_to_java;
use crate::obj_id::JavaObjId;
use crate::obj_type::JavaObjType;
use crate::prop::JProp;
use crate::AUTOMERGE_EXCEPTION;
use crate::{interop::AsPointerObj, read_ops::ReadOps};
Expand All @@ -22,14 +23,14 @@ mod cursor;
mod get;
mod get_all;
mod get_at;
mod get_object_type;
mod heads;
mod keys;
mod length;
mod list_items;
mod map_entries;
mod marks;
mod text;

macro_rules! catch {
($env:ident, $e:expr) => {
match $e {
Expand Down Expand Up @@ -436,6 +437,23 @@ impl SomeReadPointer {
};
index as i64
}

unsafe fn get_object_type(self, env: jni::JNIEnv<'_>, obj_pointer: jobject) -> jobject {
let obj = JavaObjId::from_raw(&env, obj_pointer).unwrap();
let read = SomeRead::from_pointer(env, self);
let obj_type = match read.object_type(obj) {
Ok(o) => o,
Err(automerge::AutomergeError::InvalidObjId(_)) => {
return make_empty_option(&env).unwrap().into_raw();
}
Err(e) => {
env.throw_new(AUTOMERGE_EXCEPTION, e.to_string()).unwrap();
return JObject::null().into_raw();
}
};
let val = JavaObjType::from(obj_type).to_java_enum(env).unwrap();
return make_optional(&env, val.into()).unwrap().into_raw();
}
}

unsafe fn maybe_heads(
Expand Down
26 changes: 26 additions & 0 deletions rust/src/read_methods/get_object_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use automerge_jni_macros::jni_fn;
use jni::sys::jobject;

use super::SomeReadPointer;

#[no_mangle]
#[jni_fn]
pub unsafe extern "C" fn getObjectTypeInDoc(
env: jni::JNIEnv,
_class: jni::objects::JClass,
doc_pointer: jni::sys::jobject,
obj_pointer: jni::sys::jobject,
) -> jobject {
SomeReadPointer::doc(doc_pointer).get_object_type(env, obj_pointer)
}

#[no_mangle]
#[jni_fn]
pub unsafe extern "C" fn getObjectTypeInTx(
env: jni::JNIEnv,
_class: jni::objects::JClass,
tx_pointer: jni::sys::jobject,
obj_pointer: jni::sys::jobject,
) -> jobject {
SomeReadPointer::tx(tx_pointer).get_object_type(env, obj_pointer)
}

0 comments on commit 28addf7

Please sign in to comment.