Skip to content

Commit

Permalink
Documentation updates
Browse files Browse the repository at this point in the history
  • Loading branch information
jwharm committed Jan 11, 2025
1 parent 64890b2 commit 764ad1b
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 45 deletions.
42 changes: 33 additions & 9 deletions docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
In most cases, memory management of native resources is automatically taken care of. Java-GI uses GObject toggle references to dispose the native object when the Java instance is garbage-collected, and manages all memory allocations for marshaling of string, array and struct parameters.

### Allocating structs with an Arena
Struct definitions (in contrast to GObjects) in native code are mapped to Java classes.
Struct and union definitions (in contrast to GObjects) in native code are mapped to Java classes.

Because structs don't necessarily have a constructor method, the Java classes offer two constructor to allocate a new struct:
Because structs don't necessarily have a constructor method, the Java classes offer two constructors to allocate a new struct:
- A constructor without parameters, that will allocate an uninitialized structs
- A constructor with parameters to set all struct members to an initial value

The constructors optionally take an `Arena` to allocate memory for the new instances in native memory. Users can choose between different arenas in OpenJDK, depending on the use case. All constructors generated by Java-GI for record (struct) and union types take an optional Arena parameter, defaulting to `Arena.ofAuto()`. Some examples:
The constructors optionally take an `Arena` to specify how to allocate memory for the new instances in native memory. Users can choose between different arenas in OpenJDK, depending on the use case. The constructors generated by Java-GI default to `Arena.ofAuto()`. Some examples:

=== "Java"

Expand Down Expand Up @@ -52,7 +52,7 @@ The constructors optionally take an `Arena` to allocate memory for the new insta
```

!!! warning
The Java garbage collector does not know about the native resources, and might wait an indefinite amount of time before the objects are effectively disposed. Therefore, the default `Arena.ofAuto()` can lead to excessive memory usage. When allocating and discarding many struct instances in a tight loop, use `try-with-resources` to explicitly manage the memory allocations.
The Java garbage collector does not know about the native resources, and might wait an indefinite amount of time before the objects are effectively disposed. Therefore, the default `Arena.ofAuto()` can lead to excessive memory usage. When allocating and discarding many struct instances in a tight loop, use `Arena.ofConfined()` or `Arena.ofShared()` with a `try {}` block to explicitly manage the memory allocations.
Read the [OpenJDK Arena class documentation](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/foreign/Arena.html) to learn how to use arenas to manage the lifecycle of native memory segments.

## Builder pattern
Expand Down Expand Up @@ -83,6 +83,28 @@ You can construct an object with GObject properties using a Builder pattern. For

With a `Builder` you can set the properties of the class, its parents, and all implemented interfaces. Behind the scenes, this will call `g_object_new_with_properties()`.

Builders also offer `on...()` methods to connect signals:

=== "Java"

```java
var button = Button.builder()
.setLabel("Close window")
.onClicked(window::close)
.build()
```

=== "Kotlin"

```kotlin
val button = Button.builder()
.setLabel("Close window")
.onClicked(window::close)
.build()
```

This can result in cleaner-looking code, but the signals cannot be managed and disconnected. To properly connect and disconnect signals, use the `SignalHandler` object that is returned by the `on...()` methods on the created instances. You should only connect signals from a builder when they don't need to be disconnected later.

## Exceptions

`GError**` parameters are mapped to Java `GErrorException`s.
Expand Down Expand Up @@ -117,7 +139,7 @@ Nullability of parameters (as defined in the GObject-introspection attributes) i

Java-GI copies all array parameters into a newly allocated array in native memory. When an array is returned, the contents are copied back into a Java array. This impacts performance, so keep this in mind when working with arrays in a tight loop.

C functions that work with arrays, often expect the array length as an additional parameter. In the corresponding Java methods, that parameter is omitted, because Java-GI will set it automatically.
C functions that work with arrays, often expect the length as an additional parameter. In the corresponding Java methods, that parameter is omitted, because Java-GI will set it automatically.

## Out-parameters

Expand Down Expand Up @@ -157,18 +179,18 @@ Enumerations and flags (bitfields) are available as Java enums. To combine multi
entry.inputHints = setOf(InputHints.WORD_COMPLETION, InputHints.NO_SPELLCHECK)
```

When you just want to set a single flag, you can omit `Set.of()` and pass the flag directly. All methods with flag parameters are overloaded for this purpose:
When the flags are the last parameter, you can omit `Set.of()` and pass the flags directly als variadic arguments. All methods with flags as the last parameter are overloaded for this purpose:

=== "Java"

```java
entry.setInputHints(InputHints.NO_EMOJI);
entry.setInputHints(InputHints.WORD_COMPLETION, InputHints.NO_EMOJI);
```

=== "Kotlin"

```kotlin
entry.setInputHints(InputHints.NO_EMOJI)
entry.setInputHints(InputHints.WORD_COMPLETION, InputHints.NO_EMOJI)
```


Expand Down Expand Up @@ -238,6 +260,8 @@ For every signal, a method to connect (e.g. `onClicked`) and emit the signal (`e

Functions with callback parameters are supported too. The generated Java bindings contain `@FunctionalInterface` definitions for all callback functions to ensure type safety.

To learn about creating your own signals, read [Registering new types](register.md).

## Closures

[Closures](https://docs.gtk.org/gobject/struct.Closure.html) can be marshaled to Java methods. Similar to the `CClosure` type in C code, Java-GI offers a [JavaClosure](https://jwharm.github.io/java-gi/javadoc/io/github/jwharm/javagi/gobject/JavaClosure.html). You can create a JavaClosure for a lambda fuction, functional interface or `java.lang.reflect.Method`, and then pass it to native code (for example, the last two parameters of [`GObject.bindPropertyFull()`](https://jwharm.github.io/java-gi/glib/org.gnome.glib/org/gnome/gobject/GObject.html#bindPropertyFull(java.lang.String,org.gnome.gobject.GObject,java.lang.String,org.gnome.gobject.BindingFlags,org.gnome.gobject.Closure,org.gnome.gobject.Closure))).
Expand All @@ -260,7 +284,7 @@ To create a Gtk composite template class (coupled to a ui definition in XML or B
A great tool for developing GNOME applications is GNOME Builder. Builder primarily supports C, JavaScript, Rust, Python and Vala applications, but you can use it to develop Java applications as well, if you have the Eclipse JDT language server (JDTLS) installed.

!!! warning
In my limited experience this is still very unpolished and fragile. For any serieus work, by all means use a well-supported Java IDE.
In my limited experience this is still very unpolished and fragile. For any serious work, by all means use a well-supported Java IDE.

1. Install GNOME Builder. Preferably the newest release from Flathub, but at least version 44.
2. Download JDTLS [here](https://download.eclipse.org/jdtls/milestones/). Pick the latest milestone version and download the file `jdt-language-server-...-tar.gz`.
Expand Down
86 changes: 53 additions & 33 deletions docs/register.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,45 +15,44 @@ When you extend a Java class from an existing GObject-derived class, Java will t
```


However, the GObject type system itself will not recognize it as its own class. Therefore, you need to register your class as a new GType. To do this, Java-GI offers an easy-to-use wrapper function: `Types.register(classname)`. This will use reflection to determine the name, parent class, implemented interfaces and overridden methods, and will register it as a new GType.
However, the GObject type system itself will not recognize it as its own class. To do that, you need to register your class as a new GType. Java-GI offers an easy-to-use function to achieve this: `Types.register(classname)`. This will use reflection to determine the name, parent class, implemented interfaces and overridden methods, and will register it as a new GType.

It is recommended to register the new gtype in a static field `gtype` like this:
It is recommended to register the new gtype in a static block like this:

=== "Java"

```java
private static final Type gtype = Types.register(MyObject.class);
public static Type getType() {
return gtype;
{
Types.register(MyObject.class);
}
```

=== "Kotlin"

```kotlin
companion object {
val gtype: Type = Types.register<MyObject, ObjectClass>(MyObject::class.java)
init {
Types.register<MyObject, ObjectClass>(MyObject::class.java)
}
}
```

By using a static initializer, the GObject type will be registered immediately when the JVM classloader initializes the Java class. Similarly, you can register all your classes in your `main()` method.

By declaring the `gtype` as a static field in this way, it will be registered immediately when the JVM classloader initializes the Java class.

When instantiating a new instance of the object, pass the `gtype` to `GObject::new()`:
When instantiating a new instance of the object, pass the class to `GObject::new()`:

=== "Java"

```java
public MyObject() {
super(gtype, null);
super(MyObject.class);
}
```

=== "Kotlin"

```kotlin
constructor() : super(gtype, null)
constructor() : super(MyObject::class.java)
```

Alternatively, create a static factory method with a descriptive name like `create` or `newInstance` that calls `GObject::newInstance()`:
Expand All @@ -62,7 +61,7 @@ Alternatively, create a static factory method with a descriptive name like `crea

```java
public static MyObject create() {
return GObject.newInstance(gtype);
return GObject.newInstance(MyObject.class);
}
```

Expand All @@ -73,14 +72,14 @@ Alternatively, create a static factory method with a descriptive name like `crea
val gtype: Type = ...
fun create(): MyObject {
return newInstance(gtype)
return newInstance(MyObject::class.java)
}
}
```

Now, when you call `MyObject.create()`, you will have a Java object that is also instantiated as a native GObject instance.

If your class contains GObject class or instance initializer method (see below), the constructor **must** be a static factory method; a regular constructor that calls `super(gtype, null)` **will not work** correctly with GObject initializers.
If your class contains GObject class or instance initializer method (see below), the constructor **must** be a static factory method; a regular constructor that calls `super(MyObject.class)` **will not work** correctly with GObject initializers.

Finally, add the default memory-address-constructor for Java-GI Proxy objects:

Expand All @@ -100,7 +99,7 @@ Finally, add the default memory-address-constructor for Java-GI Proxy objects:
}
```

Ignore warnings that the constructor appears unused: This constructor should exist in all Java-GI proxy classes. It enables a Java class to be instantiated automatically for new instances returned from native function calls.
Ignore warnings that the constructor appears unused: This constructor should exist in all Java-GI proxy classes. It enables a Java object to be instantiated automatically for GObject instances returned from native function calls.

If your Java application is module-based, you must export your package to the `org.gnome.gobject` module in your `module-info.java` file, to allow the reflection to work:

Expand Down Expand Up @@ -130,7 +129,9 @@ A GType has a unique name, like 'GtkLabel', 'GstObject' or 'GListModel'. (You ca
...
```

If you don't intend to override the name of the GType, you can safely omit the `@RegisteredType` annotation.
To prefix all type names in a package with a shared namespace identifier, use the `@Namespace(name="...")` annotation in your `package-info.java` file.

If you don't intend to override the name of the GType, you can safely omit the `@RegisteredType` and `@Namespace` annotations.

## Method overrides

Expand All @@ -144,7 +145,9 @@ When a virtual method is not available as a regular instance method, you can saf

## Properties

You can define GObject properties with the `@Property` annotation on the getter and setter methods. You must annotate both the getter and setter methods (if applicable). All `@Property` annotation parameters are optional.
When your class contains getter and setter method pairs, Java-GI will register them as GObject properties. For boolean properties, you can also use `isFoo()`/`setFoo()` pairs. Kotlin creates get- and set-methods for Kotlin properties automatically, so in practice a Kotlin property will be registered as a GObject property.

You can define GObject properties with the `@Property` annotation on other methods besides getter/setter pairs, or when you want to change the property name or change other parameters. When you use `@Property`, you must annotate both the getter and setter methods (if applicable).

Example definition of an `int` property with name `n-items`:

Expand All @@ -153,12 +156,10 @@ Example definition of an `int` property with name `n-items`:
```java
private int size = 0;

@Property
public int getNItems() {
return size;
}

@Property
public void setNItems(int nItems) {
size = nItems;
}
Expand All @@ -167,28 +168,45 @@ Example definition of an `int` property with name `n-items`:
=== "Kotlin"

```kotlin
var size: Int = 0
@Property get
@Property set
var nItems: Int = 0
```

### Property annotation parameters

The `@Property` annotation can be used to set a property name and other attributes. It can also be used to mark methods as properties that don't conform to the getter/setter convention. Finally, a `@Property(skip=true)` annotation can be used to prevent getter/setter methods getting registered as a GObject property.

For properties with a getter and setter method, either both or neither methods must be annotated with `@Property` (or else they will not be recognized by Java-GI as a pair). The annotation parameters must be specified on the getter. They can be specified on the setter too, but only the parameters on the getter are actually used.

The `@Property` annotation accepts the following parameters:

| Parameter | Type | Default value |
|----------------|-----------|---------------|
| name | String | inferred |
| type | ParamSpec | inferred |
| readable | Boolean | true |
| writable | Boolean | true |
| construct | Boolean | false |
| constructOnly | Boolean | false |
| explicitNotify | Boolean | false |
| deprecated | Boolean | false |
| Parameter | Type | Default value |
|----------------|-----------|-----------------|
| name | String | inferred |
| type | ParamSpec | inferred |
| readable | Boolean | true |
| writable | Boolean | true |
| construct | Boolean | false |
| constructOnly | Boolean | false |
| explicitNotify | Boolean | false |
| deprecated | Boolean | false |
| minimumValue | String | type-dependent |
| maximumValue | String | type-dependent |
| defaultValue | String | type-dependent |
| skip | Boolean | false |

All `@Property` annotation parameters are optional.

When the name is not specified, it will be inferred from the name of the method (provided that the method names follow the `getX()`/`setX(...)` pattern), stripping the "get" or "set" prefix and converting CamelCase to kebab-case. If you do specify a name, it must be present on **both** the getter and setter methods (otherwise Java-GI will create two properties, with different names).

When the type is not specified, it will be inferred from the parameter or return-type of the method. When the type is specified, it must be one of the subclasses of `GParamSpec`. The boolean parameters are `GParamFlags` arguments, and are documented [here](https://docs.gtk.org/gobject/flags.ParamFlags.html).

The `@Property` parameters `minimumValue`, `maximumValue` and `defaultValue` expect String values. They are transformed by Java-GI to the proper type using `Boolean.parseBoolean()`, `Integer.parseInt()` etcetera. Using these three parameters, you can set the minimum, maximum and default values on the `GParamSpec` of a property that was defined in Java.

* The minimum and maximum values are not enforced by Java-GI, so on the Java side the benefits are negligible. In most cases it is advisable to implement minimum and maximum property values in Java itself.
* The default value is returned by Java-GI for properties that have only a setter method in Java.

When the `skip` parameter is set, the method will *not* be registered as a GObject property.

## Class and instance init functions

To implement a custom class initializer or instance initializer function, use the `@ClassInit` and `@InstanceInit` attributes:
Expand Down Expand Up @@ -229,6 +247,8 @@ To implement a custom class initializer or instance initializer function, use th
}
```

Be aware that the instance initializer will only run for objects constructed with a static factory method. A regular constructor that calls `super(MyObject.class)` will not work.

## Signals

You can define custom signals in Java classes that extend GObject. For example:
Expand Down
6 changes: 3 additions & 3 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ An example Gtk application with a "Hello world" button can be created as follows
}
public HelloWorld(String[] args) {
var app = new Application("my.example.HelloApp", ApplicationFlags.DEFAULT_FLAGS);
var app = new Application("my.example.HelloApp");
app.onActivate(() -> activate(app));
app.run(args);
}
Expand Down Expand Up @@ -126,7 +126,7 @@ An example Gtk application with a "Hello world" button can be created as follows
import org.gnome.gtk.*

fun main(args: Array<String>) {
val app = Application("my.example.HelloApp", ApplicationFlags.DEFAULT_FLAGS)
val app = Application("my.example.HelloApp")
app.onActivate { activate(app) }
app.run(args)
}
Expand Down Expand Up @@ -180,7 +180,7 @@ An example Gtk application with a "Hello world" button can be created as follows

object HelloWorld {
def main(args: Array[String]) = {
val app = Application("my.example.HelloApp", ApplicationFlags.DEFAULT_FLAGS)
val app = Application("my.example.HelloApp")
app.onActivate(() => HelloWorld().activate(app))
app.run(args);
()
Expand Down

0 comments on commit 764ad1b

Please sign in to comment.