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

JRebirth Origins
The small story of JRebirth project.

Introduction

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 has got 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 developers.

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

To address 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, you should find a way to make it easier unless you will be probably struggle with incomprehensible problems so hard to debug. Moreover this kind of problems always occur when you are demo-ing your application to 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, within JRebirth Layers shall be seen as Area because there is no hierarchy between them, all Areas can communicate with others but they provide real concerns separation.

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 developers
  5. Be lightweight and modularizable
  6. Follow OSS spirit and Java Best Practices

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

Installation

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 directly from the home page or here: apps.jrebirth.org

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 all 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 an optional library used to manage logs. The second one is the core library of JRebirth Application Framework. The last one is used to displayed basic JRebirth Preloader.

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
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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
<?xml version="1.0" encoding="UTF-8"?>
    <modelVersion>4.0.0</modelVersion>
  
    <parent>
        <groupId>org.jrebirth</groupId>
        <artifactId>af</artifactId>
        <version>8.6.0</version>
        <relativePath>..</relativePath>
    </parent>
  
    <groupId>org.jrebirth.af</groupId>
    <artifactId>sample</artifactId>
    <packaging>jar</packaging>
  
    <name>Sample Application</name>
    <url>http://www.sample.org</url>
    <description>Built with JRebirth Framework</description>
  
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  
        <storepass>storepass</storepass>
        <storetype>JKS</storetype>
        <keyalias>keyalias</keyalias>
        <keypass>keypass</keypass>
  
        <deletekeystore>true</deletekeystore>
        <genkeystore>true</genkeystore>
  
        <permissions>all-permissions</permissions> <!-- or sandbox -->
        <deployUrl>http://apps.jrebirth.org</deployUrl>
        <deployPath>${project.artifactId}/${project.version}</deployPath>
        <codebase>${deployUrl}/${deployPath}</codebase>
  
        <jnlpFilename>sample.jnlp</jnlpFilename>
        <preloaderClass>org.jrebirth.af.preloader.JRebirthPreloader</preloaderClass>
        <appClass>org.jrebirth.af.sample.SampleApplication</appClass>
  
        <appletWidth>1024</appletWidth>
        <appletHeight>768</appletHeight>
  
        <updateCheck>background</updateCheck>
        <updatePolicy>prompt-update</updatePolicy>
  
    </properties>
  
    <organization>
        <name>JRebirth</name>
        <url>http://www.jrebirth.org</url>
    </organization>
  
    <build>
  
        <resources>
            <resource>
                <filtering>false</filtering>
                <directory>${basedir}/src/main/java</directory>
                <includes>
                    <include>**/*.fxml</include>
                    <include>**/*.properties</include>
                    <include>**/*.txt</include>
                </includes>
            </resource>
            <resource>
                <filtering>true</filtering>
                <directory>${basedir}/src/main/resources</directory>
                <includes>
                    <include>**/*.*</include>
                </includes>
                <excludes>
                    <exclude>**/*.ttf</exclude>
                </excludes>
            </resource>
            <resource>
                <filtering>false</filtering>
                <directory>${basedir}/src/main/resources</directory>
                <includes>
                    <include>**/*.ttf</include>
                </includes>
            </resource>
        </resources>
  
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
  
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>2.4</version>
  
                    <configuration>
                        <archive>
                            <manifestEntries>
                                <JavaFX-Version>2.0</JavaFX-Version>
                                <Main-Class>${appClass}</Main-Class>
                                <JavaFX-Application-Class>${appClass}</JavaFX-Application-Class>
                            </manifestEntries>
  
                            <manifest>
                                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
                                <addClasspath>true</addClasspath>
                            </manifest>
                        </archive>
                    </configuration>
                </plugin>
  
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>webstart-maven-plugin</artifactId>
                    <version>1.0-beta-6</version>
  
                    <dependencies>
                        <dependency>
                            <groupId>org.codehaus.mojo</groupId>
                            <artifactId>keytool-api-1.7</artifactId>
                            <version>1.4</version>
                        </dependency>
                        <dependency>
                            <groupId>org.codehaus.mojo</groupId>
                            <artifactId>webstart-pack200-impl</artifactId>
                            <version>1.0-beta-6</version>
                        </dependency>
                    </dependencies>
  
                    <executions>
                        <execution>
                            <phase>package</phase>
                            <goals>
                                <goal>jnlp-inline</goal>
                            </goals>
                        </execution>
                    </executions>
  
                    <configuration>
  
                        <filenameMapping>full</filenameMapping>
  
                        <updateManifestEntries>
                            <Application-Name>${project.name}</Application-Name>
                            <Trusted-Library>true</Trusted-Library>
                            <Permissions>${permissions}</Permissions>
                            <Codebase>${codebase}</Codebase>
                            <Trusted-Only>true</Trusted-Only>
                        </updateManifestEntries>
  
  
                        <jnlpFiles>${jrebirth.jnlp.filename}</jnlpFiles>
                        <excludeTransitive>false</excludeTransitive>
  
                        <libPath>lib</libPath>
                        <codebase>${codebase}</codebase>
  
                        <jnlp>
                            <outputFile>${jnlpFilename}</outputFile>
                            <mainClass>${appClass}</mainClass>
                            <offlineAllowed>true</offlineAllowed>
                            <allPermissions>true</allPermissions>
                        </jnlp>
  
                        <sign>
                            <keystore>${keystore}</keystore>
                            <keypass>${keypass}</keypass>
                            <storepass>${storepass}</storepass>
                            <storetype>${storetype}</storetype>
  
                            <alias>${keyalias}</alias>
  
                            <validity>360</validity>
                            <dnameCn>JRebirth Self-signed Certificate</dnameCn>
                            <dnameOu>JRebirth OSS</dnameOu>
                            <dnameO>JRebirth</dnameO>
                            <dnameL>Toulouse</dnameL>
                            <dnameSt>Haute-Garonne</dnameSt>
                            <dnameC>FR</dnameC>
                            <verify>true</verify>
  
                            <keystoreConfig>
                                <delete>${deletekeystore}</delete>
                                <gen>${genkeystore}</gen>
                            </keystoreConfig>
  
                        </sign>
  
                        <unsignAlreadySignedJars>true</unsignAlreadySignedJars>
  
                        <pack200>
                            <enabled>true</enabled>
                        </pack200>
                        <gzip>true</gzip>
  
                        <outputJarVersions>false</outputJarVersions>
  
                        <install>false</install>
                        <verbose>true</verbose>
                    </configuration>
  
                </plugin>
  
  
                <!-- Project Quality -->
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>sonar-maven-plugin</artifactId>
                    <version>2.5</version>
                </plugin>
  
            </plugins>
        </pluginManagement>
  
        <extensions>
            <extension>
                <groupId>org.apache.maven.wagon</groupId>
                <artifactId>wagon-ftp</artifactId>
                <version>2.2</version>
            </extension>
        </extensions>
    </build>
  
    <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>core</artifactId>
            <version>8.6.0</version>
        </dependency>
  
        <dependency>
            <groupId>org.jrebirth.af</groupId>
            <artifactId>preloader</artifactId>
            <version>8.6.0</version>
        </dependency>
    </dependencies>
  
</project>

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

Maven Repositories

Choose your destiny ! …and finish brillantly 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 (Snapshots and Releases) oss.jfrog.org
  2. Our public repository powered by Bintray (Only Releases, On demand Jenkins build)
  3. JCenter Bintray repository (Only Releases, Superset of Maven Central)
  4. Our internal repository powered by Artifactory (could be offline)
  5. Maven Central, all releases are synchronized with Central repository
  6. Any subset of all available repositories

JRebirth is published on Maven Central repository with the help of Bintray synchronization (without having to modify your pom files). If you don’t want to bother yourself with all these settings, you should just proxy JCenter repository.

When searching the latest versio of JRebirth, 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.

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
<?xml version="1.0" encoding="UTF-8" ?>
<project>
    <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>
        <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>
    </pluginRepositories>
</project>

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.

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
<?xml version="1.0" encoding="UTF-8" ?>
    <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>
    <activeProfiles>
        <activeProfile>OJO</activeProfile>
    </activeProfiles>
</settings>

Bintray

JRebirth artifacts are pushed to Bintray repository and then automatically synchronized to JCenter main Bintray repository. Only release version are managed. These repository can also host other kind of binaries.

It’s possible to add only JRebirth Bintray repository to your project, but it’s more convenient to add JCenter repository which aggregate all configured 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, moreover since it has became a superset of Maven Central.

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

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
<?xml version="1.0" encoding="UTF-8" ?>
    <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>
    <activeProfiles>
        <activeProfile>JCenter</activeProfile>
    </activeProfiles>
</settings>

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 (like Applications) that are not pushed on OJO or Bintray.

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

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
<?xml version="1.0" encoding="UTF-8" ?>
    <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>
    <activeProfiles>
        <activeProfile>JRebirth</activeProfile>
    </activeProfiles>
</settings>

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

Maven Central

Maven Central is the default repository used by any Maven installation, so you don’t have to configure something to be able to grab JRebirth artifacts. JRebirth artifacts are synchronized with Central since 7.7.0 version.

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) or 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

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
<?xml version="1.0" encoding="UTF-8" ?>
    <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>
</settings>

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

Overview

How does JRebirth work ?

JRebirth uses a WB-CS-MVC pattern with 7 main layers of abstractions:

  • Wave
  • Behavior
  • Command
  • Service
  • Model
  • View
  • Controller

The illustration below shows 5 of them (Waves and Behaviors are not shown).

Components

3 of these elements are considered as JRebirth Components:

  • Command
  • Service
  • Model

Each Component can do generic things, like communicating with others Components synchronously or asynchronously. All these components are handled by 3 Facades (CF, CF, UF).

Learn more about Components

Application

The Application (App) is the starting class to extends to build a JavaFX 8 application that uses JRebirth Application Framework. Default implementation contains some convenient method to speed up development and to start the JRebirth engine.

Learn more about Application

GlobalFacade

The GlobalFacade (GF) is started by the Application and is just used to simplify access to all Components.

Learn more about GlobalFacade

Notifier and Wave

The Notifier (N) is the event bus that carry on small notifications called Waves (W). This notification engine is integrated into the core of JRebirth. Its role is to manage communication throught all JRebirth Components. A Wave is a quick and lightweight message that can be addressed to one or several other Components. There are a lot of convenient method available to create and launch Waves into the application.

Learn more about Notifier

CommandFacade and Commands

Command Components are used to manage complex group of actions: MultiCommand (MC) synchronously or asynchronously, or atomic reusable action :Command (C). They can be processed into one of the 3 kinds of thread managed by using annotation or class inheritance. All interactions between layers should be managed by Commands to keep control on thread involved.

Learn more about Commands

ServiceFacade and Services

Service (S) Components are used to communicate with others Applications (like a Server), to perform long tasks (computations). It can use Spring HttpRequests, Web Services, Restlets, files or whatever you want. Service’s methods are called into a dedicated thread pool, and their results are sent to other Components that have declared they are interested in. It also manages the thread in which the receiver will process the result.

Learn more about Services

UiFacade and Models

Model (M) Components are used to manage the User Interface, it has control on the View element and shall provide data to display for example by calling a Service. A Model creates its attached View automatically by reflection. A Model manage the View logic.

Learn more about Models

Views

The View (V) elements are not Components, they are used to create user interface by using JavaFX visual components (with plain java code or by loading FXML files).

Each view has a Model used to load data and to be linked with other Components. Each view have a Controller used to manage JavaFX nodes events, the 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 packages. It can also use an annotation to automatically call a dedicated method into the controller.

Controllers

Controller (C) elements are not Components, they are used to manage the View’s Interactions logic. They intercept all JavaFX components’ events. A controller can implements custom Adapters 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. They also provide a set of convenient methods used to perform common event handling, ie: sending a wave when a button click is performed.

Application Starter
How to create your first start class.

Application

Overview

JRebirth Application Framework offers a custom class that extends the basic javafx.Application class. Its 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 :

103
113
124
133
143
protected static void preloadAndLaunch(final String... args) {
protected static void preloadAndLaunch(final Class<? extends Preloader> preloaderClass, final String... args) {
protected static void preloadAndLaunch(final Class<? extends Application> appClass, final Class<? extends Preloader> preloaderClass, final String... args) {
protected static void launchNow(final String... args) {
protected static void launchNow(final Class<? extends Application> appClass, final String... args) {

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

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

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

1
2
3
4
5
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
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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
<?xml version="1.0" encoding="UTF-8"?>
    <modelVersion>4.0.0</modelVersion>
  
    <parent>
        <groupId>org.jrebirth</groupId>
        <artifactId>af</artifactId>
        <version>8.6.0</version>
        <relativePath>..</relativePath>
    </parent>
  
    <groupId>org.jrebirth.af</groupId>
    <artifactId>sample</artifactId>
    <packaging>jar</packaging>
  
    <name>Sample Application</name>
    <url>http://www.sample.org</url>
    <description>Built with JRebirth Framework</description>
  
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  
        <storepass>storepass</storepass>
        <storetype>JKS</storetype>
        <keyalias>keyalias</keyalias>
        <keypass>keypass</keypass>
  
        <deletekeystore>true</deletekeystore>
        <genkeystore>true</genkeystore>
  
        <permissions>all-permissions</permissions> <!-- or sandbox -->
        <deployUrl>http://apps.jrebirth.org</deployUrl>
        <deployPath>${project.artifactId}/${project.version}</deployPath>
        <codebase>${deployUrl}/${deployPath}</codebase>
  
        <jnlpFilename>sample.jnlp</jnlpFilename>
        <preloaderClass>org.jrebirth.af.preloader.JRebirthPreloader</preloaderClass>
        <appClass>org.jrebirth.af.sample.SampleApplication</appClass>
  
        <appletWidth>1024</appletWidth>
        <appletHeight>768</appletHeight>
  
        <updateCheck>background</updateCheck>
        <updatePolicy>prompt-update</updatePolicy>
  
    </properties>
  
    <organization>
        <name>JRebirth</name>
        <url>http://www.jrebirth.org</url>
    </organization>
  
    <build>
  
        <resources>
            <resource>
                <filtering>false</filtering>
                <directory>${basedir}/src/main/java</directory>
                <includes>
                    <include>**/*.fxml</include>
                    <include>**/*.properties</include>
                    <include>**/*.txt</include>
                </includes>
            </resource>
            <resource>
                <filtering>true</filtering>
                <directory>${basedir}/src/main/resources</directory>
                <includes>
                    <include>**/*.*</include>
                </includes>
                <excludes>
                    <exclude>**/*.ttf</exclude>
                </excludes>
            </resource>
            <resource>
                <filtering>false</filtering>
                <directory>${basedir}/src/main/resources</directory>
                <includes>
                    <include>**/*.ttf</include>
                </includes>
            </resource>
        </resources>
  
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
  
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>2.4</version>
  
                    <configuration>
                        <archive>
                            <manifestEntries>
                                <JavaFX-Version>2.0</JavaFX-Version>
                                <Main-Class>${appClass}</Main-Class>
                                <JavaFX-Application-Class>${appClass}</JavaFX-Application-Class>
                            </manifestEntries>
  
                            <manifest>
                                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
                                <addClasspath>true</addClasspath>
                            </manifest>
                        </archive>
                    </configuration>
                </plugin>
  
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>webstart-maven-plugin</artifactId>
                    <version>1.0-beta-6</version>
  
                    <dependencies>
                        <dependency>
                            <groupId>org.codehaus.mojo</groupId>
                            <artifactId>keytool-api-1.7</artifactId>
                            <version>1.4</version>
                        </dependency>
                        <dependency>
                            <groupId>org.codehaus.mojo</groupId>
                            <artifactId>webstart-pack200-impl</artifactId>
                            <version>1.0-beta-6</version>
                        </dependency>
                    </dependencies>
  
                    <executions>
                        <execution>
                            <phase>package</phase>
                            <goals>
                                <goal>jnlp-inline</goal>
                            </goals>
                        </execution>
                    </executions>
  
                    <configuration>
  
                        <filenameMapping>full</filenameMapping>
  
                        <updateManifestEntries>
                            <Application-Name>${project.name}</Application-Name>
                            <Trusted-Library>true</Trusted-Library>
                            <Permissions>${permissions}</Permissions>
                            <Codebase>${codebase}</Codebase>
                            <Trusted-Only>true</Trusted-Only>
                        </updateManifestEntries>
  
  
                        <jnlpFiles>${jrebirth.jnlp.filename}</jnlpFiles>
                        <excludeTransitive>false</excludeTransitive>
  
                        <libPath>lib</libPath>
                        <codebase>${codebase}</codebase>
  
                        <jnlp>
                            <outputFile>${jnlpFilename}</outputFile>
                            <mainClass>${appClass}</mainClass>
                            <offlineAllowed>true</offlineAllowed>
                            <allPermissions>true</allPermissions>
                        </jnlp>
  
                        <sign>
                            <keystore>${keystore}</keystore>
                            <keypass>${keypass}</keypass>
                            <storepass>${storepass}</storepass>
                            <storetype>${storetype}</storetype>
  
                            <alias>${keyalias}</alias>
  
                            <validity>360</validity>
                            <dnameCn>JRebirth Self-signed Certificate</dnameCn>
                            <dnameOu>JRebirth OSS</dnameOu>
                            <dnameO>JRebirth</dnameO>
                            <dnameL>Toulouse</dnameL>
                            <dnameSt>Haute-Garonne</dnameSt>
                            <dnameC>FR</dnameC>
                            <verify>true</verify>
  
                            <keystoreConfig>
                                <delete>${deletekeystore}</delete>
                                <gen>${genkeystore}</gen>
                            </keystoreConfig>
  
                        </sign>
  
                        <unsignAlreadySignedJars>true</unsignAlreadySignedJars>
  
                        <pack200>
                            <enabled>true</enabled>
                        </pack200>
                        <gzip>true</gzip>
  
                        <outputJarVersions>false</outputJarVersions>
  
                        <install>false</install>
                        <verbose>true</verbose>
                    </configuration>
  
                </plugin>
  
  
                <!-- Project Quality -->
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>sonar-maven-plugin</artifactId>
                    <version>2.5</version>
                </plugin>
  
            </plugins>
        </pluginManagement>
  
        <extensions>
            <extension>
                <groupId>org.apache.maven.wagon</groupId>
                <artifactId>wagon-ftp</artifactId>
                <version>2.2</version>
            </extension>
        </extensions>
    </build>
  
    <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>core</artifactId>
            <version>8.6.0</version>
        </dependency>
  
        <dependency>
            <groupId>org.jrebirth.af</groupId>
            <artifactId>preloader</artifactId>
            <version>8.6.0</version>
        </dependency>
    </dependencies>
  
</project>

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.

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
public class JRebirthPreloader extends AbstractJRebirthPreloader {
  
    /**
     * {@inheritDoc}
     */
    @Override
    protected Scene createPreloaderScene() {
  
        final StackPane p = new StackPane();
        p.setStyle("-fx-background-color: transparent;");
  
        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));
  
        final Scene scene = new Scene(p, 600, 200, Color.TRANSPARENT);
        return scene;
    }
  
    /**
     * {@inheritDoc}
     */
    @Override
    protected void hideStage() {
        final Stage stage = this.preloaderStage;
  
        final ScaleTransition st = new ScaleTransition();
        st.setFromX(1.0);
        st.setToX(0.0);
        st.setDuration(Duration.millis(400));
        st.setNode(stage.getScene().getRoot());
        st.setOnFinished(actionEvent -> stage.hide());
        st.play();
    }
  
    public static void main(String... args) {
        launch(args);
    }
  
}

You can extends the AbstractJRebirthPreloader to just have to populate your scene within createPreloaderScene method, and to add a nice animation used to hide the preloader stage within hideStage method.

Application Customization

To define your own Application class you have 2 options:

The AbstractApplication class will do all extra stuff required to launch JRebirth engine. You don’t have to bother about it.

This class skeleton provides some hooks to allow customization of the application start up.

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

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

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

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

Root Node

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

First Model Class

The method Class<? extends Model> firstModelClass() 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 applicationTitle() 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”));

Resources preloading

XXX TODO review with @Preload

Custom case for fonts: 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<ResourceEnum> getResourceToPreload() is used to preload resources to allow them to be used earlier (ie font in 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>preBootWaveList()
  • List<Wave>postBootWaveList()

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 :

Default key shortcuts

The AbstractApplication class is using two defaults hotkey:

  • 619
    protected KeyCode fullScreenKeyCode() {
    KeyCode fullScreenKeyCode() => to go to to fullscreen mode (default is <F10>)

  • 630
    protected KeyCode iconifiedKeyCode() {

    KeyCode iconifiedKeyCode() => 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

    648
    protected UncaughtExceptionHandler getJatUncaughtExceptionHandler() {

  • Exception handler of the JRebirth Internal Thread

    657
    protected UncaughtExceptionHandler getJitUncaughtExceptionHandler() {

  • Default Exception handler of all other threads

    639
    protected UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() {

Initialization Phases

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:

206
207
208
209
/**
 * 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(MESSAGEID)) where MESSAGE_ID is 200 or 300.

211
212
213
214
/**
 * 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(MESSAGEID)) 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:

76
@Configuration(".*jrebirth")

Hereafter you have a full annotation usage:

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

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:

77
@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
Writing concurrent application could be painful ! Not with JRebirth

Thread Management

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 and 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.250 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 or if the platform has not enought cpu cores.

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 should create an higher priority task to trigger an User interface refresh. The HPTP will be used to trigger 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 Swing 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

JIT does same things than JAT but for managing communication between JRebirth Components. It mainly manage the JRebirth Notifier. So any call to Notifier outside JIT will raise a new Exception. For a standrad usage it will be totally transparent for you.

But sometimes you can want to trigger a command within JIT to be sure it will be executed before another wave is handled. This is a really risky task, do it on your own responsability.

JRebirth Thread Pool

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

All Wave processing tasks are automatically done into the JIT.

When the JRebirth Framework needs to update UI (thanks to Model area), it’s done automatically done into the JAT. No matters to have !

But when you call a component directly (synchronous method: getCommand, getService, getModel), your call is processed into the current thread, so you must pay attention to what you are doing.

Examples

If you are into the JIT

  • getCommand => the code will be processed synchronously into JIT, but if you call the run method of the command it will be run into the thread defined by the command configuration.
  • getModel => all called methods of the model will be processed into the JIT ==> Can Break !!! Toolkit will raise an error if any node displayed within the current scene is updated.
  • getService => all methods called of the service will be processed into the JIT, can dramatically slow down messaging.

When you are into the JIT you can use JRebirth.runIntoJAT that call internally Platform.runLater to perform UI updates.

JRebirth Framework offers some default commands to do the trick (DefaultUiCommand). Moreover if you send a Wave with a WaveType listened by a model, it will be automatically processed into the JAT (Cool JRebirth Magic).

If you are into the JAT

  • getCommand, getService => this code will be processed into JAT: Don’t perform long task or you will freeze the application !!!
  • getModel => actions will be done synchronously into JAT ==> tolerated for short UI actions

When you are in the JAT you must use JRebirth.runIntoJIT that call internally JRebirthThread.runLater to run tasks into the core thread. You can also use JRebirth.runIntoJTP to run task within a slot of available thread pool.

JRebirth Framework offers some default commands to force to run into JIT & JTP (DefaultCommand and DefaultPoolCommand). You can also send a wave that will trigger a command, or call a service into another thread that JAT.

Run Synchronously

Sometimes you want to run a task within another thread but synchronously. It’s a bad idea but can be required in some particular cases. You can use custom methods:

  • JRebirth.runIntoJATSync
  • JRebirth.runIntoJITSync
  • JRebirth.runIntoJTPSync

Each will wait synchronously the end of the task run within the wanted thread.

Use Case

If you want to call a service which make an asynchronous call to a remote server. You can use a Service Task method to initiate the call (By default this call will be managed into JRebirth Thread Pool: JTP), but the return must be managed into the JAT:

  • Either use a wave to send data received from remote server, default service will generate a such wave (automatically processed into the JAT for models that listen it)
  • Either call the model from the JAT and access to service results (the first way way is the best to use)

Wave customization

When components communicate using Wave, any Wave handler method can be managed in a specific thread by using @RunInto annotation.

The default behavior is:

  • Into JAT for Model
  • Into JIT for Service
  • Into JIT for Command

3 kinds of Wave are managed differently (not customizable) because they trigger special Component interaction:

  • attachUI => always into JAT
  • returnData => always into JTP
  • callCommand => Depends on Command called

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

Logging

My first idea about logging for JavaFX apps 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.

4
5
6
7
8
<dependency>
    <groupid>org.slf4j</groupid>
    <artifactid>slf4j-api</artifactid>
    <version>1.7.5</version>
</dependency>

The slf4j api allow to declare logger for JRebirth Framework but doesn’t provide any logger implementation. So you need to add another dependency to choose the implementation you want to use.

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.

There are 3 implementations available:

  • NOP
  • LOGback
  • SimpleLogger

NOP

To avoid this error message to appear you can add the No-OPeration dependency, it will log nothing but avoid the previous error message:

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 customization
  • Disable Message resolution for high performance

Note that you can still use your logger basic features.

Short UML Diagram:

Logging Class Diagram

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

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

Then you can log your message like this:

105
108
159
174
273
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 occurred into the JRebirth Thread". */
    MessageItem JIT_ERROR = create(new LogMessage("jrebirth.concurrent.jitError", JRLevel.Error, JRebirthMarkers.CONCURRENT));
  
    /** "An error occurred while shutting 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));
  
}

Facades Area
Simplify component accessibility.

Facade(s)

JRebirth provides 4+1 Facades used to manage its dedicated pattern wB-CS-Mvc.

Overview

Short UML Diagram:

Global Facade

One Facade to rule them all

JRebirth uses a global facade automatically created by JRebirth AbstractApplication class. Its main role is to manage all JRebirth components by using Facades.

Internal Communication

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

Linked with Application

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

Manage Local Facades

It allows to manage the three main facades (+extra one):

  • CommandFacade
  • ServiceFacade
  • UiFacade
  • BehaviorFacade

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

52
57
62
67
LocalFacade<Model> uiFacade();
LocalFacade<Service> serviceFacade();
LocalFacade<Command> commandFacade();
LocalFacade<Behavior<?, ?>> behaviorFacade();

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.

Local Facades

There are 4 LocalFacade, each one is responsible of an area storing same kind of components.

Basic Features

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

  • register : store a pre-built component
  • unregister : remove a component
  • exists : check if the component is already registered, it could be released
  • retrieve : used to get a component (build and register it if needed)
43
53
62
72
83
95
107
120
132
145
156
<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> boolean exists(final UniqueKey<E> uniqueKey);
<E extends R> boolean exists(final Class<E> clazz, 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> List<E> retrieveMany(final UniqueKey<E> uniqueKey);
<E extends R> List<E> retrieveMany(final Class<E> clazz, final Object... keyPart);
<E extends R> List<E> retrieveFilter(UniqueKey<E> uniqueKey);

Component Key

Each Component (b-CSM) are registered into the facade using a UniqueKey, there are 2 options:

ClassKey

The simplest key is the ClassKey, it’s only composed by the class object of the component, thus the can only be stored once into the facade (=singleton).

MultitonKey

The MultitonKey uses also the class of the component but add a variable part to be able to store several instances of a same Component.

Convenient Builder

JRebirth API has got a lot of methods that take the Component Class with additional objects as key parts, but the API also provides a convenient class used to build key.

41
63
84
98
113
static <C> UniqueKey<C> create(final Class<C> clazz, final Object... keyPart) {
static <C> UniqueKey<C> create(final Class<C> clazz, final Object[] optionalData, final Object... keyPart) {
static <C> UniqueKey<C> createSingle(final Class<C> clazz, final Object... optionalData) {
static <C> UniqueKey<C> createMulti(final Class<C> clazz, final Object... keyPart) {
static <C> UniqueKey<C> createMulti(final Class<C> clazz, final Object[] keyPart, final Object... optionalData) {

Provide Tracking

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

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

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

Messaging engine

JRebirth provides its own event bus to manage asynchronous communication between all components. Synchronous communication can be achieved by using direct method call.

Communication Engine - Class Diagram

Notifier aim

JRebirth has got its own Notification Engine to allow each component to communicate with each other. All these communications are processed into a dedicated thread described before. The notification engine uses custom objects to transport data : Waves.

We will have an overview of them within the next section.

Wave Area
Allow asynchronous communication between components

Wave Overview

What’s a Wave

A Wave is a temporary object created to carry data througt CSM components. They allow JRebirth to handle application internal events asynchronously between each component, 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 (JIT).

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 Item

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 !

This is the only way to reference a generic type at compile time.

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

67
68
69
70
71
72
73
/** The file containing all events serialized. */
WaveItemBase<File> EVENTS_FILE = new WaveItemBase<File>() {
};
  
/** The name of the events. */
WaveItemBase<List<JRebirthEvent>> EVENTS = new WaveItemBase<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.

106
107
108
109
110
111
/** This wave item will be used only into a WaveData to pass the current Service task handled by the wave. */
WaveItem<ServiceTaskBase<?>> SERVICE_TASK = new WaveItemBase<ServiceTaskBase<?>>(false) {
};
  
/** This wave item will be used only into a WaveData to pass the right progress property used by service task. */
WaveItem<DoubleProperty> PROGRESS_PROPERTY = new WaveItemBase<DoubleProperty>(false) {
151
152
// Attach ServiceTask to the source wave
sourceWave.addDatas(WBuilder.waveData(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)
39
40
41
42
43
/** Trigger a Unload wave. */
WaveType DO_UNLOAD = WBuilder.waveType("UNLOAD");
  
/** Trigger a Play wave. */
WaveType DO_PLAY = WBuilder.waveType("PLAY");
  • Into a service class (here with on argument) :
41
42
43
44
45
46
47
48
/** Wave type use to load events. */
WaveType DO_LOAD_EVENTS = WBuilder.waveType("LOAD_EVENTS")
                                  .items(EditorWaves.EVENTS_FILE)
                                  .returnAction("EVENTS_LOADED")
                                  .returnItem(EditorWaves.EVENTS);
  
/**
 * Parse the event file.

WaveType name

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

A best practice is to store them within static final field or within an interface to avoid their replication (possible source of error) especially when used within @OnWave annotation.

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 : All Wave Handlers have been called, or the Service Method or the Command has been called
  6. Handled : All Wave Handlers are terminated
  7. Failed : The wave process has generated an error
  8. Destroyed : the wave is available for garbage collection

Chained Wave

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

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
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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
/**
 * 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.concurrent;
  
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
  
import javafx.scene.Scene;
  
import org.jrebirth.af.api.application.JRebirthApplication;
import org.jrebirth.af.api.concurrent.JRebirthRunnable;
import org.jrebirth.af.api.exception.CoreException;
import org.jrebirth.af.api.exception.JRebirthThreadException;
import org.jrebirth.af.api.facade.GlobalFacade;
import org.jrebirth.af.api.log.JRLogger;
import org.jrebirth.af.api.wave.Wave;
import org.jrebirth.af.core.command.basic.showmodel.DisplayModelWaveBean;
import org.jrebirth.af.core.command.basic.showmodel.ShowModelCommand;
import org.jrebirth.af.core.facade.GlobalFacadeBase;
import org.jrebirth.af.core.key.Key;
import org.jrebirth.af.core.log.JRLoggerFactory;
import org.jrebirth.af.core.resource.provided.parameter.CoreParameters;
import org.jrebirth.af.core.service.basic.StyleSheetTrackerService;
import org.jrebirth.af.core.wave.WBuilder;
  
/**
 * The class <strong>JRebirthThread</strong>.
 *
 * @author Sébastien Bordes
 */
public final class JRebirthThread extends Thread implements ConcurrentMessages {
  
    /** The JRebirth Internal Thread name [JIT]. */
    public static final String JIT_NAME = "JIT";
  
    /** The class logger. */
    private static final JRLogger LOGGER = JRLoggerFactory.getLogger(JRebirthThread.class);
  
    /** The unique instance of the current class. */
    private static JRebirthThread internalThread;
  
    /** The Global Facade object that handle other sub facade. */
    private transient GlobalFacade facade;
  
    /** The javaFX application that launch this thread. */
    private transient JRebirthApplication<?> application;
  
    /** The list of tasks being processed, all access MUST BE synchronized. */
    private final PriorityBlockingQueue<JRebirthRunnable> processingTasks;
  
    /** Flag indicating that current thread has started and is ready to process events. */
    private final AtomicBoolean hasStarted = new AtomicBoolean(false);
  
    /** Flag to stop the infinite loop that process JRebirth Events. */
    private final AtomicBoolean infiniteLoop = new AtomicBoolean(true);
  
    /** Flag that indicate that the closure must be forced. */
    private final AtomicBoolean forceClose = new AtomicBoolean(false);
  
    /**
     * private final boolean readyToShutdown = false;
     *
     * /** Build the JRebirth Thread.
     */
    private JRebirthThread() {
        super(JIT_NAME);
  
        // Daemon-ize this thread, thus it will be killed with the main JavaFX Thread
        setDaemon(true);
  
        // Initialize the queue
        this.processingTasks = new PriorityBlockingQueue<>(10, new JRebirthRunnableComparator());
    }
  
    /**
     * Run into thread pool.
     *
     * If a slot is available the task will be run immediately.<br />
     * Otherwise it will run as soon as a slot will be available according to the existing task queue
     *
     * @param runnable the task to run
     */
    public void runIntoJTP(final JRebirthRunnable runnable) {
  
        if (getFacade().executorService().checkAvailability(runnable.priority())) {
            getFacade().executorService().execute(runnable);
            LOGGER.log(JTP_QUEUED, runnable.toString());
        } else {
            getFacade().highPriorityExecutorService().execute(runnable);
            LOGGER.log(HPTP_QUEUED, runnable.toString());
        }
  
    }
  
    /**
     * Run this task as soon as possible. Enqueue the task to be run at the next event pulse. Run into the JRebirth Thread
     *
     * @param runnable the task to run
     */
    public void runLater(final JRebirthRunnable runnable) {
        this.processingTasks.add(runnable);
    }
  
    /**
     * Launch the JRebirth thread.
     *
     * @param application the javaFX application instance
     */
    public void prepare(final JRebirthApplication<?> application) {
  
        // Link the current application
        this.application = application;
        // Build the global facade at startup
        this.facade = new GlobalFacadeBase(application);
  
        // Start the thread (infinite loop)
        // start();
    }
  
    /**
     * Return true if JRebirth has been correctly started (boot action is done).
     *
     * @return true if JRebirth has been correctly started
     */
    public boolean hasStarted() {
        return this.hasStarted.get();
    }
  
    /**
     * {@inheritDoc}
     */
    @Override
    public void run() {
  
        manageStyleSheetReloading(this.application.scene());
  
        // Attach the first view and run pre and post command
        try {
            bootUp();
        } catch (final JRebirthThreadException e) {
            LOGGER.error(BOOT_UP_ERROR, e);
        }
  
        // JRebirth thread has boot up and is ready to process events
        this.hasStarted.set(true);
  
        while (this.infiniteLoop.get()) {
            try {
                if (!this.forceClose.get()) {
                    final JRebirthRunnable jrr = this.processingTasks.poll(100, TimeUnit.MILLISECONDS);
                    if (jrr != null) {
                        jrr.run();
                    }
                }
            } catch (final InterruptedException e) {
                LOGGER.error(JIT_ERROR, e);
            }
        }
        // Shutdown the application more properly
        shutdown();
    }
  
    /**
     * Manage style sheet reloading by using a custom service provide by JRebirth Core.
     *
     * @param scene the scene to reload in case of Style Sheet update
     */
    private void manageStyleSheetReloading(final Scene scene) {
        if (CoreParameters.DEVELOPER_MODE.get() && scene != null) {
  
            for (final String styleSheet : scene.getStylesheets()) {
  
                getFacade().serviceFacade().retrieve(StyleSheetTrackerService.class).listen(styleSheet, this.application.scene());
            }
            getFacade().serviceFacade().retrieve(StyleSheetTrackerService.class).start();
        }
    }
  
    /**
     * Attach the first view and run pre and post command.
     *
     * @throws JRebirthThreadException if a problem occurred while calling the command
     */
    public void bootUp() throws JRebirthThreadException {
  
        final List<Wave> chainedWaveList = new ArrayList<>();
  
        // Manage waves to run before the First node creation
        final List<Wave> preBootList = getApplication().preBootWaveList();
        if (preBootList != null && !preBootList.isEmpty()) {
            chainedWaveList.addAll(preBootList);
        }
  
        // Manage the creation of the first node and show it !
  
        final Wave firstViewWave = getLaunchFirstViewWave();
        if (firstViewWave != null) {
            chainedWaveList.add(firstViewWave);
        }
  
        // Manage waves to run after the First node creation
        final List<Wave> postBootList = getApplication().postBootWaveList();
        if (postBootList != null && !postBootList.isEmpty()) {
            chainedWaveList.addAll(postBootList);
        }
  
        if (!chainedWaveList.isEmpty()) {
            getFacade().notifier().sendWave(WBuilder.chainWaveCommand(chainedWaveList));
        }
    }
  
    /**
     * {@inheritDoc}
     */
    @Override
    public void interrupt() {
        super.interrupt();
  
        // Release all resources
        shutdown();
    }
  
    /**
     * This method can be called a lot of time while application is running.
     *
     * The first time to stop the infinite loop, then to purge all queues and let the thread terminate itself.
     */
    public void close() {
  
        // Infinite loop is still active
        if (this.infiniteLoop.get()) {
            // First attempt to close the application
            this.infiniteLoop.set(false);
        } else {
            // N-th attempt to close the application
            this.forceClose.set(true);
  
            // All Task Queues are cleared
            // this.queuedTasks.clear();
            this.processingTasks.clear();
        }
  
    }
  
    /**
     * Release all resources.
     */
    private void shutdown() {
        try {
            this.facade.stop();
            this.facade = null;
            // Destroy the static reference
            destroyInstance();
        } catch (final CoreException e) {
            LOGGER.log(SHUTDOWN_ERROR, e);
        }
    }
  
    /**
     * Destroy the singleton that hold the thread.
     */
    private static void destroyInstance() {
        internalThread = null;
    }
  
    /**
     * Launch the first view by adding it into the root node.
     *
     * @return the wave responsible of the creation of the first view
     */
    @SuppressWarnings("unchecked")
    protected Wave getLaunchFirstViewWave() {
        Wave firstWave = null;
        // Generates the command wave directly to win a Wave cycle
        if (this.application != null && this.application.rootNode() != null && this.application.firstModelClass() != null) {
  
            firstWave = WBuilder.callCommand(ShowModelCommand.class).waveBean(
                                                                              DisplayModelWaveBean.create()
                                                                                                  .childrenPlaceHolder(this.application.rootNode().getChildren())
                                                                                                  .showModelKey(Key.create(this.application.firstModelClass())));
            //
            //
            // ShowModelWaveBuilder.create()
            // .childrenPlaceHolder(this.application.getRootNode().getChildren())
            // .showModelKey(getFacade().getUiFacade().buildKey((Class<Model>) this.application.getFirstModelClass()))
            // .build();
        }
        return firstWave;
  
    }
  
    /**
     * @return Returns the application.
     */
    public JRebirthApplication<?> getApplication() {
        return this.application;
    }
  
    /**
     * @return Returns the facade.
     */
    public GlobalFacade getFacade() {
        return this.facade;
    }
  
    /**
     * Return the JRebirthThread used to manage facades and waves.
     *
     * @return the JRebirthThread
     */
    public static JRebirthThread getThread() {
        synchronized (JRebirthThread.class) {
            if (internalThread == null) {
                internalThread = new JRebirthThread();
            }
        }
        return internalThread;
    }
  
}

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

Command Overview

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 (even across several threads) or in parallel.

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

34
35
36
37
38
39
40
41
42
43
44
45
46
public class ShowModelCommand extends DefaultMultiBeanCommand<DisplayModelWaveBean> {
  
    /**
     * {@inheritDoc}
     */
    @Override
    protected List<UniqueKey<? extends Command>> defineSubCommand() {
        return Arrays.asList(
                             getCommandKey(PrepareModelCommand.class),
                             getCommandKey(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 within JAT).

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 or store data. 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 using four different ways:

Please note that Commands are JRebirth top-level components (with Services and Models), they follow the component lifecycle as described in Facade Page.

Thus AbstractBaseCommand extends BehavioredComponent and Component 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 can provide either the Command Class or its unique key (if required).

47
58
70
81
<C extends Command> C getCommand(final Class<C> clazz, final Object... keyPart);
<C extends Command> C getCommand(final UniqueKey<C> commandKey);
<C extends Command> List<C> getCommands(final Class<C> clazz, final Object... keyPart);
<C extends Command> List<C> getCommands(final UniqueKey<C> commandKey);

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. The perform method of the command will be executed into the Thread Type chosen (JAT, JIT, JTP) by the Command declaration.

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. (in this case no timestamp is added to the command key)

Indirect way

You can trigger a command execution by calling callCommand from any component, it will use JIT to trigger the command then the command will be run using the thread declared.

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

234
242
public final Wave callCommand(final Class<? extends CommandBean<? extends WaveBean>> commandClass, final WaveBean waveBean, final WaveBean... waveBeans) {
public final Wave callCommand(final Class<? extends Command> commandClass, final WaveData<?>... data) {

You can also provide a WaveBean object to store all data.

Convenient Link method

UI’s controllers provide a convenient method named void linkCommand(Node, EventType<E>, Class<? extends Command>, WaveData<?>…)

This method is useful to declare with only one line of code the call of a command triggered when the chosen JavaFX event occurred 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(view().getOpenButton(), MouseEvent.MOUSE_CLICKED, OpenEventTrackerFileCommand.class, CHECK_MOUSE_SINGLE_CLICK);

Messaging 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, or using @OnWave) 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 using 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 :

33
public class AttachModelCommand extends DefaultUIBeanCommand<DisplayModelWaveBean> {

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

37
public class ChainWaveCommand extends DefaultCommand implements WaveListener {

(implementation of WaveListener is optional)

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

36
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.

103
public AbstractBaseCommand(final RunType runType, final PriorityLevel 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:

  • beforePerform
  • perform
  • afterPerform

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

Wave Bean

A WaveBean is a Java Bean that allow carrying a lot of named 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 command’s properties.

Service Area
Process configurable action

Services

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.

Any service listening a WaveType will be retained by Notifier so it will not be elligible for garbage colelction.

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.

41
42
43
44
45
/** Wave type use to load events. */
WaveType DO_LOAD_EVENTS = WBuilder.waveType("LOAD_EVENTS")
                                  .items(EditorWaves.EVENTS_FILE)
                                  .returnAction("EVENTS_LOADED")
                                  .returnItem(EditorWaves.EVENTS);

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.

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

Exit Point Wave Type

The return Wave Type is automatically created with by the using the returnAction value with returnItem as WaveItem. The code above will generate a WaveType that uses only one WaveItem to store the list of events loaded from the given file.

71
72
73
/** The name of the events. */
WaveItemBase<List<JRebirthEvent>> EVENTS = new WaveItemBase<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:

55
56
57
58
59
@Override
public void initService() {
  
    listen(DO_LOAD_EVENTS);
}
<!--|snippet-start-offset=3" />-->

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 except if you want to receive an empty notification when the Job is done; just use JRebirthItems.voidItem.

You can also register your service task by putting @OnWave annoation using the action name of the WaveType. A best practice is to add annoation within interface methods, thus all implementations can avoid to use listen methods.

Perform the Job!

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
public void initService() {
  
    listen(DO_LOAD_EVENTS);
}
  
/**
 * {@inheritDoc}
 */
@Override
@Priority(PriorityLevel.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
    final 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,
           WBuilder.waveData(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.DO_LOAD_EVENTS.returnWaveType());
    listen(EditorWaves.DO_UNLOAD);
}

Add a method that suits 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) {
    view().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 PriorityLevel.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:

55
56
@Priority(PriorityLevel.High)
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:

70
updateMessage(wave, "Parsing events");

You can update progress bar indicator by calling this method:

84
updateProgress(wave, processedLines, totalLines);

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

UI Roles

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

Model Overview

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).

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
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 initInnerComponents() {
        // 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
    }
  
}

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
124
125
126
127
128
129
130
131
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.api.exception.CoreException;
import org.jrebirth.af.api.ui.annotation.OnMouse;
import org.jrebirth.af.api.ui.annotation.type.Mouse;
import org.jrebirth.af.core.ui.AbstractView;
  
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(Mouse.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");
  
        node().setCenter(
                         LabelBuilder.create()
                                     .text("JRebirth Sample")
                                     .build());
  
        node().setBottom(FlowPaneBuilder.create().children(
                                                           this.defaultCommand,
                                                           this.uiCommand,
                                                           this.pooledCommand)
                                        .build());
    }
  
    /**
     * {@inheritDoc}
     */
    @Override
    protected void bootView() {
        // Nothing to do yet
    }
  
    /**
     * {@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
package org.jrebirth.af.sample.ui;
  
import javafx.scene.input.MouseEvent;
  
import org.jrebirth.af.api.exception.CoreException;
import org.jrebirth.af.core.ui.AbstractController;
import org.jrebirth.af.core.ui.adapter.DefaultMouseAdapter;
import org.jrebirth.af.core.wave.WBuilder;
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(view().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
        view().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
        model().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");
  
            model().sendWave(WBuilder.callCommand(SamplePoolCommand.class));
        }
  
    }
  
}

Adapters & Handlers

Behavior Area
Extend your Components.

Behavior

Overview

Short UML Diagram:

Behavior Data

Using Resources
Decrease your memory footprint by using resource wrapper

Resources

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

  • Colors
  • Fonts
  • Images
  • Parameters
  • CSS
  • FXML
  • AudioClip (WIP)
  • Media (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:

Resource loading

Heavyweight resource object can be retrieved by calling the get() method of ResourceItem. If the resource was already loaded it is retrieved from cache otherwise it’s loaded another time and stored weakly. When system goes low memory, all resrouces not strictly retained by a Component or a Scene’s node will elligible for garbage collection to release some bits.

ResourceItem

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:

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

Example of Web color declaration:

35
36
/** 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 use a custom syntax to use default method.

26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public enum EnumColors implements ColorEnum {
  
    // @formatter:off
  
    /** Color for slide title, white. */
    SLIDE_TITLE {{ web("FFFFFF", 1.0); }},
  
    /** Color for blue shape, xxx. */
    SHAPE_BLUE{{ web("3495CE", 1.0); }},
  
    /** Color for drop shadow, black. */
    DROP_SHADOW{{ web("000000", 0.8); }},
  
    /** Color for inner shadow, white. */
    INNER_SHADOW{{ web("FFFFFE", 0.3); }};
  
}

Severals default methods exist, at least one per ResourceParams to create.

Dynamically

We can load dynamicalli resource by creating “anonymous” ResourceItem, the resource will be kept within cache if memory is not constrained otherwise it will be deleted. Another call with the same path will hit the cache and the image will be only stored once, but created as many times as deleted and retrieved again.

524
final Image image = Resources.create(new RelImage(item.getImage())).get();

In this sample item.getImage() return the relative srtring path of the image.

Samples

Color

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

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

Font

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

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

Hereafter the enumeration that stores font names.

10
11
12
13
14
15
16
17
public enum TestFontNames implements FontName {
  
    /** Turtles. */
    Turtles,
  
    /** Report 1942. */
    Report_1942,
}