package {{ package_name }};
{%- if callback.has_async_methods() %}
import java.util.concurrent.CompletableFuture;
{%- endif %}
final class {{ callback.callbacks_class_name() }} {
private static final java.util.concurrent.ConcurrentHashMap<Long, {{ callback.interface_name }}> HANDLE_MAP = new java.util.concurrent.ConcurrentHashMap<>();
private static final java.util.concurrent.atomic.AtomicLong COUNTER = new java.util.concurrent.atomic.AtomicLong(1L);
{%- if callback.supports_cleaner %}
private static final java.lang.ref.Cleaner CLEANER = java.lang.ref.Cleaner.create();
{%- endif %}
private {{ callback.callbacks_class_name() }}() {}
private static long insert({{ callback.interface_name }} impl_) {
long handle = COUNTER.getAndAdd(2L);
HANDLE_MAP.put(handle, impl_);
return handle;
}
public static void free(long handle) {
HANDLE_MAP.remove(handle);
}
public static long clone(long handle) {
{{ callback.interface_name }} obj = HANDLE_MAP.get(handle);
return obj != null ? insert(obj) : 0L;
}
{%- if callback.supports_cleaner %}
private static final class ProxyReleaser implements Runnable {
private long handle;
private ProxyReleaser(long handle) {
this.handle = handle;
}
@Override
public void run() {
if (handle == 0L) return;
Native.{{ callback.proxy_release_native_name() }}(handle);
handle = 0L;
}
}
{%- endif %}
private static final class Proxy implements {{ callback.interface_name }} {
private final long handle;
{%- if callback.supports_cleaner %}
private final java.lang.ref.Cleaner.Cleanable cleanable;
{%- endif %}
private Proxy(long handle) {
this.handle = handle;
{%- if callback.supports_cleaner %}
this.cleanable = CLEANER.register(this, new ProxyReleaser(handle));
{%- endif %}
}
long rawHandle() {
return handle;
}
{%- if !callback.supports_cleaner %}
@Override
protected void finalize() throws Throwable {
try {
if (handle != 0L) {
Native.{{ callback.proxy_release_native_name() }}(handle);
}
} finally {
super.finalize();
}
}
{%- endif %}
{%- for method in callback.sync_methods %}
@Override
public {{ method.proxy.return_type }} {{ method.name }}({% for param in method.proxy.params %}{{ param.java_type }} {{ param.name }}{% if !loop.last %}, {% endif %}{% endfor %}) {
{%- let proxy = method.proxy %}
{%- let handle_name = "handle" %}
{%- include "render_java/proxy_sync_method_body.txt" %}
}
{%- endfor %}
{%- for method in callback.async_methods %}
@Override
@SuppressWarnings("unchecked")
public CompletableFuture<{{ method.boxed_return_type() }}> {{ method.name }}({% for param in method.proxy.params %}{{ param.java_type }} {{ param.name }}{% if !loop.last %}, {% endif %}{% endfor %}) {
CompletableFuture<{{ method.boxed_return_type() }}> future = new CompletableFuture<>();
long callbackData = BoltFFICallbackFutureMap.insert(
{%- if !method.proxy.return_plan.is_void() %}
value -> future.complete(({{ method.proxy.boxed_return_type() }}) value),
{%- else %}
ignored -> future.complete(null),
{%- endif %}
future::completeExceptionally
);
try {
{%- if method.proxy.wire_writers.is_empty() %}
Native.{{ method.proxy.native_name }}(handle{% if !method.proxy.params.is_empty() %}, {% endif %}{% for param in method.proxy.params %}{{ param.native_expr }}{% if !loop.last %}, {% endif %}{% endfor %}, callbackData);
{%- else %}
{%- for writer in method.proxy.wire_writers %}
try (WireWriter {{ writer.binding_name }} = new WireWriter({{ writer.size_expr }})) {
{{ writer.encode_expr }};
{%- endfor %}
Native.{{ method.proxy.native_name }}(handle{% if !method.proxy.params.is_empty() %}, {% endif %}{% for param in method.proxy.params %}{{ param.native_expr }}{% if !loop.last %}, {% endif %}{% endfor %}, callbackData);
{%- for _writer in method.proxy.wire_writers %}
}
{%- endfor %}
{%- endif %}
} catch (Throwable throwable) {
BoltFFICallbackFutureMap.completeFailure(callbackData, throwable);
}
return future;
}
{%- endfor %}
}
static {{ callback.interface_name }} wrap(long handle) {
{{ callback.interface_name }} existing = HANDLE_MAP.get(handle);
return existing != null ? existing : new Proxy(handle);
}
{%- if callback.has_async_methods() %}
private static Throwable unwrapAsyncThrowable(Throwable throwable) {
Throwable unwrapped = throwable;
while (unwrapped instanceof java.util.concurrent.CompletionException
|| unwrapped instanceof java.util.concurrent.ExecutionException) {
Throwable cause = unwrapped.getCause();
if (cause == null) {
return unwrapped;
}
unwrapped = cause;
}
return unwrapped;
}
{%- endif %}
{%- for method in callback.sync_methods %}
public static {% match method.return_info %}{% when Some with (ret) %}{{ ret.jni_type() }}{% when None %}void{% endmatch %} {{ method.ffi_name }}(long handle{% for param in method.params %}, {{ param.jni_type }} {{ param.name }}{% endfor %}) {
{{ callback.interface_name }} impl_ = HANDLE_MAP.get(handle);
if (impl_ == null) throw new RuntimeException("invalid callback handle");
{%- match method.return_info %}
{%- when Some with (ret) %}
{%- match ret %}
{%- when crate::render::java::plan::JavaBridgeReturn::Value with (value_return) %}
{%- if value_return.is_encoded() %}
{{ value_return.java_type }} value = impl_.{{ method.name }}({% for param in method.params %}{{ param.decode_expr }}{% if !loop.last %}, {% endif %}{% endfor %});
try (WireWriter wire = new WireWriter({{ value_return.encode_size_expr() }})) {
{{ value_return.encode_expr() }};
return wire.toByteArray();
}
{%- else %}
return {{ value_return.direct_prefix() }}impl_.{{ method.name }}({% for param in method.params %}{{ param.decode_expr }}{% if !loop.last %}, {% endif %}{% endfor %}){{ value_return.direct_suffix() }};
{%- endif %}
{%- when crate::render::java::plan::JavaBridgeReturn::Result with (result_return) %}
BoltFFIResult<{{ result_return.ok_java_type }}, {{ result_return.err_java_type }}> result;
try {
result = BoltFFIResult.ok(impl_.{{ method.name }}({% for param in method.params %}{{ param.decode_expr }}{% if !loop.last %}, {% endif %}{% endfor %}));
{% if result_return.has_exception_class() %}
} catch ({{ result_return.exception_class() }} exception) {
result = BoltFFIResult.err(exception.getError());
{% endif %}
{% if result_return.error_capture.is_string %}
} catch (RuntimeException exception) {
result = BoltFFIResult.err(exception.getMessage());
{% endif %}
{% if !result_return.has_exception_class() && !result_return.error_capture.is_string %}
} catch (Exception exception) {
throw new RuntimeException("callback returned unexpected error", exception);
{% endif %}
}
try (WireWriter wire = new WireWriter({{ result_return.encode_size_expr }})) {
{{ result_return.encode_expr }};
return wire.toByteArray();
}
{%- endmatch %}
{%- when None %}
impl_.{{ method.name }}({% for param in method.params %}{{ param.decode_expr }}{% if !loop.last %}, {% endif %}{% endfor %});
{%- endmatch %}
}
{%- endfor %}
{%- for method in callback.async_methods %}
public static void {{ method.ffi_name }}(long handle{% for param in method.params %}, {{ param.jni_type }} {{ param.name }}{% endfor %}, long callbackPtr, long callbackData) {
{{ callback.interface_name }} impl_ = HANDLE_MAP.get(handle);
if (impl_ == null) throw new RuntimeException("invalid callback handle");
CompletableFuture<{{ method.boxed_return_type() }}> future;
try {
future = impl_.{{ method.name }}({% for param in method.params %}{{ param.decode_expr }}{% if !loop.last %}, {% endif %}{% endfor %});
} catch (Throwable throwable) {
future = new CompletableFuture<>();
future.completeExceptionally(throwable);
}
future.whenComplete((value, throwable) -> {
if (throwable != null) {
Throwable callbackThrowable = unwrapAsyncThrowable(throwable);
{% match method.return_info %}
{% when Some with (ret) %}
{% match ret %}
{% when crate::render::java::plan::JavaBridgeReturn::Result with (result_return) %}
{% if result_return.has_exception_class() %}
if (callbackThrowable instanceof {{ result_return.exception_class() }}) {
{{ result_return.exception_class() }} exception = ({{ result_return.exception_class() }}) callbackThrowable;
BoltFFIResult<{{ result_return.ok_java_type }}, {{ result_return.err_java_type }}> result = BoltFFIResult.err(exception.getError());
try (WireWriter wire = new WireWriter({{ result_return.encode_size_expr }})) {
{{ result_return.encode_expr }};
Native.{{ method.success_invoker_name() }}(callbackPtr, callbackData, wire.toByteArray());
}
return;
}
{% endif %}
{% if result_return.error_capture.is_string %}
if (callbackThrowable instanceof RuntimeException) {
RuntimeException exception = (RuntimeException) callbackThrowable;
BoltFFIResult<{{ result_return.ok_java_type }}, {{ result_return.err_java_type }}> result = BoltFFIResult.err(exception.getMessage());
try (WireWriter wire = new WireWriter({{ result_return.encode_size_expr }})) {
{{ result_return.encode_expr }};
Native.{{ method.success_invoker_name() }}(callbackPtr, callbackData, wire.toByteArray());
}
return;
}
{% endif %}
{% when crate::render::java::plan::JavaBridgeReturn::Value with (_value_return) %}
{% endmatch %}
{% when None %}
{% endmatch %}
Native.{{ method.failure_invoker_name() }}(callbackPtr, callbackData);
return;
}
{% match method.return_info %}
{% when Some with (ret) %}
{% match ret %}
{% when crate::render::java::plan::JavaBridgeReturn::Value with (value_return) %}
{% if value_return.is_encoded() %}
try (WireWriter wire = new WireWriter({{ value_return.encode_size_expr() }})) {
{{ value_return.encode_expr() }};
Native.{{ method.success_invoker_name() }}(callbackPtr, callbackData, wire.toByteArray());
}
{% else %}
Native.{{ method.success_invoker_name() }}(callbackPtr, callbackData, {{ value_return.direct_prefix() }}value{{ value_return.direct_suffix() }});
{% endif %}
{% when crate::render::java::plan::JavaBridgeReturn::Result with (result_return) %}
BoltFFIResult<{{ result_return.ok_java_type }}, {{ result_return.err_java_type }}> result = BoltFFIResult.ok(value);
try (WireWriter wire = new WireWriter({{ result_return.encode_size_expr }})) {
{{ result_return.encode_expr }};
Native.{{ method.success_invoker_name() }}(callbackPtr, callbackData, wire.toByteArray());
}
{% endmatch %}
{% when None %}
Native.{{ method.success_invoker_name() }}(callbackPtr, callbackData);
{% endmatch %}
});
}
static void {{ method.proxy.success_name }}(long callbackData{% if method.proxy.return_plan.is_void() %}{% elif method.proxy.return_plan.is_direct() || method.proxy.return_plan.is_c_style_enum() %}, {{ method.proxy.return_plan.native_return_type }} value{% elif method.proxy.return_plan.is_handle() || method.proxy.return_plan.is_callback() %}, long value{% else %}, byte[] value{% endif %}) {
{%- if method.proxy.return_plan.is_void() %}
BoltFFICallbackFutureMap.completeSuccess(callbackData, null);
{%- elif method.proxy.return_plan.is_direct() %}
BoltFFICallbackFutureMap.completeSuccess(callbackData, value);
{%- elif method.proxy.return_plan.is_c_style_enum() %}
BoltFFICallbackFutureMap.completeSuccess(callbackData, {{ method.proxy.return_plan.c_style_enum_class() }}.fromValue(value));
{%- elif method.proxy.return_plan.is_handle() %}
{%- if method.proxy.return_plan.handle_nullable() %}
BoltFFICallbackFutureMap.completeSuccess(callbackData, value == 0L ? null : new {{ method.proxy.return_plan.handle_class() }}(value));
{%- else %}
BoltFFICallbackFutureMap.completeSuccess(callbackData, new {{ method.proxy.return_plan.handle_class() }}(value));
{%- endif %}
{%- elif method.proxy.return_plan.is_callback() %}
{%- if method.proxy.return_plan.callback_nullable() %}
BoltFFICallbackFutureMap.completeSuccess(callbackData, value == 0L ? null : {{ method.proxy.return_plan.callback_bridge_class() }}.wrap(value));
{%- else %}
BoltFFICallbackFutureMap.completeSuccess(callbackData, {{ method.proxy.return_plan.callback_bridge_class() }}.wrap(value));
{%- endif %}
{%- elif method.proxy.return_plan.is_decode() %}
if (value == null) {
BoltFFICallbackFutureMap.completeFailure(callbackData, new RuntimeException("FFI call returned null buffer"));
return;
}
WireReader reader = new WireReader(value);
BoltFFICallbackFutureMap.completeSuccess(callbackData, {{ method.proxy.return_plan.decode_expr() }});
{%- elif method.proxy.return_plan.is_result() %}
if (value == null) {
BoltFFICallbackFutureMap.completeFailure(callbackData, new RuntimeException("FFI call returned null buffer"));
return;
}
WireReader reader = new WireReader(value);
if (reader.readI8() != 0) {
{%- if method.proxy.return_plan.result_err_throws_directly() %}
BoltFFICallbackFutureMap.completeFailure(callbackData, {{ method.proxy.return_plan.result_err_decode() }});
{%- elif method.proxy.return_plan.result_has_typed_exception() %}
BoltFFICallbackFutureMap.completeFailure(callbackData, new {{ method.proxy.return_plan.result_err_exception_class() }}({{ method.proxy.return_plan.result_err_decode() }}));
{%- elif method.proxy.return_plan.result_err_is_string() %}
BoltFFICallbackFutureMap.completeFailure(callbackData, new RuntimeException({{ method.proxy.return_plan.result_err_decode() }}));
{%- else %}
BoltFFICallbackFutureMap.completeFailure(callbackData, new RuntimeException(String.valueOf({{ method.proxy.return_plan.result_err_decode() }})));
{%- endif %}
return;
}
BoltFFICallbackFutureMap.completeSuccess(callbackData, {{ method.proxy.return_plan.result_ok_decode() }});
{%- endif %}
}
static void {{ method.proxy.failure_name }}(long callbackData) {
BoltFFICallbackFutureMap.completeFailure(callbackData, new RuntimeException("callback failed"));
}
{%- endfor %}
static long create({{ callback.interface_name }} impl_) {
return insert(impl_);
}
}