Why is "." replaced by "$" in classpath of java.lang.ClassNotFoundException? - java

I have an FXML Document where I am using a custom control ImageButton it is stored in the package net.aninnovation.baseUtility.view.ImageButton This contains 3 files
ImageButton.class, ImageButton.fxml and ImageButton.css. The .class file is the controller, the .fmxl is the FXML document and the .css file is the stylesheet.
Now I am creating an FXML document in the package net.aninnovation.csp.main. The name of the file is Default.fxml. This is basically a VBox as shown below:
<?xml version="1.0" encoding="UTF-8"?>
<?import ...........................?>//Other required Imports
<?import net.aninnovation.baseUtility.view.ImageButton.ImageButton?>
<VBox id="defaultPanel" fx:id="defaultPanel" styleClass="centerBox" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="net.aninnovation.CSP.main.DefaultController">
<children>
<FlowPane>
<children>
.
.
.
. //Other
. //Components
.
<ImageButton/>
.
. //Other
. //Components
.
</children>
</FlowPane>
</children>
<stylesheets>
<URL value="#Default.css" />
</stylesheets>
</VBox>
When I try to load this file in SceneBuilder it gives the following Exception:
Caused by: java.lang.ClassNotFoundException: net.aninnovation.baseUtility.view.ImageButton$ImageButton
at java.lang.ClassLoader.findClass(ClassLoader.java:530)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at javafx.fxml.FXMLLoader.loadTypeForPackage(FXMLLoader.java:2916)
at javafx.fxml.FXMLLoader.loadType(FXMLLoader.java:2905)
at javafx.fxml.FXMLLoader.importClass(FXMLLoader.java:2846)
... 20 more
Here I find that though I had imported net.aninnovation.baseUtility.view.ImageButton.ImageButton, the Exception states net.aninnovation.baseUtility.view.ImageButton$ImageButton. Now why is the . being replaced by $? Is there something wrong I have done?
The constructor of ImageButton class is loading the FXML document as here:
public ImageButton() {
FXMLLoader loader = new FXMLLoader(getClass().getResource("ImageButton.fxml"));
loader.setRoot(this);
loader.setController(this);
try {
loader.load();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
ImageButton.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.SVGPath?>
<fx:root stylesheets="#ImageButton.css" type="Button" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<graphic>
<VBox>
<children>
<SVGPath fx:id="svgImage" />
<Label fx:id="label" />
</children>
</VBox>
</graphic>
</fx:root>
These are located in the package net.aninnovation.baseUtility.view.ImageButton. As stated earlier there are 3 files in the location ImageButton.class, ImageButton.fxml and ImageButton.css
When I moved the ImageButton to package net.aninnovation.csp.main and net.aninnovation.csp.main.imageButton. I have got the error as:
Caused by: java.lang.ClassNotFoundException: net.aninnovation.csp$main$ImageButton
at java.lang.ClassLoader.findClass(ClassLoader.java:530)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at javafx.fxml.FXMLLoader.loadTypeForPackage(FXMLLoader.java:2916)
at javafx.fxml.FXMLLoader.loadType(FXMLLoader.java:2905)
at javafx.fxml.FXMLLoader.importClass(FXMLLoader.java:2846)
... 20 more
and
Caused by: java.lang.ClassNotFoundException: net.aninnovation.csp$main$imageButton$ImageButton
at java.lang.ClassLoader.findClass(ClassLoader.java:530)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at javafx.fxml.FXMLLoader.loadTypeForPackage(FXMLLoader.java:2916)
at javafx.fxml.FXMLLoader.loadType(FXMLLoader.java:2905)
at javafx.fxml.FXMLLoader.importClass(FXMLLoader.java:2846)
... 20 more

One of the reason's could be net.aninnovation.baseUtility.view.ImageButton.ImageButton is a NestedClass of net.aninnovation.baseUtility.view.ImageButton
For Example:
public class Number {
class NestedNumber {
}
}
When this code gets compiled, java generates 2 files.
Namely:
Number$NestedNumber.class
Number.class
But, when you access in code (for imports) you still use Number.NestedNumber instead of $.
However for Reflection you use $.
public static void main(String args[]) throws ClassNotFoundException {
Class<?> forName = Class.forName("Number$NestedNumber");
System.out.println(forName);
}

Related

What would be the right way to encrypt a JSON file on a system using Java [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 9 hours ago.
This post was edited and submitted for review 1 hour ago.
Improve this question
I understand clearly that this kind of question has been submitted on StackOverflow. But...
Everything that i could get my eyes on (including other websites) is either :
not fulfilling the requirements (that i have)
has comments revoking answers, even accepted ones
has what i call « fine prints » making it suddenly utterly complicated to implement
uses third party libraries that i can’t be sure if they are still maintained
I think it is time to update this kind of question and remove dust.
I’m looking for a simple and efficient method to encrypt a JSON file on a system.
The requirements :
The file is stored on the host system (Win : AppData / Linux : home)
It should be decrypted with a user password.
The target system is isolated from Internet.
If something goes wrong on one JSON entry the rest of the file should be recoverable
in case of a leaked file, the attacker should not be able to use « dictionary / brute force » tools that easily.
It shouldn't use tons of libraries (stay simple)
So as of 2023 what would be the right way to encrypt a JSON file on a system using Java ?
("Right way" as being confident on the security matters)
The final goal is to provide an answer that anyone with basic encryption understanding can find here and use. Anyone should be able to follow the steps to implement it without being the specialist. The solution doesn't have to be the "Ultimate One" as there is none in this area. It will be updated upon changes on those matters. The solution should provide a "suffisent level of confidence" (meaning people agree on that).
Here is a little JavaFx project you can modify and play around with it.
Remember the encrypted file is saved on the host system in the end. Not like this example.
Main class:
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
FXMLLoader flObj = new FXMLLoader(getClass().getResource("encrypt.fxml"));
Parent pflObj = flObj.load();
Scene sceneScrMain = new Scene(pflObj);
primaryStage.setScene(sceneScrMain);
primaryStage.setTitle("StackOverflow question : How to encrypt a JSON file with a password");
EncryptController ecObj = (EncryptController) flObj.getController();
ecObj.resetForm();
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Controller
package application;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
public class EncryptController {
#FXML
private TextArea textAreaOriginal;
#FXML
private TextArea textAreaDecrypted;
#FXML
private TextArea textAreaEncrypted;
#FXML
private Button buttonRun;
#FXML
private Button buttonReset;
/**
*
*/
public void resetForm() {
this.textAreaOriginal.setText("This is the string to encrypt ### #§%îµ%¨£*-+ ###");
this.textAreaEncrypted.setText("");
this.textAreaDecrypted.setText("");
}
#FXML
private void buttonResetAction(){
this.resetForm();
}
#FXML
private void buttonRunAction(){
// Your code here
this.textAreaEncrypted.setText("There should be something here.");
this.textAreaDecrypted.setText("There should be something here that is exactly like the original.");
}
}
FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefWidth="512.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.EncryptController">
<children>
<GridPane hgap="8.0" layoutX="-61.0" layoutY="-100.0" vgap="8.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="ALWAYS" minWidth="128.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="128.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints />
<RowConstraints vgrow="NEVER" />
<RowConstraints minHeight="10.0" prefHeight="30.0" />
<RowConstraints vgrow="ALWAYS" />
<RowConstraints vgrow="NEVER" />
</rowConstraints>
<children>
<Button fx:id="buttonRun" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#buttonRunAction" text="Run" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<TextArea fx:id="textAreaEncrypted" minWidth="64.0" prefHeight="128.0" GridPane.rowIndex="3" />
<TextArea fx:id="textAreaDecrypted" minWidth="64.0" prefHeight="128.0" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<Button fx:id="buttonReset" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#buttonResetAction" text="Reset" GridPane.columnIndex="1" GridPane.rowIndex="4" />
<Label text="Encrypted" GridPane.rowIndex="2" />
<Label text="Decrypted" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<TextArea fx:id="textAreaOriginal" maxHeight="64.0" minWidth="64.0" GridPane.columnSpan="2147483647" />
</children>
<padding>
<Insets bottom="8.0" left="8.0" right="8.0" top="8.0" />
</padding>
</GridPane>
</children>
</AnchorPane>
A JSON file is just a regular text file in the end, and regular text is encrypted rather easily. You could just use AES with a custom key to encrypt the file, save it to disk, and decrypt it once needed.
// encrypt:
String fileContents = """
{
"some": "example",
"json": [
"file"
]
}
""";
// hash key with sha256 to make sure key is always the same valid length for aes
byte[] key = MessageDigest.getInstance("SHA-256").digest("your key here".getBytes(StandardCharsets.UTF_8));
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"));
byte[] bytes = cipher.doFinal(fileContents.getBytes(StandardCharsets.UTF_8));
// write bytes out to file
// decrypt:
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"));
byte[] decryptedBytes = cipher.doFinal(bytes);
String originalJson = new String(decryptedBytes);

fx:include with "resources" throws MissingResourceException but works in Java Code

I'm trying to use the resource tag inside fx:include in a FXML file.
What I don't understand is, that loading the resource bundle manually with ResourceBundle.getBundle() works completely fine.
I already tried a lot of variants in the fxml like:
"lang_challenges"
"wand555/github/io/challengesreworkedgui/lang_challenges"
package wand555.github.io.challengesreworkedgui;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.Set;
public class ChallengeApplication extends Application {
#Override
public void start(Stage stage) throws IOException {
ResourceBundle.clearCache();
ResourceBundle bundle = new ResourceBundleWrapper(ResourceBundle.getBundle("wand555/github/io/challengesreworkedgui/lang_challenges"));
System.out.println(bundle.getString("challenge.name")); // outputs "abc"
FXMLLoader loader = new FXMLLoader(ChallengeApplication.class.getResource("overview.fxml"), bundle);
Parent root = loader.load();
Scene scene = new Scene(root, 1000, 1000);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
// this class effectively does nothing, but it will be loaded by the
// application class loader
// instead of the system class loader.
private static class ResourceBundleWrapper extends ResourceBundle {
private final ResourceBundle bundle;
ResourceBundleWrapper(ResourceBundle bundle) {
this.bundle = bundle;
}
#Override
protected Object handleGetObject(String key) {
return bundle.getObject(key);
}
#Override
public Enumeration<String> getKeys() {
return bundle.getKeys();
}
#Override
public boolean containsKey(String key) {
return bundle.containsKey(key);
}
#Override
public Locale getLocale() {
return bundle.getLocale();
}
#Override
public Set<String> keySet() {
return bundle.keySet();
}
}
}
overview.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<AnchorPane prefHeight="500.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="wand555.github.io.challengesreworkedgui.controllers.OverviewController">
<children>
<VBox>
<children>
<StackPane alignment="CENTER_LEFT">
<children>
<Button fx:id="exportButton" mnemonicParsing="false" onAction="#onExport" text="Export" />
</children>
</StackPane>
<HBox prefHeight="100.0" prefWidth="200.0" spacing="25.0">
<children>
<fx:include fx:id="challengesOverview" source="challenges/challenges_overview.fxml" resources="lang_challenges" charset="utf-8"/>
<Separator orientation="VERTICAL" prefHeight="200.0" />
<fx:include source="goals/goal_overview.fxml" />
<Separator orientation="VERTICAL" prefHeight="200.0" />
</children>
</HBox>
</children>
</VBox>
</children>
</AnchorPane>
The lang_challenges.properties contains a single key-value pair
challenge.name=abc
The `lang_challenges_de.properties' has the same content
challenge.name=abc
And this is the error message I'm getting
Exception in Application start method
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at javafx.graphics#19/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:465)
at javafx.graphics#19/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:364)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1082)
Caused by: java.lang.RuntimeException: Exception in Application start method
at javafx.graphics#19/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:901)
at javafx.graphics#19/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:196)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: javafx.fxml.LoadException:
/Users/felixnaumann/Documents/ChallengesReworked/ChallengesReworkedGUI/target/classes/wand555/github/io/challengesreworkedgui/overview.fxml:21
at javafx.fxml#19/javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2707)
at javafx.fxml#19/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2685)
at javafx.fxml#19/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2548)
at javafx.fxml#19/javafx.fxml.FXMLLoader.load(FXMLLoader.java:2516)
at challenges.reworked.gui/wand555.github.io.challengesreworkedgui.ChallengeApplication.start(ChallengeApplication.java:22)
at javafx.graphics#19/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:847)
at javafx.graphics#19/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:484)
at javafx.graphics#19/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:457)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at javafx.graphics#19/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:456)
at javafx.graphics#19/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
Caused by: java.util.MissingResourceException: Can't find bundle for base name lang_challenges, locale de_DE
at java.base/java.util.ResourceBundle.throwMissingResourceException(ResourceBundle.java:2045)
at java.base/java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1683)
at java.base/java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1575)
at java.base/java.util.ResourceBundle.getBundle(ResourceBundle.java:1280)
at javafx.fxml#19/javafx.fxml.FXMLLoader$IncludeElement.processAttribute(FXMLLoader.java:1100)
at javafx.fxml#19/javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:230)
at javafx.fxml#19/javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:755)
at javafx.fxml#19/javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2808)
at javafx.fxml#19/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2634)
... 9 more
Exception running application wand555.github.io.challengesreworkedgui.ChallengeApplication
Whew, finally figured it out after digging deep into the source code of ResourceBundle and ClassLoader.
How to fix it:
Really easy way:
Put your .properties files at the root of the resources folder. Then use it inside fxml
<fx:include source="second.fxml" resources="second_bundle"/>
and then everything should work fine.
However this approach is not suitable for larger projects which subdivide the resource bundles into different packages.
Using sub-packages in resources package
Give the fully qualified path name in the resources tag.
So for example if your package structure from src is com/example/demo/ (also in the resources folder) then use
<fx:include source="second.fxml" resources="com/example/demo/second_bundle"/>
But we are not done yet. You need to open the package to all modules in the module-info.java, explicitly using
opens com.example.demo to javafx.fxml;
does not work.
Instead you need to write
opens com.example.demo;
My two cents
Honestly to me this sounds like a bug.
I debugged the entire loading process and when the second_bundle is loaded from inside the fxml file, the caller module is javafx.fxml. And as the java doc for getResourceAsStream (which is internally called when loading) states:
A package name is derived from the resource name. If the package name is a package in the module then the resource can only be located by the caller of this method when the package is open to at least the caller's module. If the resource is not in a package in the module then the resource is not encapsulated.
So in theory opening your package to just javafx.fxml should work, but it doesn't...
Full demo project
This demo project uses sub-packages.
Project structure:
HelloApplication:
public class HelloApplication extends Application {
#Override
public void start(Stage stage) throws IOException {
ResourceBundle bundle = new ResourceBundleWrapper(ResourceBundle.getBundle("test_bundle"));
System.out.println(bundle.getString("first.button")); // outputs "English/Deutsch"
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("first.fxml"), bundle);
Scene scene = new Scene(fxmlLoader.load(), 320, 240);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
// this class effectively does nothing, but it will be loaded by the
// application class loader
// instead of the system class loader.
private static class ResourceBundleWrapper extends ResourceBundle {
private final ResourceBundle bundle;
ResourceBundleWrapper(ResourceBundle bundle) {
this.bundle = bundle;
}
#Override
protected Object handleGetObject(String key) {
return bundle.getObject(key);
}
#Override
public Enumeration<String> getKeys() {
return bundle.getKeys();
}
#Override
public boolean containsKey(String key) {
return bundle.containsKey(key);
}
#Override
public Locale getLocale() {
return bundle.getLocale();
}
#Override
public Set<String> keySet() {
return bundle.keySet();
}
}
}
first.fxml:
<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml"
fx:controller="com.example.demo.HelloController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
</padding>
<Label fx:id="welcomeText"/>
<Button text="%first.button" onAction="#onHelloButtonClick"/>
<fx:include source="second.fxml" resources="com/example/demo/second_bundle"/>
</VBox>
second.fxml:
<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
</padding>
<Label fx:id="welcomeText"/>
<Button text="%second.button"/>
</VBox>
test_bundle.properties:
first.button=English
second_bundle.properties:
second.button=Hello
module-info.java:
module com.example.demo {
requires javafx.controls;
requires javafx.fxml;
opens com.example.demo;
exports com.example.demo;
}

JavaFX 8: Custom controller from an inner class

The Problem
I am getting a ClassNotFoundException when trying to use the custom controller inside Joystick.java.
Here is my file hierarchy:
--view
----MainWindow.java
----mainWindow.fxml
----Joystick
------Joystick.java
------joystick.fxml
In mainWindow.fxml, I have the lines:
<?import view.Joystick.Joystick?>
...
<Joystick fx:id="manualJsk" layoutX="459.0" layoutY="195.0" />
I want to display my custom controller, but I keep getting the ClassNotFoundException, despite importing it.
Interestingly, everything works if I move the file to the view package:
--view
----MainWindow.java
----mainWindow.fxml
----Joystick.java
----Joystick
------joystick.fxml
... which makes me think the import is bad. Why could that be?
The Exception
java.lang.ClassNotFoundException: view.Joystick$Joystick
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:602)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
at javafx.fxml/javafx.fxml.FXMLLoader.loadTypeForPackage(FXMLLoader.java:2931)
at javafx.fxml/javafx.fxml.FXMLLoader.loadType(FXMLLoader.java:2920)
at javafx.fxml/javafx.fxml.FXMLLoader.importClass(FXMLLoader.java:2861)
... 15 more
Edit:
Joystick Code:
public class Joystick extends Pane {
public Joystick() {
super();
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("joystick.fxml"));
loader.setRoot(this);
try {
loader.load();
} catch (IOException e) {
e.printStackTrace();
}
}
}
joystick.fxml
<fx:root xmlns:fx="http://javafx.com/fxml" type="javafx.scene.layout.Pane">
<children>
<Button text="Click Me"/>
</children>
</fx:root>

Explain why FXML objects were null

I came across a problem in my code that all the objects that were related to the FXML file for a controller class were null even though the styling from the FXML was working and all the fx:id tags were the same. Here is the FXML code:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.geometry.Insets?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
alignment="center"
spacing="10"
prefHeight="750"
prefWidth="1000"
style="-fx-background-color: lightslategray">
<padding><Insets top="0" bottom="10" left="10" right="10"></Insets></padding> <!-- Set the padding at 10px for each side of the window -->
<Label fx:id="titleLabel" style="-fx-font-weight: bold; -fx-font-size: 32;" wrapText="true" text="Deck Title"/>
<HBox spacing="10">
<Button fx:id="backButton" text="Back" prefWidth="50"/>
<ProgressBar fx:id="progressIndicator" GridPane.columnIndex="0" GridPane.rowIndex="0" prefWidth="920" progress="0.0"/>
</HBox>
<HBox spacing="20">
<Label fx:id="qLabel" style="-fx-background-color: white; -fx-border-color: black" prefWidth="480" prefHeight="400" wrapText="true"/>
<Label fx:id="aLabel" style="-fx-background-color: white; -fx-border-color: black; -fx-cursor: hand" prefWidth="480" prefHeight="400" wrapText="true" text="Click here to reveal the answer" onMouseClicked="#updateAnswer"/>
</HBox>
<HBox spacing="780">
<Button fx:id="incorrectButton" text="Incorrect" prefWidth="100"/>
<Button fx:id="correctButton" text="Correct" prefWidth="100"/>
</HBox>
</VBox>
Here is the code for the controller class:
public class openCardsController {
#FXML Button backButton;
#FXML ProgressBar progressIndicator;
#FXML Label qLabel;
#FXML Label aLabel;
#FXML Label titleLabel;
#FXML Button incorrectButton;
#FXML Button correctButton;
public void openCards() throws IOException, ParseException {
Stage window = Main.getStage();
window.setWidth(1000);
window.setHeight(750);
// Had to swap Parent root = FXMLLoader.load(getClass().getResource("./mainPage.fxml")); for the following lines
File file = new File(System.getProperty("user.dir") + "/src/flashcardApplication/openCardsPage.fxml");
FXMLLoader loader = new FXMLLoader(file.toURI().toURL());
loader.setController(this);
VBox root = loader.load();
backButton.setOnAction(e -> {
try {
backButtonPressed();
} catch (IOException ignored) {}
});
incorrectButton.setOnAction(e -> incorrect());
correctButton.setOnAction(e -> correct());
window.setTitle("Flashcard Application - Open Cards");
Scene mainMenuScene = new Scene(root, 1000, 750);
window.setScene(mainMenuScene);
int deckid = chooseCards();
String fileURL = "ftp://appuser:pass123.#127.0.0.1/decks/" + Integer.toString(deckid) + ".json";
URL url = new URL(fileURL); // Lines 43 to 45 come from https://www.javaworld.com/article/2073325/java-ftp-client-libraries-reviewed.html
URLConnection urlc = url.openConnection();
InputStream inputStream = urlc.getInputStream();
JSONParser jsonParser = new JSONParser();
JSONObject jsonObject = (JSONObject)jsonParser.parse(new InputStreamReader(inputStream, "UTF-8"));
String title = (String) jsonObject.get("name");
titleLabel.setText(title);
}
I had to remove the attribute fx:controller="flashcardApplication.openCardsController from the FXML file and I had to replace the line Parent root = FXMLLoader.load(getClass().getResource("./mainPage.fxml")); with the following lines:
File file = new File(System.getProperty("user.dir") + "/src/flashcardApplication/openCardsPage.fxml");
FXMLLoader loader = new FXMLLoader(file.toURI().toURL());
loader.setController(this);
Please could someone explain why I had to use the different solution because I used the one line solution in two other FXML files and their corresponding controller classes without any issues
The #FXML-annotated fields are initialized in the controller when the FXML is loaded. By default, the FXMLLoader creates an instance of the class specified by the fx:controller attribute, and uses it as the controller.
Consequently, in your original code, the controller is not the current instance of OpenCardsController on which openCards() is being invoked, but is the new instance of the same class. Thus the #FXML-annotated fields are not initialized in the current instance, but in the new instance that is created by the FXMLLoader.
By removing the fx:controller attribute, and explicitly setting the controller to the exact object you need (the current instance of OpenCardsController), you achieve what you need: the controller is now the current instance and the FXML-annotated fields are initialized in that object.
Note that it's a bit unusual to load the FXML file from the controller itself. The typical approach is to load the FXML from some other code, and display the resulting UI; the controller is usually a separate object. It may be more natural (and easier to maintain in the long run) if you refactor your code so that the FXML is loaded from somewhere else. (This is really a separation of concerns and single-responsibility issue: the controller should only be responsible for processing user input from the corresponding FXML file; it should not be responsible for loading the FXML as well.)

NullPointer Exception thrown with JavaFX

My application is throwing a NullPointer exception when I call the variable empName.
ResultSet result = stmnt.executeQuery("select FirstName, LastName from emp_info where EmployeeID "
+ "= '" + empID + "'");
// while(result.next()){
// empName.setText("result.getString(1));
// }
empName.setText("asdf");
rootLayout.setCenter(controlData);
connection.close();
}
}
It should be able to run fine since I initialize the Text variable like so:
#FXML
public Text empName;
Also when I use scenebuilder, it sometimes shows the fx:id empName but when I exit out of it and reopen it doesn't show it. I think that's where the problem is. My xml file is:
<AnchorPane prefHeight="300.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="studentempsignin.MainPage_Controller">
<children>
<BorderPane prefHeight="300.0" prefWidth="500.0">
<center>
<Text fx:id="empName" strokeType="OUTSIDE" strokeWidth="0.0" text="Text" BorderPane.alignment="CENTER" />
</center>
</BorderPane>
</children>
</AnchorPane>
The exception I get is:
SEVERE: null
java.lang.NullPointerException
at studentempsignin.MainPage_Controller.signingIn(MainPage_Controller.java:190)
at studentempsignin.SignIn_Controller.lambda$0(SignIn_Controller.java:66)
at studentempsignin.SignIn_Controller$$Lambda$143/575703892.handle(Unknown Source)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
Edit:
The method empIDAccess is being called when I click the signIn button:
signInBtn.setOnAction((e) -> {
try {
//used dbaseDriver...idk why
// DBaseDriver connect = new DBaseDriver("jdbc:mysql://localhost:3306/AUStudentEmployees",
// "kheneahm", "kennygoham");
String empID = empIDField.getText();
MainPage_Controller empIDAccess = new MainPage_Controller();
empIDAccess.signingIn(empID, invalidID, signInBtn);
} catch (Exception ex) {
Logger.getLogger(SignIn_Controller.class.getName()).log(Level.SEVERE, null, ex);
}
});
You're creating a new controller instance. The FXMLLoader creates a controller instance and initializes the #FXML-annotated fields in that instance, when you load the FXML file. Those fields won't be initialized in the new instance you create (and even if they were, they wouldn't refer to the same objects displayed in the UI).
You need to get a reference to the controller that is created when you load the FXML.

Categories

Resources