Fork me on GitHub

Application Layer

How to create your first start class.

Application

Overview

JRebirth Application Framework offers a custom class that extends the basic javafx.Application class, the aim is to automatically start the JRebirth underlying Application Framework without doing complex stuff.

Short UML Diagram:

Application Class Diagram

Application Start-Up

To trigger the start-up of your JavaFX application you must add a static void main method in order to call one of the static protected method provided :

106
116
127
136
146
protected static void preloadAndLaunch(final String... args) {
protected static void preloadAndLaunch(final Class<? extends Preloader> preloaderClass, final String... args) {
protected static void preloadAndLaunch(final Class<? extends Application> appClass, final Class<? extends Preloader> preloaderClass, final String... args) {
protected static void launchNow(final String... args) {
protected static void launchNow(final Class<? extends Application> appClass, final String... args) {

In example, SampleApplication will be launched with default JRebirth preloader (Application and Preloader classes are omitted) like below:

30
31
32
public static void main(final String... args) {
    preloadAndLaunch(args);
}

If you want to use the JRebirthPreloader, you must include the JRebirth preloader artifact to your pom.xml file

1
2
3
4
5
<dependency>
    <groupId>org.jrebirth.af</groupId>
    <artifactId>preloader</artifactId>
    <version>8.0.3</version>
</dependency>

You can create your own Preloader class, JRebirth send only ProgressNotification with two kind of values:

  • With double value: 0.0 to 1.0 to update the progress bar
  • With integer value: > 1 to be translated into a message

Hereafter you have the default JRebirthPreloader implementation.

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
public class JRebirthPreloader extends Preloader {
 
    /** The Progress Bar. */
    private ProgressBar progressBar;
 
    /** The preloader Stage. */
    private Stage preloaderStage;
 
    /** The text that will display message received. */
    private Text messageText;
 
    /** Flag that indicate if the application is initialized. */
    private boolean jrebirthInitialized = false;
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void init() throws Exception {
        super.init(); // Nothing to do for the preloader
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void start(final Stage stage) throws Exception {
        // Store the preloader stage to reuse it later
        this.preloaderStage = stage;
 
        // Configure the stage
        stage.centerOnScreen();
        stage.initStyle(StageStyle.TRANSPARENT);
        stage.setScene(createPreloaderScene());
 
        // Let's start the show
        stage.show();
    }
 
    /**
     * Creates the preloader scene.
     *
     * @return the scene
     */
    private Scene createPreloaderScene() {
 
        final StackPane p = new StackPane();
 
        final ImageView logo = new ImageView(new Image("JRebirth_Title.png"));
        p.getChildren().add(logo);
        StackPane.setAlignment(logo, Pos.CENTER);
 
        this.progressBar = new ProgressBar(0.0);
        this.progressBar.setPrefSize(460, 20);
        p.getChildren().add(this.progressBar);
        StackPane.setAlignment(this.progressBar, Pos.BOTTOM_CENTER);
        StackPane.setMargin(this.progressBar, new Insets(30));
 
        this.messageText = new Text("Loading");
        p.getChildren().add(this.messageText);
        StackPane.setAlignment(this.messageText, Pos.BOTTOM_CENTER);
        StackPane.setMargin(this.messageText, new Insets(10));
 
        return new Scene(p, 600, 200, Color.TRANSPARENT);
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void stop() throws Exception {
        super.stop();
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void handleProgressNotification(final ProgressNotification pn) {
        if (this.jrebirthInitialized && pn.getProgress() >= 0.0 && pn.getProgress() <= 1.0) {
            this.progressBar.setProgress(pn.getProgress());
        } else {
            this.messageText.setText(getMessageFromCode((int) pn.getProgress()));
        }
    }
 
    /**
     * Gets the message from code.
     *
     * @param messageCode the message code
     *
     * @return the message from code
     */
    private String getMessageFromCode(final int messageCode) {
        String res = "";
 
        switch (messageCode) {
            case 100:
                res = "Initializing";
                break;
            case 200:
                res = "";// Provisioned for custom pre-init task
                break;
            case 300:
                res = "";// Provisioned for custom pre-init task
                break;
            case 400:
                res = "Loading Messages Properties";
                break;
            case 500:
                res = "Loading Parameters Properties";
                break;
            case 600:
                res = "Preparing Core Engine";
                break;
            case 700:
                res = "Preloading Resources";
                break;
            case 800:
                res = "Preloading Modules";
                break;
            case 900:
                res = "";// Provisioned for custom post-init task
                break;
            case 1000:
                res = "Starting";
                break;
            default:
        }
        return res;
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void handleApplicationNotification(final PreloaderNotification n) {
        if (n instanceof MessageNotification) {
            this.messageText.setText(((MessageNotification) n).getMessage());
        } else if (n instanceof ProgressNotification) {
            handleProgressNotification((ProgressNotification) n);
        }
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean handleErrorNotification(final ErrorNotification arg0) {
        return super.handleErrorNotification(arg0);
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void handleStateChangeNotification(final StateChangeNotification event) {
        switch (event.getType()) {
            case BEFORE_LOAD:
                break;
            case BEFORE_INIT:
                this.jrebirthInitialized = true;
                break;
            case BEFORE_START:
                beforeStart();
                break;
            default:
        }
    }
 
    /**
     * Perform actions before the application start.
     *
     * @throws InterruptedException
     */
    private void beforeStart() {
        final Stage stage = this.preloaderStage;
 
        ScaleTransitionBuilder.create()
                              .fromX(1.0)
                              .toX(0.0)
                              .duration(Duration.millis(400))
                              .node(stage.getScene().getRoot())
                              .onFinished(new EventHandler<ActionEvent>() {
 
                                  @Override
                                  public void handle(final ActionEvent arg0) {
                                      stage.hide();
 
                                  }
                              })
                              .build().play();
 
    }
 
}

Application Customization

To define your own Application class you have 2 options:

The AbstractApplication class will do all extra stuff required to launch JRebirth engine. You don't have to bother about it.
This class skeleton provides some hooks to allow customization of the application start up.

Please note that one method is mandatory ! You must define a first Model Class to load the first Model that will initialize the first Node attached to the RootNode (automagically created) )of your Scene.

If you have used the Maven archetype org.jrebirth.archetype you obtained this source code otherwise that you can copy-paste:

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public final class SampleApplication extends DefaultApplication<StackPane> {
 
    /**
     * Application launcher.
     *
     * @param args the command line arguments
     */
    public static void main(final String... args) {
        preloadAndLaunch(args);
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public Class<? extends Model> getFirstModelClass() {
        return SampleModel.class;
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    protected void customizeStage(final Stage stage) {
        stage.setFullScreen(false);
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    protected void customizeScene(final Scene scene) {
        addCSS(scene, SampleStyles.MAIN);
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    protected List<? extends ResourceItem<?, ?, ?>> getResourceToPreload() {
        return Arrays.asList(new FontItem[] {
                SampleFonts.SPLASH,
        });
    }
 
}

The SampleModel.class is shown and explained into the Ui page.

Root Node

AbstractApplication & DefaultApplication classes are using a generic type that represents the first JavaFX node hold by the scene. This node will be instantiated automatically by the framework and could be accessed by calling the protected method getRootNode(). You must define it in the class definition as generic type, this type must extend Pane class.

First Model Class

The method Class<? extends Model> getFirstModelClass() is mandatory to define which UI model will be attached to the root node of the scene.

This first model will be created into the JRebirth Thread Pool (JTP), then it will be attached to the root node into the JavaFX Application Thread (JAT).

Application Title

The method String getApplicationTitle() is simply used to define the name of the application displayed by the stage (OS window) and used by OS task bar.

By default it will retrieve values from properties file (default is jrebirth.properties):

  • applicationName={} powered by JRebirth ({} is replaced by application class simple name)
  • applicationVersion=1.0

Stage customization

The first stage object is provided by the JavaFX launcher, the method void customizeStage(final Stage stage) allows doing some stage customizations.

Scene customization

The scene object automatically attached to the default stage stage is built by the protected method Scene buildScene() that could be overridden as needed. By defaut it creates a default scene with these attributes :

  • Width = 800 (applicationSceneWidth parameter)
  • Height = 600 (applicationSceneHeight parameter)
  • Background Color = Transparent (applicationSceneBgColor parameter)
  • Root = Automatically built according to the generic type used

Theses properties are customizable with a properties file, this is explained below into the Configuration section.

Another method let you customize the scene : void customizeSscene(final Scene scene) . For example you can listen some key binding to perform a global action. The Presentation application uses it to listen <Ctrl> + <+> and <Ctrl> + <-> key combo to zoom in/out the whole scene.

This method is also useful to attach a stylesheet to the scene like this : scene.getStylesheets().add(loadCSS("style/sample.css"));

Fonts preloading

JavaFX applications are able to use fonts through programmatic declarations or with CSS declaration (in .css files. or inline).
If font used by CSS are not provided by the platform running the application, it must be provided and loaded by the application.
JRebirth provides a simple way to embed and declare font: this mechanism is explained in the custom topic: Managing Fonts.

The method List<FontEnum> getFontToPreload() is used to preload fonts to allow them to be used by CSS declaration. They are loaded at boot in the same time than stylesheets.

Running Waves

The JRebirth Application class allow running Waves before and after the creation of the first model class.

A Wave is a JRebirth Event that could be process by any JRebirth components, they are carrying throught JRebirth classes and threads.

You can add your own wave with the two following methods :

  • List<Wave> getPreBootWaveList()
  • List<Wave> getPostBootWaveList()

The waves returnes will be processed sequentially althought they could be processed by different threads.

In this method you are allowed to call visible methods from the javafx.application.Application class, in example the getParameter() will give you the arguments passed to command line

Don't forget that you can chain your waves if you need to do more than one thing.

JRebirth Analyzer example :

96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
@Override
public List<Wave> getPostBootWaveList() {
 
    final List<Wave> waveList = new ArrayList<>();
 
    // Get Java command line parameters
    final Parameters p = getParameters();
    if (p.getRaw().size() >= 1) {
 
        // The first parameter must contains the log file to parse
        final String logFileName = p.getRaw().get(0);
        final File logFile = new File(logFileName);
 
        if (logFile.exists()) {
 
            // Call the service that will load and parse the log file
            waveList.add(
                    Builders.wave()
                            .waveGroup(WaveGroup.RETURN_DATA)
                            .waveType(LoadEdtFileService.DO_LOAD_EVENTS)
                            .componentClass(LoadEdtFileService.class)
                            .addDatas(Builders.waveData(EditorWaves.EVENTS_FILE, logFile))
                    );
 
            // Start the animation to show all components creation
            waveList.add(Builders.wave().waveType(EditorWaves.DO_PLAY));
        }
    }
    return waveList;
}

Default key shortcuts

The AbstractApplication class is using two defaults hotkey:

  • 638
    protected KeyCode getFullScreenKeyCode() {
    KeyCode getFullScreenKeyCode() => to go to to fullscreen mode (default is < F10 >)
  • 649
    protected KeyCode getIconifiedKeyCode() {
    KeyCode getIconifiedKeyCode()=> to go to iconified mode (default is < F11 >)

These methods could be overridden if you want to change them, you can avoid these shortcut by returning null .

Exception Manager

JRebirth creates its own uncaught exception handlers in order to log exceptions that were not caught by application code.
It's possible to customize them by overriding methods listed hereafter:

  • Exception handler of the JavaFX Application Thread
  • 667
    protected UncaughtExceptionHandler getJatUncaughtExceptionHandler() {
  • Exception handler of the JRebirth Internal Thread
  • 676
    protected UncaughtExceptionHandler getJitUncaughtExceptionHandler() {
  • Default Exception handler of all other threads
  • 658
    protected UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() {

Initialization Phases

JavaFX applications haves 2 main phases while starting up:

Init Phase

The Initialization phase is composed by:

  • 00-10% - Begin Initialization (Loading Resources transition)
  • 10-30% - Customizable Pre-Init Steps
  • 30-40% - Loading Message Files
  • 40-50% - Loading Configuration Files
  • 50-60% - Attach Exception Handler and Prepare the JIT
  • 60-70% - Preload Fonts and attach CSS Files
  • 70-90% - Customizable Pre-Init Steps
  • 100% - trigger the start phase

Customizable steps are handled by 2 methods to override:

209
210
211
212
/**
 * Perform custom task before application initialization phase.
 */
protected abstract void preInit();

You can use notifyPreloader(new ProgressNotification(PROGRESS)) method where PROGRESS value is between 0.11 and 0.29 in order to update finely the progression.
You can also display custom message (understandable by your preloader) by calling notifyPreloader(new ProgressNotification(MESSAGE_ID)) where MESSAGE_ID is 200 or 300.

214
215
216
217
/**
 * Perform custom task after application initialization phase and before starting phase.
 */
protected abstract void postInit();

You can use notifyPreloader(new ProgressNotification(PROGRESS)) method where PROGRESS value is between 0.71 and 0.89 in order to update finely the progression.
You can also display custom message (understandable by your preloader) by calling notifyPreloader(new ProgressNotification(MESSAGE_ID)) where MESSAGE_ID is 800 or 900.

Start Phase

The Start phase will build and attach the scene object.
Then it will start the JRebirth Thread (JIT) and show the stage.

Manage Configuration

JRebirth provides a configuration engine that allow to parse configuration files and inject values into application.

@Configuration

Your application class can use the dedicated @Configuration annotation. The AbstractApplication class use a default one:

79
@Configuration(".*jrebirth")

Hereafter you have a full annotation usage:

14
@Configuration(value = ".*-jrebirth", extension = "properties", schedule = 60)

This annotation has 3 properties:

  • value
  • extension
  • schedule

Value (default property)

Define the wildcard used to find configuration files.
The format is the same as Regex Pattern (ie: .*-jrebirth => for abc-jrebirth.EXTENSION files, abc matches the .* regex part)
The default value is empty, no search will be done

Extension

Define the file extension to find configuration files.
The extension must not included the first dot (ie: properties => for abc-jrebirth.properties files)
The default value is "properties" to load properties files

Schedule

Define the delay used to check if the file has changed in order to reload configuration files.
This value is in seconds.
The default value is 0, no check will be done (this feature is still under development)

Avoid Configuration

It's possible to avoid configuration mechanism, for example if you want to use your own and don't need another process at start-up.
You can disable it by setting an empty @Configuration annotation.

14
@Configuration

Manage Localization

JRebirth provides a Internationalization engine that allow to localize internal resources and also your resources. It parses properties files and inject values into application.

@Localized

Your application class can use the dedicated @Localized annotation. The AbstractApplication class use a default one:

80
@Localized(".*_rb")

This annotation has 2 properties:

  • value
  • schedule

Value (default property)

Define the wildcard used to find configuration files.
The format is the same as Regex Pattern (ie: .*_rb => for MyMessages_rb.properties files, MyMessages matches the .* regex part)
The default value is empty, no search will be done

Schedule

Define the delay used to check if the file has changed in order to reload properties files.
This value is in seconds.
The default value is 0, no check will be done (this feature is still under development)

Avoid Localization

It's possible to avoid localization mechanism, for example if you want to improve performance by avoiding any translation into logs.
You can disable it by setting an empty @Localized annotation.

15
@Localized