Fork me on GitHub

JRebirth Application Framework Documentation

Learn how to use JRebirth to help you building sophisticated application in minutes.

All In One Documentation Page

Introduction

JRebirth Origins
The small story of JRebirth project.

Origins

Just before the 2011's summer I was bored to work with some RIA toolkit that don't give me enought satisfaction.

Like many people I thought that web applications were the ultimate solution to build quickly beautiful and sophisticated applications. Before the advent of JQuery's like toolkit I built mine, a modularized javascript framework written with OOP spirit. Browser compatibility stops the projects because it requires too much time to manage their particularities. But the biggest pitfall is the Javascript language that is not enough powerful to build cleanly big application and not enough efficient than pre-compiled ones.

Then I was upset by the single thread used by Flash and Flex application, even if I really have pleasure to work with FlexBuilder with PureMVC framework. Making extension of Flex components was also sometimes pretty hard due to inheritance of Flash widgets (partially resolved with new spark components).

Unfortunately I also worked several month with Silverlight (C#) provided by Microsoft, the language itself have some interesting feature but ecosystem is not as good as the Java one. The MVVM pattern (Model-View-ViewModel) is a fake that hide a MVVCVM (Model-View-ViewController-ViewModel) where VC is the .xaml.cs file...

Before working with RIA, I obviously worked with Swing and SWT/JFace toolkit. And finally Swing was not so bad !

It suffered from a lot drawback like graphical performance issues, concurrency problems and old layout and components. But it was possible to write nice application powered with a pleasant Look & Feel.

So JRebirth was first thought to be a rebirth of Java Swing Toolkit in order to create the missing Application Framework that Swing never had.

I posted on the ToulouseJUG mailing list a question about my Swing revival vision, and during the discussion I learned that Oracle had planned to rewrite the first JavaFX entirely in Java (and leave the awful fxscript language). So I spent the summer in reading beta documentation and during the autumn I began to play with it ... and I decided to build my own Framework.

The name was readily found: JRebirth (for Java Rebirth) JavaFX Application Framework

Everything began on a local SVN and then was pushed on Github on Feb 22, 2012, few days after my first talk about JavaFX (2).

JRebirth Objectives

The main goal of building yet another application framework is to be the more convenient as possible for developpers.

The major issue when building graphical application is to create unresponsive memory-hungry application, while dealing with ugly code hard to maintain.

To adress the first point JRebirth offers a way to manage threads on your behalf, for the second JRebirth defines layers to maintain a good Separation of Concern (SoC).

Why don't manage myself my application thread ?

Writing concurrent program is more complex than you can think, problems can occur whenever and always when you are demoing your application with your client.

Layers sucks !

Are you sure ? Layer is the more basic way to organize and reuse smartly your code. It could seem to add some overhead to it but when you are working with a small or big team over years, you will enjoy to retrieve at the same place the same logic. It requires to respect some rules.

Moreover you will have the capability to customize your layer to fit your need by factorizing your code.

JRebirth Key features

Main features of JRebirth Application Framework are:

  1. Simplify Thread Management
  2. Avoid memory leak
  3. Maintain a good SoC
  4. Be the more convenient as possible for developpers
  5. Be lightweight (and modularizable)
  6. Follow OSS spirit and Java Best Practices

Installation

How to use JRebirth Application Framework
This page will help you setting up your project to work with JRebirth.

Manual Download

JRebirth is composed by a set of jar files, you just have to add them into your application classpath to use them.

To get them you can download manually JRebirth distribution archive, but it could be out of date.

This distribution mode will be improved in the future for those you don't want to deal with an artifact repository.

Maven Way

Currently the easy way to get JRebirth jar files is to use maven convention.

You just need to add these dependencies into your pom.xml to begin to play with JRebirth Core features.
The first one is the core library of JRebirth Application Framework. The second one is used to displayed basic JRebirth Preloader.
The last one an optional library used to manage logs.

204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
<dependencies>
 
    <!-- Use logback logger -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.0.13</version>
    </dependency>
 
    <dependency>
        <groupId>org.jrebirth.af</groupId>
        <artifactId>preloader</artifactId>
        <version>7.7.5</version>
    </dependency>
 
    <dependency>
        <groupId>org.jrebirth.af</groupId>
        <artifactId>core</artifactId>

We will see in the next chapter how to configure your build to get all JRebirth dependencies.

Maven Repositories

Choose your destiny !
...and finish it ! (aka your wonderful application :)

All JRebirth libraries are pushed to several repositories and you must choose how you want to retrieve them.
They are pushed in this order :

  1. OJO, Open Source Repository hosted by JFrog and powered by Artifactory oss.jfrog.org
  2. Our public repository powered by Bintray (On demand Jenkins build)
  3. JCenter Bintray repository (Only Releases)
  4. Our internal repository powered by Artifactory (could be offline)
  5. Any subset of all available repositories

JRebirth is not yet on Maven Central repository because the upload policy is too restrictive (actually it requires to update your pom to be allowed to send your artifacts on a third-party repository that will be synchronized with Central).
If you don't want to bother yourself with all these settings, you should just proxy OJO repository.

The first location to check is OJO because releases and snapshots are pushed automatically to it, so check it out first !

Let's have a macro look on all these alternatives.

OJO Repo

JRebirth is firstly pushed to OJO ( oss.jfrog.org/simple/oss-release-local/org/jrebirth/ - Open Source Software repo provided by JFrog as an artifactory instance ).

You have 2 ways to plug your maven build to this server:

  1. Add a repositories section into the pom.xml of your project.
  2. Add a profile section into your settings.xml

OJO into Pom.xml

You can simply add this declaration into your pom.xml file to let Maven downloading all JRebirth dependencies.
A lot of people don't recommend to put repositories declaration into pom files, because a Maven build should only depend on Maven Central dependencies without any third library repositories.
JRebirth is not pushed yet on Maven Central repo, so this snippet does the trick to start to work.

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<repositories>
    <repository>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
        <id>central</id>
        <name>libs-release</name>
    </repository>
    <repository>
        <snapshots />
        <id>snapshots</id>
        <name>libs-snapshot</name>
    </repository>
</repositories>

OJO into Settings.xml

This way is so far better but has an important drawback, your build won't work if you don't use the right profile.
So you should share this declaration somewhere in order to let your project 'buildable' by other contributors.

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<profiles>
    <profile>
        <id>OJO</id>
        <repositories>
            <repository>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
                <id>central</id>
                <name>libs-release</name>
                <url>http://oss.jfrog.org/artifactory/libs-release</url>
            </repository>
            <repository>
                <snapshots />
                <id>snapshots</id>
                <name>libs-snapshot</name>
                <url>http://oss.jfrog.org/artifactory/libs-snapshot</url>
            </repository>
        </repositories>
        <pluginRepositories>
            <pluginRepository>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
                <id>central</id>
                <name>plugins-release</name>
                <url>http://oss.jfrog.org/artifactory/plugins-release</url>
            </pluginRepository>
            <pluginRepository>
                <snapshots />
                <id>snapshots</id>
                <name>plugins-snapshot</name>
                <url>http://oss.jfrog.org/artifactory/plugins-snapshot</url>
            </pluginRepository>
        </pluginRepositories>
    </profile>
</profiles>

Bintray

JRebirth artifacts are pushed to Bintray repository and then automatically synchronized to JCenter main Bintray repository. Only release version are managed.

It's possible to add only JRebirth Bintray repository to your project, but it's more convenient to add JCenter repository which aggregate all Bintray repositories.

JCenter

JCenter is a new social Java repository hosted on Bintray website. It allows to broadcast thousand of libraries without the pitfall to update your pom.xml in order to push them on a proxy repository. JCenter is absolutely a fabulous alternative to Maven Central, it can also host other kinf of binaries.

If you want to use JCenter repository, you must add this into your Maven settings.xml. Pay attention that at this time JCenter contains only 'Release' artifacts, no 'snapshot' versions

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<profiles>
    <profile>
        <id>JCenter</id>
        <repositories>
            <repository>
                <id>central</id>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
                <name>bintray</name>
                <url>http://jcenter.bintray.com</url>
            </repository>
        </repositories>
        <pluginRepositories>
            <pluginRepository>
                <id>central</id>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
                <name>bintray-plugins</name>
                <url>http://jcenter.bintray.com</url>
            </pluginRepository>
        </pluginRepositories>
    </profile>
</profiles>

JRebirth Repo

JRebirth has its own artifact repository ( repo.jrebirth.org ), but the server is not 7/7 24/24 ready due to energy savings. It could be offline when you need to recompile your application, so you shouldn't use it into your builds. It's mainly used as proxy for our developers, although it also hosts all JRebirth binaries.

You have 2 ways to plug your maven build to our server:

  1. Add a repositories section into the pom.xml of your project
  2. Add a profile section into your settings.xml

JRebirth repo into Settings.xml

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<profiles>
    <profile>
        <id>JRebirth</id>
        <repositories>
            <repository>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
                <id>jrebirth-release</id>
                <name>libs-release</name>
                <url>http://repo.jrebirth.org/libs-release</url>
            </repository>
            <repository>
                <snapshots />
                <id>jrebirth-snapshot</id>
                <name>libs-snapshot</name>
                <url>http://repo.jrebirth.org/libs-snapshot</url>
            </repository>
        </repositories>
        <pluginRepositories>
            <pluginRepository>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
                <id>jrebirth-plugin-release</id>
                <name>plugins-release</name>
                <url>http://repo.jrebirth.org/plugins-release</url>
            </pluginRepository>
            <pluginRepository>
                <snapshots />
                <id>jrebirth-plugin-snapshot</id>
                <name>plugins-snapshot</name>
                <url>http://repo.jrebirth.org/plugins-snapshot</url>
            </pluginRepository>
        </pluginRepositories>
    </profile>
</profiles>

It's also possible to add this declaration into the profile section of your user settings.xml or enterprise settings.xml

Hybrid Configuration

You can add both OJO, JCenter, and JRebirth Artifactory repositories, if you want to be up-to-date at any time.

If you just want to deal with stable release use only OJO (Release repo) and JCenter.
If sometimes you need to test a snapshot version use OJO (Snapshot repo) and our Artifactory instance or ask us a custom build.

All-In-One Profile

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<profiles>
    <profile>
        <id>JRebirth_All-In-One</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <repositories>
            <repository>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
                <id>central</id>
                <name>libs-release</name>
                <url>http://oss.jfrog.org/artifactory/libs-release</url>
            </repository>
            <repository>
                <snapshots />
                <id>snapshots</id>
                <name>libs-snapshot</name>
                <url>http://oss.jfrog.org/artifactory/libs-snapshot</url>
            </repository>
            <repository>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
                <id>jcenter</id>
                <name>bintray</name>
                <url>http://jcenter.bintray.com</url>
            </repository>
            <repository>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
                <id>jrebirth-release</id>
                <name>libs-release</name>
                <url>http://repo.jrebirth.org/libs-release</url>
            </repository>
            <repository>
                <snapshots>
                    <enabled>true</enabled>
                </snapshots>
                <id>jrebirth-snapshot</id>
                <name>libs-snapshot</name>
                <url>http://repo.jrebirth.org/libs-snapshot</url>
            </repository>
        </repositories>
        <pluginRepositories>
            <pluginRepository>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
                <id>central</id>
                <name>plugins-release</name>
                <url>http://oss.jfrog.org/artifactory/plugins-release</url>
            </pluginRepository>
            <pluginRepository>
                <snapshots />
                <id>snapshots</id>
                <name>plugins-snapshot</name>
                <url>http://oss.jfrog.org/artifactory/plugins-snapshot</url>
            </pluginRepository>
            <pluginRepository>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
                <id>jcenter-plugin</id>
                <name>bintray-plugins</name>
                <url>http://jcenter.bintray.com</url>
            </pluginRepository>
            <pluginRepository>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
                <id>jrebirth-plugin-release</id>
                <name>plugins-release</name>
                <url>http://repo.jrebirth.org/plugins-release</url>
            </pluginRepository>
            <pluginRepository>
                <snapshots>
                    <enabled>true</enabled>
                </snapshots>
                <id>jrebirth-plugin-snapshot</id>
                <name>plugins-snapshot</name>
                <url>http://repo.jrebirth.org/plugins-snapshot</url>
            </pluginRepository>
        </pluginRepositories>
    </profile>
</profiles>

Overview

Overview of the JRebirth Core Architecture
JRebirth Application Framework offers a very efficient pattern for build real Professional JavaFX 2.0 applications.

How does JRebirth work ?

JRebirth uses a WCS-MVC pattern with 6 main layers:


Application

The App lication is the starting class to extends to build a JavaFX 2.0 application that use JRebirth framework.

Learn more about Application

GlobalFacade

The GlobalFacade is just used to simplify acess from any layer to any other.
Learn more about GlobalFacade

Notifier and Wave

The Notifier layer will transport small notifications called Waves. This notification engine is integrated into the core of JRebirth. Its role is to manage communication throught all JRebirth layers.

Instead of propagating events or notifications we are using Waves. Why Waves ? Because it sounds great.

When I send a wave I feel like a surfer !

A wave can be sent or received by three kinds of components :

  • Command
  • Service
  • Model

There are a lot of convenient method available to create and launch waves into the application.

Learn more about Notifier

CommandFacade and Commands

The Command layer is used to manage complex group of actions (MultiCommand) (synchronously or asynchronously), or atomic reusable action (Command).

They can be processed into one of the 3 kind of thread managed by using annotation.

All interactions between layers must be managed by Commands.

Learn more about Commands

ServiceFacade and Services

The Service layer is used to retrieve Data from outside the application or to communicate with others applications.

It can use Spring HttpRequests, Web Services, Restlets, files or whatever you want. They are managed as Worker tasks in a dedicated thread pool.

Learn more about Services

UiFacade and Models

The Model layer is used to manage the loading of the data displayed into a view, for example by calling a service.

A model manage the View logic.

A Model creates its View automatically by reflection.

Learn more about Models

Controllers

A Controller manage the View's Interactions logic. it's used to intercept all JavaFX components' events.

A controller must implements an custom Adapter to manage events (like MouseAdapter) and link its built-in Handler to the View's nodes (like MouseHandler).

Or it can simply implement custom methods if annotations are used.

Views

The view layer is used to create user interface by using JavaFX visual components.

A View manage the View's Presentation logic.

Each view have a Model used to acessinterrogate to display data.

Each view have a Controller used to manage JavaFX components events.

A view creates its Controller automatically by reflection.

Each JavaFX Node that need to be controlled by a Controller can declare a getter with package visibility to restrict access from other layers.

It can also use an annotation to automatically call a dedicated method into the controller.

Application

Application Layer
How to create your first start class.

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 :

94
104
115
124
134
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:

29
30
31
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>7.7.5</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 prelaoder 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 Fonts";
                break;
            case 800:
                res = "";// Provisioned for custom post-init task
                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:

  • Extend AbstractApplication and implement abstract methods
  • Extend DefaultApplication and only override abstract methods you need

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:

22
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
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
    public List<FontItem> getFontToPreload() {
        return Arrays.asList(new FontItem[] {
                SampleFonts.SPLASH,
        });
    }
 
}

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 :

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
@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(
                    WaveBuilder.create()
                            .waveGroup(WaveGroup.RETURN_DATA)
                            .waveType(LoadEdtFileService.DO_LOAD_EVENTS)
                            .relatedClass(LoadEdtFileService.class)
                            .data(WaveData.build(EditorWaves.EVENTS_FILE, logFile))
                            .build()
                    );
 
            // Start the animation to show all components creation
            waveList.add(
                    WaveBuilder.create()
                            .waveType(EditorWaves.DO_PLAY)
                            .build()
                    );
        }
    }
    return waveList;
}

Default key shortcuts

The AbstractApplication class is using two defaults hotkey:

  • 552
    protected KeyCode getFullScreenKeyCode() {
    KeyCode getFullScreenKeyCode() => to go to to fullscreen mode (default is < F10 >)
  • 563
    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
  • 581
    protected UncaughtExceptionHandler getJatUncaughtExceptionHandler() {
  • Exception handler of the JRebirth Internal Thread
  • 590
    protected UncaughtExceptionHandler getJitUncaughtExceptionHandler() {
  • Default Exception handler of all other threads
  • 572
    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:

190
191
192
193
/**
 * 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.

195
196
197
198
/**
 * 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:

67
@Configuration(".*jrebirth")

Hereafter you have a full annotation usage:

15
@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.

15
@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:

68
@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.

16
@Localized

Thread Management

Thread Management
Writing concurrent application could be painful ! Not with JRebirth

Threading overview

JRebirth is multi-threaded, not only by using Task Worker provided by JavaFX APIs. JRebirth has got its own Thread to manage local events (called waves) allowing components to communicate with each others. It also allows to manage multiple threads in a very simple manner with its included thread pool.

Thus all inner JRebirth tasks are processed into a custom thread and don't infer with the JavaFX Application Thread which have to manage user events and some UI instantiation.

Lags, UI Freeze ... are lost to history :D

What happens when you debug a JRebirth application ? You can observe a lot of threads, but don't be scared !! Everything is at the right isolated place doing the right thing.

The most important are :

  • JAT - JavaFX Application Thread
  • JIT - JRebirth Internal Thread
  • JTP - JRebirth Thread Pool (name pattern is JTP Slot 'n' )
  • HPTP - High Priority Thread Pool (name pattern is HPTP Slot 'n' )

Other threads are related to JavaFX platform, JRebirth only creates one thread with two Thread Pools.

JRebirth Thread Pool and High Priority Thread Pool have the same size.
computed like this: Number of available Core * threadPoolSizeRatio parameter
The default parameter value is 2, so each thread pool will have a size of 16 on Core i7 (Quad Core with HyperThreading). You can use a double value to divide this amount if you think that it can disturb your platform performances (0.125 value will only provide a thread pool size of 2 on a such platform).
Obviously a minimum size of 1 will be used if your thread pool ratio is too low.

Concurrent Class Diagram

Thread Usage

Each thread shall manage specific tasks, let's see their aims.

JAT

JavaFX Application Thread is the graphical thread of the toolkit, you shouldn't perform long task into it, but you must update all node attached to the displayed scene into it. It's the equivalent of the former EDT (Event Dispatch Thread) in Swing. If you try to update a node attached to the main JavaFX scene outside it you will obtain an ugly Exception.

JIT

JRebirth Internal Thread is the internal thread of JRebirth Application Framework. It will be used to host the communication engine between all JRebirth Components. If you start a long task into it it will freeze all communication tasks because all runnable added will be executed synchronously according to FIFO rule (First-In First-Out).

JTP

JRebirth Thread Pool is designed to perform all other long task your application require, you just have to remember that any graphical update still require to be done into JAT. Moreover any JRebirth internal configuration must be performed into JIT (ie: listen a WaveType).

HPTP

High Priority Thread Pool has been added to be able to assign a priority to a long task. On small platform or when using very long task, the JTP can be overloaded and we must used a higher priority task to trigger an User interface refresh. The HPTP will be used to start higher priority task when JTP is full. It has same settings than JTP.

Thread Rules

JavaFX Application Thread (JAT)

JAT is the the graphical thread of the JavaFX toolkit (identical to Swinf EDT), all UI tasks must be perform into it only if a direct link can be establish between the graphical component and the displayed scene. So if you call setText("myText") on a Text node:

  • You must call it into JAT if the node is displayed or linked to the displayed scene.
  • You can call in whatever thread you want if the node isn't linked to the displayed scene.

JavaFX toolkit let you prepare some tree of node into a separate thread and then attached it to the displayed scene. This is how JRebirth load and display any Model, the PrepareModelCommand will run into JTP to create the Model root Node with all its children. Then the AttachModelCommand will be run into JAT to attach the model root node into another node already displayed into the Scene.

JRebirth Internal Thread

JRebirth Thread Pool

JRebirth provides 2 thread pools to run long task asynchronously without freezing neither the User Interface (JAT) nor JRebirth Messaging (JIT).

Examples

Logging

To log or not to log !
Logging is so important to diagnose UI bugs.

My first idea about logging for JavaFX was to build myself a small lightweight and powerful module to add log capability to JRebirth Framework.

But after some hundred of lines of code written, I realized that using a real logging library could be interesting and not too heavy to embed.

Logging Facade

I choose to add dependency to slf4j-api : Simple Logging Facade 4 Java

This API is lightweight because the jar is near to 10 kb.

1
2
3
4
5
<dependency>
    <groupid>org.slf4j</groupid>
    <artifactid>slf4j-api</artifactid>
    <version>1.7.5</version>
</dependency>

NOP

The slf4j api allow to declare logger for JRebirth Framework but doesn't provide any logger implementation.

By default all logs are rejected and you will have this message into the Java console at startup:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

To avoid this error message to appear you can add the No-OPeration dependency:

1
2
3
4
5
<dependency>
    <groupid>org.slf4j</groupid>
    <artifactid>slf4j-nop</artifactid>
    <version>1.7.5</version>
</dependency>

LOGback

Personally I choose LOGback implementation because it represents a good compromise between performances and customization. It was finely integrated because it was written by the same team as slf4j.

LOGBack jars are quite heavy and weight more than 800 kb so it could be a problem for tiny applications. To use it you must add this dependency into your pom.xml :

1
2
3
4
5
<dependency>
    <groupid>ch.qos.logback</groupid>
    <artifactid>logback-classic</artifactid>
    <version>1.0.13</version>
</dependency>

Then you must add a configuration file like this one into the application classpath:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
 
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>JRebirthAnalyzer.log</file>
        <encoder>
            <pattern>%date %level [%thread{6}] %logger{10} [%file:%line] %msg%n</pattern>
        </encoder>
    </appender>
 
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
        <!-- %level %msg%n -->
            <pattern>%level [%thread] %file:%line - %msg%n</pattern>
        </encoder>
    </appender>
 
 
    <root level="trace">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root>
 
</configuration>

SimpleLogger

If you don't want to embed 800 kb of jar to provide logging feature you can use the simple logger provided by slf4j, it's only a 10 kb jar file.

You just have to add this dependency to your pom.xml:

1
2
3
4
5
<dependency>
    <groupid>org.slf4j</groupid>
    <artifactid>slf4j-simple</artifactid>
    <version>1.7.5</version>
</dependency>

JRebirth Logs

All JRebirth logs use an internationalized engine allowing us to provide extensible log messaging.
It could be seen as an overkill feature but it has some advantages:

  • Provide localized log message
  • Manage log level activation automatically
  • Parameterize log level, to increase or decrease it by custo
  • Disable Message resolution for high performance

Note that you can still use your logger basic features.

JRebirth provides its own LoggerFactory that you can initialize like this:

56
57
/** The class logger. */
private static final JRLogger LOGGER = JRLoggerFactory.getLogger(JRebirthThread.class);

Then you can log your message like this:

107
110
161
176
280
LOGGER.log(JTP_QUEUED, runnable.toString());
LOGGER.log(HPTP_QUEUED, runnable.toString());
LOGGER.error(BOOT_UP_ERROR, e);
    LOGGER.error(JIT_ERROR, e);
LOGGER.log(SHUTDOWN_ERROR, e);

All these log messages are store into an interface implemented by the class to let them accessible

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
69
70
71
72
73
public interface ConcurrentMessages extends MessageContainer {
 
    /** JRebirthThread. */
 
    /** "Runnable submitted to JTP with hashCode={0}" . */
    MessageItem JTP_QUEUED = create(new LogMessage("jrebirth.concurrent.jtpQueued", JRLevel.Trace, JRebirthMarkers.CONCURRENT));
 
    /** "Runnable submitted to HPTP with hashCode={0}" . */
    MessageItem HPTP_QUEUED = create(new LogMessage("jrebirth.concurrent.hptpQueued", JRLevel.Trace, JRebirthMarkers.CONCURRENT));
 
    /** "An exception occurred during JRebirth BootUp" . */
    MessageItem BOOT_UP_ERROR = create(new LogMessage("jrebirth.concurrent.bootUpError", JRLevel.Error, JRebirthMarkers.CONCURRENT));
 
    /** "An exception occured into the JRebirth Thread". */
    MessageItem JIT_ERROR = create(new LogMessage("jrebirth.concurrent.jitError", JRLevel.Error, JRebirthMarkers.CONCURRENT));
 
    /** "An error occurred while shuting down the application ". */
    MessageItem SHUTDOWN_ERROR = create(new LogMessage("jrebirth.concurrent.shutdownError", JRLevel.Error, JRebirthMarkers.CONCURRENT));
 
    /** AbstractJrbRunnable. */
 
    /** "Run> {0}". */
    MessageItem RUN_IT = create(new LogMessage("jrebirth.concurrent.runIt", JRLevel.Trace, JRebirthMarkers.CONCURRENT));
 
    /** "Thread error : {0} ". */
    MessageItem THREAD_ERROR = create(new LogMessage("jrebirth.concurrent.threadError", JRLevel.Error, JRebirthMarkers.CONCURRENT));
 
    /** JRebirthThreadPoolExecutor. */
 
    /** "JTP returned an error" . */
    MessageItem JTP_ERROR = create(new LogMessage("jrebirth.concurrent.jtpError", JRLevel.Error, JRebirthMarkers.CONCURRENT));
 
    /** "JTP returned an error with rootCause =>". */
    MessageItem JTP_ERROR_EXPLANATION = create(new LogMessage("jrebirth.concurrent.jtpErrorExplanation", JRLevel.Error, JRebirthMarkers.CONCURRENT));
 
    /** "Future (hashcode={}) returned object : {0}". */
    MessageItem FUTURE_DONE = create(new LogMessage("jrebirth.concurrent.futureDone", JRLevel.Trace, JRebirthMarkers.CONCURRENT));
 
}

Facade(s)

Facades Layer
Simplify component accessibility.

JRebirth provide 3+1 Facades used to manage its dedicated pattern wCS-Mvc.

Overview

Short UML Diagram:

Global Facade

One Facade to rule them all

JRebirth uses a global facade automatically created by JRebirth AbstractApplication class.

Internal Communication

The global facade create automatically the JRebirth notification engine. You should read the Notifier page to have more informations.

Linked with Application

The global facade also allow to communicate with the Application class and therefore with its stage and scene.

Manage Local Facades

It allows to manage the three main facades:

  • CommandFacade
  • ServiceFacade
  • UiFacade

The GlobaleFacadeBase allow to get each of these facades by calling appropriate getters:

50
55
60
LocalFacade<Model> getUiFacade();
LocalFacade<Service> getServiceFacade();
LocalFacade<Command> getCommandFacade();

This link is bidirectionnal because global facade is accessible from the 3 main facades by calling : getGlobalFacade() (each facade extends the AbstractGlobalReady abstract class which implement GlobalReady interface.

34
GlobalFacade getGlobalFacade();

Local Facades

There are 3 LocalFacade, each one is responsible of its layer components.

Basic Features

Each Facade can manage its components (also called readyObject) throught some public methods.

  • retrieve : used to get a component (build and register it if needed)
  • exists : check if the component is already registered, it could be released
  • register : store a pre-built component
  • unregister : remove a component
  • buildKey : build a unique key for the given component
41
51
60
70
82
95
106
118
130
<E extends R> void register(final UniqueKey<E> uniqueKey, final E readyObject);
<E extends R> void register(final E readyObject, final Object... keyPart);
<E extends R> void unregister(final UniqueKey<E> uniqueKey);
<E extends R> void unregister(final E readyObject, final Object... keyPart);
<E extends R> E retrieve(final UniqueKey<E> uniqueKey);
<E extends R> E retrieve(final Class<E> clazz, final Object... keyPart);
<E extends R> boolean exists(final UniqueKey<E> uniqueKey);
<E extends R> boolean exists(final Class<E> clazz, final Object... keyPart);
<E extends R> UniqueKey<E> buildKey(final Class<E> clazz, final Object... keyPart);

Component Key

Define how to manage component Key

Provide Tracking

JRebirth allows to track all creation & finalization of each component (Command, Service, Model, View) and also track the emission of waves and their path.

They are logged in order to be processed by the JRebirthAnalyzer tool.

Component definition

Notifier & Components
Provide a communication means between top-level components

A component is a top-level actor of the wCS-Mvc pattern.

Each component can communicate with other in a simple way.

They are loosely coupled with each other and with the engine itself.

There are three kinds of components:

  • Command
  • Service
  • Model

Wave, View and Controller are not JRebirth Components.

Link Layer - Class Diagram

Notifier aim

JRebirth have got its own Notification Engine to allow each component to communicate with each other.

All these communications are processed into a dedicated thread.

The notification engine uses custom objects to transport data : the Waves.

Short UML Diagram:

Wave Overview

Wave Layer
Allow asynchronous communication between components

What's a Wave

A Wave is a temporary object created to carry data througt CS-MVC components.

They allow JRebirth to handle application internal events asynchronously between each components, at the opposite you can use getModel, getCommand and getService method to perform synchronous operations.

Waves are handled by the notifier engine which are running within the JRebirthThread .

Short UML Diagram:

Wave Group

There are 4 main group of waves :

  1. Call Command : used to trigger a command action
  2. Call Service : used to call a service, later another wave will be used
  3. Attach Ui : used to link two JavaFX nodes
  4. Undefined : to handle all other waves, the wave type must be defined

Wave ItemType

A wave item designate a Java type (precisely a generic type that can be for example: List<String>). It could be used with a custom name or as a parameter.

Don't forget the opening and closing braces used { } to allow anonymous class to define the super generic type !

Wave items are used by Wave Type to describe an api contract.

66
67
68
69
70
71
72
/** The file containing all events serialized. */
WaveItem<File> EVENTS_FILE = new WaveItem<File>() {
};
 
/** The name of the events. */
WaveItem<List<JRebirthEvent>> EVENTS = new WaveItem<List<JRebirthEvent>>() {
};

Wave items can also be used to define the unique identifier of a value wrapped into a WaveData wrapper stored into a wave.

70
71
72
73
74
75
/** This wave item will be used only into a WaveData to pass the current Service task handled by the wave. */
WaveItem<ServiceTask<?>> SERVICE_TASK = new WaveItem<ServiceTask<?>>(false) {
};
 
/** This wave item will be used only into a WaveData to pass the right progress bar used by service task. */
WaveItem<ProgressBar> PROGRESS_BAR = new WaveItem<ProgressBar>(false) {
150
151
// Attach ServiceTask to the source wave
sourceWave.addData(WaveData.build(JRebirthWaves.SERVICE_TASK, task));

Wave Type

The wave type is where black magic resides. It defines a contract between the emitter (the one who creates the wave) and the receiver (the one who handles the waves).

This contract is dynamic because it relies on a String and WaveItem objects.

A WaveType has a unique name and a set of WaveItem objects. It must be created and stored like this:

    • Into an Interface to define wave contract (here without argument)
38
39
40
41
42
/** Trigger a Unload wave. */
WaveTypeBase DO_UNLOAD = WaveTypeBase.build("UNLOAD");
 
/** Trigger a Play wave. */
WaveTypeBase DO_PLAY = WaveTypeBase.build("PLAY");
    • Into a service class (here with on argument) :
48
49
50
51
52
/** Wave type use to load events. */
public static final WaveTypeBase DO_LOAD_EVENTS = WaveTypeBase.build("LOAD_EVENTS", EditorWaves.EVENTS_FILE);
 
/** Wave type to return events loaded. */
public static final WaveTypeBase RE_EVENTS_LOADED = WaveTypeBase.build("EVENTS_LOADED", EditorWaves.EVENTS);

WaveType name

This string is used to link to a component method , this call is made by reflection when the wave is processed.

WaveItem List

Each WaveItem will be passed to the method as an argument

Wave argument

Each wave procesing method must add a least argument : the source wave, thus it will be possible to know the handling context for this method call.

The wave argument is useful to access to wave bean or other information like source wave.

Wave Lifecycle

Wave lifecycle are defined by the Status enumeration:

  1. Created : The Wave object has just been built by someone.
  2. Sent : The wave has been sent to the JRebirth Notifier Engine
  3. Processing : The wave is being dispatched
  4. Cancelled : The wave has been cancelled by an user action
  5. Consumed : The wave task is achieved and is marked as consumed
  6. Failed : The wave process has generated an error
  7. Destroyed : the wave is available for garbage collection

How they are consumed

Chained Wave

It's possible to chain command by using the ChainWaveCommand class. A sample is used into the JRebirthThread.bootUp() method.

Command Overview

Command Layer
Reuse common code and don't be scared by threading issue

A command is an atomic reusable action that could be triggered from anywhere.

There are two kinds of Commands:

  • Single Command
  • Multi Command (composed by a set of Single Command)
Command - Class Diagram

Single Command

Single Commands are atomic and are run independently.

If you trigger several commands in-a-row you will trigger them in parallel according to their predefined running thread. JRebirth engine will serialize their instantiation and their startup but they will be processed into JAT, JIT or one of JTP slots. JAT and JIT will process command one after the other. JTP will act in the same manner but internal engine will dispatch all actions to its pooled threads (by default {2 x Number of CPU core} slots are available by Thread Pool).

MultiCommand

MultiCommand provides the ability to run some Single Commands sequentially or in parallel.

Hereafter you will find an example of MultiCommand used to display a model UI:

29
30
31
32
33
34
35
36
37
38
39
40
41
public class ShowModelCommand extends DefaultMultiBeanCommand<DisplayModelWaveBean> {
 
    /**
     * {@inheritDoc}
     */
    @Override
    protected void manageSubCommand() {
        addCommandClass(PrepareModelCommand.class);
        addCommandClass(AttachModelCommand.class);
 
    }
 
}

The multi command code will be run into JIT, but its sub-command will be run respectively into JTP and JAT (according to their own configuration).

Why are they using these threads ?
ShowModelCommand use the annotation defined into DefaultMultiCommand to run into JIT .
PrepareModelCommand use the annotation defined into DefaultPoolCommand to run into JTP .
ShowModelCommand use the annotation defined into DefaultUICommand to run into JAT (it's mandatory to update scene's nodes).

How to trigger a Command

Commands are designed to be disposable after usage, but they could be retained by strong references to be executed twice or more. Each call will return a new instance of the command class, because each command was stored with a timestamp key-based Timestamp is added to the default key and also for custom key).
You can create a command by four different ways:

Please note that Commands are JRebirth top components (with Services and Models), they follow the component lifecycle as described in Facade Page .
Thus AbstractBaseCommand extends AbstractWaveReady and their descendants must provide initCommand(), processAction() and execute() methods.

Direct way

It's possible to call a command from any JRebirth component (Command, Service, Model).

You just need to call the getCommand method to build an instance. You must provide the Command Class and its unique key (if required).

44
<C extends Command> C getCommand(final Class<C> clazz, final Object... keyPart);

Once you have retrieved your command, you can store it with a strong reference to avoid GC collecting it. Your command life will depend on the lifetime of your strong reference. Thus you will be able to configure directly your command properties. Finally to trigger it you must call its run() method.

36
Wave run();

Be careful : As explained here each call to getCommand could retrieve the same OR another instance of the command class depending on key parts provided and instance's strong references.

Indirect way

You can trigger a command execution by calling callCommand from any component.

You can provide some parameters into WaveData that will be hold by the wave and so available into the command.

248
256
public final <WB extends WaveBean> Wave callCommand(final Class<? extends CommandBean<WB>> commandClass, final WB waveBean) {
public final Wave callCommand(final Class<? extends Command> commandClass, final WaveData<?>... data) {

Convenient Link method

Controllers provide a convenient method named void linkCommand(Node, EventType<E>, Class<? extends Command>, WaveData<?>...)

This method useful to declare with only one line of code the trigger of a command when a prefined JavaFX event occured on a node belonging to the View.

It's also possible to add a callback function, in example to manage double-click detection (see LinkedCallback).

54
linkCommand(getView().getOpenButton(), MouseEvent.MOUSE_CLICKED, OpenEventTrackerFileCommand.class, LinkedCallback.CHECK_MOUSE_SINGLE_CLICK);

Component Callback way

Any Command is a JRebirth component and can benefits of Observer features. It's possible to listen a WaveType (registration done into the initModel, initService and initCommand methods) in order to be notified when a such wave is sent. You can manage custom methods called by reflection to handle waves and can you catch all waves into the processAction(Wave) method.

More information is available in Notifier & Component page.

Be careful, commands can handle asynchronous wave only if they haven't been collected by the Garbage Collector! So you need to create a instance and to keep a strong references on it somewhere.

Command Properties

A command is an atomic action reusable or not that can be run into a predefined thread. A command provides specific features:

How to manage Threading issues

Each command will be launch by JRebirth Internal engine and run into a dedicated thread. Threads involved in a JRebirth application are explained into the Thread page .

The runner thread can be configured by two ways:

The priority rule is : Annotation > Constructor argument > Default value

The default value is : JIT (JRebirth Internal Thread)

The top-level annotation will be systematically used overriding lower ones and also constructor arguments

Annotation usage

To run a command into the JAT (JavaFX Application Thread), use this annotation :

31
@RunInto(RunType.JAT)

To run a command into the JIT (JRebirth Internal Thread), use this annotation :

31
@RunInto(RunType.JIT)

To run a command into JTP (JRebirth Thread Pool, the command will be run into a slot), use this annotation :

31
@RunInto(RunType.JTP)

Class inheritance usage

To run a command into the JAT (JavaFX Application Thread), extends the DefaultUICommand class :

32
public class AttachModelCommand extends DefaultUIBeanCommand<DisplayModelWaveBean> {

To run a command into the JIT (JRebirth Internal Thread), extends the DefaultCommand class :

34
public class ChainWaveCommand extends DefaultCommand implements WaveListener {

WaveListener implementation is optional

To run a command into JTP (JRebirth Thread Pool, the command will be run into a slot), extends the DefaultPoolCommand class :

33
public class PrepareModelCommand extends DefaultPoolBeanCommand<DisplayModelWaveBean> {

Constructor argument usage

It's also possible to define the runType (The thread which will handle the command) by passing RunType , each descendant classes of AbstractBaseCommand offer at least one constructor allowing this. enum value to the command constructor.

92
public AbstractBaseCommand(final RunType runType, final RunnablePriority priority) {

Component Key

The component key as described in the Facade page allow storing unique commands.

All objects provided as key part will be serialized (toString call) to build the key.

Warning : When a command is built consequently to a wave reception, the wave UID will be concatenated to the class name instead of using key parts.

Execute Code

Command are run in a custom thread in 3 steps:

  • preExecute
  • execute
  • postExecute

Only the execute method must be implemented to perform your action.

Wave Bean

A WaveBean is a Java Bean that allow carrying a lot of names properties into a Wave .

Command Classes can declare a generic type that allow to cast the Wave Bean into the right one, it allows to use getWaveBean().getMyProperty() into source code which is more convenient than parsing WaveData (but it implies to create a dedicated Java Class).

Custom properties

Each command is a simple Java Object, you can add fields or JavaFX Properties to help configuring your execution code.

You must pay attention that these values will be kept until the command is disposed (after execution if no strong references exists).

For example you can attach a command to a Model and launch it several times while updating its internal properties.

Services

Service Layer
Process configurable action

Overview

A Service Object is a JRebirth Component (part of CSM pattern, Command-Service-Model).
It can be retrieved from the ServiceFacade.

A Service can hold several Tasks defined by a WaveType. Each Task requires:

  1. Define Call Wave Type (entry point)
  2. Define Return Wave Type (exit point)
  3. Register the Callback
  4. Define the Task process into the right method name.

That's all ! You don't have to bother about threading issues and asynchronous tasks.

Warning:
You must pay attention to the lifecycle of your service instance. As a JRebirth Component, each service object is eligible to garbage collection if it isn't retained by another object currently used.
So your data stored or processed into your service can be loss if you didn't manage correctly your Service life.
The most easy way is to hold your service with a strong reference into a long-living object like a top-levelModel.

Short UML Diagram:

Service Class Diagram

Defining Wave Types

Entry Point Wave Type

This is the WaveType used to process a Wave generated anywhere into the application.

48
49
/** Wave type use to load events. */
public static final WaveTypeBase DO_LOAD_EVENTS = WaveTypeBase.build("LOAD_EVENTS", EditorWaves.EVENTS_FILE);

This WaveType uses only one WaveItem to store the file that must be loaded. WaveItem wrap the type of the object we want to use, thus it's possible to check that API contract isn't broken.

66
67
68
/** The file containing all events serialized. */
WaveItem<File> EVENTS_FILE = new WaveItem<File>() {
};

Exit Point Wave Type

51
52
/** Wave type to return events loaded. */
public static final WaveTypeBase RE_EVENTS_LOADED = WaveTypeBase.build("EVENTS_LOADED", EditorWaves.EVENTS);

This WaveType uses only one WaveItem to store the list of events loaded from the given file.

70
71
72
/** The name of the events. */
WaveItem<List<JRebirthEvent>> EVENTS = new WaveItem<List<JRebirthEvent>>() {
};

Task Registration

Each Task requires to be registered in order to generate the right WaveType that wrap the return value.
This registration must be done into the JRebirth's Component void initService() method like this:

60
61
62
63
64
@Override
public void initService() {
 
    registerCallback(DO_LOAD_EVENTS, RE_EVENTS_LOADED);
}

If you don't declare the return WaveType a exception will be thrown at runtime when trying to send back the Service Task output.

If your Service Task doesn't return anything (void return) you can dismiss this step.

Perform the Job!

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
/**
 * Parse the event file.
 *
 * @param selecteFile the event file selected
 * @param wave the wave that trigger the action
 *
 * @return the list of loaded events
 */
@Priority(RunnablePriority.High)
public List<JRebirthEvent> doLoadEvents(final File selecteFile, final Wave wave) {
 
    final List<JRebirthEvent> eventList = new ArrayList<>();
 
    updateMessage(wave, "Parsing events");
 
    // Get number of line to calculate the task progression
    int totalLines = ServiceUtility.countFileLines(selecteFile);
 
    try (BufferedReader br = new BufferedReader(new FileReader(selecteFile));)
    {
        int processedLines = 0;
 
        String strLine = br.readLine();
 
        // Read File Line By Line
        while (strLine != null) {
            processedLines++;
 
            updateProgress(wave, processedLines, totalLines);
 
            if (strLine.contains(JRebirthMarkers.JREVENT.getName())) {
                // Convert the string to a JRebirth event and add it to the list
                addEvent(eventList, strLine.substring(strLine.indexOf(">>") + 2));
            }
 
            // Read the next line to process
            strLine = br.readLine();
        }
 
    } catch (final IOException e) {
        LOGGER.error("Error while processing event file", e);
    }
    return eventList;
 
}

How to use the Service Feature

Call the Service

To call this Service Feature, you can use the returnData from any JRebirth's Component. It takes at least 2 mandatory arguments:

  1. The Service Class Object
  2. The WaveType that is related to the Service Feature
  3. An unordered list of WaveData objects that wrap values required by WaveType contract

Hereafter you will fin an example of service call with only one arguments passed:

58
59
60
returnData(LoadEdtFileService.class,
        LoadEdtFileService.DO_LOAD_EVENTS,
        WaveData.build(EditorWaves.EVENTS_FILE, selected));

Process the Service Result

The Service Feature Result is sent as a Wave that wrap returned value. So to be informed when the result is available, there is two things to do :

  • Let your component listening this WaveType.
  • Add Wave handler code to process the result.

Each JRebirth's Component are able to listen some WaveType's waves by calling the listen method with one or several Wave Types.
This call must be done into one of the following method according to component used:

  • initCommand : for Command classes
  • initService : for Service classes
  • initModel : for Model classes

Each initXXX method is called into JRebirth Internal Thread by ready method.

38
39
40
41
42
@Override
protected void initModel() {
    listen(LoadEdtFileService.RE_EVENTS_LOADED);
    listen(EditorWaves.DO_UNLOAD);
}

Add a method that suit the WaveType convention.
The name must used the predefined prefix (in our case DO_ converted to do), then the WaveType's name converted in a camel cased format.
Method parameters must be compliant with Wave Items defined into the Wave Type.
A final parameter must be added, the Wave itself taht could be useful to get extra data, for example when chained waves are used.

51
52
53
public void doEventsLoaded(final List<JRebirthEvent> eventList, final Wave wave) {
    getView().activateButtons(!eventList.isEmpty());
}

Threading

Which Threads are involved ?

All communications with Service Component is done into JIT and all Service Tasks are performed into one slot of JTP or HPTP according to priority defined with annotation.

Threading Priority

Each Service feature call will be processed by the JTP (JRebirth Thread Pool) or by the HPTP (High Priority Thread Pool) according to their predefined priority.
By default the Priority is set to RunnablePriority.Low (a level below Normal) to let other task to be performed into JTP before Service feature calls (like Command).

Its possible to increase or decrease this value by adding an annotation on the Service Feature method like this:

74
75
@Priority(RunnablePriority.High)
public List<JRebirthEvent> doLoadEvents(final File selecteFile, final Wave wave) {

Follow Task progression

Each Service Task are able to update a progress bar with integer value and message. In both cases you must provide the wave provided as a method argument, it will be used to link the associated ServiceTask and find the right ProgressBar and Text widgets to update.
You can update the message test by calling this method:

79
updateMessage(wave, "Parsing events");

You can update progress bar indicator by calling this method:

94
updateProgress(wave, processedLines, totalLines);

UI Roles

User Interface Layer
Use a custom MVC pattern with a lot of convenient tricks

The User Interface layer is composed by three main parts :

  • Model
  • View
  • Controller

Each of these must do predefined tasks to maintain a good Separation of Concerns (SoC).

Models are JRebirth Components like Commands and Services, they are mandatory whereas Views and Controllers are optionals.

Short UML Diagram:

Models

Model are JRebirth Components, they are retrievable from UiFacade and they can be dynamically attached to any placeholder. They are able to listen Wave and to communicate with other components.
Models store business objects with custom method that allow to bound an object to a model and then to use its internal properties to bound them to UI widget.
Models' aim is to manage UI lifecycle and communication, you can place into some part of your business logic.

Views

Views are automatically created by Models according to generic type used, their main objectives is to build and wrap the graphical root Node. You can manage Model-View interaction in both direction: Model-to-View by adding some 'package' Node getters into the view (preferred way for binding declarations), or View-to-Model by adding some 'package' methods into Model (only for some call).

Controllers

Controllers are dedicated to manage event handling by providing several ways to facilitate developers' life. They are automatically created by the view according to generic type used.
Event Handler can be attached from View-to-Controller, or from Controller-to-View (preferred way, to centralize event handlers declarations).

About Generics

You can ask why do MVC objects use so much generics type that hurt eyes !! The reason is really simple: To avoid lot of cast ! When you want to call a method from another part you will have a method to grab the part with the right type, so no cast are needed. An alternative would be to write ourselves getter method with right cast but it's really painful and doesn't have any value. So you will write once this quite complex (especially when you use intermediate classes) class declaration with its generic type and you will enjoy coding without having to cast them.

Main Usage

User Interface layer is versatile and will adapt itself to your use case to avoid boiler plate code. Hereafter you will find a list of all possible ways to use a Model:

  • M - Only Model: rely on SimpleModel implementation
  • MV - Model with a View: Controller is omitted
  • MVC - Model, View, Controller: Basic Implementation
  • FMFFC - FxmlModel , Fxml, FxmlController: View and Controller are respectively replaced by Fxml and FxmlController
  • MVCFFC - FxmlModel, View, Controller, Fxml, FxmlController: Basic + Fxml files

Models

Models are directly synchronized with the UIFacade and can send & receive Waves, they can also use any other components.

The goal of Models is to retrieve data from other layers, and to define Business Logic (business rules, authorizations ...).

The Model automatically build its attached view (except SimpleModel).

14
15
16
17
18
19
20
21
22
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
public final class SampleModel extends DefaultModel<SampleModel, SampleView> {
 
    /** The class logger. */
    private static final Logger LOGGER = LoggerFactory.getLogger(SampleModel.class);
 
    /**
     * {@inheritDoc}
     */
    @Override
    protected void initModel() {
        LOGGER.debug("Init Sample Model");
        // Put the code to initialize your model here
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    protected void initInnerModels() {
        // Put the code to initialize inner models here (if any)
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    protected void bind() {
        // Put the code to manage model object binding (if any)
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    protected void processWave(final Wave wave) {
        // Process a wave action, you must listen the wave type before
    }
 
}

InnerModels

Views Overview

As explained before, the main goal of a View is to create the rootNode attached to the Model.
Fortunately the rootNode is automatically created according to the generic type used in the View header.

View's initialization

The View's initialization code perform several operations:

  1. Link the model (strong reference)
  2. Create the root Node object according to the first generic type that extends Node.class
  3. Create the Controller according to the first generic type that extends Controller.class

If the construction fails, mainly due to Controller error, a custom error Node is created in order to display the stack trace of the exception.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
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
package org.jrebirth.af.sample.ui;
 
import javafx.scene.control.Button;
import javafx.scene.control.LabelBuilder;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPaneBuilder;
 
import org.jrebirth.af.core.exception.CoreException;
import org.jrebirth.af.core.ui.AbstractView;
import org.jrebirth.af.core.ui.annotation.OnMouse;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
/**
 * The class <strong>SampleView</strong>.
 *
 * @author
 */
public final class SampleView extends AbstractView<SampleModel, BorderPane, SampleController> {
 
    /** The class logger. */
    private static final Logger LOGGER = LoggerFactory.getLogger(SampleView.class);
 
    /** Button used to trigger the SampleCommand. */
    @OnMouse(OnMouse.MouseType.Clicked)
    private Button defaultCommand;
 
    /** Button used to trigger the SampleUICommand. */
    private Button uiCommand;
 
    /** Button used to trigger the SamplePoolCommand. */
    private Button pooledCommand;
 
    /**
     * Default Constructor.
     *
     * @param model the controls view model
     *
     * @throws CoreException if build fails
     */
    public SampleView(final SampleModel model) throws CoreException {
        super(model);
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    protected void initView() {
 
        this.defaultCommand = new Button("Trigger a default Command into JIT");
        this.uiCommand = new Button("Trigger an UI Command into JAT");
        this.pooledCommand = new Button("Trigger a pooled Command into JTP");
 
        getRootNode().setCenter(
                LabelBuilder.create()
                        .text("JRebirth Sample")
                        .build()
                );
 
        getRootNode().setBottom(FlowPaneBuilder.create().children(
                this.defaultCommand,
                this.uiCommand,
                this.pooledCommand
                ).build());
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void start() {
        LOGGER.debug("Start the Sample View");
        // Custom code to process when the view is displayed the first time
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void reload() {
        LOGGER.debug("Reload the Sample View");
        // Custom code to process when the view is displayed the 1+n time
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void hide() {
        LOGGER.debug("Hide the Sample View");
        // Custom code to process when the view is hidden
    }
 
    /**
     * Return the button that trigger the default command.
     *
     * @return the button that trigger the default command
     */
    Button getDefaultCommand() {
        return this.defaultCommand;
    }
 
    /**
     * Return the button that trigger the UI command.
     *
     * @return the button that trigger the UI command
     */
    Button getUiCommand() {
        return this.uiCommand;
    }
 
    /**
     * Return the button that trigger the pooled command.
     *
     * @return the button that trigger the pooled command
     */
    Button getPooledCommand() {
        return this.pooledCommand;
    }
 
}

Annotation Event Handler

Controllers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
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
package org.jrebirth.af.sample.ui;
 
import javafx.scene.input.MouseEvent;
 
import org.jrebirth.af.core.exception.CoreException;
import org.jrebirth.af.core.ui.AbstractController;
import org.jrebirth.af.core.ui.adapter.DefaultMouseAdapter;
import org.jrebirth.af.core.wave.WaveBuilder;
import org.jrebirth.af.core.wave.WaveGroup;
import org.jrebirth.af.sample.command.SampleCommand;
import org.jrebirth.af.sample.command.SamplePoolCommand;
import org.jrebirth.af.sample.command.SampleUICommand;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
/**
 * The class <strong>SampleController</strong>.
 *
 * @author
 */
public final class SampleController extends AbstractController<SampleModel, SampleView> {
 
    /** The class logger. */
    private static final Logger LOGGER = LoggerFactory.getLogger(SampleController.class);
 
    /**
     * Default Constructor.
     *
     * @param view the view to control
     *
     * @throws CoreException if an error occurred while creating event handlers
     */
    public SampleController(final SampleView view) throws CoreException {
        super(view);
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    protected void initEventAdapters() throws CoreException {
 
        // Manage Ui Command Button
        linkCommand(getView().getUiCommand(), MouseEvent.MOUSE_CLICKED, SampleUICommand.class);
 
        // Use the inner class
        addAdapter(new SampleMouseAdapter());
 
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    protected void initEventHandlers() throws CoreException {
        // Listen events
 
        // Manage Pooled Command Button
        getView().getPooledCommand().setOnMouseClicked(getHandler(MouseEvent.MOUSE_CLICKED));
    }
 
    /**
     * Manage Mouse click of widget that have annotation.
     *
     * @param event the mouse event
     */
    void onMouseClicked(final MouseEvent event) {
 
        LOGGER.debug("MouseClicked => Call Sample Command");
 
        // Manage Default Command Button
        getModel().getCommand(SampleCommand.class).run();
 
    }
 
    /**
     * The class <strong>SampleMouseAdapter</strong>.
     */
    private class SampleMouseAdapter extends DefaultMouseAdapter<SampleController> {
 
        @Override
        public void mouseClicked(final MouseEvent mouseEvent) {
            super.mouseClicked(mouseEvent);
 
            LOGGER.debug("MouseClicked => Call Sample Pool Command");
 
            getModel().sendWave(
                    WaveBuilder.create()
                            .waveGroup(WaveGroup.CALL_COMMAND)
                            .relatedClass(SamplePoolCommand.class)
                            .build()
                    );
        }
 
    }
 
}

Adapters & Handlers

Resources

Using Resources
Decrease your memory footprint by using resource wrapper

The JRebirth Framework provides an useful way to deal with your local resources, we currently support :

  • Colors
  • Fonts
  • Images
  • Parameters
  • CSS (WIP)
  • FXML (WIP)

These resources can consume a lot of memory if you don't dispose them (especially big images and fonts) when you stop using them.
JRebirth provides a mechanism to store them weakly and to rebuild them if necessary in order to use the less memory as required. To do this JRebirth stores a ResourceParam lightweight object that contains all information to create the heavyweight resource.
This one is linked by a ResourceItem to facilitate its usage.

UML Diagram Overview:

There are 3 ways to declare resources, each one is available for all kind of resources with special feature for parameters.

With Resources.create(ResourceParam)

The first way is to hold static field declaration instantiated with custom 'factory' Resources (not named, with an overloaded create method to avoid later cast).
This static fields can be hold in any class you want but we recommend to store them into an Interface used 'as' an enum. It's better to have an interface per resource type to avoid big file that blends all resources.
Another interesting trick, is to add a static import on related static Resource.create method to shorten the resource declaration (Ctrl+Shift+M with Eclipse).

Static import declaration:

26
import static org.jrebirth.af.core.resource.Resources.create;

Example of Web color declaration:

40
41
/** The web color. */
ColorItem TEST_COLOR_WEB_1 = create(new WebColor("0088D3"));

With Enumeration

The other way to declare is a little bit complex, it implies to create an enum that implements a ResourceItem interface.
But it requires to add some copy/paste code into implemented methods.

Dynamically

Not implemented yet

To manage these resources we use a enum hack to cleanly define them and most important to have a concise way to use them without calling singleton getter or another complex set of methods.

So if you want to handle resources with JRebirth mechanism, you just have to create an enumeration that implement the interface of the resource you want, with a custom constructor.

Color

For example to manage web color, (basic hexadecimal string #00CC00), you have to use this declaration:

40
41
/** The web color. */
ColorItem TEST_COLOR_WEB_1 = create(new WebColor("0088D3"));

Hereafter an example of an interface that hold multiple colors.

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
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
/**
 * The class <strong>TestColors</strong>.
 *
 * @author Sébastien Bordes
 */
public interface TestColors {
 
    /**************************************************************************************/
    /** ___________________________________Web Colors.___________________________________ */
    /**************************************************************************************/
 
    /** The web color. */
    ColorItem TEST_COLOR_WEB_1 = create(new WebColor("0088D3"));
 
    /** The web color. */
    ColorItem TEST_COLOR_WEB_2 = create(new WebColor("0D88D3", 0.4));
 
    /** The web color. */
    ColorItem TEST_COLOR_WEB_3 = create(new WebColor("0087D3", 1.0));
 
    /**************************************************************************************/
    /** __________________________________Gray Colors.___________________________________ */
    /**************************************************************************************/
 
    /** The gray color. */
    ColorItem TEST_COLOR_GRAY_1 = create(new GrayColor(0.3));
 
    /** The gray color. */
    ColorItem TEST_COLOR_GRAY_2 = create(new GrayColor(0.74, 0.9));
 
    /** The gray color. */
    ColorItem TEST_COLOR_GRAY_3 = create(new GrayColor(0.5, 1.0));
 
    /**************************************************************************************/
    /** ___________________________________HSB Colors.___________________________________ */
    /**************************************************************************************/
 
    /** The hsb color. */
    ColorItem TEST_COLOR_HSB_1 = create(new HSBColor(96.0, 0.4, 0.9));
 
    /** The hsb color. */
    ColorItem TEST_COLOR_HSB_2 = create(new HSBColor(45.0, 0.3, 0.8, 0.45));
 
    /** The hsb color. */
    ColorItem TEST_COLOR_HSB_3 = create(new HSBColor(153.0, 0.6, 0.75, 1.0));
 
    /**************************************************************************************/
    /** _________________________________RGB 01 Colors.__________________________________ */
    /**************************************************************************************/
 
    /** The rgb 0-1 color. */
    ColorItem TEST_COLOR_RGB01_1 = create(new RGB01Color(0.22, 0.752, 0.78));
 
    /** The rgb 0-1 color. */
    ColorItem TEST_COLOR_RGB01_2 = create(new RGB01Color(0.78, 0.653, 0.85, 0.12));
 
    /** The rgb 0-1 color. */
    ColorItem TEST_COLOR_RGB01_3 = create(new RGB01Color(0.96, 0.851, 0.41, 1.0));
 
    /**************************************************************************************/
    /** ___________________________________RGB 255 Colors._______________________________ */
    /**************************************************************************************/
 
    /** The rgb 255 color. */
    ColorItem TEST_COLOR_RGB255_1 = create(new RGB255Color(107, 69, 251));
 
    /** The rgb 255 color. */
    ColorItem TEST_COLOR_RGB255_2 = create(new RGB255Color(255, 248, 189, 70 / 100));
 
    /** The rgb 255 color. */
    ColorItem TEST_COLOR_RGB255_3 = create(new RGB255Color(112, 60, 63, 1.0));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
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
/**
 * Get more info at : www.jrebirth.org .
 * Copyright JRebirth.org © 2011-2013
 * Contact : sebastien.bordes@jrebirth.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jrebirth.af.core.resource.color;
 
import java.util.Arrays;
import java.util.List;
 
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
 
/**
 * The class <strong>WebColor</strong> used to create a Web Color.
 *
 * @author Sébastien Bordes
 */
public class WebColor extends AbstractBaseColor {
 
    /** The hexadecimal string value [0-9A-F]{6} (without 0x or #). */
    private final StringProperty hexProperty = new SimpleStringProperty();
 
    /**
     * Default Constructor.
     *
     * @param hex the hexadecimal value [0-9A-F]{6} (without 0x or #)
     */
    public WebColor(final String hex) {
        super();
        this.hexProperty.set(hex);
    }
 
    /**
     * Default Constructor.
     *
     * @param hex the hexadecimal value [0-9A-F]{6} (without 0x or #)
     * @param opacity the color opacity [0.0-1.0]
     */
    public WebColor(final String hex, final double opacity) {
        super(opacity);
        this.hexProperty.set(hex);
    }
 
    /**
     * Return the hexadecimal string value [0-9A-F]{6} (without 0x or #).
     *
     * @return Returns the hexadecimal value.
     */
    public String hex() {
        return this.hexProperty.get();
    }
 
    /**
     * @return Returns the hex property.
     */
    public StringProperty hexProperty() {
        return this.hexProperty;
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void parse(final String[] parameters) {
        if (parameters.length >= 1) {
 
            String hexValue = parameters[0];
            if (hexValue.startsWith("0x")) {
                hexValue = hexValue.substring(2);
            }
            if (hexValue.charAt(0) == '#') {
                hexValue = hexValue.substring(1);
            }
 
            switch (hexValue.length()) {
            // 0x r g b
                case 3:
                    this.hexProperty.set(hexValue);
                    break;
                // 0x rr gg bb
 
                // 0x rr gg bb oo
                case 8:
                    // Not managed yet
                    break;
                case 6:
                default:
                    this.hexProperty.set(hexValue);
                    break;
 
            }
 
        }
        // Opacity
        if (parameters.length == 2) {
            opacityProperty().set(Double.parseDouble(parameters[1]));
        }
 
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    protected List<? extends Object> getFieldValues() {
        return Arrays.asList(hex(), opacity());
    }
 
}

But this interface doesn't explain how to register a resource, so let's see an example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
/**
 * Get more info at : www.jrebirth.org .
 * Copyright JRebirth.org © 2011-2013
 * Contact : sebastien.bordes@jrebirth.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jrebirth.af.presentation.resources;
 
import org.jrebirth.af.core.resource.color.ColorItem;
import org.jrebirth.af.core.resource.color.RGB255Color;
import org.jrebirth.af.core.resource.color.WebColor;
 
import static org.jrebirth.af.core.resource.Resources.create;
 
 
/**
 * The class <strong>PrezColors</strong>.
 *
 * @author Sébastien Bordes
 *
 */
public interface PrezColors {
 
    /** Color for slide title, white. */
    ColorItem SLIDE_TITLE = create(new WebColor("FFFFFF", 1.0));
 
    /** Color for slide title, white. */
    ColorItem SUB_TITLE = create(new WebColor("FFF6EF", 1.0));
 
    /** Color for blue shape, xxx. */
    ColorItem SHAPE_BLUE = create(new WebColor("3495CE", 1.0));
 
    /** Color for drop shadow, black. */
    ColorItem DROP_SHADOW = create(new WebColor("000000", 0.8));
 
    /** Color for inner shadow, white. */
    ColorItem INNER_SHADOW = create(new WebColor("FFFFFE", 0.3));
 
    /** Color for first gradient, xxx. */
    ColorItem GRADIENT_1 = create(new WebColor("1AA2AC", 1.0));
 
    /** Color for second gradient, xxx. */
    ColorItem GRADIENT_2 = create(new WebColor("F04F24", 1.0));
 
    /** Color for third gradient, xxxx. */
    ColorItem GRADIENT_3 = create(new WebColor("FFF200", 1.0));
 
    /** Color for splash text, xxx. */
    ColorItem SPLASH_TEXT = create(new RGB255Color(60, 60, 70));
 
}

Font

For example to manage the Turtlesfont, you have to use this declaration:

41
42
/** The real font. */
FontItem TEST_REAL_FONT_1 = create(new RealFont(TestFontNames.Turtles, 10.0));

Hereafter an example of an interface that hold multiple colors.

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
/**
 * The class <strong>TestFonts</strong>.
 *
 * @author Sébastien Bordes
 */
public interface TestFonts {
 
    /**************************************************************************************/
    /** ___________________________________Real Fonts.___________________________________ */
    /**************************************************************************************/
 
    /** The real font. */
    FontItem TEST_REAL_FONT_1 = create(new RealFont(TestFontNames.Turtles, 10.0));
 
    /** The real font. */
    FontItem TEST_REAL_FONT_2 = create(new RealFont(TestFontNames.Turtles, 12.0));
 
    /**************************************************************************************/
    /** _________________________________Family Fonts.___________________________________ */
    /**************************************************************************************/
 
    /** The family font. */
    FontItem TEST_FAMILY_FONT_1 = create(new FamilyFont("serif", 10.0, FontWeight.BOLD, FontPosture.ITALIC));
 
    /** The family font. */
    FontItem TEST_FAMILY_FONT_2 = create(new FamilyFont("sansserif", 16.0, FontWeight.BOLD));
 
    /** The family font. */
    FontItem TEST_FAMILY_FONT_3 = create(new FamilyFont("monospaced", 17.0, FontPosture.ITALIC));
 
    /** The family font. */
    FontItem TEST_FAMILY_FONT_4 = create(new FamilyFont("monospaced", 8.0));
 
}

Parameters