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

Add GObject constructor that takes a Java class as first argument #185

Merged
merged 1 commit into from
Jan 11, 2025
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
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