Skip to content

Commit

Permalink
Call tests (#21)
Browse files Browse the repository at this point in the history
* Improvements Get/Set IEnvironment.PendingException.
* Improvements Get/Set primitive array region.
* Span for Calls args.
* Constructor, Method and Function call tests.
* Get/Set field tests.
* ThrowNew tests.
  • Loading branch information
josephmoresena authored Sep 5, 2024
1 parent 9671fbd commit 2fe7535
Show file tree
Hide file tree
Showing 81 changed files with 3,800 additions and 361 deletions.
13 changes: 13 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,19 @@ jobs:
if ($LastExitCode -ne 0) { exit $LastExitCode }
.\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
if ($LastExitCode -ne 0) { exit $LastExitCode }
- name: Patch Native ILLink.Substitutions
working-directory: ./src/Intermediate/Rxmxnx.JNetInterface.Native.Intermediate/ILLink
run: sed -i -z 's/Rxmxnx.JNetInterface.Native.Intermediate/Rxmxnx.JNetInterface.Core/g;' ILLink.Substitutions.xml
- name: Patch Implementation ILLink.Substitutions
working-directory: ./src/Intermediate/Rxmxnx.JNetInterface.Implementation.Intermediate/ILLink
run: sed -i -z 's/Rxmxnx.JNetInterface.Implementation.Intermediate/Rxmxnx.JNetInterface/g;' ILLink.Substitutions.xml
- name: Pack core assembly
working-directory: ./package/Rxmxnx.JNetInterface.Core
run: dotnet pack -c Release -o ../Nuget /p:Version=9999.99.99.99-tmp
- name: Pack main assembly
working-directory: ./package/Rxmxnx.JNetInterface
run: dotnet pack -c Release -o ../Nuget /p:Version=9999.99.99.99-tmp

run-linux:
runs-on: ubuntu-latest
Expand Down
285 changes: 285 additions & 0 deletions src/ApplicationTest/Rxmxnx.JNetInterface.FsApplicationTest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
# F# Support

To support the use of `JNetInterface` in F#, it is necessary to explicitly use some constructors and methods that are
not usually required in C# code.

## Primitives
In C#, `JNetInterface` uses operators to convert from one primitive type to another. However, this functionality must
be done explicitly in F# using constructors.

```fsharp
let booleanValue = JBoolean true
let byteValue = JByte -2y
let charValue = JChar '\n'
let doubleValue = JDouble 3.14159265359
let floatValue = JFloat 2.71828f
let intValue = JInt 486
let longValue = JLong 3000000000L
let shortValue = JShort 1024s
```

## Reference Types
To map reference types from Java through `JNetInterface`, it is necessary to create classes (according to the rules
established for each case) that implement the indicated interfaces according to the reference type to be mapped.

Unlike C#, F# does not support virtual implementation of static methods in interfaces, so it may be necessary to
implement homonymous methods according to the `JNetInterface` hierarchy.

### Extensible class (`java.lang.Package`)
```fsharp
type JPackageObject =
inherit JLocalObject
static let typeMetadata =
JLocalObject.TypeMetadataBuilder<JPackageObject>
.Create("java/lang/Package"B)
.Build()
new(initializer: IReferenceType.ClassInitializer) = { inherit JLocalObject(initializer) }
new(initializer: IReferenceType.GlobalInitializer) = { inherit JLocalObject(initializer) }
new(initializer: IReferenceType.ObjectInitializer) = { inherit JLocalObject(initializer) }
interface IDataType<JPackageObject> with
static member get_Metadata() = typeMetadata
interface IReferenceType<JPackageObject> with
static member Create(initializer: IReferenceType.ObjectInitializer) = new JPackageObject(initializer)
interface IClassType<JPackageObject> with
static member get_Metadata() = typeMetadata
static member Create(initializer: IReferenceType.ClassInitializer) = new JPackageObject(initializer)
static member Create(initializer: IReferenceType.GlobalInitializer) = new JPackageObject(initializer)
static member Create(initializer: IReferenceType.ObjectInitializer) = new JPackageObject(initializer)
static member GetPackage(env: IEnvironment, packageName: String) : JPackageObject =
GetPackageDefinition.GetPackage(env, packageName)
and private GetPackageDefinition private () =
inherit JFunctionDefinition<JPackageObject>("getPackage"B, JArgumentMetadata.Get<JStringObject>())
static member val private Instance = GetPackageDefinition() with get
member this.GetPackage(packageName: JStringObject) : JPackageObject =
let env = packageName.Environment
let args = this.CreateArgumentsArray()
let packageClass = JClassObject.GetClass<JPackageObject>(env)
args.[0] <- packageName
this.StaticInvoke(packageClass, args)
static member GetPackage(env: IEnvironment, packageName: String) : JPackageObject =
use jString = JStringObject.Create(env, packageName)
env.WithFrame(3, jString, GetPackageDefinition.Instance.GetPackage)
```

### Non-instantiable class (`java.lang.Math`):
```fsharp
[<Sealed>]
type JMathObject private () =
inherit JLocalObject.Uninstantiable<JMathObject>()
static let typeMetadata =
JLocalObject.TypeMetadataBuilder<JMathObject>.Create("java/lang/Math"B).Build()
static let eDef = JFieldDefinition<JDouble>("E"B)
static let piDef = JFieldDefinition<JDouble>("PI"B)
static let absDef = MathFuncDefinition<JDouble, JDouble>("abs"B)
static let atan2Def = MathFuncDefinition<JDouble, JDouble, JDouble>("atan2"B)
static member GetE(env: IEnvironment) : JDouble = JMathObject.GetField(env, eDef)
static member GetPi(env: IEnvironment) : JDouble = JMathObject.GetField(env, piDef)
static member Abs(env: IEnvironment, value: JDouble) : JDouble = absDef.Invoke(env, value)
static member Atan2(env: IEnvironment, y: JDouble, x: JDouble) : JDouble = atan2Def.Invoke(env, y, x)
interface IDataType<JMathObject> with
static member get_Metadata() = typeMetadata
interface IReferenceType<JMathObject> with
static member Create(initializer: IReferenceType.ObjectInitializer) =
IUninstantiableType.ThrowInstantiation<JMathObject>()
interface IUninstantiableType<JMathObject> with
static member get_Metadata() = typeMetadata
static member Create(initializer: IReferenceType.ClassInitializer) =
IUninstantiableType.ThrowInstantiation<JMathObject>()
static member Create(initializer: IReferenceType.GlobalInitializer) =
IUninstantiableType.ThrowInstantiation<JMathObject>()
static member Create(initializer: IReferenceType.ObjectInitializer) =
IUninstantiableType.ThrowInstantiation<JMathObject>()
static member private GetField<'T
when 'T: unmanaged and 'T :> IPrimitiveType<'T> and 'T: (new: unit -> 'T) and 'T :> ValueType>
(
env: IEnvironment,
field: JFieldDefinition<'T>
) : 'T =
let state = { Environment = env; Def = field }
env.WithFrame(2, state, JMathObject.GetField<'T>)
static member private GetField<'T
when 'T: unmanaged and 'T :> IPrimitiveType<'T> and 'T: (new: unit -> 'T) and 'T :> ValueType>
(state: FieldState<'T>)
: 'T =
let mathClass = JClassObject.GetClass<JMathObject>(state.Environment)
state.Def.StaticGet(mathClass)
and [<Struct>] FieldState<'T
when 'T: struct and 'T :> IPrimitiveType<'T> and 'T: unmanaged and 'T: (new: unit -> 'T) and 'T :> ValueType> =
{ Environment: IEnvironment
Def: JFieldDefinition<'T> }
and private MathFuncDefinition<'TF, 'TX when 'TF :> IDataType<'TF> and 'TX :> IDataType<'TX>>
(funcName: ReadOnlySpan<byte>) =
inherit JFunctionDefinition<'TF>(funcName, JArgumentMetadata.Get<'TX>())
member this.Invoke(env: IEnvironment, x: 'TX) : 'TF =
let state = { Environment = env; Def = this; X = x }
env.WithFrame(2, state, MathFuncDefinition<'TF, 'TX>.Invoke)
static member Invoke(state: State<'TF, 'TX>) : 'TF =
let mathClass = JClassObject.GetClass<JMathObject>(state.Environment)
let args = state.Def.CreateArgumentsArray()
args.[0] <- state.X :> IObject
state.Def.StaticInvoke(mathClass, args)
and [<Struct>] private State<'TF, 'TX when 'TF :> IDataType<'TF> and 'TX :> IDataType<'TX>> =
{ Environment: IEnvironment
Def: MathFuncDefinition<'TF, 'TX>
X: 'TX }
and private MathFuncDefinition<'TF, 'TX, 'TY
when 'TF :> IDataType<'TF> and 'TX :> IDataType<'TX> and 'TY :> IDataType<'TY>>(funcName: ReadOnlySpan<byte>) =
inherit JFunctionDefinition<'TF>(funcName, JArgumentMetadata.Get<'TX>(), JArgumentMetadata.Get<'TY>())
member this.Invoke(env: IEnvironment, x: 'TX, y: 'TY) : 'TF =
let state =
{ Environment = env
Def = this
X = x
Y = y }
env.WithFrame(2, state, MathFuncDefinition<'TF, 'TX, 'TY>.Invoke)
static member Invoke(state: State<'TF, 'TX, 'TY>) : 'TF =
let mathClass = JClassObject.GetClass<JMathObject>(state.Environment)
let args = state.Def.CreateArgumentsArray()
args.[0] <- state.X :> IObject
args.[1] <- state.Y :> IObject
state.Def.StaticInvoke(mathClass, args)
and [<Struct>] private State<'TF, 'TX, 'TY
when 'TF :> IDataType<'TF> and 'TX :> IDataType<'TX> and 'TY :> IDataType<'TY>> =
{ Environment: IEnvironment
Def: MathFuncDefinition<'TF, 'TX, 'TY>
X: 'TX
Y: 'TY }
```

### Throwable class (`java.lang.CloneNotSupportedException`):
```fsharp
type JCloneNotSupportedExceptionObject =
inherit JExceptionObject
static let typeMetadata =
JThrowableObject.TypeMetadataBuilder<JExceptionObject>
.Create<JCloneNotSupportedExceptionObject>("java/lang/CloneNotSupportedException"B)
.Build()
new(initializer: IReferenceType.ClassInitializer) = { inherit JExceptionObject(initializer) }
new(initializer: IReferenceType.GlobalInitializer) = { inherit JExceptionObject(initializer) }
new(initializer: IReferenceType.ObjectInitializer) = { inherit JExceptionObject(initializer) }
interface IDataType<JCloneNotSupportedExceptionObject> with
static member get_Metadata() = typeMetadata
interface IReferenceType<JCloneNotSupportedExceptionObject> with
static member Create(initializer: IReferenceType.ObjectInitializer) = new JCloneNotSupportedExceptionObject(initializer)
interface IClassType<JCloneNotSupportedExceptionObject> with
static member get_Metadata() = typeMetadata
static member Create(initializer: IReferenceType.ClassInitializer) = new JCloneNotSupportedExceptionObject(initializer)
static member Create(initializer: IReferenceType.GlobalInitializer) = new JCloneNotSupportedExceptionObject(initializer)
static member Create(initializer: IReferenceType.ObjectInitializer) = new JCloneNotSupportedExceptionObject(initializer)
interface IThrowableType<JCloneNotSupportedExceptionObject> with
static member get_Metadata() = typeMetadata
```

### Enum class (`java.lang.Thread.State`):
Note that in this case, the enum values were not declared. However, this functionality is supported in F#.

```fsharp
[<Sealed>]
type JThreadStateObject =
inherit JEnumObject<JThreadStateObject>
static let typeMetadata =
JEnumObject.TypeMetadataBuilder<JThreadStateObject>
.Create("java/lang/Thread$State"B)
.Build()
new(initializer: IReferenceType.ClassInitializer) = { inherit JEnumObject<JThreadStateObject>(initializer) }
new(initializer: IReferenceType.GlobalInitializer) = { inherit JEnumObject<JThreadStateObject>(initializer) }
new(initializer: IReferenceType.ObjectInitializer) = { inherit JEnumObject<JThreadStateObject>(initializer) }
interface IDataType<JThreadStateObject> with
static member get_Metadata() = typeMetadata
interface IReferenceType<JThreadStateObject> with
static member Create(initializer: IReferenceType.ObjectInitializer) = new JThreadStateObject(initializer)
interface IEnumType<JThreadStateObject> with
static member get_Metadata() = typeMetadata
static member Create(initializer: IReferenceType.ClassInitializer) = new JThreadStateObject(initializer)
static member Create(initializer: IReferenceType.GlobalInitializer) = new JThreadStateObject(initializer)
static member Create(initializer: IReferenceType.ObjectInitializer) = new JThreadStateObject(initializer)
```

### Interface type (`java.lang.AutoCloseable`):
```fsharp
[<Sealed>]
type JAutoCloseableObject =
inherit JInterfaceObject<JAutoCloseableObject>
static let typeMetadata =
JInterfaceObject.TypeMetadataBuilder<JAutoCloseableObject>
.Create("java/lang/AutoCloseable"B)
.Build()
new(initializer: IReferenceType.ObjectInitializer) = { inherit JInterfaceObject<JAutoCloseableObject>(initializer) }
interface IDataType<JAutoCloseableObject> with
static member get_Metadata() = typeMetadata
interface IReferenceType<JAutoCloseableObject> with
static member Create(initializer: IReferenceType.ObjectInitializer) = new JAutoCloseableObject(initializer)
interface IInterfaceType<JAutoCloseableObject> with
static member get_Metadata() = typeMetadata
static member Create(initializer: IReferenceType.ObjectInitializer) = new JAutoCloseableObject(initializer)
```

### Annotation type (`java.lang.Deprecated`):
```fsharp
[<Sealed>]
type JDeprecatedObject =
inherit JAnnotationObject<JDeprecatedObject>
static let typeMetadata =
JInterfaceObject.TypeMetadataBuilder<JDeprecatedObject>
.Create("java/lang/Deprecated"B)
.Build()
new(initializer: IReferenceType.ObjectInitializer) = { inherit JAnnotationObject<JDeprecatedObject>(initializer) }
interface IDataType<JDeprecatedObject> with
static member get_Metadata() = typeMetadata
interface IReferenceType<JDeprecatedObject> with
static member Create(initializer: IReferenceType.ObjectInitializer) = new JDeprecatedObject(initializer)
interface IInterfaceType<JDeprecatedObject> with
static member get_Metadata() = typeMetadata
static member Create(initializer: IReferenceType.ObjectInitializer) = new JDeprecatedObject(initializer)
```
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,5 @@ public class GetObjectDefinition : JFunctionDefinition<JLocalObject>
private GetObjectDefinition() : base("getObject"u8, JArgumentMetadata.Get<JInt>()) { }

public JLocalObject? Invoke(JClassObject helloDotnetClass, JInt value)
{
IObject?[] invokeArgs = this.CreateArgumentsArray();
invokeArgs[0] = value;
return this.StaticInvoke(helloDotnetClass, invokeArgs);
}
=> this.StaticInvoke(helloDotnetClass, [value,]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Visual Basic .NET Support

`JNetInterface` is partially compatible with Visual Basic .NET because the latter does not support many of the modern
.NET features such as ref structs and static members in interfaces.

Thus, any application or library in Visual Basic .NET can use the APIs and types exposed in `JNetInterface` (or in F#
or C# assemblies derived from it), but it will not be able to create Java reference types as in C# or F#.

## Primitives
In C#, `JNetInterface` uses operators to convert from one primitive type to another. In Visual Basic .NET, `widening` can be used natively, but to use `narrowing` (which is implemented in C# through explicit operators), you must use the functions `CBool`, `CSByte`, `CChar`, `CDbl`, `CSng`, `CInt`, `CLng`, and `CShort` on the `Value` property of each primitive type.

```vb
Dim booleanValue As JBoolean = True
Dim byteValue As JByte = CSByte(-2)
Dim charValue As JChar = "."C
Dim doubleValue As JDouble = 3.14159265359
Dim floatValue As JFloat = 2.71828F
Dim intValue As JInt = 486
Dim longValue As JLong = 3000000000L
Dim shortValue As JShort = 1024S
```
10 changes: 10 additions & 0 deletions src/Intermediate/Rxmxnx.JNetInterface.Base.Intermediate/IObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,14 @@ internal void CopyTo(Span<Byte> span)
/// <param name="span">Binary span.</param>
/// <param name="index">Index to copy current value.</param>
internal void CopyTo(Span<JValue> span, Int32 index);
/// <summary>
/// Indicates current instance is default value.
/// </summary>
/// <returns><see langword="true"/> if current instance is default; otherwise, <see langword="false"/>.</returns>
internal Boolean IsDefault()
{
Span<Byte> values = stackalloc Byte[JValue.Size];
this.CopyTo(values);
return values.SequenceEqual(NativeUtilities.AsBytes(in JValue.Empty));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ public abstract partial class JCallDefinition
/// </summary>
/// <param name="name">Call defined name.</param>
/// <param name="metadata">Metadata of the types of call arguments.</param>
private protected JCallDefinition(ReadOnlySpan<Byte> name, params JArgumentMetadata[] metadata) : this(
name, stackalloc Byte[1] { CommonNames.VoidSignatureChar, }, metadata) { }
private protected JCallDefinition(ReadOnlySpan<Byte> name, ReadOnlySpan<JArgumentMetadata> metadata = default) :
this(name, [CommonNames.VoidSignatureChar,], metadata) { }
/// <summary>
/// Internal constructor.
/// </summary>
/// <param name="name">Call defined name.</param>
/// <param name="returnTypeSignature">Method return type defined signature.</param>
/// <param name="metadata">Metadata of the types of call arguments.</param>
private protected JCallDefinition(ReadOnlySpan<Byte> name, ReadOnlySpan<Byte> returnTypeSignature,
params JArgumentMetadata[] metadata) : base(new CStringSequence(
name,
JCallDefinition.CreateDescriptor(
returnTypeSignature, out Int32 size, out Int32[] sizes,
out Int32 referenceCount, metadata)))
ReadOnlySpan<JArgumentMetadata> metadata) : base(new CStringSequence(
name,
JCallDefinition.CreateDescriptor(
returnTypeSignature, out Int32 size, out Int32[] sizes,
out Int32 referenceCount, metadata)))
{
this._callSize = size;
this._sizes = sizes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public abstract partial class JCallDefinition
/// <param name="metadata">Metadata of the types of call arguments.</param>
/// <returns>Method descriptor.</returns>
private static CString CreateDescriptor(ReadOnlySpan<Byte> returnSignature, out Int32 totalSize, out Int32[] sizes,
out Int32 referenceCount, params JArgumentMetadata[] metadata)
out Int32 referenceCount, ReadOnlySpan<JArgumentMetadata> metadata)
{
referenceCount = 0;
totalSize = 0;
Expand Down
Loading

0 comments on commit 2fe7535

Please sign in to comment.