From 013629dec65bd0a193a94e9e6cc4d0aeee8c1bc9 Mon Sep 17 00:00:00 2001
From: Emily Toop
Date: Mon, 14 May 2018 16:20:36 +0100
Subject: [PATCH] iOS and Android (Java) sdk framework (#643)
Documents the FFI layer for Mentat, and provides transaction functionality via an EDN string. Creates two native libraries for iOS (Swift) and Android (Java) and fully tests the FFI for both platforms.
Closes #619 #614 #611
---
.gitignore | 25 +
android_build_all.sh | 4 +
build/version.rs | 2 +-
core/src/types.rs | 21 +
ffi/Cargo.toml | 8 +-
ffi/src/lib.rs | 802 ++++++++++++------
ffi/src/utils.rs | 9 +-
sdks/android/Mentat/.gitignore | 9 +
sdks/android/Mentat/.project | 17 +
sdks/android/Mentat/build.gradle | 27 +
sdks/android/Mentat/gradle.properties | 17 +
.../gradle/wrapper/gradle-wrapper.properties | 6 +
sdks/android/Mentat/gradlew | 160 ++++
sdks/android/Mentat/gradlew.bat | 90 ++
sdks/android/Mentat/library/.classpath | 6 +
sdks/android/Mentat/library/.gitignore | 1 +
sdks/android/Mentat/library/.project | 23 +
sdks/android/Mentat/library/build.gradle | 42 +
.../android/Mentat/library/proguard-rules.pro | 25 +
.../java/com/mozilla/mentat/Expectation.java | 27 +
.../mozilla/mentat/FFIIntegrationTest.java | 725 ++++++++++++++++
.../library/src/main/AndroidManifest.xml | 6 +
.../com/mozilla/mentat/AttributeList.java | 48 ++
.../com/mozilla/mentat/ColResultIterator.java | 54 ++
.../java/com/mozilla/mentat/CollResult.java | 60 ++
.../com/mozilla/mentat/CollResultHandler.java | 18 +
.../src/main/java/com/mozilla/mentat/JNA.java | 101 +++
.../main/java/com/mozilla/mentat/Mentat.java | 147 ++++
.../main/java/com/mozilla/mentat/Query.java | 330 +++++++
.../java/com/mozilla/mentat/RelResult.java | 86 ++
.../com/mozilla/mentat/RelResultHandler.java | 18 +
.../com/mozilla/mentat/RelResultIterator.java | 54 ++
.../java/com/mozilla/mentat/RustObject.java | 34 +
.../java/com/mozilla/mentat/RustResult.java | 62 ++
.../mozilla/mentat/ScalarResultHandler.java | 18 +
.../java/com/mozilla/mentat/TupleResult.java | 168 ++++
.../mozilla/mentat/TupleResultHandler.java | 18 +
.../java/com/mozilla/mentat/TxChange.java | 64 ++
.../java/com/mozilla/mentat/TxChangeList.java | 56 ++
.../mozilla/mentat/TxObserverCallback.java | 20 +
.../java/com/mozilla/mentat/TxReport.java | 89 ++
.../java/com/mozilla/mentat/TypedValue.java | 157 ++++
.../src/main/jniLibs/arm64/libjnidispatch.so | Bin 0 -> 101976 bytes
.../src/main/jniLibs/arm64/libmentat_ffi.so | Bin 0 -> 1192 bytes
.../main/jniLibs/armeabi/libjnidispatch.so | Bin 0 -> 101596 bytes
.../src/main/jniLibs/armeabi/libmentat_ffi.so | Bin 0 -> 1036 bytes
.../src/main/jniLibs/x86/libjnidispatch.so | Bin 0 -> 99392 bytes
.../src/main/jniLibs/x86/libmentat_ffi.so | Bin 0 -> 11367480 bytes
.../library/src/main/res/values/strings.xml | 3 +
sdks/android/Mentat/settings.gradle | 1 +
.../Mentat/Mentat.xcodeproj/project.pbxproj | 615 ++++++++++++++
.../contents.xcworkspacedata | 7 +
.../xcshareddata/IDEWorkspaceChecks.plist | 8 +
.../xcshareddata/WorkspaceSettings.xcsettings | 5 +
.../xcschemes/Mentat Debug.xcscheme | 107 +++
.../xcshareddata/xcschemes/Mentat.xcscheme | 99 +++
.../swift/Mentat/Mentat/Core/TypedValue.swift | 181 ++++
sdks/swift/Mentat/Mentat/Errors/Errors.swift | 30 +
.../Mentat/Mentat/Extensions/Date+Int64.swift | 22 +
.../Mentat/Extensions/Result+Unwrap.swift | 50 ++
sdks/swift/Mentat/Mentat/Info.plist | 24 +
sdks/swift/Mentat/Mentat/Mentat.h | 18 +
sdks/swift/Mentat/Mentat/Mentat.swift | 170 ++++
sdks/swift/Mentat/Mentat/Query/Query.swift | 337 ++++++++
.../swift/Mentat/Mentat/Query/RelResult.swift | 106 +++
.../Mentat/Mentat/Query/TupleResult.swift | 217 +++++
.../Mentat/Rust/OptionalRustObject.swift | 68 ++
.../swift/Mentat/Mentat/Rust/RustObject.swift | 49 ++
.../Mentat/Mentat/Transact/TxReport.swift | 61 ++
sdks/swift/Mentat/Mentat/module.map | 4 +
sdks/swift/Mentat/Mentat/store.h | 171 ++++
sdks/swift/Mentat/MentatTests/Info.plist | 22 +
.../Mentat/MentatTests/MentatTests.swift | 765 +++++++++++++++++
src/conn.rs | 17 -
src/lib.rs | 1 +
src/query_builder.rs | 21 +-
76 files changed, 6545 insertions(+), 288 deletions(-)
create mode 100755 android_build_all.sh
create mode 100644 sdks/android/Mentat/.gitignore
create mode 100644 sdks/android/Mentat/.project
create mode 100644 sdks/android/Mentat/build.gradle
create mode 100644 sdks/android/Mentat/gradle.properties
create mode 100644 sdks/android/Mentat/gradle/wrapper/gradle-wrapper.properties
create mode 100755 sdks/android/Mentat/gradlew
create mode 100644 sdks/android/Mentat/gradlew.bat
create mode 100644 sdks/android/Mentat/library/.classpath
create mode 100644 sdks/android/Mentat/library/.gitignore
create mode 100644 sdks/android/Mentat/library/.project
create mode 100644 sdks/android/Mentat/library/build.gradle
create mode 100644 sdks/android/Mentat/library/proguard-rules.pro
create mode 100644 sdks/android/Mentat/library/src/androidTest/java/com/mozilla/mentat/Expectation.java
create mode 100644 sdks/android/Mentat/library/src/androidTest/java/com/mozilla/mentat/FFIIntegrationTest.java
create mode 100644 sdks/android/Mentat/library/src/main/AndroidManifest.xml
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/AttributeList.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/ColResultIterator.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/CollResult.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/CollResultHandler.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/JNA.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/Mentat.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/Query.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResult.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResultHandler.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResultIterator.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustObject.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustResult.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/ScalarResultHandler.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResult.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResultHandler.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxChange.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxChangeList.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxObserverCallback.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxReport.java
create mode 100644 sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TypedValue.java
create mode 100644 sdks/android/Mentat/library/src/main/jniLibs/arm64/libjnidispatch.so
create mode 100644 sdks/android/Mentat/library/src/main/jniLibs/arm64/libmentat_ffi.so
create mode 100644 sdks/android/Mentat/library/src/main/jniLibs/armeabi/libjnidispatch.so
create mode 100644 sdks/android/Mentat/library/src/main/jniLibs/armeabi/libmentat_ffi.so
create mode 100644 sdks/android/Mentat/library/src/main/jniLibs/x86/libjnidispatch.so
create mode 100755 sdks/android/Mentat/library/src/main/jniLibs/x86/libmentat_ffi.so
create mode 100644 sdks/android/Mentat/library/src/main/res/values/strings.xml
create mode 100644 sdks/android/Mentat/settings.gradle
create mode 100644 sdks/swift/Mentat/Mentat.xcodeproj/project.pbxproj
create mode 100644 sdks/swift/Mentat/Mentat.xcodeproj/project.xcworkspace/contents.xcworkspacedata
create mode 100644 sdks/swift/Mentat/Mentat.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
create mode 100644 sdks/swift/Mentat/Mentat.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
create mode 100644 sdks/swift/Mentat/Mentat.xcodeproj/xcshareddata/xcschemes/Mentat Debug.xcscheme
create mode 100644 sdks/swift/Mentat/Mentat.xcodeproj/xcshareddata/xcschemes/Mentat.xcscheme
create mode 100644 sdks/swift/Mentat/Mentat/Core/TypedValue.swift
create mode 100644 sdks/swift/Mentat/Mentat/Errors/Errors.swift
create mode 100644 sdks/swift/Mentat/Mentat/Extensions/Date+Int64.swift
create mode 100644 sdks/swift/Mentat/Mentat/Extensions/Result+Unwrap.swift
create mode 100644 sdks/swift/Mentat/Mentat/Info.plist
create mode 100644 sdks/swift/Mentat/Mentat/Mentat.h
create mode 100644 sdks/swift/Mentat/Mentat/Mentat.swift
create mode 100644 sdks/swift/Mentat/Mentat/Query/Query.swift
create mode 100644 sdks/swift/Mentat/Mentat/Query/RelResult.swift
create mode 100644 sdks/swift/Mentat/Mentat/Query/TupleResult.swift
create mode 100644 sdks/swift/Mentat/Mentat/Rust/OptionalRustObject.swift
create mode 100644 sdks/swift/Mentat/Mentat/Rust/RustObject.swift
create mode 100644 sdks/swift/Mentat/Mentat/Transact/TxReport.swift
create mode 100644 sdks/swift/Mentat/Mentat/module.map
create mode 100644 sdks/swift/Mentat/Mentat/store.h
create mode 100644 sdks/swift/Mentat/MentatTests/Info.plist
create mode 100644 sdks/swift/Mentat/MentatTests/MentatTests.swift
diff --git a/.gitignore b/.gitignore
index 07102c19..8abb0fce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,3 +54,28 @@ pom.xml.asc
/fixtures/*.db-shm
/fixtures/*.db-wal
/query-parser/out/
+## Build generated
+/sdks/swift/Mentat/build/
+DerivedData
+build.xcarchive
+
+## Various settings
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+/sdks/swift/Mentat/*.xcodeproj/project.xcworkspace/xcuserdata
+
+## Other
+*.xccheckout
+*.moved-aside
+*.xcuserstate
+*.xcscmblueprint
+
+## Obj-C/Swift specific
+*.hmap
+*.ipa
\ No newline at end of file
diff --git a/android_build_all.sh b/android_build_all.sh
new file mode 100755
index 00000000..61430482
--- /dev/null
+++ b/android_build_all.sh
@@ -0,0 +1,4 @@
+# This will eventually become a complete build script, not just for Android
+cargo build -p mentat_ffi --target i686-linux-android --release
+cargo build -p mentat_ffi --target armv7-linux-androideabi --release
+cargo build -p mentat_ffi --target aarch64-linux-android --release
diff --git a/build/version.rs b/build/version.rs
index 66b38976..f67ff18c 100644
--- a/build/version.rs
+++ b/build/version.rs
@@ -19,7 +19,7 @@ use rustc_version::{
/// MIN_VERSION should be changed when there's a new minimum version of rustc required
/// to build the project.
-static MIN_VERSION: &'static str = "1.24.0";
+static MIN_VERSION: &'static str = "1.25.0";
fn main() {
let ver = version().unwrap();
diff --git a/core/src/types.rs b/core/src/types.rs
index ad426a12..ca3b86d4 100644
--- a/core/src/types.rs
+++ b/core/src/types.rs
@@ -755,6 +755,27 @@ impl Binding {
_ => None,
}
}
+
+ pub fn into_c_string(self) -> Option<*mut c_char> {
+ match self {
+ Binding::Scalar(v) => v.into_c_string(),
+ _ => None,
+ }
+ }
+
+ pub fn into_kw_c_string(self) -> Option<*mut c_char> {
+ match self {
+ Binding::Scalar(v) => v.into_kw_c_string(),
+ _ => None,
+ }
+ }
+
+ pub fn into_uuid_c_string(self) -> Option<*mut c_char> {
+ match self {
+ Binding::Scalar(v) => v.into_uuid_c_string(),
+ _ => None,
+ }
+ }
}
#[test]
diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml
index cd872585..7c1e7c10 100644
--- a/ffi/Cargo.toml
+++ b/ffi/Cargo.toml
@@ -1,10 +1,14 @@
[package]
name = "mentat_ffi"
-version = "0.1.0"
+version = "0.0.1"
authors = ["Emily Toop "]
+[lib]
+name = "mentat_ffi"
+crate-type = ["lib", "staticlib", "cdylib"]
+
[dependencies]
libc = "0.2"
[dependencies.mentat]
-path = ".."
+path = "../"
diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs
index b657e871..fc7d734b 100644
--- a/ffi/src/lib.rs
+++ b/ffi/src/lib.rs
@@ -8,6 +8,65 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
+//! This module exposes an Foreign Function Interface (FFI) that allows Mentat to be
+//! called from other languages.
+//!
+//! Functions that are available to other languages in this module are defined as
+//! extern "C" functions which allow them to be layed out correctly for the
+//! platform's C ABI. They all have a `#[no_mangle]` decorator to ensure
+//! Rust's name mangling is turned off, so that it is easier to link to.
+//!
+//! Mentat's FFI contains unsafe code. As it is an interface between foreign code
+//! and native Rust code, Rust cannot guarantee that the types and data that have been passed
+//! to it from another language are present and in the format it is expecting.
+//! This interface is designed to ensure that nothing unsafe passes through this module
+//! and enters Mentat proper
+//!
+//! Structs defined with `#[repr(C)]` are guaranteed to have a layout that is compatible
+//! with the platform's representation in C.
+//!
+//! This API passes pointers in two ways, depending on the lifetime of the value and
+//! what value owns it.
+//! Pointers to values that are guaranteed to live beyond the lifetime of the function,
+//! are passed over the FFI as a raw pointer.
+//!
+//! `value as *const Binding`
+//!
+//! Pointers to values that cannot be guaranteed to live beyond the lifetime of the function
+//! are first `Box`ed so that they live on the heap, and the raw pointer passed this way.
+//!
+//! `Box::into_raw(Box::new(value))`
+//!
+//! The memory for a value that is moved onto the heap before being passed over the FFI
+//! is no longer managed by Rust, but Rust still owns the value. Therefore the pointer
+//! must be returned to Rust in order to be released. To this effect a number of `destructor`
+//! functions are provided for each Rust value type that is passed, as is a catch all destructor
+//! to release memory for `#[repr(C)]` values.
+//! The destructors reclaim the memory via [Box](std::boxed::Box) and then drop the reference, causing the
+//! memory to be released.
+//!
+//! A macro has been provided to make defining destructors easier.
+//!
+//! `define_destructor!(query_builder_destroy, QueryBuilder);`
+//!
+//! Passing a pointer to memory that has already been released will cause Mentat to crash,
+//! so callers have to be careful to ensure they manage their pointers properly.
+//! Failure to call a destructor for a value on the heap will cause a memory leak.
+//!
+//! Generally, the functions exposed in this module have a direct mapping to existing Mentat APIs,
+//! in order to keep application logic to a minumum and provide the greatest flexibility
+//! for callers using the interface. However, in some cases a single convenience function
+//! has been provided in order to make the interface easier to use and reduce the number
+//! of calls that have to be made over the FFI to perform a task. An example of this is
+//! `store_register_observer`, which takes a single native callback function that is then
+//! wrapped inside a Rust closure and added to a [TxObserver](mentat::TxObserver) struct. This is then used to
+//! register the observer with the store.
+//!
+//! [Result](std::result::Result) and [Option](std::option::Option) Rust types have `repr(C)` structs that mirror them. This is to provide a more
+//! native access pattern to callers and to enable easier passing of optional types and error
+//! propogation. These types have implemented [From](std::convert::From) such that conversion from the Rust type
+//! to the C type is as painless as possible.
+
extern crate libc;
extern crate mentat;
@@ -17,6 +76,7 @@ use std::collections::{
use std::os::raw::{
c_char,
c_int,
+ c_longlong,
c_void,
};
use std::slice;
@@ -25,9 +85,8 @@ use std::sync::{
};
use std::vec;
-use libc::time_t;
-
pub use mentat::{
+ Binding,
Entid,
FindSpec,
HasSchema,
@@ -38,15 +97,24 @@ pub use mentat::{
QueryInputs,
QueryOutput,
QueryResults,
+ RelResult,
Store,
Syncable,
TypedValue,
TxObserver,
+ TxReport,
Uuid,
ValueType,
Variable,
};
+pub use mentat::entity_builder::{
+ BuildTerms,
+ EntityBuilder,
+ InProgressBuilder,
+ IntoThing,
+};
+
pub mod android;
pub mod utils;
@@ -56,24 +124,53 @@ pub use utils::strings::{
string_to_c_char,
};
-pub type TypedValueIterator = vec::IntoIter;
-pub type TypedValueListIterator = vec::IntoIter>;
+pub use utils::log;
+pub type BindingIterator = vec::IntoIter;
+pub type BindingListIterator = std::slice::Chunks<'static, mentat::Binding>;
+
+/// A C representation of the change provided by the transaction observers
+/// from a single transact.
+/// Holds a transaction identifier, the changes as a set of affected attributes
+/// and the length of the list of changes.
+///
+/// #Safety
+///
+/// Callers are responsible for managing the memory for the return value.
+/// A destructor `destroy` is provided for releasing the memory for this
+/// pointer type.
#[repr(C)]
#[derive(Debug, Clone)]
-pub struct ExternTxReport {
+pub struct TransactionChange {
pub txid: Entid,
- pub changes: Box<[Entid]>,
pub changes_len: usize,
+ pub changes: Box<[Entid]>,
}
+ /// A C representation of the list of changes provided by the transaction observers.
+ /// Provides the list of changes as the length of the list.
+///
+/// #Safety
+///
+/// Callers are responsible for managing the memory for the return value.
+/// A destructor `destroy` is provided for releasing the memory for this
+/// pointer type.
#[repr(C)]
#[derive(Debug)]
-pub struct ExternTxReportList {
- pub reports: Box<[ExternTxReport]>,
+pub struct TxChangeList {
+ pub reports: Box<[TransactionChange]>,
pub len: usize,
}
+/// A C representation Rust's [Option](std::option::Option).
+/// A value of `Some` results in `value` containing a raw pointer as a `c_void`.
+/// A value of `None` results in `value` containing a null pointer.
+///
+/// #Safety
+///
+/// Callers are responsible for managing the memory for the return value.
+/// A destructor `destroy` is provided for releasing the memory for this
+/// pointer type.
#[repr(C)]
#[derive(Debug)]
pub struct ExternOption {
@@ -88,6 +185,16 @@ impl From
+ *
+ * Note that iteration is consuming and can only be done once.
+ */
+public class RelResult extends RustObject implements Iterable {
+
+ public RelResult(Pointer pointer) {
+ this.rawPointer = pointer;
+ }
+
+ /**
+ * Fetch the row at the requested index.
+ * TODO: Throw an exception if the result set has already been iterated.
+ * @param index the index of the row to be fetched
+ * @return The row at the requested index as a `TupleResult`, if present, or nil if there is no row at that index.
+ */
+ public TupleResult rowAtIndex(int index) {
+ this.validate();
+ Pointer pointer = JNA.INSTANCE.row_at_index(this.rawPointer, index);
+ if (pointer == null) {
+ return null;
+ }
+
+ return new TupleResult(pointer);
+ }
+
+ @Override
+ public RelResultIterator iterator() {
+ this.validate();
+ Pointer iterPointer = JNA.INSTANCE.typed_value_result_set_into_iter(this.rawPointer);
+ this.rawPointer = null;
+ if (iterPointer == null) {
+ return null;
+ }
+ return new RelResultIterator(iterPointer);
+ }
+
+ @Override
+ public void close() {
+ if (this.rawPointer != null) {
+ JNA.INSTANCE.typed_value_result_set_destroy(this.rawPointer);
+ }
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResultHandler.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResultHandler.java
new file mode 100644
index 00000000..f9a4e39c
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResultHandler.java
@@ -0,0 +1,18 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * Copyright 2018 Mozilla
+ * Licensed 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. */
+
+package com.mozilla.mentat;
+
+/**
+ * Interface defining the structure of a callback from a query returning a {@link RelResult}.
+ */
+public interface RelResultHandler {
+ void handleRows(RelResult rows);
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResultIterator.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResultIterator.java
new file mode 100644
index 00000000..28577a3f
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResultIterator.java
@@ -0,0 +1,54 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * Copyright 2018 Mozilla
+ * Licensed 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Pointer;
+
+import java.util.Iterator;
+/**
+ * Iterator for a {@link RelResult}
+ */
+public class RelResultIterator extends RustObject implements Iterator {
+
+ Pointer nextPointer;
+
+ RelResultIterator(Pointer iterator) {
+ this.rawPointer = iterator;
+ }
+
+ private Pointer getNextPointer() {
+ return JNA.INSTANCE.typed_value_result_set_iter_next(this.rawPointer);
+ }
+
+ @Override
+ public boolean hasNext() {
+ this.nextPointer = getNextPointer();
+ return this.nextPointer != null;
+ }
+
+ @Override
+ public TupleResult next() {
+ Pointer next = this.nextPointer == null ? getNextPointer() : this.nextPointer;
+ if (next == null) {
+ return null;
+ }
+
+ return new TupleResult(next);
+ }
+
+
+ @Override
+ public void close() {
+ if (this.rawPointer != null) {
+ JNA.INSTANCE.typed_value_result_set_iter_destroy(this.rawPointer);
+ }
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustObject.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustObject.java
new file mode 100644
index 00000000..35f7b8fb
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustObject.java
@@ -0,0 +1,34 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * Copyright 2018 Mozilla
+ * Licensed 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Pointer;
+
+import java.io.Closeable;
+
+/**
+ * Base class that wraps an non-optional {@link Pointer} representing a pointer to a Rust object.
+ * This class implements {@link Closeable} but does not provide an implementation, forcing all
+ * subclasses to implement it. This ensures that all classes that inherit from RustObject
+ * will have their {@link Pointer} destroyed when the Java wrapper is destroyed.
+ */
+abstract class RustObject implements Closeable {
+ Pointer rawPointer;
+
+ /**
+ * Throws a {@link NullPointerException} if the underlying {@link Pointer} is null.
+ */
+ void validate() {
+ if (this.rawPointer == null) {
+ throw new NullPointerException(this.getClass() + " consumed");
+ }
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustResult.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustResult.java
new file mode 100644
index 00000000..dc19ddc4
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustResult.java
@@ -0,0 +1,62 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * Copyright 2018 Mozilla
+ * Licensed 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Pointer;
+import com.sun.jna.Structure;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a C struct containing a {@link Pointer}s and String that map to a Rust Result.
+ * A RustResult will contain either an ok value, OR an err value, or neither - never both.
+ */
+public class RustResult extends Structure implements Closeable {
+ public static class ByReference extends RustResult implements Structure.ByReference {
+ }
+
+ public static class ByValue extends RustResult implements Structure.ByValue {
+ }
+
+ public Pointer ok;
+ public String err;
+
+ /**
+ * Is there an value attached to this result
+ * @return true if a value is present, false otherwise
+ */
+ public boolean isSuccess() {
+ return this.ok != null;
+ }
+
+ /**
+ * Is there an error attached to this result?
+ * @return true is an error is present, false otherwise
+ */
+ public boolean isFailure() {
+ return this.err != null;
+ }
+
+ @Override
+ protected List getFieldOrder() {
+ return Arrays.asList("ok", "err");
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (this.getPointer() != null) {
+ JNA.INSTANCE.destroy(this.getPointer());
+ }
+ }
+}
\ No newline at end of file
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/ScalarResultHandler.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/ScalarResultHandler.java
new file mode 100644
index 00000000..ad4b2dd3
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/ScalarResultHandler.java
@@ -0,0 +1,18 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * Copyright 2018 Mozilla
+ * Licensed 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. */
+
+package com.mozilla.mentat;
+
+/**
+ * Interface defining the structure of a callback from a query returning a single {@link TypedValue}.
+ */
+public interface ScalarResultHandler {
+ void handleValue(TypedValue value);
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResult.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResult.java
new file mode 100644
index 00000000..a9701682
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResult.java
@@ -0,0 +1,168 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * Copyright 2018 Mozilla
+ * Licensed 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Pointer;
+
+import java.nio.ByteBuffer;
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * Wraps a `Tuple` result from a Mentat query.
+ * A `Tuple` result is a single row {@link TypedValue}s.
+ * Values for individual fields can be fetched as {@link TypedValue} or converted into a requested type.
+ *
+ * Field values can be fetched as one of the following types:
+ *
+ *
{@link TypedValue}
+ *
long
+ *
Entid (as long)
+ *
Keyword (as String)
+ *
boolean
+ *
double
+ *
{@link Date}
+ *
{@link String}
+ *
{@link UUID}
+ *
+ *
+ * To iterate over the result set use standard iteration flows.
+ */
+public class TupleResult extends RustObject {
+
+ public TupleResult(Pointer pointer) {
+ this.rawPointer = pointer;
+ }
+
+ /**
+ * Return the {@link TypedValue} at the specified index.
+ * If the index is greater than the number of values then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The {@link TypedValue} at that index.
+ */
+ public TypedValue get(Integer index) {
+ this.validate();
+ Pointer pointer = JNA.INSTANCE.value_at_index(this.rawPointer, index);
+ if (pointer == null) {
+ return null;
+ }
+ return new TypedValue(pointer);
+ }
+
+ /**
+ * Return the {@link Long} at the specified index.
+ * If the index is greater than the number of values then this function will crash.
+ * If the value type if the {@link TypedValue} at this index is not `Long` then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The {@link Long} at that index.
+ */
+ public Long asLong(Integer index) {
+ this.validate();
+ return JNA.INSTANCE.value_at_index_into_long(this.rawPointer, index);
+ }
+
+ /**
+ * Return the Entid at the specified index.
+ * If the index is greater than the number of values then this function will crash.
+ * If the value type if the {@link TypedValue} at this index is not `Ref` then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The Entid at that index.
+ */
+ public Long asEntid(Integer index) {
+ this.validate();
+ return JNA.INSTANCE.value_at_index_into_entid(this.rawPointer, index);
+ }
+
+ /**
+ * Return the keyword {@link String} at the specified index.
+ * If the index is greater than the number of values then this function will crash.
+ * If the value type if the {@link TypedValue} at this index is not `Keyword` then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The keyword at that index.
+ */
+ public String asKeyword(Integer index) {
+ this.validate();
+ return JNA.INSTANCE.value_at_index_into_kw(this.rawPointer, index);
+ }
+
+ /**
+ * Return the {@link Boolean} at the specified index.
+ * If the index is greater than the number of values then this function will crash.
+ * If the value type if the {@link TypedValue} at this index is not `Boolean` then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The {@link Boolean} at that index.
+ */
+ public Boolean asBool(Integer index) {
+ this.validate();
+ return JNA.INSTANCE.value_at_index_into_boolean(this.rawPointer, index) == 0 ? false : true;
+ }
+
+ /**
+ * Return the {@link Double} at the specified index.
+ * If the index is greater than the number of values then this function will crash.
+ * If the value type if the {@link TypedValue} at this index is not `Double` then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The {@link Double} at that index.
+ */
+ public Double asDouble(Integer index) {
+ this.validate();
+ return JNA.INSTANCE.value_at_index_into_double(this.rawPointer, index);
+ }
+
+ /**
+ * Return the {@link Date} at the specified index.
+ * If the index is greater than the number of values then this function will crash.
+ * If the value type if the {@link TypedValue} at this index is not `Instant` then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The {@link Date} at that index.
+ */
+ public Date asDate(Integer index) {
+ this.validate();
+ return new Date(JNA.INSTANCE.value_at_index_into_timestamp(this.rawPointer, index));
+ }
+
+ /**
+ * Return the {@link String} at the specified index.
+ * If the index is greater than the number of values then this function will crash.
+ * If the value type if the {@link TypedValue} at this index is not `String` then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The {@link String} at that index.
+ */
+ public String asString(Integer index) {
+ this.validate();
+ return JNA.INSTANCE.value_at_index_into_string(this.rawPointer, index);
+ }
+
+ /**
+ * Return the {@link UUID} at the specified index.
+ * If the index is greater than the number of values then this function will crash.
+ * If the value type if the {@link TypedValue} at this index is not `Uuid` then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The {@link UUID} at that index.
+ */
+ public UUID asUUID(Integer index) {
+ this.validate();
+ Pointer uuidPtr = JNA.INSTANCE.value_at_index_into_uuid(this.rawPointer, index);
+ byte[] bytes = uuidPtr.getByteArray(0, 16);
+ ByteBuffer bb = ByteBuffer.wrap(bytes);
+ long high = bb.getLong();
+ long low = bb.getLong();
+
+ return new UUID(high, low);
+ }
+
+ @Override
+ public void close() {
+ if (this.rawPointer != null) {
+ JNA.INSTANCE.typed_value_list_destroy(this.rawPointer);
+ }
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResultHandler.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResultHandler.java
new file mode 100644
index 00000000..4eb7f766
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResultHandler.java
@@ -0,0 +1,18 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * Copyright 2018 Mozilla
+ * Licensed 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. */
+
+package com.mozilla.mentat;
+
+/**
+ * Interface defining the structure of a callback from a query returning a {@link TupleResult}.
+ */
+public interface TupleResultHandler {
+ void handleRow(TupleResult row);
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxChange.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxChange.java
new file mode 100644
index 00000000..d5b6a830
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxChange.java
@@ -0,0 +1,64 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * Copyright 2018 Mozilla
+ * Licensed 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Pointer;
+import com.sun.jna.Structure;
+
+import java.io.Closeable;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a C struct representing changes that occured during a transaction.
+ * These changes contain the transaction identifier, a {@link Pointer} to a list of affected attribute
+ * Entids and the number of items that the list contains.
+ */
+public class TxChange extends Structure implements Closeable {
+ public static class ByReference extends TxChange implements Structure.ByReference {
+ }
+
+ public static class ByValue extends TxChange implements Structure.ByValue {
+ }
+
+ public int txid;
+ public Pointer changes;
+ public int numberOfItems;
+ // Used by the Swift counterpart, JNA does this for us automagically.
+ // But we still need it here so that the number of fields and their order is correct
+ public int changes_len;
+
+ /**
+ * Get the affected attributes for this transaction
+ * @return The changes as a list of Entids of affected attributes
+ */
+ public List getChanges() {
+ final long[] array = (long[]) changes.getLongArray(0, numberOfItems);
+ Long[] longArray = new Long[numberOfItems];
+ int idx = 0;
+ for (long change: array) {
+ longArray[idx++] = change;
+ }
+ return Arrays.asList(longArray);
+ }
+
+ @Override
+ protected List getFieldOrder() {
+ return Arrays.asList("txid", "changes", "changes_len", "numberOfItems");
+ }
+
+ @Override
+ public void close() {
+ if (this.getPointer() != null) {
+ JNA.INSTANCE.destroy(this.getPointer());
+ }
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxChangeList.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxChangeList.java
new file mode 100644
index 00000000..f27d163a
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxChangeList.java
@@ -0,0 +1,56 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * Copyright 2018 Mozilla
+ * Licensed 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Structure;
+
+import java.io.Closeable;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a C struct containing a list of {@link TxChange}s that occured.
+ */
+public class TxChangeList extends Structure implements Closeable {
+ public static class ByReference extends TxChangeList implements Structure.ByReference {
+ }
+
+ public static class ByValue extends TxChangeList implements Structure.ByValue {
+ }
+
+ public TxChange.ByReference reports;
+ public int numberOfItems;
+ // Used by the Swift counterpart, JNA does this for us automagically.
+ // // But we still need it here so that the number of fields and their order is correct
+ public int len;
+
+ /**
+ * Get the changes that occured
+ * @return a list of {@link TxChange}s for the notification
+ */
+ public List getReports() {
+ final TxChange[] array = (TxChange[]) reports.toArray(numberOfItems);
+ return Arrays.asList(array);
+ }
+
+ @Override
+ protected List getFieldOrder() {
+ return Arrays.asList("reports", "numberOfItems", "len");
+ }
+
+ @Override
+ public void close() {
+ final TxChange[] nativeReports = (TxChange[]) reports.toArray(numberOfItems);
+ for (TxChange nativeReport : nativeReports) {
+ nativeReport.close();
+ }
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxObserverCallback.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxObserverCallback.java
new file mode 100644
index 00000000..9b474954
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxObserverCallback.java
@@ -0,0 +1,20 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * Copyright 2018 Mozilla
+ * Licensed 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Callback;
+
+/**
+ * Protocol to be implemented by any object that wishes to register for transaction observation
+ */
+public interface TxObserverCallback extends Callback {
+ void transactionObserverCalled(String key, TxChangeList.ByReference reports);
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxReport.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxReport.java
new file mode 100644
index 00000000..abeaf530
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxReport.java
@@ -0,0 +1,89 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * Copyright 2018 Mozilla
+ * Licensed 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Pointer;
+
+import java.util.Date;
+
+/**
+ * This class wraps a raw pointer than points to a Rust `TxReport` object.
+ *
+ * The `TxReport` contains information about a successful Mentat transaction.
+ *
+ * This information includes:
+ *
+ *
`txId` - the identifier for the transaction.
+ *
`txInstant` - the time that the transaction occured.
+ *
a map of temporary identifiers provided in the transaction and the `Entid`s that they were mapped to.
+ *
+ *
+ * Access an `Entid` for a temporary identifier that was provided in the transaction can be done through `entid(String:)`.
+ *
+ *
{@code
+ * TxReport report = mentat.transact("[[:db/add "a" :foo/boolean true]]");
+ * long aEntid = report.getEntidForTempId("a");
+ *}
+ */
+public class TxReport extends RustObject {
+
+ private Long txId;
+ private Date txInstant;
+
+
+ public TxReport(Pointer pointer) {
+ this.rawPointer = pointer;
+ }
+
+ /**
+ * Get the identifier for the transaction.
+ * @return The identifier for the transaction.
+ */
+ public Long getTxId() {
+ if (this.txId == null) {
+ this.txId = JNA.INSTANCE.tx_report_get_entid(this.rawPointer);
+ }
+
+ return this.txId;
+ }
+
+ /**
+ * Get the time that the transaction occured.
+ * @return The time that the transaction occured.
+ */
+ public Date getTxInstant() {
+ if (this.txInstant == null) {
+ this.txInstant = new Date(JNA.INSTANCE.tx_report_get_tx_instant(this.rawPointer));
+ }
+ return this.txInstant;
+ }
+
+ /**
+ * Access an `Entid` for a temporary identifier that was provided in the transaction.
+ * @param tempId A {@link String} representing the temporary identifier to fetch the `Entid` for.
+ * @return The `Entid` for the temporary identifier, if present, otherwise `null`.
+ */
+ public Long getEntidForTempId(String tempId) {
+ Pointer longPointer = JNA.INSTANCE.tx_report_entity_for_temp_id(this.rawPointer, tempId);
+ if (longPointer == null) {
+ return null;
+ }
+
+ return longPointer.getLong(0);
+ }
+
+ @Override
+ public void close() {
+ if (this.rawPointer != null) {
+ JNA.INSTANCE.tx_report_destroy(this.rawPointer);
+ }
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TypedValue.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TypedValue.java
new file mode 100644
index 00000000..943e9753
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TypedValue.java
@@ -0,0 +1,157 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * Copyright 2018 Mozilla
+ * Licensed 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Pointer;
+
+import java.nio.ByteBuffer;
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * A wrapper around Mentat's `TypedValue` Rust object. This class wraps a raw pointer to a Rust `TypedValue`
+ * struct and provides accessors to the values according to expected result type.
+ *
+ * As the FFI functions for fetching values are consuming, this class keeps a copy of the result internally after
+ * fetching so that the value can be referenced several times.
+ *
+ * Also, due to the consuming nature of the FFI layer, this class also manages it's raw pointer, nilling it after calling the
+ * FFI conversion function so that the underlying base class can manage cleanup.
+ */
+public class TypedValue extends RustObject {
+
+ private Object value;
+
+ private boolean isConsumed() {
+ return this.rawPointer == null;
+ }
+
+ public TypedValue(Pointer pointer) {
+ this.rawPointer = pointer;
+ }
+
+ /**
+ * This value as a {@link Long}. This function will panic if the `ValueType` of this
+ * {@link TypedValue} is not a `Long`
+ * @return the value of this {@link TypedValue} as a {@link Long}
+ */
+ public Long asLong() {
+ if (!this.isConsumed()) {
+ this.value = JNA.INSTANCE.typed_value_into_long(this.rawPointer);
+ this.rawPointer = null;
+ }
+ return (Long)value;
+ }
+
+ /**
+ * This value as a Entid. This function will panic if the `ValueType` of this
+ * {@link TypedValue} is not a `Ref`
+ * @return the value of this {@link TypedValue} as a Entid
+ */
+ public Long asEntid() {
+ if (!this.isConsumed()) {
+ this.value = JNA.INSTANCE.typed_value_into_entid(this.rawPointer);
+ this.rawPointer = null;
+ }
+ return (Long)value;
+ }
+
+ /**
+ * This value as a keyword {@link String}. This function will panic if the `ValueType` of this
+ * {@link TypedValue} is not a `Keyword`
+ * @return the value of this {@link TypedValue} as a Keyword
+ */
+ public String asKeyword() {
+ if (!this.isConsumed()) {
+ this.value = JNA.INSTANCE.typed_value_into_kw(this.rawPointer);
+ this.rawPointer = null;
+ }
+ return (String)value;
+ }
+
+ /**
+ * This value as a {@link Boolean}. This function will panic if the `ValueType` of this
+ * {@link TypedValue} is not a `Boolean`
+ * @return the value of this {@link TypedValue} as a {@link Boolean}
+ */
+ public Boolean asBoolean() {
+ if (!this.isConsumed()) {
+ long value = JNA.INSTANCE.typed_value_into_boolean(this.rawPointer);
+ this.value = value == 0 ? false : true;
+ this.rawPointer = null;
+ }
+ return (Boolean) this.value;
+ }
+
+ /**
+ * This value as a {@link Double}. This function will panic if the `ValueType` of this
+ * {@link TypedValue} is not a `Double`
+ * @return the value of this {@link TypedValue} as a {@link Double}
+ */
+ public Double asDouble() {
+ if (!this.isConsumed()) {
+ this.value = JNA.INSTANCE.typed_value_into_double(this.rawPointer);
+ this.rawPointer = null;
+ }
+ return (Double)value;
+ }
+
+ /**
+ * This value as a {@link Date}. This function will panic if the `ValueType` of this
+ * {@link TypedValue} is not a `Instant`
+ * @return the value of this {@link TypedValue} as a {@link Date}
+ */
+ public Date asDate() {
+ if (!this.isConsumed()) {
+ this.value = new Date(JNA.INSTANCE.typed_value_into_timestamp(this.rawPointer) * 1_000);
+ this.rawPointer = null;
+ }
+ return (Date)this.value;
+ }
+
+ /**
+ * This value as a {@link String}. This function will panic if the `ValueType` of this
+ * {@link TypedValue} is not a `String`
+ * @return the value of this {@link TypedValue} as a {@link String}
+ */
+ public String asString() {
+ if (!this.isConsumed()) {
+ this.value = JNA.INSTANCE.typed_value_into_string(this.rawPointer);
+ this.rawPointer = null;
+ }
+ return (String)value;
+ }
+
+ /**
+ * This value as a {@link UUID}. This function will panic if the `ValueType` of this
+ * {@link TypedValue} is not a `Uuid`
+ * @return the value of this {@link TypedValue} as a {@link UUID}
+ */
+ public UUID asUUID() {
+ if (!this.isConsumed()) {
+ Pointer uuidPtr = JNA.INSTANCE.typed_value_into_uuid(this.rawPointer);
+ byte[] bytes = uuidPtr.getByteArray(0, 16);
+ ByteBuffer bb = ByteBuffer.wrap(bytes);
+ long high = bb.getLong();
+ long low = bb.getLong();
+ this.value = new UUID(high, low);
+ this.rawPointer = null;
+ }
+ return (UUID)this.value;
+ }
+
+ @Override
+ public void close() {
+ if (this.rawPointer != null) {
+ JNA.INSTANCE.typed_value_destroy(this.rawPointer);
+ }
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/jniLibs/arm64/libjnidispatch.so b/sdks/android/Mentat/library/src/main/jniLibs/arm64/libjnidispatch.so
new file mode 100644
index 0000000000000000000000000000000000000000..d2b1a8f281b2432a363f80a208248d885eb4b80e
GIT binary patch
literal 101976
zcmb?^4PaEowg2pHfbgLN2qXkFy9p{rt)Kxxt!y?Z2!0?$rPel^Y=S{PB~d|8ZbE8l
z=?7P-THkwRlYqiL+foHftjYq`YVB(&wAA{yb@{NRMfw7p_nVL1o10Cn^+hlD
z-ZN*;oH=vm%$YNDXIEc&%~f8HN0>jK_^IGqc#?tyb;7G_(>OsfN%R##F+vQJxP3HE
zrrYvsRX%eUGKc!NFEUKJRi#h5)hn}``|FzMBc~av3XfRKw@v5Urt_uln$2zco~^Ub
zdb6P>i_hok1lRqjOccW0^{co6`B>h+`j?{-XY2BMs^3dKg=p@YE3pRPr!|eQ;9h}i
z2(EA8nug2#-LD`11NR#J{0Q#pSvIh~@fz748q?nuV(t*FCuScPXy(aLvT^Ag_
zw&UVo9j^bzRfww|S8snmM&jeRuE5oTYZb1WaNUmUYFvYH*|>(|;$NA%K|_TYiEBQt
zZ{nJYYZW+v&LO#`S$%*W&Wy`U$Qmt~Ok&ac#m?itGR4&-4K|Evlrdt@mb9KT*+^cZis^9OxeIBkm_4^`y=WU7OnUB@_eGTqoagE2d7FRK@8*%aP
z8@MjVwT>_N`!24p;iBzZfa^Y7&A4XZ8iR{}E7eW@YvJGj!nIkx3GqeT7vhTH+KTHj
zTo2)T0@oH?{A3C1=jc5OpZdeULz>Q9OTG$I?iG?we{nRf;5F(d
z=4$y*cgg=4GLoMYTKQaVGs^8gBGkKLX{$kJv3ek+2({y`1;p#^ZW6&X=-{Js?zj5j&4nqE!+UqMl$mgi$bGtS^3A6|Q
zUcyECW3H92VhhUS-#Xpyn`+eiCUp~QG@q}!^y(EY=k+dmmLOjyK8sO4%bl(rAHSx*
zO5*nyZ_QKy(_h}!{c*bPznXoqPSe@auJDb1F4A-!(R57zZIbfoFJ_%m`2VSHVz-vh
zHM*j8kT3td8h`uG)jO)M{&cycHUCC`7WN?jJ2m|V&A&xQ3x8g%|6xnM%JGsKAGQ9R)a7p1@TYp<^K_|K8p(0X>0x|-R?~mWRj-Y@znpAU
zMCL+X{ClnkdDepeO!^S&0Unlg`inZ6p=X~(C*FWV*C<>%0UC;jj=g}+nN`M&07sph9t*VnJ{Z@o$(W@)&s^b@j&