Skip to content

Commit

Permalink
Merge pull request #291 from basshelal/number-tests
Browse files Browse the repository at this point in the history
Overhauled tests for numeric types
  • Loading branch information
headius authored Feb 1, 2022
2 parents a276702 + 8a69449 commit 0b72e96
Show file tree
Hide file tree
Showing 15 changed files with 1,574 additions and 639 deletions.
106 changes: 92 additions & 14 deletions docs/TypeMappings.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,104 @@
# Type Mappings

# Primitive Types
## Numeric Types

All Java primitives are mapped to their size equivalent C types:
### Signed Types

Signed numeric types map to their equivalent size Java primitive or boxed types:

| C Type | Java Type | Size |
|--------|-----------|------|
| `char` | `byte` | 8 bit integer |
| `short` | `short` | 16 bit integer |
| `int` | `int` | 32 bit integer |
| `long` | `long` | natural long, 32 bits on 32 bit systems, 64 bits on 64 bit systems |
| `float` | `float` | 32 bit floating point |
| `double` | `double` | 64 bit floating point |
| `char` | `byte` or `Byte` | 8 bit integer |
| `short` | `short` or `Short` | 16 bit integer |
| `int` | `int` or `Integer` | 32 bit integer |
| `long` | `long` or `Long` or `NativeLong` | natural long, 32 bit integer on 32 bit systems, 64 bit integer on 64 bit systems |
| `long long` | `long` or `Long` | 64 bit integer |
| `float` | `float` or `Float` | 32 bit floating point |
| `double` | `double` or `Double` | 64 bit floating point |

Java `boolean` or `Boolean` can also be used in place of a C numeric type where a boolean would be expected, check the native function's documentation
before doing this. `0` maps to `false` and any other value will be `true`. Floating point types (`float` and `double`) are not supported in this regard.

### Unsigned Types

For native unsigned types you can use the same size Java type (`unsigned char` maps to `byte` or `Byte`) but you will not be able to fit the values greater than the maximum limit for the Java type.

For example, an `unsigned char` can contain the value `220` but a Java `byte` cannot and will thus underflow to `-36`.

To remedy this you can use a larger size Java type with the corresponding annotation to let JNR-FFI to do the correct conversion. To fit the bounds of unsigned C types use the following table:

| C Type | Java Type |
|--------|-----------|
| `unsigned char` | `@u_int8_t byte` |
| `unsigned short` | `@u_int16_t int` |
| `unsigned int` | `@u_int32_t long` |

Unsigned 64 bit integers (such as `unsigned long` on 64bit systems and `unsigned long long`) are not yet supported to fit values beyond those of Java's `Long.MAX_VALUE`. [This is a documented issue.](https://github.com/jnr/jnr-ffi/issues/289)

### Enums

Native enums can be mapped using any Java integral numeric types such as `int` or `Integer`, or by using a similarly valued Java enum to the native one.

For example the C enum:

```c
enum week{Mon, Tue, Wed, Thu, Fri, Sat, Sun};
```
can be mapped in Java as:
```java
public enum Week {MON, TUE, WED, THU, FRI, SAT, SUN;}
```

This works because they have the same "value" which, if undefined, is the order in which the enum entry appears.

If the C enum contains values, the values need to match in Java by implementing a mapping function:

The signedness and width can be additionally modified using annotations for example, for C `long long` use a `long`
with the `@LongLong` annotation.
```c
enum State {On = 2, Off = 4, Broken = 8};
```
should be mapped in Java as:
```java
public static enum State implements EnumMapper.IntegerEnum {
ON(2), OFF(4), BROKEN(8);
private final int value;
public State(int value) {this.value = value;}
@Override
public int intValue() {return value;} // mapping function
}
```

C functions often use enums in "flags" and allow you to combine "flags" by logical OR-ing the values together. For example:

In addition to these types there exist numerous annotations for common C types for example `@size_t` on an `int` for C
`size_t`.
```c
void start_stream(stream_flags flags);
```
```c
// start paused and muted and allow latency adjustment
stream_flags flags = STREAM_START_PAUSED |
STREAM_START_MUTED |
STREAM_ADJUST_LATENCY;
start_stream(flags);
```

The same can be done in Java also by OR-ing the values of the enum, but more elegantly, by using an `EnumSet` where such a combined enum would be expected:

`boolean` can also be used in place of a C `int` where a boolean would be expected, check the function's documentation
before doing this.
```java
public void start_stream(EnumSet<stream_flags> flags);
```

```java
start_stream(EnumSet.of(
STREAM_START_PAUSED, STREAM_START_MUTED, STREAM_ADJUST_LATENCY
));
```

# Complex Types

Expand Down
27 changes: 11 additions & 16 deletions libtest/NumberTest.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
*/

#include <stdio.h>
#include <stdbool.h>
#if !defined(__mips___) || defined(__PASE__)
# include <stdint.h>
#endif

// Let's define the stdint.h typedefs ourselves if they can't be found
#if !defined (_STDINT_H_) && !defined(_STDINT_H) && !defined(_SYS__STDINT_H_) && !defined(_H_STDINT)
typedef signed char int8_t;
typedef signed short int16_t;
Expand All @@ -31,23 +33,19 @@ typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
#endif
typedef char Signed8;
typedef short Signed16;
typedef int Signed32;
typedef long long Signed64;
typedef float Float32;
typedef double Float64;
typedef long SignedLong;
typedef void* pointer;

typedef unsigned long ulong;
typedef long double ldouble;
typedef void* pointer;
typedef enum Enum_t {e0, e1, e2, e3} Enum;

#define ADD(T) T add_##T(T arg1, T arg2) { return arg1 + arg2; }
#define SUB(T) T sub_##T(T arg1, T arg2) { return arg1 - arg2; }
#define MUL(T) T mul_##T(T arg1, T arg2) { return arg1 * arg2; }
#define DIV(T) T div_##T(T arg1, T arg2) { return arg1 / arg2; }
#define RET(T) T ret_##T(T arg1) { return arg1; }
typedef char* ptr;
#define TEST(T) ADD(T) SUB(T) MUL(T) DIV(T) RET(T)

TEST(int8_t);
TEST(int16_t);
TEST(int32_t);
Expand All @@ -60,11 +58,8 @@ TEST(float);
TEST(double);
TEST(long);
TEST(ulong);
TEST(Signed8);
TEST(Signed16);
TEST(Signed32);
TEST(Signed64);
TEST(SignedLong);
TEST(Float32);
TEST(Float64);
TEST(ldouble);

RET(bool);
RET(Enum);
RET(pointer);
4 changes: 2 additions & 2 deletions src/main/java/jnr/ffi/provider/jffi/ToNativeOp.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ final boolean isPrimitive() {
}
m.put(float.class, new Float32(float.class));
m.put(Float.class, new Float32(Float.class));
m.put(double.class, new Float64(float.class));
m.put(Double.class, new Float64(Float.class));
m.put(double.class, new Float64(double.class));
m.put(Double.class, new Float64(Double.class));
m.put(Address.class, new AddressOp());

operations = Collections.unmodifiableMap(m);
Expand Down
34 changes: 5 additions & 29 deletions src/main/java/jnr/ffi/util/EnumMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,32 +40,24 @@ private static final class StaticDataHolder {
}

private final Class<? extends Enum> enumClass;
private final Integer[] intValues;
private final Long[] longValues;
private final int[] intValues;
private final Map<Number, Enum> reverseLookupMap = new HashMap<Number, Enum>();

private EnumMapper(Class<? extends Enum> enumClass) {
this.enumClass = enumClass;

EnumSet<? extends Enum> enums = EnumSet.allOf(enumClass);

this.intValues = new Integer[enums.size()];
this.longValues = new Long[enums.size()];
this.intValues = new int[enums.size()];
Method intValueMethod = getNumberValueMethod(enumClass, int.class);
Method longValueMethod = getNumberValueMethod(enumClass, long.class);
for (Enum e : enums) {
Number value;
if (longValueMethod != null) {
value = reflectedNumberValue(e, longValueMethod);

} else if (intValueMethod != null) {
if (intValueMethod != null) {
value = reflectedNumberValue(e, intValueMethod);

} else {
value = e.ordinal();
}
intValues[e.ordinal()] = value.intValue();
longValues[e.ordinal()] = value.longValue();

reverseLookupMap.put(value, e);
}
Expand Down Expand Up @@ -127,32 +119,16 @@ public final int intValue(Enum value) {
return integerValue(value);
}

public final Long longValue(Enum value) {
if (value.getClass() != enumClass) {
throw new IllegalArgumentException("enum class mismatch, " + value.getClass());
}

return longValues[value.ordinal()];
}

public Enum valueOf(int value) {
return reverseLookup(value);
}

public Enum valueOf(long value) {
return reverseLookup(value);
}

public Enum valueOf(Number value) {
return reverseLookup(value);
}

private Enum reverseLookup(Number value) {
private Enum reverseLookup(int value) {
Enum e = reverseLookupMap.get(value);
return e != null ? e : badValue(value);
}

private Enum badValue(Number value) {
private Enum badValue(int value) {
//
// No value found - try to find the default value for unknown values.
// This is useful for enums that aren't fixed in stone and/or where you
Expand Down
32 changes: 24 additions & 8 deletions src/test/java/jnr/ffi/DelegateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import jnr.ffi.annotations.Delegate;
import jnr.ffi.annotations.LongLong;
import jnr.ffi.types.u_int32_t;
import jnr.ffi.util.EnumMapper;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
Expand All @@ -35,6 +36,21 @@ public class DelegateTest {

public DelegateTest() {
}
public static enum TestEnum implements EnumMapper.IntegerEnum {
A(1),
B(2),
C(3),
Z(100);
TestEnum(int value) {
this.value = value;
}

public int intValue() {
return value;
}
private final int value;
}

private static TestLib lib;
@BeforeAll
public static void setUpClass() throws Exception {
Expand Down Expand Up @@ -108,12 +124,12 @@ public interface CallableIrVBoxed {
void testClosureIrV(CallableIrVBoxed closure, @u_int32_t long a1);

public interface CallableErV {
@Delegate public void call(@u_int32_t EnumTest.TestEnum a1);
@Delegate public void call(@u_int32_t TestEnum a1);
}
void testClosureIrV(CallableErV closure, @u_int32_t EnumTest.TestEnum a1);
void testClosureIrV(CallableErV closure, @u_int32_t TestEnum a1);

public static interface CallableVrE {
@Delegate public EnumTest.TestEnum call();
@Delegate public TestEnum call();
}
int testClosureVrI(CallableVrE closure);

Expand Down Expand Up @@ -369,11 +385,11 @@ public void call(Long a1) {
@Test
public void closureErV() {
final boolean[] called = { false };
final EnumTest.TestEnum[] val = { null };
final EnumTest.TestEnum MAGIC = EnumTest.TestEnum.C;
final TestEnum[] val = { null };
final TestEnum MAGIC = TestEnum.C;
TestLib.CallableErV closure = new TestLib.CallableErV() {

public void call(EnumTest.TestEnum a1) {
public void call(TestEnum a1) {
called[0] = true;
val[0] = a1;
}
Expand All @@ -386,10 +402,10 @@ public void call(EnumTest.TestEnum a1) {
@Test
public void closureVrE() {
final boolean[] called = { false };
final EnumTest.TestEnum MAGIC = EnumTest.TestEnum.C;
final TestEnum MAGIC = TestEnum.C;
TestLib.CallableVrE closure = new TestLib.CallableVrE() {

public EnumTest.TestEnum call() {
public TestEnum call() {
called[0] = true;
return MAGIC;
}
Expand Down
Loading

0 comments on commit 0b72e96

Please sign in to comment.