Skip to content
Open
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
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2024, Gluon and/or its affiliates.
* Copyright (c) 2016, 2026, Gluon and/or its affiliates.
* Copyright (c) 2012, 2014, Oracle and/or its affiliates.
* All rights reserved. Use is subject to license terms.
*
Expand Down Expand Up @@ -34,6 +34,7 @@

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -217,10 +218,11 @@ public static boolean isCaspian(Theme theme) {
* @throws FileBrowserRevealException in case the application opened indicates an error (unexpected return code).
*/
public static void open(String path) throws IOException, FileBrowserRevealException {
Path file = Path.of(path).normalize().toAbsolutePath();
List<String> args = new ArrayList<>();
if (EditorPlatform.IS_MAC) {
args.add("open"); //NOI18N
args.add(path);
args.add(file.toString());
} else if (EditorPlatform.IS_WINDOWS) {
args.add("cmd"); //NOI18N
args.add("/c"); //NOI18N
Expand All @@ -230,16 +232,16 @@ public static void open(String path) throws IOException, FileBrowserRevealExcept
args.add("\"html\""); //NOI18N
}

args.add(path);
args.add(file.toString());
} else if (EditorPlatform.IS_LINUX) {
// xdg-open does fine on Ubuntu, which is a Debian.
// I've no idea how it does with other Linux flavors.
args.add("xdg-open"); //NOI18N
args.add(path);
args.add(file.toString());
}

if (!args.isEmpty()) {
executeDaemon(args, null, 0);
executeDaemon(args, file.getParent().toFile(), 0);
}
}

Expand Down Expand Up @@ -330,6 +332,7 @@ private static void executeDaemon(List<String> cmd, File wDir, int exitCodeOk)
throws IOException, FileBrowserRevealException {
var cmdLine = String.join(" ", cmd);
long timeoutSec = 5;
LOGGER.log(Level.FINE, "Attempting to run: {0} in {1}",new Object[] {cmdLine, wDir});
try {
int exitValue = new Cmd().exec(cmd, wDir, timeoutSec);
if (exitCodeOk != exitValue) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2024, Gluon and/or its affiliates.
* Copyright (c) 2021, 2026, Gluon and/or its affiliates.
* All rights reserved. Use is subject to license terms.
*
* This file is available and licensed under the following license:
Expand Down Expand Up @@ -31,21 +31,27 @@
*/
package com.oracle.javafx.scenebuilder.kit.skeleton;

import com.oracle.javafx.scenebuilder.kit.fxom.FXOMIntrinsic;
import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject;
import com.oracle.javafx.scenebuilder.kit.fxom.FXOMPropertyT;
import com.oracle.javafx.scenebuilder.kit.util.TypeLookup;
import com.oracle.javafx.scenebuilder.kit.util.eventnames.EventNames;
import com.oracle.javafx.scenebuilder.kit.util.eventnames.FindEventNamesUtil;
import com.oracle.javafx.scenebuilder.kit.util.eventnames.ImportBuilder;

import javafx.fxml.FXML;

import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

public class SkeletonContext {

Expand Down Expand Up @@ -135,14 +141,49 @@ Builder withSettings(SkeletonSettings settings) {

public void addFxId(FXOMObject value) {
String fxId = value.getFxId();
Class<?> type = value.getSceneGraphObject().getClass();

getSceneGraphType(value).ifPresent(type -> addToSkeleton(type, fxId));
}

private void addToSkeleton(Class<?> type, String fxId) {
addImportsFor(FXML.class, type);

variables.put(fxId, type);
assertions.add(fxId);
}

private Optional<Class<?>> getSceneGraphType(FXOMObject value) {
Object node = getSceneGraphObject(value);
if (null == node) {
String message = "Failed to obtain type for: <%s fx:id=\"%s\" />";
Logger.getLogger(getClass().getName())
.log(Level.WARNING, message.formatted(value.getGlueElement().getTagName(), value.getFxId()));
return Optional.empty();
}

return TypeLookup.findFXTypes(node);
}

/**
* For fx:include elements, the FXOMIntrinsic objects deliver null values when
* the method getSceneGraphObject() is called, which is inherited from
* FXOMObject. But when calling getSourceSceneGraphObject() the root node of the
* included FXML is exposed and one can obtain its type. Hence this function
* controls, depending on the {@link FXOMObject} implementation, which object
* must be used as the Scene Graph representative.
*
* @param value {@link FXOMObject} any instance
* @return Any SceneGraph element provided by the {@link FXOMObject}. This can
* also be a null value.
*
*/
private Object getSceneGraphObject(FXOMObject value) {
if (value instanceof FXOMIntrinsic intrinsic
&& FXOMIntrinsic.Type.FX_INCLUDE.equals(intrinsic.getType())) {
return intrinsic.getSourceSceneGraphObject();
} else {
return value.getSceneGraphObject();
}
}

public void addEventHandler(FXOMPropertyT eventHandler) {
String eventName = FindEventNamesUtil.findEventName(eventHandler.getName().getName());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright (c) 2026, Gluon and/or its affiliates.
* All rights reserved. Use is subject to license terms.
*
* This file is available and licensed under the following license:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the distribution.
* - Neither the name of Oracle Corporation and Gluon nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.oracle.javafx.scenebuilder.kit.util;

import java.lang.reflect.Modifier;
import java.util.Optional;

/**
* Utility class to provide lookup of Types within a given package name space.
*/
public class TypeLookup {

private final String packageNamePrefix;

TypeLookup(String packageNamePrefix) {
this.packageNamePrefix = packageNamePrefix;
}

/**
* Attempts to identify the first public interface in any of the JavaFX
* packages. If no public interface is found, the first public super class in
* JavaFX packages will be identified.
*
* If the given class itself is already member of any JavaFX package, this class
* is provided in the result. Exceptions here are Lambdas, which are ignore. For
* anonymous and inner classes the next public interface or the next public
* super class in JavaFX package space are looked up.
*
* Lambdas are ignored and will yield empty result values.
*
* @param source Any object where a related JavaFX type is expected in type
* hierarchy.
* @return Optional holding the JavaFX related class (if present).
*/
public static Optional<Class<?>> findFXTypes(Object source) {
if (null == source) {
return Optional.empty();
}
return new TypeLookup("javafx.").findFirstPublicInterfaceOrSuperclass(source);
}

Optional<Class<?>> findFirstPublicInterfaceOrSuperclass(Object obj) {
if (obj == null || packageNamePrefix == null) {
return Optional.empty();
}

Class<?> clazz = obj.getClass();

/* Lambdas are not usable in FXML documents, hence will be ignored */
if (clazz.getName().contains("$$")) {
return Optional.empty();
}

/*
* For top level classes, no further investigation is needed as long these
* classes are already members of the package space.
*/
if (clazz.getPackageName().startsWith(packageNamePrefix)
&& !clazz.getName().contains("$")
&& Modifier.isPublic(clazz.getModifiers())) {
return Optional.of(clazz);
}

Class<?> intrface = findFirstPublicInterface(clazz);
if (intrface != null) {
return Optional.of(intrface);
}

return findFirstPublicSuperclass(clazz);
}

private Class<?> findFirstPublicInterface(Class<?> clazz) {
if (clazz == null || clazz == Object.class) {
return null;
}

for (Class<?> intrface : clazz.getInterfaces()) {
if (Modifier.isPublic(intrface.getModifiers()) && intrface.getPackageName().startsWith(packageNamePrefix)) {
return intrface;
}

Class<?> found = findFirstPublicInterface(intrface);
if (found != null) {
return found;
}
}

return findFirstPublicInterface(clazz.getSuperclass());
}

private Optional<Class<?>> findFirstPublicSuperclass(Class<?> clazz) {
Class<?> current = clazz.getSuperclass();
while (current != null && current != Object.class) {
if (Modifier.isPublic(current.getModifiers()) && current.getPackageName().startsWith(packageNamePrefix)) {
return Optional.of(current);
}
current = current.getSuperclass();
}
return Optional.of(clazz);
}
}
2 changes: 1 addition & 1 deletion kit/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
opens com.oracle.javafx.scenebuilder.kit.selectionbar to javafx.fxml;
opens com.oracle.javafx.scenebuilder.kit.skeleton to javafx.fxml;
opens com.oracle.javafx.scenebuilder.kit.template;
opens com.oracle.javafx.scenebuilder.kit.util to javafx.fxml;
opens com.oracle.javafx.scenebuilder.kit.util;
opens com.oracle.javafx.scenebuilder.kit.util.control.effectpicker to javafx.fxml;
opens com.oracle.javafx.scenebuilder.kit.util.control.effectpicker.editors to javafx.fxml;
opens com.oracle.javafx.scenebuilder.kit.util.control.paintpicker to javafx.fxml;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2022, Gluon and/or its affiliates.
* Copyright (c) 2021, 2026, Gluon and/or its affiliates.
* All rights reserved. Use is subject to license terms.
*
* This file is available and licensed under the following license:
Expand Down Expand Up @@ -39,12 +39,14 @@
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.util.List;

public class SkeletonBufferJavaTest {

Expand Down Expand Up @@ -104,6 +106,20 @@ public void skeletonToString_testFxml_fullFormat() throws IOException {
// then
assertEqualsFileContent("skeleton_java_full.txt", skeleton);
}

@Test
public void that_fxinclude_elements_with_fxid_are_properly_referenced() throws IOException {
// given
SkeletonBuffer skeletonBuffer = load("fxinclude_with_fxid_outer.fxml");
skeletonBuffer.setFormat(SkeletonSettings.FORMAT_TYPE.FULL);

// when
List<String> skeleton = skeletonBuffer.toString().lines().toList();

assertTrue(skeleton.size() > 20);
assertEquals("@FXML", skeleton.get(19).trim());
Comment thread
Oliver-Loeffler marked this conversation as resolved.
assertEquals("private Pane includedPane;", skeleton.get(20).trim());
}

private void assertEqualsFileContent(String fileName, String actual) {
URL url = this.getClass().getResource(fileName);
Expand Down
Loading
Loading