Native.java
package com.surrealdb;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
/**
* Base for types backed by a native pointer. Each instance owns its native
* object: the native side clones a shared reference or copies the data when it
* creates the instance, so it stays valid on its own, independently of the
* {@link Value} or connection it was obtained from. The native object is freed
* when the instance is garbage collected.
*/
public abstract class Native {
static {
Loader.loadNative();
}
// Owns the native pointer used by the native library to locate the object.
// It is released at most once: by an explicit close, by takePtr() when the
// native side takes ownership, or by the NativeCleaner once this object is
// unreachable.
//
// A native call that is given getPtr() must keep this object reachable until
// it returns, or the cleaner could free the pointer mid-call. For the receiver
// this is done by declaring such natives as instance methods: JNI holds the
// receiver for the duration of the call. (The Rust side ignores that
// argument, so it still declares it as a JClass.) Any other Native whose
// pointer is passed must be kept alive with reachabilityFence().
private final NativeCleaner.Handle handle;
Native(long ptr) {
this.handle = NativeCleaner.register(this, ptr, deleter());
}
/**
* Returns the function that frees this type's native object. It must be a
* static method reference: the cleaner calls it after this object is gone.
*/
abstract NativeCleaner.Deleter deleter();
abstract String toString(long ptr);
abstract int hashCode(long ptr);
abstract boolean equals(long ptr1, long ptr2);
final long getPtr() {
return handle.ptr();
}
final void deleteInstance() {
try {
handle.release();
} finally {
// Keep this object reachable until the release has completed, so a
// try-with-resources block around a native call cannot lose it early.
reachabilityFence(this);
}
}
/**
* Releases the native resources held by this instance right away, instead of
* when it is garbage collected. For a {@link Surreal}, this also closes its
* connection. Calling it more than once is safe. After this call, using the
* instance throws a {@link NullPointerException}.
* <p>
* Instances obtained from this one (a value taken from a {@link Response}, an
* element of an {@link Array}, a field of an {@link Object}, an entry's value,
* and so on) are not affected: each holds its own reference to the data, which
* is freed once every holder is closed or collected.
*/
public void close() {
deleteInstance();
}
/**
* Gives up ownership of the native object and returns its pointer, for a native
* call that takes the object over (and frees it, even when the call fails). The
* pointer is cleared before the call, so two threads handing over the same
* object cannot both pass it on: the second one gets 0, which the native side
* rejects.
*/
final long takePtr() {
try {
return handle.take();
} finally {
// Until the pointer is cleared, the cleaner must not see this object as
// unreachable: it would free a pointer the native side is about to own.
reachabilityFence(this);
}
}
/**
* {@link #takePtr()} for every element of {@code natives}, read once. A
* {@code null} element, or an element that appears twice, is rejected before
* anything is taken: the native side would otherwise get a 0 pointer for it and
* fail with a less helpful error, after the earlier elements were already
* handed over.
*/
static long[] takePtrs(List<? extends Native> natives) {
final Native[] array = natives.toArray(new Native[0]);
// Validate everything before taking anything: a failure must not leave
// the earlier elements taken from their owners and never handed over.
for (final Native n : array) {
java.util.Objects.requireNonNull(n, "A Native element cannot be null");
}
if (array.length > 1) {
final Set<Native> seen = Collections.newSetFromMap(new IdentityHashMap<Native, Boolean>(array.length));
for (final Native n : array) {
if (!seen.add(n)) {
throw new IllegalArgumentException(
"The same " + n.getClass().getSimpleName() + " was passed more than once");
}
}
}
final long[] ptrs = new long[array.length];
for (int i = 0; i < array.length; i++) {
ptrs[i] = array[i].takePtr();
}
return ptrs;
}
/**
* The pointer of every element of {@code natives}, without taking ownership.
* The caller must keep the array reachable (see {@link #reachabilityFence})
* until the native call that received the pointers has returned.
*/
static long[] getPtrs(Native[] natives) {
final long[] ptrs = new long[natives.length];
for (int i = 0; i < natives.length; i++) {
ptrs[i] = natives[i].getPtr();
}
return ptrs;
}
/**
* Keeps {@code o} reachable until this call returns. Call it after a native
* call that was given {@code o}'s pointer, when nothing else holds {@code o}.
* Does nothing for {@code null}.
*/
static void reachabilityFence(java.lang.Object o) {
if (o != null) {
NativeCleaner.reachabilityFence(o);
}
}
@Override
final public boolean equals(java.lang.Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Native n2 = (Native) o;
try {
return equals(getPtr(), n2.getPtr());
} finally {
reachabilityFence(n2);
}
}
@Override
final public int hashCode() {
return hashCode(getPtr());
}
@Override
final public String toString() {
return toString(getPtr());
}
}