Reputation: 113
I try to tidy up the file for my app by creating seperate packages according to the functionality and encounter this error:
java.lang.IllegalAccessException: class javafx.fxml.FXMLLoader$ValueElement (in module javafx.fxml) cannot access class application.login.Login (in module eSentral_Desktop_App) because module eSentral_Desktop_App does not export application.login to module javafx.fxml
My question is how to fix this and what cause it to happen?
Here are some extra Information:
This is my class hierarchy:
this is my Main.java code:
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import jfxtras.styles.jmetro.JMetro;
import jfxtras.styles.jmetro.Style;
import javafx.scene.Parent;
import javafx.scene.Scene;
import java.io.IOException;
import application.login.Users;
import application.listing.Homepage;
public class Main extends Application{
public static Object currentUser = new Object();
double x,y = 0;
private static Stage stg;
@Override
public void start(Stage primaryStage) throws Exception{
try {
stg = primaryStage;
Parent root = FXMLLoader.load(getClass().getResource("login/LoginUI.fxml"));
Scene scene = new Scene(root);
JMetro jMetro = new JMetro(scene, Style.LIGHT);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public void changeScene(String fxml) throws IOException {
Parent pane = FXMLLoader.load(getClass().getResource(fxml));
stg.getScene().setRoot(pane);
}
public static void main(String[] args) {
launch(args);
}
}
This is my LoginUI.fxml code:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.text.Font?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="700.0" prefWidth="800.1" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.login.Login">
<top>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="115.0" prefWidth="800.0" BorderPane.alignment="CENTER">
<children>
<ImageView fitHeight="80.0" fitWidth="249.0" layoutX="276.0" layoutY="18.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../resources/ic_eSentral_Logo.png" />
</image>
</ImageView>
</children>
</AnchorPane>
</top>
<bottom>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="133.0" prefWidth="800.0" BorderPane.alignment="CENTER">
<children>
<Button fx:id="btnRegister" layoutX="248.0" layoutY="63.0" mnemonicParsing="false" onMouseEntered="#MouseEnter" onMouseExited="#MouseExit" prefHeight="44.0" prefWidth="318.0" style="-fx-background-color: #169fe3;" text="Register a new account" textFill="WHITE">
<font>
<Font name="System Bold" size="20.0" />
</font>
</Button>
<Label alignment="CENTER" layoutX="311.0" layoutY="24.0" text="Don't have an account yet?" textFill="#1286bc">
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
</children>
</AnchorPane>
</bottom>
<center>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="487.0" prefWidth="800.0" BorderPane.alignment="CENTER">
<children>
<Label fx:id="lblSuccess" alignment="CENTER" layoutX="247.0" layoutY="2.0" prefHeight="24.0" prefWidth="318.0" textFill="#00ff26">
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<TextField fx:id="txtUsername" layoutX="247.0" layoutY="77.0" prefHeight="44.0" prefWidth="318.0" promptText="Email">
<font>
<Font size="15.0" />
</font>
</TextField>
<Label fx:id="lblEmail" alignment="CENTER" layoutX="247.0" layoutY="121.0" prefHeight="24.0" prefWidth="318.0" textFill="#ec1e1e">
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<PasswordField fx:id="txtPassword" layoutX="247.0" layoutY="145.0" prefHeight="44.0" prefWidth="318.0" promptText="Password">
<font>
<Font size="15.0" />
</font>
</PasswordField>
<Label fx:id="lblPassword" alignment="CENTER" layoutX="247.0" layoutY="189.0" prefHeight="24.0" prefWidth="318.0" textFill="#ec1e1e">
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<Label fx:id="lblForgot" alignment="CENTER" layoutX="326.0" layoutY="226.0" onMouseClicked="#forgetPassword" onMouseEntered="#MouseEnter" onMouseExited="#MouseExit" text="Forget your password? " textFill="#1286bc" underline="true">
<font>
<Font size="15.0" />
</font>
</Label>
<Label fx:id="lblForgot1" alignment="CENTER" layoutX="380.0" layoutY="35.0" text="Login" textFill="#1286bc">
<font>
<Font name="System Bold" size="20.0" />
</font>
</Label>
<Button fx:id="btnLogin" layoutX="247.0" layoutY="274.0" mnemonicParsing="false" onAction="#login" onMouseEntered="#MouseEnter" onMouseExited="#MouseExit" prefHeight="44.0" prefWidth="318.0" style="-fx-background-color: #169fe3;" text="Log in" textFill="WHITE">
<font>
<Font name="System Bold" size="20.0" />
</font>
</Button>
<Button fx:id="btnFB" alignment="CENTER" layoutX="247.0" layoutY="380.0" mnemonicParsing="false" onMouseEntered="#MouseEnter" onMouseExited="#MouseExit" prefHeight="44.0" prefWidth="318.0" style="-fx-background-color: #4766a9;" text="Log in with Facebook" textFill="WHITE">
<font>
<Font name="System Bold" size="20.0" />
</font>
<graphic>
<ImageView fitHeight="35.0" fitWidth="63.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../resources/ic_facebook.png" />
</image>
</ImageView>
</graphic>
</Button>
<Separator layoutX="247.0" layoutY="338.0" prefHeight="28.0" prefWidth="130.0" />
<Separator layoutX="434.0" layoutY="338.0" prefHeight="28.0" prefWidth="130.0" />
<Label layoutX="392.0" layoutY="337.0" text="OR" textFill="#1286bc">
<font>
<Font name="System Bold" size="20.0" />
</font>
</Label>
</children>
</AnchorPane>
</center>
</BorderPane>
This is module-info code:
module eSentral_Desktop_App {
requires javafx.controls;
requires java.net.http;
requires javafx.fxml;
requires javafx.base;
requires javafx.graphics;
requires com.google.gson;
requires java.logging;
requires java.desktop;
requires org.jfxtras.styles.jmetro;
opens application to javafx.graphics, javafx.fxml;
}
Upvotes: 2
Views: 2612
Reputation: 45786
TL;DR:
There are at least two solutions:
Add an opens application.login to javafx.fxml
directive to your module-info.java
file. For those of you who are not the OP, replace application.login
with your own controller's package.
Make your code non-modular by deleting the module-info.java
file. I recommend keeping JavaFX on the module-path in this case. If you don't know how to do that, see Getting Started with JavaFX 11+. If you don't keep JavaFX on the module-path you'll get this warning, as technically JavaFX does not support being placed on the class-path.
This error is related to the Java Platform Module System (JPMS) added in Java 9. If you're not familiar with modules, I recommend reading Understanding Java 9 Modules to learn the basics.
One of the goals of JPMS was to add stronger encapsulation to Java. This means:
The public1 members of a package in module A
can only be accessed by code in module B
if the package is exported by module A
to at least module B
. Exporting a package is done via an exports
directive inside the module descriptor (the module-info.java
file).
Note public members of an exported package can also be accessed via reflection.
The private2 members of a package in module A
can only be accessed reflectively by code in module B
if the package is opened by module A
to at least module B
. Opening a package is done via an opens
directive in the module descriptor.
The error message takes the form of:
class C (in module A) cannot access class D (in module B) because module B does not export P to module A
Where class D
is your class which is in your module B
. And P
is the package of class D
. Note that class C
is not really important here.
If the error message says, "does not export", then the fix is to add:
module B {
// other directives
// add this
exports P to A; // where 'to A' is optional (qualified vs unqualified exports)
}
Warning: Make sure to read the "Solution" section below before trying to use a qualified exports
.
Whereas if the error message says, "does not open", then the fix is to add:
module B {
// other directives
// add this
opens P to A; // where 'to A' is optional (qualified vs unqualified opens)
}
Note it's possible the error first talks about not exporting a package, only to then complain about not opening a package after you add an exports
directive. It depends on how the code using reflection is implemented.
1. This means public
classes and any public
members (i.e., methods, fields, etc.) of said public
class. Note that a public
.e.,g method declared in a non-public class is not considered a "public member" (in this context, at least).
2. Anything not covered by "public members", in other words protected
, package-private, and private
. Note if you want to invoke a non-public member reflectively, you have to call setAccessible(true)
(or trySetAccessible()
and checking the return value).
In your case, the error is about a class in the application.login
package. The examples below are based on that.
Your error says, "does not export". That would imply, from what I described above, that you should add an exports
directive to your module descriptor. This would be fine if, and only if:
You have no FXML-injected fields, or all those fields are public
.
You have no initialize()
method, or that method is public
.
You have no event-handler methods linked from the FXML file, or those methods are all public
.
If you meet those requirements, then having:
// WARNING: Does not actually work with JavaFX
exports application.login to javafx.fxml; // a qualified exports
Would fix the problem. At least, that would be true if JavaFX didn't perform its own checks (for methods) in addition to the normal JPMS checks. For whatever reason, JavaFX decided that qualified exports are not acceptable when invoking a method via reflection. You would have to use an unqualified exports:
exports aplication.login;
However, it's rare an FXML controller would meet all those requirements listed above. If you had no FXML-injected fields, no initialize()
method, and no event handler-methods, then why have a controller in the first place? And if you do have any of those, they really should all be private
as they're all implementation details of the controller. So, the proper solution is to add an opens
directive:
// JavaFX is perfectly fine with qualified opens, just not qualified exports
opens application.login to javafx.fxml;
As if the error message said, "does not open".
And this is all documented by Introduction to FXML:
Deploying an Application as a Module
If
FXMLLoader
is used to load types in a named module, the application must ensure that all types that are referenced in the FXML files, including the controller class and any customNode
classes, are reflectively accessible to thejavafx.fxml
module. A type is reflectively accessible if the moduleopens
the containing package to at least thejavafx.fxml
module.For example, if
com.foo.MyController
is in thefoo.app
module, themodule-info.java
might look like this:module foo.app { opens com.foo to javafx.fxml; }
Alternatively, a type is reflectively accessible if the module
exports
the containing package unconditionally.
- This is also documented by the @javafx.fxml.FXML
annotation.
An alternative solution is to make your code non-modular. You would do this by deleting the module-info.java
file from your project. This may be preferable, particularly if your code directly depends on non-modular dependencies.
That said, if you do make your code non-modular, I recommend you keep JavaFX on the module-path. If you don't, then you'll the warning described in the following Q&A:
And if you don't know how to make sure JavaFX is on the module-path, see Getting Started with JavaFX 11+.
Upvotes: 2
Reputation: 113
Thank you to jewelsea, this problem is solve. All I need to do is add
opens application.login to javafx.fxml;
into module-info.java.
It will look like this:
module eSentral_Desktop_App {
requires javafx.controls;
requires java.net.http;
requires javafx.fxml;
requires javafx.base;
requires javafx.graphics;
requires com.google.gson;
requires java.logging;
requires java.desktop;
requires org.jfxtras.styles.jmetro;
opens application.login to javafx.fxml;
opens application to javafx.graphics, javafx.fxml;
}
Upvotes: 5