Skip to content

Commit

Permalink
Generate a GObject constructor that takes a Java class as first argum…
Browse files Browse the repository at this point in the history
…ent instead of a GType, followed by varargs for (optionally) specifying property names and values.

A new utility class `VarargsUtil` was created that simplifies splitting the varargs into a "first" and "rest" component like `g_object_new` expects, and also adds a `null` at the end, in case the user forgets it.
  • Loading branch information
jwharm committed Jan 9, 2025
1 parent a64974f commit 2f896ea
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public final class ClassNames {
public static final ClassName MEMORY_CLEANER = get(PKG_INTEROP, "MemoryCleaner");
public static final ClassName INTEROP = get(PKG_INTEROP, "Interop");
public static final ClassName PLATFORM = get(PKG_INTEROP, "Platform");
public static final ClassName VARARGS_UTIL = get(PKG_INTEROP, "VarargsUtil");

public static final ClassName AUTO_CLOSEABLE = get(PKG_GIO, "AutoCloseable");
public static final ClassName LIST_MODEL_JAVA_LIST = get(PKG_GIO, "ListModelJavaList");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,11 @@ else if (cls.isInstanceOf("GObject", "ParamSpec"))
}

if ("GObject".equals(cls.cType()))
builder.addMethod(gobjectConstructor())
.addMethod(gobjectConstructorVarargs())
.addMethod(gobjectClassConstructor())
.addMethod(gobjectClassConstructorVarargs())
builder.addMethod(gobjectClassConstructor())
.addMethod(gobjectFactory())
.addMethod(gobjectFactoryVarargs())
.addMethod(gobjectClassFactory())
.addMethod(gobjectClassFactoryVarargs())
.addMethod(gobjectGetProperty())
.addMethod(gobjectSetProperty())
.addMethod(gobjectBindProperty())
Expand Down Expand Up @@ -224,7 +225,7 @@ protected MethodSpec paramSpecGetTypeMethod() {
.build();
}

private MethodSpec gobjectConstructor() {
private MethodSpec gobjectFactory() {
return MethodSpec.methodBuilder("newInstance")
.addJavadoc("""
Creates a new GObject instance of the provided GType.
Expand All @@ -243,7 +244,7 @@ private MethodSpec gobjectConstructor() {
.build();
}

private MethodSpec gobjectConstructorVarargs() {
private MethodSpec gobjectFactoryVarargs() {
return MethodSpec.methodBuilder("newInstance")
.addJavadoc("""
Creates a new GObject instance of the provided GType and with the
Expand All @@ -266,7 +267,7 @@ private MethodSpec gobjectConstructorVarargs() {
.build();
}

private MethodSpec gobjectClassConstructor() {
private MethodSpec gobjectClassFactory() {
var paramType = ParameterizedTypeName.get(
ClassName.get(java.lang.Class.class), TypeVariableName.get("T"));

Expand All @@ -292,7 +293,7 @@ private MethodSpec gobjectClassConstructor() {
.build();
}

private MethodSpec gobjectClassConstructorVarargs() {
private MethodSpec gobjectClassFactoryVarargs() {
var paramType = ParameterizedTypeName.get(
ClassName.get(java.lang.Class.class), TypeVariableName.get("T"));

Expand Down Expand Up @@ -322,6 +323,35 @@ private MethodSpec gobjectClassConstructorVarargs() {
.build();
}

private MethodSpec gobjectClassConstructor() {
var paramType = ParameterizedTypeName.get(
ClassName.get(java.lang.Class.class), TypeVariableName.get("?"));

return MethodSpec.constructorBuilder()
.addJavadoc("""
Creates a new instance of a GObject-derived class with the provided
property values. For your own GObject-derived Java classes, a GType
must have been registered using {@code Types.register(Class<?>)}
@param objectClass the Java class of the new GObject
@param propertyNamesAndValues pairs of property names and values
(Strings and Objects). Does not need to be null-terminated.
@return the newly created GObject instance
@throws ClassCastException invalid property name
""")
.addModifiers(Modifier.PUBLIC)
.addParameter(paramType, "objectClass")
.addParameter(Object[].class, "propertyNamesAndValues")
.varargs(true)
.addStatement("this(constructNew($1T.getType(objectClass),$W" +
"(String) $2T.first(propertyNamesAndValues),$W" +
"$2T.rest(propertyNamesAndValues)))",
ClassNames.TYPE_CACHE, ClassNames.VARARGS_UTIL)
.addStatement("$T.put(handle(), this)",
ClassNames.INSTANCE_CACHE)
.build();
}

private MethodSpec gobjectGetProperty() {
return MethodSpec.methodBuilder("getProperty")
.addJavadoc("""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* Java-GI - Java language bindings for GObject-Introspection-based libraries
* Copyright (C) 2022-2025 Jan-Willem Harmannij
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/

package io.github.jwharm.javagi.interop;

import org.jetbrains.annotations.Nullable;

import java.util.Arrays;

/**
* Utility functions to split an array of variadic arguments into a first
* argument and a null-terminated array of remaining arguments.
*/
public class VarargsUtil {

/**
* Return the first array element.
*
* @param array input array, can be {@code null}
* @param <T> array element type
* @return the first element, or {@code null} if the input array is
* {@code null} or empty
*/
public static <T> @Nullable T first(@Nullable T @Nullable[] array) {
return array == null || array.length == 0 ? null : array[0];
}

/**
* Return all but the first array elements, terminated with a {@code null}.
* For example, {@code [1, 2, 3]} returns {@code [2, 3, null]}.
*
* @param array input array, can be {@code null}
* @param <T> array element type
* @return a new array of all elements except the first, terminated with a
* {@code null}, or {@code null} if the input array is {@code null}
* or empty
*/
@SuppressWarnings("unchecked") // cast is safe because the array is empty
public static <T> @Nullable T @Nullable[] rest(@Nullable T @Nullable[] array) {
return array == null ? null
: array.length == 0 ? (T[]) new Object[] {}
: Arrays.copyOfRange(array, 1, array.length + 1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,18 @@ public void writeAndReadBooleanProperty() {
assertEquals(input2, object.getProperty("bool-property"));
}

@Test
public void constructWithClassParameter() {
TestObject object = new TestObject();
assertEquals(object.readGClass().readGType(), TestObject.gtype);

String input1 = "abc";
boolean input2 = true;
TestObject object2 = new TestObject(input1, input2);
assertEquals(input1, object2.getProperty("string-property"));
assertEquals(input2, object2.getProperty("bool-property"));
}

/**
* Simple GObject-derived class used in the above tests
*/
Expand All @@ -122,6 +134,16 @@ public TestObject(MemorySegment address) {
super(address);
}

public TestObject() {
super(TestObject.class);
}

public TestObject(String stringValue, boolean booleanValue) {
super(TestObject.class,
"string-property", stringValue,
"bool-property", booleanValue);
}

@ClassInit
public static void construct(GObject.ObjectClass typeClass) {
classInitHasRun = true;
Expand Down

0 comments on commit 2f896ea

Please sign in to comment.