Skip to content
Closed
28 changes: 12 additions & 16 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,19 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<!-- dependencies -->
<jna.version>5.12.1</jna.version>
<gson.version>2.10</gson.version>
<jna.version>5.13.0</jna.version>
<gson.version>2.10.1</gson.version>

<!-- testing -->
<junit-jupiter-engine.version>5.9.1</junit-jupiter-engine.version>
<assertj-core.version>3.23.1</assertj-core.version>
<junit-jupiter-engine.version>5.9.3</junit-jupiter-engine.version>
<assertj-core.version>3.24.2</assertj-core.version>

<!-- maven plugins -->
<maven-compiler-plugin.version>3.10.1</maven-compiler-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
<maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
<maven-javadoc-plugin.version>3.5.0</maven-javadoc-plugin.version>
<maven-source-plugin.version>3.2.1</maven-source-plugin.version>
<jreleaser-maven-plugin.version>1.8.0</jreleaser-maven-plugin.version>

<!-- jreleaser -->
<jreleaser.git.root.search>true</jreleaser.git.root.search>
Expand All @@ -74,7 +77,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.1</version>
<version>${maven-javadoc-plugin.version}</version>
<configuration>
<additionalJOption>-Xdoclint:none</additionalJOption>
</configuration>
Expand All @@ -90,7 +93,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<version>${maven-source-plugin.version}</version>
<executions>
<execution>
<id>attach-source</id>
Expand All @@ -103,7 +106,7 @@
<plugin>
<groupId>org.jreleaser</groupId>
<artifactId>jreleaser-maven-plugin</artifactId>
<version>1.3.1</version>
<version>${jreleaser-maven-plugin.version}</version>
<configuration>
<jreleaser>
<release>
Expand Down Expand Up @@ -183,12 +186,5 @@
<version>${assertj-core.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>uk.org.webcompere</groupId>
<artifactId>model-assert</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
5 changes: 3 additions & 2 deletions src/main/java/org/extism/sdk/CancelHandle.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* CancelHandle is used to cancel a running Plugin
*/
public class CancelHandle {
private Pointer handle;

private final Pointer handle;

public CancelHandle(Pointer handle) {
this.handle = handle;
Expand All @@ -15,7 +16,7 @@ public CancelHandle(Pointer handle) {
/**
* Cancel execution of the Plugin associated with the CancelHandle
*/
boolean cancel() {
public boolean cancel() {
return LibExtism.INSTANCE.extism_plugin_cancel(this.handle);
}
}
69 changes: 29 additions & 40 deletions src/main/java/org/extism/sdk/Extism.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.extism.sdk;

import org.extism.sdk.manifest.Manifest;
import org.extism.sdk.wasm.WasmSourceResolver;

import java.net.URI;
import java.nio.file.Path;
import java.util.Objects;

Expand All @@ -10,25 +12,38 @@
*/
public class Extism {

private static final WasmSourceResolver DEFAULT_RESOLVER = new WasmSourceResolver();

/**
* Configure a log file with the given {@link Path} and configure the given {@link LogLevel}.
*
* Creates a {@link Manifest} from the given {@code path}.
* @param path
* @param level
*
* @deprecated will be replaced with better logging API.
* @return
*/
@Deprecated(forRemoval = true)
public static void setLogFile(Path path, LogLevel level) {

public static Manifest manifestFromPath(Path path) {
Objects.requireNonNull(path, "path");
Objects.requireNonNull(level, "level");
return new Manifest(DEFAULT_RESOLVER.resolve(path));
}

var result = LibExtism.INSTANCE.extism_log_file(path.toString(), level.getLevel());
if (!result) {
var error = String.format("Could not set extism logger to %s with level %s", path, level);
throw new ExtismException(error);
}
/**
* Creates a {@link Manifest} from the given {@code name} and {@code bytes}.
* @param name
* @param bytes
* @return
*/
public static Manifest manifestFromBytes(String name, byte[] bytes) {
Objects.requireNonNull(name, "name");
Objects.requireNonNull(bytes, "bytes");
return new Manifest(DEFAULT_RESOLVER.resolve(name, bytes));
}

/**
* Creates a {@link Manifest} from the given {@code url}.
* @param url
* @return
*/
public static Manifest manifestFromUrl(String url) {
Objects.requireNonNull(url, "url");
return new Manifest(DEFAULT_RESOLVER.resolve(URI.create(url)));
}

/**
Expand All @@ -46,30 +61,4 @@ public static String invokeFunction(Manifest manifest, String function, String i
return plugin.call(function, input);
}
}

/**
* Error levels for the Extism logging facility.
*
* @see Extism#setLogFile(Path, LogLevel)
*/
public enum LogLevel {

INFO("info"), //

DEBUG("debug"), //

WARN("warn"), //

TRACE("trace");

private final String level;

LogLevel(String level) {
this.level = level;
}

public String getLevel() {
return level;
}
}
}
3 changes: 2 additions & 1 deletion src/main/java/org/extism/sdk/ExtismCurrentPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import java.nio.charset.StandardCharsets;

public class ExtismCurrentPlugin {
public Pointer pointer;

private final Pointer pointer;

public ExtismCurrentPlugin(Pointer pointer) {
this.pointer = pointer;
Expand Down
136 changes: 80 additions & 56 deletions src/main/java/org/extism/sdk/HostFunction.java
Original file line number Diff line number Diff line change
@@ -1,82 +1,86 @@
package org.extism.sdk;

import com.sun.jna.Pointer;
import com.sun.jna.PointerType;
import org.extism.sdk.LibExtism.ExtismVal;
import org.extism.sdk.LibExtism.ExtismValType;
import org.extism.sdk.LibExtism.InternalExtismFunction;

import java.util.Arrays;
import java.util.Optional;

public class HostFunction<T extends HostUserData> {

private final LibExtism.InternalExtismFunction callback;
private final Pointer pointer;

public final Pointer pointer;
private final String name;

public final String name;
private final ExtismValType[] params;

public final LibExtism.ExtismValType[] params;
private final ExtismValType[] returns;

public final LibExtism.ExtismValType[] returns;
private final T userData;

public final Optional<T> userData;
public HostFunction(String name, ExtismValType[] params, ExtismValType[] returns, ExtismFunction<T> func) {
this(name, params, returns, func, null);
}

public HostFunction(String name, LibExtism.ExtismValType[] params, LibExtism.ExtismValType[] returns, ExtismFunction f, Optional<T> userData) {
public HostFunction(String name, ExtismValType[] params, ExtismValType[] returns, ExtismFunction<T> func, T userData) {

this.name = name;
this.params = params;
this.returns = returns;
this.userData = userData;
this.callback = (Pointer currentPlugin,
LibExtism.ExtismVal inputs,
int nInputs,
LibExtism.ExtismVal outs,
int nOutputs,
Pointer data) -> {

LibExtism.ExtismVal[] outputs = (LibExtism.ExtismVal []) outs.toArray(nOutputs);

f.invoke(
new ExtismCurrentPlugin(currentPlugin),
(LibExtism.ExtismVal []) inputs.toArray(nInputs),
outputs,
userData
);

for (LibExtism.ExtismVal output : outputs) {
convertOutput(output, output);
}
};

this.pointer = LibExtism.INSTANCE.extism_function_new(
this.name,
Arrays.stream(this.params).mapToInt(r -> r.v).toArray(),
this.params.length,
Arrays.stream(this.returns).mapToInt(r -> r.v).toArray(),
this.returns.length,
this.callback,
userData.map(PointerType::getPointer).orElse(null),
null
int[] inputTypeValues = Arrays.stream(this.params).mapToInt(r -> r.v).toArray();
int[] outputTypeValues = Arrays.stream(this.returns).mapToInt(r -> r.v).toArray();
InternalExtismFunction callback = createCallbackFunction(func, userData);
Pointer userDataPointer = userData != null ? userData.getPointer() : null;

this.pointer = LibExtism.INSTANCE.extism_function_new( //
name, //
inputTypeValues, //
inputTypeValues.length, //
outputTypeValues, //
outputTypeValues.length, //
callback, //
userDataPointer, //
null //
);
}

void convertOutput(LibExtism.ExtismVal original, LibExtism.ExtismVal fromHostFunction) {
if (fromHostFunction.t != original.t)
throw new ExtismException(String.format("Output type mismatch, got %d but expected %d", fromHostFunction.t, original.t));

if (fromHostFunction.t == LibExtism.ExtismValType.I32.v) {
original.v.setType(Integer.TYPE);
original.v.i32 = fromHostFunction.v.i32;
} else if (fromHostFunction.t == LibExtism.ExtismValType.I64.v) {
original.v.setType(Long.TYPE);
original.v.i64 = fromHostFunction.v.i64;
} else if (fromHostFunction.t == LibExtism.ExtismValType.F32.v) {
original.v.setType(Float.TYPE);
original.v.f32 = fromHostFunction.v.f32;
} else if (fromHostFunction.t == LibExtism.ExtismValType.F64.v) {
original.v.setType(Double.TYPE);
original.v.f64 = fromHostFunction.v.f64;
} else
throw new ExtismException(String.format("Unsupported return type: %s", original.t));
private InternalExtismFunction createCallbackFunction(ExtismFunction<T> func, T userData) {
return (Pointer pluginPointer, ExtismVal inputs, int nInputs, ExtismVal outputs, int nOutputs, Pointer data) -> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the data pointer is not used here, is wrong?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, we probably don't need Extism to keep track of this pointer since we're using a closure and Java keeps the userData in scope. The pointer is for languages or functions that can't do this and they need to look back up the object from a pointer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we then leave out the data parameter on the interface?
If not, then we should document that the data parameter is not used here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect we can leave it out, yes. If not then we should mark it as unused.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests worked fine with this interface definition

interface InternalExtismFunction extends Callback {

        /**
         * Host function implementation.
         */
        void invoke(Pointer plugin, ExtismVal inputs, int nInputs, ExtismVal outputs, int nOutputs
        //         , Pointer data
        );
    }

And this callback function:

 private InternalExtismFunction createCallbackFunction(ExtismFunction<T> func, T userData) {
        return (Pointer pluginPointer, ExtismVal inputs, int nInputs, ExtismVal outputs, int nOutputs
        //         , Pointer data
        ) -> {

            var outputValues = (ExtismVal[]) outputs.toArray(nOutputs);
            var inputValues = (ExtismVal[]) inputs.toArray(nInputs);
            var currentPlugin = new ExtismCurrentPlugin(pluginPointer);

            func.invoke(currentPlugin, inputValues, outputValues, Optional.ofNullable(userData));

            for (ExtismVal output : outputValues) {
                coerceType(output);
            }
        };
    }
    ```


var outputValues = (ExtismVal[]) outputs.toArray(nOutputs);
var inputValues = (ExtismVal[]) inputs.toArray(nInputs);
var currentPlugin = new ExtismCurrentPlugin(pluginPointer);

func.invoke(currentPlugin, inputValues, outputValues, Optional.ofNullable(userData));

for (ExtismVal output : outputValues) {
coerceType(output);
}
};
}

void coerceType(ExtismVal value) {

switch (value.t) {
case ExtismValType.I32_KEY:
value.v.setType(Integer.TYPE);
break;
case ExtismValType.I64_KEY:
value.v.setType(Long.TYPE);
break;
case ExtismValType.F32_KEY:
value.v.setType(Float.TYPE);
break;
case ExtismValType.F64_KEY:
value.v.setType(Double.TYPE);
break;
default:
throw new ExtismException(String.format("Unsupported return type: %s", value.t));
}
}

public void setNamespace(String name) {
Expand All @@ -85,8 +89,28 @@ public void setNamespace(String name) {
}
}

HostFunction withNamespace(String name) {
public HostFunction<T> withNamespace(String name) {
this.setNamespace(name);
return this;
}

public Optional<ExtismValType[]> params() {
return Optional.ofNullable(params).map(ExtismValType[]::clone);
}

public Optional<ExtismValType[]> returns() {
return Optional.ofNullable(returns).map(ExtismValType[]::clone);
}

public Optional<T> userData() {
return Optional.ofNullable(userData);
}

public String name() {
return name;
}

/* package scoped */ Pointer getPointer() {
return pointer;
}
}
1 change: 0 additions & 1 deletion src/main/java/org/extism/sdk/HostUserData.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@
import com.sun.jna.PointerType;

public class HostUserData extends PointerType {

}
Loading