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

Custom components disappear from Custom list after importing a new jar #70

Open
gluon-bot opened this issue Apr 13, 2016 · 5 comments
Open
Labels
bug Something isn't working custom control Custom controls usage in Scene Builder major

Comments

@gluon-bot
Copy link

Originally reported by: Jose Pereda (Bitbucket: JPereda, GitHub: JPereda)


When Scene Builder starts, a task is launched to load all the custom components from the user library folder. For this, a classloader is created based on the jars found in that folder. The jars are scanned and their clases are instantiated once to find out about valid components.

In case the user imports a new jar, the task is stopped, the jar is added to the folder, and the task is started again.

While the classloader is created again, there are some cases where some classes may have not been garbage collected, and the second time they are instantiated, the instance fails with and exception, and those components, that were before on the Custom list, now won't be included.

It can be reproduced for instance with NotificationPane from ControlsFX.

How to reproduce:

  1. Import ControlsFX 8.40.10 or 8.40.11-SNAPSHOT in case it is not imported yet.

  2. Accept all the components, or at least NotificationPane.

  3. Close Scene Builder, and open it again. Notice that NotificationPane is available under the Custom tab.

  4. Now import any other jar. After selecting its valid components, the Custom list is refreshed. Notice that NotificationPane won't be at the list. (Closing and opening SB will reveal it again)

The reason for this issue can be found in the exception thrown by the attempt of creating the new instance in `JarExplorer.instantiateWithFXMLLoader():

#!java
java.lang.NoClassDefFoundError: Could not initialize class org.controlsfx.control.NotificationPane
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at java.lang.Class.newInstance(Class.java:442)
	at sun.reflect.misc.ReflectUtil.newInstance(ReflectUtil.java:51)
	at javafx.fxml.FXMLLoader$InstanceDeclarationElement.constructValue(FXMLLoader.java:1009)
	at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:746)
	at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
	at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
	at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2425)
	at com.oracle.javafx.scenebuilder.kit.library.util.JarExplorer.instantiateWithFXMLLoader(JarExplorer.java:109)
	at com.oracle.javafx.scenebuilder.kit.library.util.JarExplorer.exploreEntry(JarExplorer.java:159)
	at com.oracle.javafx.scenebuilder.kit.library.util.JarExplorer.explore(JarExplorer.java:67)
	at com.oracle.javafx.scenebuilder.kit.library.user.LibraryFolderWatcher.exploreAndUpdateLibrary(LibraryFolderWatcher.java:319)
	at com.oracle.javafx.scenebuilder.kit.library.user.LibraryFolderWatcher.runDiscovery(LibraryFolderWatcher.java:135)
	at com.oracle.javafx.scenebuilder.kit.library.user.LibraryFolderWatcher.run(LibraryFolderWatcher.java:94)
	at java.lang.Thread.run(Thread.java:745)

A short test can be made to try to create the instance of NotificationPane in the same way.

#!java

public static void test() {
        try {
            File file = new File("controlsfx-8.40.11-SNAPSHOT.jar");
            ClassLoader classLoader = new URLClassLoader(new URL[]{file.toURI().toURL()});
            Class<?> entryClass = classLoader.loadClass("org.controlsfx.control.NotificationPane");
            entryClass.getConstructors()[0].newInstance();
        } catch (MalformedURLException | ClassNotFoundException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            e.printStackTrace();
        } 
    }

public static void main(String[] args) {
        Test.test();
        Test.test();
    }

Running it twice will throw the same exception as above, including also this:

#!java

Caused by: java.lang.IllegalArgumentException: EventType "NOTIFICATION_PANE_ON_SHOWING"with parent "EVENT" already exists
	at javafx.event.EventType.register(EventType.java:186)
	at javafx.event.EventType.<init>(EventType.java:128)
	at org.controlsfx.control.NotificationPane.<clinit>(NotificationPane.java:158)
	... 12 more

Having a look at the NotificationPane source code, an static EventType object is created when the class is instantiated. This is registered in a WeakHashMap (see EventType.register()), and when calling UserLibrary.stopWatching() and setting the url class loader to null it doesn't get garbage collected.

That's why the next time the component is instantiated (by running UserLibrary.startWatching() a second time), this exception will be thrown because the event type is already registered:

#!java
EventType.register() {
    ...
    throw new IllegalArgumentException("EventType \"" + subType + "\""
                        + "with parent \"" + subType.getSuperType()+"\" already exists");

}

While setting the classloader to null and adding a call to System.gc() works in the test, it is not the case in Scene Builder.


@gluon-bot
Copy link
Author

Original comment by Adam Paul (Bitbucket: adampaul112591, GitHub: adampaul112591):


Jose I've discovered a work around for this using Scene Builder 8.1.1 on Windows. To replicate...

  1. Import charm-2.1.1.jar

  2. Close application

  3. Add the charm-2.1.1.jar to app.classpath in SceneBuilder.cfg, along with the controlsfx-8.40.10.jar

  4. Start Scene Builder and import controlsfx-8.40.10.jar using the same file specified on the classpath

@gluon-bot
Copy link
Author

Original comment by Jose Pereda (Bitbucket: JPereda, GitHub: JPereda):


Thanks, Adam, but if the solution involves closing the application, that already solves the issue, without any additional modification.

@gluon-bot
Copy link
Author

Original comment by Adam Paul (Bitbucket: adampaul112591, GitHub: adampaul112591):


Well if you consider it a full solution, then this can be closed. If you struggle to replicate it I can upload my install with the config to bitbucket.

@gluon-bot
Copy link
Author

Original comment by Jose Pereda (Bitbucket: JPereda, GitHub: JPereda):


I haven't tested your workaround but, as I said, if it implies closing the application, I don't think it is a solution, unless we consider the need to restart SceneBuilder every time we add a jar, like when installing a plugin on the IDE...

@gluon-bot gluon-bot added major bug Something isn't working labels May 9, 2018
@brunnermeier
Copy link

I run into a related issue when trying to add the jar file of our yFiles for JavaFX product (https://www.yworks.com/products/yfiles-for-javafx). The jar contains several custom controls that reference a custom fx event but the static initializer of this event is called multiple times when the jar is parsed leading to an IllegalArgumentException as described above.

I attached a minimal example of this setup that breaks in the same way as our library jar:

scenebuildertest.zip

The workaround with adding the jar to Scene Builders classpath does work but isn't intuitive for our customers which may want to use SceneBuilder to create their yFiles-based application.

@AlmasB AlmasB added the custom control Custom controls usage in Scene Builder label Jan 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working custom control Custom controls usage in Scene Builder major
Projects
None yet
Development

No branches or pull requests

3 participants