Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inferred property names #73

Merged
merged 2 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,24 +176,24 @@ public class MyWidget extends Widget {
}
```

You can define custom GObject properties and signals using annotations:
You can define custom GObject properties and signals using annotations. The following example defines an `int` property named `"lives"` and a `"game-over"` signal with a `String` parameter:

```java
@Property(name="lives")
@Property
public int getLives() {
return lives;
}

@Property(name="lives")
@Property
public void setLives(int value) {
this.lives = value;
if (value == 0) emit("game-over", limit);
if (value == 0)
emit("game-over", player.name());
}

@Signal(name="game-over")
@FunctionalInterface
@Signal
public interface GameOver {
public void apply(int limit);
void apply(String playerName);
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Property {
String name();
String name() default "";
Class<? extends ParamSpec> type() default ParamSpec.class;
boolean readable() default true;
boolean writable() default true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,17 @@ public static <T extends GObject> T newGObjectWithProperties(Type objectType, Ob
}
}

// Convert "CamelCase" to "kebab-case"
private static String getPropertyName(String methodName) {
if (methodName.startsWith("get") || methodName.startsWith("set")) {
String value = methodName.substring(3);
return value.replaceAll("([a-z0-9])([A-Z])", "$1-$2")
.toLowerCase().replaceAll("\\.", "");
}
throw new IllegalArgumentException("Cannot infer property name from method named %s"
.formatted(methodName));
}

// Infer the ParamSpec class from the Java class that is used in the getter/setter method.
private static Class<? extends ParamSpec> inferType(Method method) {
// Determine the Java class of the property
Expand Down Expand Up @@ -243,8 +254,11 @@ public static <T extends GObject, TC extends GObject.ObjectClass> Consumer<TC> i
continue;
}

// Name is specified with the annotation, or infer it form the method name
String name = p.name().isEmpty() ? getPropertyName(method.getName()) : p.name();

// Check if this property has already been added from another method
if (propertyNames.contains(p.name())) {
if (propertyNames.contains(name)) {
continue;
}

Expand All @@ -261,43 +275,43 @@ public static <T extends GObject, TC extends GObject.ObjectClass> Consumer<TC> i

ParamSpec ps;
if (paramspec.equals(ParamSpecBoolean.class)) {
ps = GObjects.paramSpecBoolean(p.name(), p.name(), p.name(), false, getFlags(p));
ps = GObjects.paramSpecBoolean(name, name, name, false, getFlags(p));
} else if (paramspec.equals(ParamSpecChar.class)) {
ps = GObjects.paramSpecChar(p.name(), p.name(), p.name(), Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) 0, getFlags(p));
ps = GObjects.paramSpecChar(name, name, name, Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) 0, getFlags(p));
} else if (paramspec.equals(ParamSpecDouble.class)) {
ps = GObjects.paramSpecDouble(p.name(), p.name(), p.name(), -Double.MAX_VALUE, Double.MAX_VALUE, 0.0d, getFlags(p));
ps = GObjects.paramSpecDouble(name, name, name, -Double.MAX_VALUE, Double.MAX_VALUE, 0.0d, getFlags(p));
} else if (paramspec.equals(ParamSpecFloat.class)) {
ps = GObjects.paramSpecFloat(p.name(), p.name(), p.name(), -Float.MAX_VALUE, Float.MAX_VALUE, 0.0f, getFlags(p));
ps = GObjects.paramSpecFloat(name, name, name, -Float.MAX_VALUE, Float.MAX_VALUE, 0.0f, getFlags(p));
} else if (paramspec.equals(ParamSpecGType.class)) {
ps = GObjects.paramSpecGtype(p.name(), p.name(), p.name(), Types.NONE, getFlags(p));
ps = GObjects.paramSpecGtype(name, name, name, Types.NONE, getFlags(p));
} else if (paramspec.equals(ParamSpecInt.class)) {
ps = GObjects.paramSpecInt(p.name(), p.name(), p.name(), Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getFlags(p));
ps = GObjects.paramSpecInt(name, name, name, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getFlags(p));
} else if (paramspec.equals(ParamSpecInt64.class)) {
ps = GObjects.paramSpecInt64(p.name(), p.name(), p.name(), Long.MIN_VALUE, Long.MAX_VALUE, 0, getFlags(p));
ps = GObjects.paramSpecInt64(name, name, name, Long.MIN_VALUE, Long.MAX_VALUE, 0, getFlags(p));
} else if (paramspec.equals(ParamSpecLong.class)) {
ps = GObjects.paramSpecLong(p.name(), p.name(), p.name(), Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getFlags(p));
ps = GObjects.paramSpecLong(name, name, name, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getFlags(p));
} else if (paramspec.equals(ParamSpecPointer.class)) {
ps = GObjects.paramSpecPointer(p.name(), p.name(), p.name(), getFlags(p));
ps = GObjects.paramSpecPointer(name, name, name, getFlags(p));
} else if (paramspec.equals(ParamSpecString.class)) {
ps = GObjects.paramSpecString(p.name(), p.name(), p.name(), null, getFlags(p));
ps = GObjects.paramSpecString(name, name, name, null, getFlags(p));
} else if (paramspec.equals(ParamSpecUChar.class)) {
ps = GObjects.paramSpecUchar(p.name(), p.name(), p.name(), (byte) 0, Byte.MAX_VALUE, (byte) 0, getFlags(p));
ps = GObjects.paramSpecUchar(name, name, name, (byte) 0, Byte.MAX_VALUE, (byte) 0, getFlags(p));
} else if (paramspec.equals(ParamSpecUInt.class)) {
ps = GObjects.paramSpecUint(p.name(), p.name(), p.name(), 0, Integer.MAX_VALUE, 0, getFlags(p));
ps = GObjects.paramSpecUint(name, name, name, 0, Integer.MAX_VALUE, 0, getFlags(p));
} else if (paramspec.equals(ParamSpecUInt64.class)) {
ps = GObjects.paramSpecUint64(p.name(), p.name(), p.name(), 0, Long.MAX_VALUE, 0, getFlags(p));
ps = GObjects.paramSpecUint64(name, name, name, 0, Long.MAX_VALUE, 0, getFlags(p));
} else if (paramspec.equals(ParamSpecULong.class)) {
ps = GObjects.paramSpecUlong(p.name(), p.name(), p.name(), 0, Integer.MAX_VALUE, 0, getFlags(p));
ps = GObjects.paramSpecUlong(name, name, name, 0, Integer.MAX_VALUE, 0, getFlags(p));
} else if (paramspec.equals(ParamSpecUnichar.class)) {
ps = GObjects.paramSpecUnichar(p.name(), p.name(), p.name(), 0, getFlags(p));
ps = GObjects.paramSpecUnichar(name, name, name, 0, getFlags(p));
} else {
GLib.log(LOG_DOMAIN, LogLevelFlags.LEVEL_CRITICAL,
"Unsupported ParamSpec %s in class %s:\n",
paramspec.getName(), cls.getName());
return null;
}
propertySpecs.add(ps);
propertyNames.add(p.name());
propertyNames.add(name);
}

// No properties found?
Expand All @@ -314,7 +328,11 @@ public static <T extends GObject, TC extends GObject.ObjectClass> Consumer<TC> i
continue;
}
Property property = method.getDeclaredAnnotation(Property.class);
int idx = propertyNames.indexOf(property.name());

// Name is specified with the annotation, or infer it form the method name
String name = property.name().isEmpty() ? getPropertyName(method.getName()) : property.name();

int idx = propertyNames.indexOf(name);

// Returns void -> setter, else -> getter
if (method.getReturnType().equals(void.class)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,12 @@ public void setStringProperty(String value) {

private boolean boolProperty = false;

@Property(name="bool-property")
@Property // name will be inferred: "bool-property"
public boolean getBoolProperty() {
return boolProperty;
}

@Property(name="bool-property")
@Property
public void setBoolProperty(boolean boolProperty) {
this.boolProperty = boolProperty;
}
Expand Down
20 changes: 12 additions & 8 deletions website/docs/register.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@ Finally, add the default memory-address-constructor for Java-GI Proxy objects:

This constructor must exist in all Java-GI Proxy objects. It enables a Proxy class to be instantiated automatically for new instances returned from native function calls.

If your application is module-based, you must export your package to the `org.gnome.glib` module in your `module-info.java` file, to allow the reflection to work:
If your 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:

```
exports [package.name] to org.gnome.glib;
module my.module.name {
exports my.package.name to org.gnome.gobject;
}
```

## Specifying the name of the GType
Expand Down Expand Up @@ -84,19 +86,19 @@ public void finalize_() {

## 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). The `@Property` annotation must always specify the `name` parameter; all other parameters are optional.
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). The `@Property` annotation can optionally specify the `name` parameter; all other parameters are optional.

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

```java
private int size;

@Property(name="n-items")
@Property
public int getNItems() {
return size;
}

@Property(name="n-items")
@Property
public void setNItems(int nItems) {
size = nItems;
}
Expand All @@ -106,7 +108,7 @@ The `@Property` annotation accepts the following parameters:

| Parameter | Type | Default value |
|----------------|-----------|---------------|
| name | Mandatory | n/a |
| name | String | inferred |
| type | ParamSpec | inferred |
| readable | Boolean | true |
| writable | Boolean | true |
Expand All @@ -115,6 +117,8 @@ The `@Property` annotation accepts the following parameters:
| explicitNotify | Boolean | false |
| deprecated | Boolean | false |

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).

## Class and instance init functions
Expand Down Expand Up @@ -163,7 +167,7 @@ public class Counter extends GObject {
}
```

The "limit-reached" signal is defined with a functional interface annotated as `@Signal`. The method signature of the functional interface is used to define the signal parameters and return value. The signal name is inferred from the interface too (converting CamelCase to kebab-case) but can be overridden.
The "limit-reached" signal in the example is defined with a functional interface annotated as `@Signal`. The method signature of the functional interface is used to define the signal parameters and return value. The signal name is inferred from the interface too (converting CamelCase to kebab-case) but can be overridden.

You can connect to the custom signal, like this:

Expand All @@ -173,7 +177,7 @@ counter.connect("limit-reached", (Counter.LimitReached) (limit) -> {
}
```

Because the signal declaration is an ordinary functional interface, the declaration in the above example can also be declared as an `IntConsumer`:
Because the signal declaration is an ordinary functional interface, it is equally valid to extend from a standard functional interface like `Runnable`, `BooleanSupplier`, or any other one, like (in the above example) an `IntConsumer`:

```
@Signal
Expand Down