Add wrapper classes and documentation for FFI functions

This commit is contained in:
Emily Toop 2018-04-24 13:24:25 +01:00
parent b83bec566a
commit 08387d4754
20 changed files with 1641 additions and 0 deletions

View file

@ -0,0 +1,51 @@
/* -*- 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 android.util.Log;
import com.sun.jna.Structure;
import com.sun.jna.ptr.IntByReference;
import java.io.Closeable;
import java.util.Arrays;
import java.util.List;
/**
* Represents a C struct of a list of Strings containing attributes in the format
* `:namespace/name`.
*/
public class AttributeList extends Structure implements Closeable {
public static class ByReference extends AttributeList implements Structure.ByReference {
}
public static class ByValue extends AttributeList implements Structure.ByValue {
}
public IntByReference attributes;
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;
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("attributes", "numberOfItems", "len");
}
@Override
public void close() {
Log.i("AttributeList", "close");
if (this.getPointer() != null) {
JNA.INSTANCE.destroy(this.getPointer());
}
}
}

View file

@ -0,0 +1,58 @@
/* -*- 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 android.util.Log;
import com.sun.jna.Pointer;
import java.io.IOException;
import java.util.Iterator;
/**
* Iterator for a {@link CollResult}
*/
public class ColResultIterator extends RustObject implements Iterator {
Pointer nextPointer;
ColResultIterator(Pointer iterator) {
this.rawPointer = iterator;
}
private Pointer getNextPointer() {
return JNA.INSTANCE.values_iter_next(this.rawPointer);
}
@Override
public boolean hasNext() {
this.nextPointer = getNextPointer();
return this.nextPointer != null;
}
@Override
public TypedValue next() {
Pointer next = this.nextPointer == null ? getNextPointer() : this.nextPointer;
if (next == null) {
return null;
}
return new TypedValue(next);
}
@Override
public void close() {
Log.i("TupleResult", "close");
if (this.rawPointer != null) {
JNA.INSTANCE.typed_value_list_iter_destroy(this.rawPointer);
}
}
}

View file

@ -0,0 +1,63 @@
/* -*- 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 android.util.Log;
import com.sun.jna.Pointer;
import java.util.Date;
import java.util.UUID;
/**
* Wraps a `Coll` result from a Mentat query.
* A `Coll` result is a list of rows of single values of type {@link TypedValue}.
* Values for individual rows can be fetched as {@link TypedValue} or converted into a requested type.
* <p>
* Row values can be fetched as one of the following types:
* <ul>
* <li>{@link TypedValue}</li>
* <li>long</li>
* <li>Entid (as long)</li>
* <li>Keyword (as String)</li>
* <li>boolean</li>
* <li>double</li>
* <li>{@link Date}</li>
* <li>{@link String}</li>
* <li>{@link UUID}</li>
* </ul>
* <p>
* To iterate over the result set use standard iteration flows.
*/
public class CollResult extends TupleResult implements Iterable<TypedValue> {
public CollResult(Pointer pointer) {
super(pointer);
}
@Override
public void close() {
Log.i("CollResult", "close");
if (this.rawPointer != null) {
JNA.INSTANCE.destroy(this.rawPointer);
}
}
@Override
public ColResultIterator iterator() {
Pointer iterPointer = JNA.INSTANCE.values_iter(this.rawPointer);
this.rawPointer = null;
if (iterPointer == null) {
return null;
}
return new ColResultIterator(iterPointer);
}
}

View file

@ -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 CollResult}.
*/
public interface CollResultHandler {
void handleList(CollResult list);
}

View file

@ -0,0 +1,101 @@
/* -*- 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.Library;
import com.sun.jna.Native;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Pointer;
/**
* JNA interface for FFI to Mentat's Rust library
* Each function definition here link directly to a function in Mentat's FFI crate.
* Signatures must match for the linking to work correctly.
*/
public interface JNA extends Library {
String JNA_LIBRARY_NAME = "mentat_ffi";
NativeLibrary JNA_NATIVE_LIB = NativeLibrary.getInstance(JNA_LIBRARY_NAME);
JNA INSTANCE = (JNA) Native.loadLibrary(JNA_LIBRARY_NAME, JNA.class);
Pointer store_open(String dbPath);
void destroy(Pointer obj);
void query_builder_destroy(Pointer obj);
void store_destroy(Pointer obj);
void typed_value_destroy(Pointer obj);
void typed_value_list_destroy(Pointer obj);
void typed_value_list_iter_destroy(Pointer obj);
void typed_value_result_set_destroy(Pointer obj);
void typed_value_result_set_iter_destroy(Pointer obj);
void tx_report_destroy(Pointer obj);
// transact
RustResult store_transact(Pointer store, String transaction);
Pointer tx_report_entity_for_temp_id(Pointer report, String tempid);
long tx_report_get_entid(Pointer report);
long tx_report_get_tx_instant(Pointer report);
// sync
RustResult store_sync(Pointer store, String userUuid, String serverUri);
// observers
void store_register_observer(Pointer store, String key, Pointer attributes, int len, TxObserverCallback callback);
void store_unregister_observer(Pointer store, String key);
long store_entid_for_attribute(Pointer store, String attr);
// Query Building
Pointer store_query(Pointer store, String query);
RustResult store_value_for_attribute(Pointer store, long entid, String attribute);
void query_builder_bind_long(Pointer query, String var, long value);
void query_builder_bind_ref(Pointer query, String var, long value);
void query_builder_bind_ref_kw(Pointer query, String var, String value);
void query_builder_bind_kw(Pointer query, String var, String value);
void query_builder_bind_boolean(Pointer query, String var, int value);
void query_builder_bind_double(Pointer query, String var, double value);
void query_builder_bind_timestamp(Pointer query, String var, long value);
void query_builder_bind_string(Pointer query, String var, String value);
void query_builder_bind_uuid(Pointer query, String var, Pointer value);
// Query Execution
RustResult query_builder_execute(Pointer query);
RustResult query_builder_execute_scalar(Pointer query);
RustResult query_builder_execute_coll(Pointer query);
RustResult query_builder_execute_tuple(Pointer query);
// Query Result Processing
long typed_value_as_long(Pointer value);
long typed_value_as_entid(Pointer value);
String typed_value_as_kw(Pointer value);
String typed_value_as_string(Pointer value);
Pointer typed_value_as_uuid(Pointer value);
int typed_value_as_boolean(Pointer value);
double typed_value_as_double(Pointer value);
long typed_value_as_timestamp(Pointer value);
Pointer typed_value_value_type(Pointer value);
Pointer row_at_index(Pointer rows, int index);
Pointer rows_iter(Pointer rows);
Pointer rows_iter_next(Pointer iter);
Pointer values_iter(Pointer rows);
Pointer values_iter_next(Pointer iter);
Pointer value_at_index(Pointer rows, int index);
long value_at_index_as_long(Pointer rows, int index);
long value_at_index_as_entid(Pointer rows, int index);
String value_at_index_as_kw(Pointer rows, int index);
String value_at_index_as_string(Pointer rows, int index);
Pointer value_at_index_as_uuid(Pointer rows, int index);
long value_at_index_as_boolean(Pointer rows, int index);
double value_at_index_as_double(Pointer rows, int index);
long value_at_index_as_timestamp(Pointer rows, int index);
}

View file

@ -0,0 +1,152 @@
/* -*- 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 android.util.Log;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
/**
* The primary class for accessing Mentat's API.<br/>
* This class provides all of the basic API that can be found in Mentat's Store struct.<br/>
* The raw pointer it holds is a pointer to a Store.
*/
public class Mentat extends RustObject {
static {
System.loadLibrary("mentat_ffi");
}
/**
* Open a connection to a Store in a given location.<br/>
* If the store does not already exist, one will be created.
* @param dbPath The URI as a String of the store to open.
*/
public Mentat(String dbPath) {
this.rawPointer = JNA.INSTANCE.store_open(dbPath);
}
/**
* Open a connection to an in-memory Store.
*/
public Mentat() {
this.rawPointer = JNA.INSTANCE.store_open("");
}
/**
* Create a new Mentat with the provided pointer to a Mentat Store
* @param rawPointer A pointer to a Mentat Store.
*/
public Mentat(Pointer rawPointer) { this.rawPointer = rawPointer; }
/**
* Simple transact of an EDN string.
* TODO: Throw an exception if the transact fails
* @param transaction The string, as EDN, to be transacted.
* @return The {@link TxReport} of the completed transaction
*/
public TxReport transact(String transaction) {
RustResult result = JNA.INSTANCE.store_transact(this.rawPointer, transaction);
if (result.isFailure()) {
Log.i("Mentat", result.err);
return null;
}
if (result.isSuccess()) {
return new TxReport(result.ok);
} else {
return null;
}
}
/**
* Get the the `Entid` of the attribute
* @param attribute The string represeting the attribute whose `Entid` we are after. The string is represented as `:namespace/name`.
* @return The `Entid` associated with the attribute.
*/
public long entIdForAttribute(String attribute) {
return JNA.INSTANCE.store_entid_for_attribute(this.rawPointer, attribute);
}
/**
* Start a query.
* @param query The string represeting the the query to be executed.
* @return The {@link Query} representing the query that can be executed.
*/
public Query query(String query) {
return new Query(JNA.INSTANCE.store_query(this.rawPointer, query));
}
/**
* Retrieve a single value of an attribute for an Entity
* TODO: Throw an exception if there is no the result contains an error.
* @param attribute The string the attribute whose value is to be returned. The string is represented as `:namespace/name`.
* @param entid The `Entid` of the entity we want the value from.
* @return The {@link TypedValue} containing the value of the attribute for the entity.
*/
public TypedValue valueForAttributeOfEntity(String attribute, long entid) {
RustResult result = JNA.INSTANCE.store_value_for_attribute(this.rawPointer, entid, attribute);
if (result.isSuccess()) {
return new TypedValue(result.ok);
}
if (result.isFailure()) {
Log.i("Mentat", result.err);
}
return null;
}
/**
* Register an callback and a set of attributes to observer for transaction observation.
* The callback function is called when a transaction occurs in the `Store` that this `Mentat`
* is connected to that affects the attributes that an observer has registered for.
* @param key `String` representing an identifier for the observer.
* @param attributes An array of Strings representing the attributes that the observer wishes
* to be notified about if they are referenced in a transaction.
* @param callback the function to call when an observer notice is fired.
*/
public void registerObserver(String key, String[] attributes, TxObserverCallback callback) {
// turn string array into int array
long[] attrEntids = new long[attributes.length];
for(int i = 0; i < attributes.length; i++) {
attrEntids[i] = JNA.INSTANCE.store_entid_for_attribute(this.rawPointer, attributes[i]);
}
Log.i("Mentat", "Registering observer {" + key + "} for attributes:");
for (int i = 0; i < attrEntids.length; i++) {
Log.i("Mentat", "entid: " + attrEntids[i]);
}
final Pointer entidsNativeArray = new Memory(8 * attrEntids.length);
entidsNativeArray.write(0, attrEntids, 0, attrEntids.length);
JNA.INSTANCE.store_register_observer(rawPointer, key, entidsNativeArray, attrEntids.length, callback);
}
/**
* Unregister the observer that was registered with the provided key such that it will no longer be called
* if a transaction occurs that affects the attributes that the observer was registered to observe.
* <p/>
* The observer will need to re-register if it wants to start observing again.
* @param key String representing an identifier for the observer.
*/
public void unregisterObserver(String key) {
JNA.INSTANCE.store_unregister_observer(rawPointer, key);
}
@Override
public void close() {
Log.i("Mentat", "close");
if (this.rawPointer != null) {
JNA.INSTANCE.store_destroy(this.rawPointer);
}
}
}

View file

@ -0,0 +1,332 @@
/* -*- 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 android.util.Log;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.UUID;
/**
* This class allows you to construct a query, bind values to variables and run those queries against a mentat DB.
* <p/>
* This class cannot be created directly, but must be created through `Mentat.query(String:)`.
* <p/>
* The types of values you can bind are:
* <ul>
* <li>{@link TypedValue}</li>
* <li>long</li>
* <li>Entid (as long)</li>
* <li>Keyword (as String)</li>
* <li>boolean</li>
* <li>double</li>
* <li>{@link Date}</li>
* <li>{@link String}</li>
* <li>{@link UUID}</li>
* </ul>
* <p>
* <p/>
* Each bound variable must have a corresponding value in the query string used to create this query.
* <p/>
* <pre>{@code
* String query = "[:find ?name ?cat\n" +
* " :in ?type\n" +
* " :where\n" +
* " [?c :community/name ?name]\n" +
* " [?c :community/type ?type]\n" +
* " [?c :community/category ?cat]]";
* mentat.query(query).bindKeywordReference("?type", ":community.type/website").run(new RelResultHandler() {
* @Override
* public void handleRows(RelResult rows) {
* ...
* }
* });
*}</pre>
* <p/>
* Queries can be run and the results returned in a number of different formats. Individual result values are returned as `TypedValues` and
* the format differences relate to the number and structure of those values. The result format is related to the format provided in the query string.
* <p/>
* - `Rel` - This is the default `run` function and returns a list of rows of values. Queries that wish to have `Rel` results should format their query strings:
*
* <pre>{@code
* String query = "[: find ?a ?b ?c\n" +
* " : where ... ]";
* mentat.query(query).run(new RelResultHandler() {
* @Override
* public void handleRows(RelResult rows) {
* ...
* }
* });
*}</pre>
* <p/>
* - `Scalar` - This returns a single value as a result. This can be optional, as the value may not be present. Queries that wish to have `Scalar` results should format their query strings:
*
* <pre>{@code
* String query = "[: find ?a .\n" +
* " : where ... ]";
* mentat.query(query).runScalar(new ScalarResultHandler() {
* @Override
* public void handleValue(TypedValue value) {
* ...
* }
* });
*}</pre>
* <p/>
* - `Coll` - This returns a list of single values as a result. Queries that wish to have `Coll` results should format their query strings:
* <pre>{@code
* String query = "[: find [?a ...]\n" +
* " : where ... ]";
* mentat.query(query).runColl(new ScalarResultHandler() {
* @Override
* public void handleList(CollResult list) {
* ...
* }
* });
*}</pre>
* <p/>
* - `Tuple` - This returns a single row of values. Queries that wish to have `Tuple` results should format their query strings:
* <pre>{@code
* String query = "[: find [?a ?b ?c]\n" +
* " : where ... ]";
* mentat.query(query).runTuple(new TupleResultHandler() {
* @Override
* public void handleRow(TupleResult row) {
* ...
* }
* });
*}</pre>
*/
public class Query extends RustObject {
public Query(Pointer pointer) {
this.rawPointer = pointer;
}
/**
* Binds a long value to the provided variable name.
* TODO: Throw an exception if the query raw pointer has been consumed.
* @param varName The name of the variable in the format `?name`.
* @param value The value to be bound
* @return This {@link Query} such that further function can be called.
*/
Query bindLong(String varName, long value) {
this.validate();
JNA.INSTANCE.query_builder_bind_long(this.rawPointer, varName, value);
return this;
}
/**
* Binds a Entid value to the provided variable name.
* TODO: Throw an exception if the query raw pointer has been consumed.
* @param varName The name of the variable in the format `?name`.
* @param value The value to be bound
* @return This {@link Query} such that further function can be called.
*/
Query bindEntidReference(String varName, long value) {
this.validate();
JNA.INSTANCE.query_builder_bind_ref(this.rawPointer, varName, value);
return this;
}
/**
* Binds a String keyword value to the provided variable name.
* TODO: Throw an exception if the query raw pointer has been consumed.
* @param varName The name of the variable in the format `?name`.
* @param value The value to be bound
* @return This {@link Query} such that further function can be called.
*/
Query bindKeywordReference(String varName, String value) {
this.validate();
JNA.INSTANCE.query_builder_bind_ref_kw(this.rawPointer, varName, value);
return this;
}
/**
* Binds a keyword value to the provided variable name.
* TODO: Throw an exception if the query raw pointer has been consumed.
* @param varName The name of the variable in the format `?name`.
* @param value The value to be bound
* @return This {@link Query} such that further function can be called.
*/
Query bindKeyword(String varName, String value) {
this.validate();
JNA.INSTANCE.query_builder_bind_kw(this.rawPointer, varName, value);
return this;
}
/**
* Binds a boolean value to the provided variable name.
* TODO: Throw an exception if the query raw pointer has been consumed.
* @param varName The name of the variable in the format `?name`.
* @param value The value to be bound
* @return This {@link Query} such that further function can be called.
*/
Query bindBoolean(String varName, boolean value) {
this.validate();
JNA.INSTANCE.query_builder_bind_boolean(this.rawPointer, varName, value ? 1 : 0);
return this;
}
/**
* Binds a double value to the provided variable name.
* TODO: Throw an exception if the query raw pointer has been consumed.
* @param varName The name of the variable in the format `?name`.
* @param value The value to be bound
* @return This {@link Query} such that further function can be called.
*/
Query bindDouble(String varName, double value) {
this.validate();
JNA.INSTANCE.query_builder_bind_double(this.rawPointer, varName, value);
return this;
}
/**
* Binds a {@link Date} value to the provided variable name.
* TODO: Throw an exception if the query raw pointer has been consumed.
* @param varName The name of the variable in the format `?name`.
* @param value The value to be bound
* @return This {@link Query} such that further function can be called.
*/
Query bindDate(String varName, Date value) {
this.validate();
long timestamp = value.getTime() * 1000;
JNA.INSTANCE.query_builder_bind_timestamp(this.rawPointer, varName, timestamp);
return this;
}
/**
* Binds a {@link String} value to the provided variable name.
* TODO: Throw an exception if the query raw pointer has been consumed.
* @param varName The name of the variable in the format `?name`.
* @param value The value to be bound
* @return This {@link Query} such that further function can be called.
*/
Query bindString(String varName, String value) {
this.validate();
JNA.INSTANCE.query_builder_bind_string(this.rawPointer, varName, value);
return this;
}
/**
* Binds a {@link UUID} value to the provided variable name.
* TODO: Throw an exception if the query raw pointer has been consumed.
* @param varName The name of the variable in the format `?name`.
* @param value The value to be bound
* @return This {@link Query} such that further function can be called.
*/
Query bindUUID(String varName, UUID value) {
this.validate();
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(value.getMostSignificantBits());
bb.putLong(value.getLeastSignificantBits());
byte[] bytes = bb.array();
final Pointer bytesNativeArray = new Memory(bytes.length);
bytesNativeArray.write(0, bytes, 0, bytes.length);
JNA.INSTANCE.query_builder_bind_uuid(this.rawPointer, varName, bytesNativeArray);
return this;
}
/**
* Execute the query with the values bound associated with this {@link Query} and call the provided
* callback function with the results as a list of rows of {@link TypedValue}s.
* TODO: Throw an exception if the query raw pointer has been consumed or the query fails to execute
* @param handler the handler to call with the results of this query
*/
void run(final RelResultHandler handler) {
this.validate();
RustResult result = JNA.INSTANCE.query_builder_execute(rawPointer);
rawPointer = null;
if (result.isFailure()) {
Log.e("Query", result.err);
return;
}
handler.handleRows(new RelResult(result.ok));
}
/**
* Execute the query with the values bound associated with this {@link Query} and call the provided
* callback function with the results with the result as a single {@link TypedValue}.
* TODO: Throw an exception if the query raw pointer has been consumed or the query fails to execute
* @param handler the handler to call with the results of this query
*/
void runScalar(final ScalarResultHandler handler) {
this.validate();
RustResult result = JNA.INSTANCE.query_builder_execute_scalar(rawPointer);
rawPointer = null;
if (result.isFailure()) {
Log.e("Query", result.err);
return;
}
if (result.isSuccess()) {
handler.handleValue(new TypedValue(result.ok));
} else {
handler.handleValue(null);
}
}
/**
* Execute the query with the values bound associated with this {@link Query} and call the provided
* callback function with the results with the result as a list of single {@link TypedValue}s.
* TODO: Throw an exception if the query raw pointer has been consumed or the query fails to execute
* @param handler the handler to call with the results of this query
*/
void runColl(final CollResultHandler handler) {
this.validate();
RustResult result = JNA.INSTANCE.query_builder_execute_coll(rawPointer);
rawPointer = null;
if (result.isFailure()) {
Log.e("Query", result.err);
return;
}
handler.handleList(new CollResult(result.ok));
}
/**
* Execute the query with the values bound associated with this {@link Query} and call the provided
* callback function with the results with the result as a list of single {@link TypedValue}s.
* TODO: Throw an exception if the query raw pointer has been consumed or the query fails to execute
* @param handler the handler to call with the results of this query
*/
void runTuple(final TupleResultHandler handler) {
this.validate();
RustResult result = JNA.INSTANCE.query_builder_execute_tuple(rawPointer);
rawPointer = null;
if (result.isFailure()) {
Log.e("Query", result.err);
return;
}
if (result.isSuccess()) {
handler.handleRow(new TupleResult(result.ok));
} else {
handler.handleRow(null);
}
}
@Override
public void close() {
Log.i("Query", "close");
if (this.rawPointer == null) {
return;
}
JNA.INSTANCE.query_builder_destroy(this.rawPointer);
}
}

View file

@ -0,0 +1,90 @@
/* -*- 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 android.util.Log;
import com.sun.jna.Pointer;
/**
* Wraps a `Rel` result from a Mentat query.
* A `Rel` result is a list of rows of `TypedValues`.
* Individual rows can be fetched or the set can be iterated.
* </p>
* To fetch individual rows from a `RelResult` use `row(Int32)`.
* </p>
* <pre>{@code
* mentat.query(query).run(new RelResultHandler() {
* @Override
* public void handleRows(RelResult rows) {
* TupleResult row1 = rows.rowAtIndex(0);
* TupleResult row2 = rows.rowAtIndex(1);
* ...
* }
* });
*}</pre>
* </p>
* To iterate over the result set use standard iteration flows.
* <pre>{@code
* mentat.query(query).run(new RelResultHandler() {
* @Override
* public void handleRows(RelResult rows) {
* for (TupleResult row: rows) {
* ...
* }
* }
* });
*}</pre>
* </p>
* Note that iteration is consuming and can only be done once.
*/
public class RelResult extends RustObject implements Iterable<TupleResult> {
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.rows_iter(this.rawPointer);
this.rawPointer = null;
if (iterPointer == null) {
return null;
}
return new RelResultIterator(iterPointer);
}
@Override
public void close() {
Log.i("RelResult", "close");
if (this.rawPointer != null) {
JNA.INSTANCE.typed_value_result_set_destroy(this.rawPointer);
}
}
}

View file

@ -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);
}

View file

@ -0,0 +1,57 @@
/* -*- 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 android.util.Log;
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.rows_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() {
Log.i("TupleResult", "close");
if (this.rawPointer != null) {
JNA.INSTANCE.typed_value_result_set_iter_destroy(this.rawPointer);
}
}
}

View file

@ -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");
}
}
}

View file

@ -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<String> getFieldOrder() {
return Arrays.asList("ok", "err");
}
@Override
public void close() throws IOException {
if (this.getPointer() != null) {
JNA.INSTANCE.destroy(this.getPointer());
}
}
}

View file

@ -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);
}

View file

@ -0,0 +1,170 @@
/* -*- 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 android.util.Log;
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.
* <p>
* Field values can be fetched as one of the following types:
* <ul>
* <li>{@link TypedValue}</li>
* <li>long</li>
* <li>Entid (as long)</li>
* <li>Keyword (as String)</li>
* <li>boolean</li>
* <li>double</li>
* <li>{@link Date}</li>
* <li>{@link String}</li>
* <li>{@link UUID}</li>
* </ul>
* <p>
* 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_as_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_as_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_as_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_as_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_as_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_as_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_as_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_as_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() {
Log.i("TupleResult", "close");
if (this.rawPointer != null) {
JNA.INSTANCE.typed_value_list_destroy(this.rawPointer);
}
}
}

View file

@ -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);
}

View file

@ -0,0 +1,67 @@
/* -*- 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 android.util.Log;
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<Long> 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<String> getFieldOrder() {
return Arrays.asList("txid", "changes", "changes_len", "numberOfItems");
}
@Override
public void close() {
Log.i("TxChange", "close");
if (this.getPointer() != null) {
JNA.INSTANCE.destroy(this.getPointer());
}
}
}

View file

@ -0,0 +1,59 @@
/* -*- 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 android.util.Log;
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<TxChange> getReports() {
final TxChange[] array = (TxChange[]) reports.toArray(numberOfItems);
return Arrays.asList(array);
}
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("reports", "numberOfItems", "len");
}
@Override
public void close() {
Log.i("TxChangeList", "close");
final TxChange[] nativeReports = (TxChange[]) reports.toArray(numberOfItems);
for (TxChange nativeReport : nativeReports) {
nativeReport.close();
}
}
}

View file

@ -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);
}

View file

@ -0,0 +1,92 @@
/* -*- 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 android.util.Log;
import com.sun.jna.Pointer;
import java.util.Date;
/**
* This class wraps a raw pointer than points to a Rust `TxReport` object.
* </p>
* The `TxReport` contains information about a successful Mentat transaction.
* </p>
* This information includes:
* <ul>
* <li>`txId` - the identifier for the transaction.</li>
* <li>`txInstant` - the time that the transaction occured.</li>
* <li>a map of temporary identifiers provided in the transaction and the `Entid`s that they were mapped to.</li>
* </ul>
* </p>
* Access an `Entid` for a temporary identifier that was provided in the transaction can be done through `entid(String:)`.
* </p>
* <pre>{@code
* TxReport report = mentat.transact("[[:db/add "a" :foo/boolean true]]");
* long aEntid = report.getEntidForTempId("a");
*}</pre>
*/
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() {
Log.i("TxReport", "close");
if (this.rawPointer != null) {
JNA.INSTANCE.tx_report_destroy(this.rawPointer);
}
}
}

View file

@ -0,0 +1,161 @@
/* -*- 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 android.util.Log;
import com.sun.jna.Pointer;
import java.io.BufferedReader;
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.
* </p>
* 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.
* </p>
* 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_as_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_as_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_as_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_as_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_as_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_as_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_as_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_as_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() {
Log.i("TypedValue", "close");
if (this.rawPointer != null) {
JNA.INSTANCE.typed_value_destroy(this.rawPointer);
}
}
}