Sunday, January 12, 2014

Game loops and applying them to JavaFX (1/2)

After spending almost an entire year doing nothing but JavaEE, I decided it was time to have some fun and got back to one of the items on my to-do list: write a series of classic game programming examples using Java and JavaFX. The first such example should be online in the near future, but for now, I'd like to share some thoughts on an important topic for beginning game programmers (that includes myself): the game loop.

The basic loop

If you've done any GUI programming at all, you'll know that GUI frameworks are event-driven. This means that the framework doesn't do very much, it simply waits for an event to occur, processes it, then waits for the next one. Games on the other hand tend to use a loop as a heartbeat that moves time forward.

The simplest possible game loop looks something like:

while (!gameOver)
{
    processEvents();
    updateWorld();
    renderFrame();
}

As you can probably tell, this loops handles any input that needs handling, moves the state of the game world one step forward in time, then renders this new state on screen.

This implementation of the game loop isn't very useful. It simply runs the loop as fast as possible. The result of this is that the faster your machine is, the faster time will move forward in the game. You could end up with a game character walking in slow motion on one machine, while running at the speed of light on another. This approach to a game loop is often called a CPU dependent game loop, for obvious reasons. It could work in situations where the speed of the machine is fixed and known, but that's usually not the case.

In what follows we'll explore several possibilities to improve upon this basic approach. Note that while the code examples will be in Java, they are intended only as illustrations and do not cover every single detail or edge case, so they should be viewed as pseudo-code only.

Variable time steps

Think about this for a while: instead of using a fixed time step (as we did in the basic approach), why not simply measure how much time has passed since the previous iteration of the loop, and use that time difference to update the game world? By synchronising the game time with the real time, the game time will progress at a constant speed, no matter the speed of the machine.

Variable time steps - variable frame rate


A first implementation of this idea could look like:


long previousTime = System.nanoTime();

while (!gameOver)
{
    long currentTime = System.nanoTime();
    long elapsedNanos = currentTime - previousTime;
    double deltaTime = elapsedNanos / 1_000_000_000; /* to seconds */

    processEvents();
    updateWorld(deltaTime);
    renderFrame();

    previousTime = currentTime;
}

In this approach the game world is no longer updated with fixed time steps, but with time steps that depend on the actual time that has passed since the previous iteration of the loop. If one machine is able to run this loop 100 times per second, that machine will update the world 100 times with time steps averaging 10ms. If another machine is only able to run the loop 50 times per second, that machine will update the world 50 times per second with time steps averaging 20ms. The result is the same on both machines: after 1 second of real time, the game time has advanced by 1 second as well.

This approach may seem great at first: not only does the game progress at a constant speed independent of the underlying hardware, the loop actually adjusts the frame rate to the underlying hardware as well as to the current load. The loop runs as fast as possible, but when the load increases (for example: if there are lots of objects that need to be updated), the time steps will increase as well, lowering the frame rate. If the load decreases, the time steps will decrease as well, raising the frame rate.

Variable frame rates are not without their issues however, especially when physics are involved:
  • Low frame rates result in large time steps. Large time steps can cause objects to 'jump' between points, instead of moving smoothly between them. This in turn could result in collisions going undetected, allowing your game objects to pass through one another.
  • High frame rates result in small time steps. As you probably know, floating point numbers only have limited precision, so floating point calculations often involve some sort of rounding error. If the time steps get too small, an unnecessary amount of calculations is performed. This could result in rounding errors accumulating to a point where your game objects end up in the wrong place.
Instead of throwing variable frame rates out the window, let's see if we can address some of these issues.

Variable time steps - variable frame rate with a target frame rate


The first issue we'll address is small time steps as a result of high frame rates. The solution for this is quite simple: impose an upper bound on the frame rate by making the loop wait until a minimum amount of time has passed.

This approach looks something like:

final double targetDelta = 0.0166; /* 16.6ms ~ 60fps */
long previousTime = System.nanoTime();

while (!gameOver)
{
    long currentTime = System.nanoTime();
    double deltaTime = (currentTime - previousTime) / 1_000_000_000; 

    processEvents();
    updateWorld(deltaTime);
    renderFrame();

    previousTime = currentTime;

    double frameTime = (System.nanoTime() - currentTime) / 1_000_000_000;
    if (frameTime < targetDelta) {
        /* wait targetDelta - frameTime seconds */
    }
}

As you can see, we measure the time it took to update the world and if necessary, make the loop wait a while before starting the next iteration. The resulting loop is said to have a target frame rate of 60fps. Because every iteration takes at least 16.6ms, the time steps will never be smaller than 16.6ms.

Variable time steps - variable frame rate with a target frame rate and a maximum time step


The technique we used to address small time steps unfortunately cannot be applied to large time steps as well: while we can tell the loop to wait a while, we can't tell it to 'hurry up' because our frame rate is getting too low. If the hardware can't keep up with our demand, the performance will degrade. There isn't much to be done about that. But we do have a choice as to how the game degrades:

  • We stick to the previous implementation and let the frame rate drop. As a result, the game time keeps progressing at a constant speed but the time steps will grow larger, possibly making the game unplayable due to increased latency and physics issues.
  • We impose an upper bound on the time steps. As a result, we are effectively slowing down time in the game because the time steps used to update the game will be smaller than the actual time that has passed. Whether or not this makes the game any more playable at low frame rates than with the previous approach is hard to say.

If you prefer the second option, your implementation could look like:

final double targetDelta = 0.0166;
final double maxDelta = 0.05;
long previousTime = System.nanoTime();

while (!gameOver)
{
    long currentTime = System.nanoTime();
    double deltaTime = (currentTime - previousTime) / 1_000_000_000; 
    
    if (deltaTime > maxDelta) {
        deltaTime = maxDelta;
    }

    processEvents();
    updateWorld(deltaTime);
    renderFrame();

    previousTime = currentTime;

    double frameTime = (System.nanoTime() - currentTime) / 1_000_000_000;
    if (frameTime < targetDelta) {
        /* wait targetDelta - frameTime seconds */
    }
}

Fixed time steps

It turns out our basic loop did have one advantage: fixed time steps. While the variable time step approach in the previous section might work fine for you, there are a few reasons why you should consider opting for a truly fixed time step.

Fixed time steps are the only way to get truly deterministic (and thus reproducible) behavior. As we discussed before, floating point calculations are not without errors. The result of a 16.4ms time step followed by a 16.8ms time step might not be the same as the result of two 16.6ms time steps. Fixed time steps and the resulting determinism makes it possible to:
  • track down bugs by reproducing the exact circumstances in which the bug occurred.
  • synchronise network play.
  • provide instant replays.

Fixed time steps using an accumulator


Let's take a stab at re-implementing our game loop using fixed time steps:

final double timeStep = 0.0166;
long previousTime = System.nanoTime();
double accumulatedTime = 0;


while (!gameOver)
{
    long currentTime = System.nanoTime();
    
double deltaTime = (currentTime - previousTime) / 1_000_000_000;
    
accumulatedTime += deltaTime;

    processEvents();


    while (
accumulatedTime >= timeStep) {
        updateWorld(timeStep);
        
accumulatedTime -= timeStep;
    }


    renderFrame();


    previousTime = currentTime;
}


The technique here is to slice the amount of time passed into timeStep-sized pieces. If we measured a delta time of 34ms and our time step is 16ms, we simply step the world twice. The remaining 2ms are saved in the accumulator and carried over to the next iteration. By doing this, the game time will progress at a relatively constant speed, yet we have all the benefits of a fixed time step.

There are two issues with this approach however. The first issue is that the number of steps taken in each iteration of the loop can vary. Let's say our time step is set at 16ms, yet most frames take 17-18ms. This 1-2ms difference will slowly build up in the accumulator until it reaches 16ms. At that point the game will move forward 32ms instead of the usual 16ms. This leads to what is knows as temporal aliasing.

The second issue is the so called 'spiral of death' where if one interation takes up a lot of time, the next iteration will have to take additional time steps, causing that iteration to take up even more time, etc. This issue didn't exist with variable time steps because we were updating the world only once, albeit with a large time step.


Fixed time steps using an accumulator and a maximum delta time


Let's start by addressing the spiral of death:

final double timeStep = 0.0166;
final double maxDelta = 0.05;
long previousTime = System.nanoTime();
double accumulatedTime = 0;

while (!gameOver)
{
    long currentTime = System.nanoTime();
    double deltaTime = (currentTime - previousTime) / 1_000_000_000;

    if (deltaTime > maxDelta) {
        deltaTime = maxDelta;
    }

    accumulatedTime += deltaTime;

    processEvents();

    while (accumulatedTime >= timeStep) {
        updateWorld(timeStep);
        accumulatedTime -= timeStep;
    }

    renderFrame();

    previousTime = currentTime;
}

As you can see, this is exactly the same as what we did with the variable time steps. The result is the same as well: by limiting delta time to maxDelta, the game time will effectively slow down once delta time gets larger than maxDelta.


Fixed time steps using an accumulator, a maximum delta time and interpolation


Now, how do we take care of temporal aliasing? Temporal anti-aliasing ofcourse, which in this case boils down to a seemingly simple interpolation:

final double timeStep = 0.0166;
final double maxDelta = 0.05;
long previousTime = System.nanoTime();
double accumulatedTime = 0;

while (!gameOver)
{
    long currentTime = System.nanoTime();
    double deltaTime = (currentTime - previousTime) / 1_000_000_000;

    if (deltaTime > maxDelta) {
        deltaTime = maxDelta;
    }

    accumulatedTime += deltaTime;

    processEvents();

    while (accumulatedTime >= timeStep) {
        updateWorld(timeStep);
        accumulatedTime -= timeStep;
    }

    interpolateWithAlpha(accumulatedTime / timeStep);

    renderFrame();

    previousTime = currentTime;
}

What the interpolation does is simply guess where all the game objects would have been, had we taken the remaining accumulated time into account. This can be expressed as follows:

interpolatedState = currentState + alpha * (nextState - currentState)

which is equal to:

interpolatedState = (1 - alpha) * currentState + alpha * nextState

For example: if the remaining accumulated time is 25% of a complete time step, this interpolation guesses where the game objects would have been, had we taken an additional 25% of a time step.

While this all looks simple in pseudo-code, implementing interpolation is not as easy as the pseudo-code makes it look. In order to calculate the interpolated state, you need to maintain two separate copies of the game state: the current state as well as the next state.

Also note that because the physics system always needs to calculate one step ahead (we need the next state to do the interpolation), this technique adds additional input latency to your game.


Results

So which implementation should you use? Personally I'd say: if your game doesn't need the benefits of using a fixed time step, a variable time step with a target frame rate and a maximum time step should work just fine for you. If you could benefit from a fixed time step, you should weigh those benefits against the cost of implementing interpolation and see if it's worth it.

That's it for part 1. In part 2 I'll cover how to apply all of this to JavaFX. As that is still a work in progress, feedback on part 1 is greatly appreciated!


References

I learned the most from the following sources, so go and check them out:

Monday, December 16, 2013

Teaching JavaFX, Part 3

As part of the preparations for next semester, I taught another introductory JavaFX workshop. I tried to compress as much as I could into 3 hours and focused a lot more on properties and binding than I did last year.

The projects we built can be found here:

You may have already seen part one, as it's simply an update of my Guybrush Threepwood animation example. This example covers the basics of using JavaFX, as well as event handling and animation.


Part two is a new example that covers properties and binding, layout panes, using MVC with FXML and Scene Builder, as well as some basic CSS. If you're an intermediate student, you should particularly pay attention to the way MVC is implemented, namely with the Passive View style of MVC.


Happy coding!

Friday, November 8, 2013

Java for the next generation: what Java can do to appeal more to students

With Java 8 around the corner, I'd like to offer a perspective on the future of Java. In case you didn't know already: I am a full-time Java lecturer, not a full-time Java developer. Hence, this text is written from the perspective of a new generation of computer science students, learning to program with Java as their main language. The items below are based on questions and remarks I have gotten from students over the past years. I have compiled them into a list of things Java can do to appeal more to the current and future generations of students. Whether or not these things are feasible, or even desirable, is not always clear to me. I'll leave that discussion to the experts.

Unified types


Student: "Why are there so many types that do the same thing? Can't I just say 'number' and have Java pick the right one?"

About every student first learning to program asks himself (or herself) that very question. But even after 12 years of Java programming, I still ask myself the same thing: can't the compiler and/or virtual machine be made smart enough to use the right type internally, so we don't have to worry about different size integers and floating point numbers?

Unifying primitive and boxed types is one step in the right direction, but unifying all integer types, or even unifying both integer and floating point types into one number type, could make our favorite programming language a lot easier to learn and use.

Let's apply the principles we use in object-oriented development to our language and hide all those different implementations behind a common interface :)

Tuples


Student: "Why can a method only return one thing?"

This limitation feels quite artificial to students. I don't know of anything inherently wrong with a method returning multiple things, yet the result of this limitation is that students end up jumping through several hoops just to get the desired behavior: passing in references to objects the method is supposed to change, creating new classes just to group multiple object into one, ...

I believe tuple types would be a natural solution to this problem. They shouldn't be too hard to understand, given that students are already familiar with them from their high school math classes.

Arrays


Student: "Why didn't you just teach us ArrayList last semester?"

Arrays is one of those topics students often struggle with. We (my colleagues and I) teach them during the first semester, but once collections come into play, we end up never using them ever again. Personally, I can't even remember when I last used an array instead of a collection type.

Maybe we can get rid of arrays and reuse the syntax as a shorthand for collections?

Properties


Student: "Why do I have to generate every single getter and setter myself? Can't Java just do that for me?"


Java has always had a lot of boilerplate when it comes to JavaBean-style properties. FX-style properties are neat and easy to use, but make the boilerplate problem even worse:

private DoubleProperty value = new SimpleDoubleProperty();

public double getValue() {
    return value.get();
}

public void setValue(double value) {
    this.value.set(value);
}

public DoubleProperty valueProperty() {
    return value;
}

Surely it can't be too hard to just replace the code above with something like:

@Property
private DoubleProperty value;

or even:

@Property
private double value;

with variations like:

@Property(propertyType=PropertyType.READ_ONLY)
private double value;

to have the missing accessor methods automatically generated. 

No more scrolling through screens and screens of accessor methods just to find that one method that actually does something!

Equality


Student: "Since when is equals not equal to equals?"

Yet another topic of confusion for students is the difference between == and equals(). Why don't we unify == and equals() and add an isSameObjectAs() method to cover referential identity?

Exceptions


Student: "Wait, weren't we supposed to ..."

A class on exceptions usually ends with a lot of 'Yes, but'-ing. The Java Tutorial summarizes the discussion on checked vs. unchecked as follows: "If a client can reasonably be expected to recover from an exception, make it a checked exception. If a client cannot do anything to recover from the exception, make it an unchecked exception." That seems perfectly logical and understandable. The problem here is that the Java APIs are littered with methods that throw unchecked exceptions for recoverable conditions. Some examples:
  • Double.parseDouble() throws an unchecked NumberFormatException when it cannot parse a number.
  • Query.getSingleResult() throws an unchecked NoResultException when it cannot find a result. Yet EntityManager.find(), wich is also expected to return a single result, returns null when it cannot find a result.
This is really confusing.

Write once, run anywhere


Student: "If Java was designed to be 'write once, run anywhere', then why doesn't it run on the platforms we actually care about?"

This ofcourse refers to iOS, Android and to a lesser extent Windows Phone smartphones and tablets. Personally, I believe Oracle needs to back Java(FX) on these platforms in a big way and as soon as possible.

Why do I believe that?
  • It isn't too hard to realize that desktop is now a niche platform. Even if I'm not a consultant, I often work with external partners on student projects and internships. Almost all our students majoring in application development end up doing either web or mobile application development. For those students, Java almost always equals Java EE.
  • A lot of Java developers passionately hate having to use JavaScript for web or hybrid application development. We aren't too keen on Android development either and would rather not have to invest in learning Objective-C and Cocoa just to run our apps on iOS. Being able to use JavaFX to develop cross-platform mobile apps would be a life changer.

And last but certainly not least:

"Garbage collection"


Student: "If we're not supposed to use it, then why is it still there? Java should run garbage collection on its own APIs!"

Nuff said :)

Saturday, September 14, 2013

Running GlassFish 4 with MySQL on OpenShift

As people have been asking me how I got GlassFish 4 running on OpenShift, I decided to write down everything I learned in the process. I started out by following these examples, but ran into a lot of trouble along the way. I hope this post can make the process a lot easier for you.


Prerequisites


Obviously, you need to get yourself an OpenShift account first. Then, via the website, create a new application with the DIY cartridge and add a MySQL cartridge to it as well. I will refer to this application as "yourapp" from now on. Lastly, you will need the OpenShift Client Tools, so install those as well.

As for GlassFish, you can download my archive here:

This is the official GlassFish 4 release, modified in the following ways:
  • The domain.xml configuration file has been modified to run on OpenShift.
  • It already includes the MySQL driver.
The archive also contains the start and stop hooks you need.

Setting up the server


Start by cloning your repository to your local machine:

rhc git-clone yourapp

This will create a yourapp directory.

Next, unzip the archive you downloaded and move the files into yourapp as follows:
  • Move the glassfish4 directory (the entire directory, not just its contents) into yourapp/diy/.
  • Move the start and stop hooks into yourapp/.openshift/action_hooks/ and check to make sure they're executable.
Finally, add, commit and push the changes you made back to OpenShift:

cd yourapp
git add .
git status
git commit -m "Added GlassFish"
git push

The push will cause your application to restart and execute the new start hook. Give it a few minutes just to be sure, then enjoy your amazing "Your server is now running" page at http://yourapp-youraccount.rhcloud.com.

I added the git status command in there so you can verify if Git picked up all the changes you made. If not, use git add to add the files Git missed.

If this is your first time using Git from the command-line, I recommend you set up your name and email address and change your default editor to something other than vi:

git config --global user.name "My Name"
git config --global user.email my@email.com
git config --global core.editor nano

Deploying applications


To deploy an application, simply copy its war into yourapp/diy/glassfish4/glassfish/domains/domain1/autodeploy/ and run the previous commands again to add, commit and push the changes. GlassFish will automatically deploy your application after launch. If you think something went wrong, ssh into your application:

rhc app ssh yourapp

Then, look for a your.war_deployed file in the autodeploy directory:

cd $OPENSHIFT_REPO_DIR/
cd diy/glassfish4/glassfish/domains/domain1/autodeploy/
ls

If there is no such file, take a look at the server logs to find out what went wrong:

cd ../logs/
cat server.log | tail -n100

Setting up resources


One thing that did cause me some troubles was the following: you cannot use the GlassFish Administration Console with OpenShift. Neither does glassfish-resources.xml seem to be supported. If you need to set up JDBC resources, connection pools, security realms, ... on your server, I recommend you use the following approach:
  • Set up the resources you need on a local GlassFish server.
  • Take a look at that server's domain.xml to see what changes were made.
  • Make the same changes to yourapp/diy/glassfish4/glassfish/domains/domain1/config/domain.xml. Usually, this just means adding one or two extra elements.
  • As usual: add, commit and push the changes.

Connecting to your MySQL server


Working with your MySQL server on OpenShift is actually pretty easy, once you figure out how to connect to it from MySQL Workbench. So launch Workbench and add a new connection. As you can't connect to MySQL on OpenShift directly, you need to use a TCP/IP over SSH connection.

The settings I used for my application can be seen in the following screenshot:



To find the settings you need, use the rhc app show yourapp command. This is what I get for my glassfish application:

MacBook-Air:OpenShift Steven$ rhc app show glassfish
glassfish @ http://glassfish-svanimpe.rhcloud.com/ (uuid: 5232bc5f500446f41000014c)
-----------------------------------------------------------------------------------
  Domain:  svanimpe
  Created: Sep 13  9:18 AM
  Gears:   1 (defaults to small)
  Git URL: ssh://5232bc5f500446f41000014c@glassfish-svanimpe.rhcloud.com/~/git/glassfish.git/
  SSH:     5232bc5f500446f41000014c@glassfish-svanimpe.rhcloud.com

  diy-0.1 (Do-It-Yourself 0.1)
  ----------------------------
    Gears: Located with mysql-5.1

  mysql-5.1 (MySQL Database 5.1)
  ------------------------------
    Gears:          Located with diy-0.1
    Connection URL: mysql://$OPENSHIFT_MYSQL_DB_HOST:$OPENSHIFT_MYSQL_DB_PORT/
    Database Name:  glassfish
    Password:       supersecret
    Username:       adminHWvBmju

The output will give you all of the values you need (shown in bold), except for the MySQL hostname and port, as the command only shows the environment variables and not the actual values. To find out those values, ssh into your application and use the env and grep commands:

[glassfish-svanimpe.rhcloud.com 5232bc5f500446f41000014c]\> env | grep MYSQL
OPENSHIFT_MYSQL_DIR=/var/lib/openshift/5232bc5f500446f41000014c/mysql/
OPENSHIFT_MYSQL_DB_PORT=3306
OPENSHIFT_MYSQL_DB_HOST=127.9.226.130
OPENSHIFT_MYSQL_DB_PASSWORD=supersecret
OPENSHIFT_MYSQL_IDENT=redhat:mysql:5.1:0.2.2
OPENSHIFT_MYSQL_DB_USERNAME=adminHWvBmju
OPENSHIFT_MYSQL_DB_SOCKET=/var/lib/openshift/5232bc5f500446f41000014c/mysql//socket/mysql.sock
OPENSHIFT_MYSQL_DB_URL=mysql://adminHWvBmju:asIQ9W3wc26y@127.9.226.130:3306/
OPENSHIFT_MYSQL_DB_LOG_DIR=/var/lib/openshift/5232bc5f500446f41000014c/mysql//log/


That's it! You should now be up and running with both GlassFish and MySQL. If you're not, feel free to contact me or leave a comment.

Friday, September 13, 2013

Reminders: the full Java EE 7 webservice

After spending an entire summer with Objective-C and iOS 7, I finally got back to work on finishing the Reminders examples. The first thing I did was finish the webservice and update it for Java EE 7. You can find it (and its API documentation) here:

https://glassfish-svanimpe.rhcloud.com/reminders

The service is hosted on a free OpenShift account. 


Changes


Compared to the previous version, the following has been added or changed:
  • Authentication and authorisation has been added. More specifically: HTTP basic authentication over SSL, using a JDBC realm.
  • Thanks to @Transactional and CDI, EJBs are gone.
  • The new JSON API is used to implement custom MessageBodyReaders and MessageBodyWriters.
  • Bean Validation is used more extensively, including custom annotations and validation groups.
  • The image upload and download code from this example is used to manage profile pictures for users of the service.

Front-ends wanted


Feel free to play around with the service and report bugs if you find them. If build a front-end as part of your learning process, please let me know and consider sharing your code. I'd be happy to post it on the blog, no matter what platform or technologies you chose.


Source code


The source code for the service (exactly as it has been deployed to OpenShift) can be found here:
https://docs.google.com/file/d/0B-q-VDW3dbtyU2FfYWhsMXc1U0U/edit?usp=sharing

Setup


If you want to run this service on your own machine, you will need to do the following:
  1. Create an empty database and a JDBC Resource named jdbc/reminders that points to it. The schema will be created automatically upon first launch.
  2. Set up the security realm. You can do this via the GlassFish administration console (http://localhost:4848). Browse to Configurations > server-config > Security > Realms and create a realm with the following settings:
    • Realm Name: remindersRealm
    • Class Name: com.sun.enterprise.security.ee.auth.realm.jdbc.JDBCRealm
    • JAAS Context: jdbcRealm
    • JNDI: jdbc/reminders
    • User Table: USER_PASSWORD
    • User Name Column: USERNAME
    • Password Column: PASSWORD
    • Group Table: USER_ROLES
    • Group Table User Name Column: USERNAME
    • Group Name Column: ROLES
    • Password Encryption Algorithm: SHA-256
  3. Edit the source code in the following places:
    • In util.HttpsFilter, uncomment the line that forwards to port 8181.
    • In rest.resources.Users, change the value of BASE_DIR to an existing directory on your machine.
  4. Copy the default profile picture (default.png) to the BASE_DIR you configured.
If you can't get it running, feel free to contact me or leave a comment.

Monday, June 17, 2013

Some student projects

Today I'd like to share two projects my students developed during the previous semester. Their goal was to build an application that allows citizens to communicate amongst themselves and with their city government on various topics, like issues that need attention, or events that are being organized.

These applications consist of an HTML5 front-end built on top of a RESTful web service (JAX-RS). They are hosted on OpenShift, with GlassFish 4 and MySQL 5.

The first project is called "Community Share", is developed by:
  • Arne Lammens
  • Benjamin Pieteraerents
  • Glenn Derycke
  • Joris D'Hoe


The second project is called "Fixity", is developed by:
  • Geoffry Van Den Eede
  • Reinhard Staveaux
  • Tim Van De Velde


Please keep the following in mind when evaluating these applications:
  • They are built by second-year undergraduate students.
  • It was their first time building a comprehensive web application.
  • It was their first time using a RESTful architecture.
  • It was their first time using Java EE and GlassFish.
  • They are in Dutch ;)

Taking all this into account, I believe they can be proud of their work!


Friday, May 31, 2013

JavaFX properties in JPA entity classes

This past week, the following question came to mind: can I use JavaFX properties in domain classes that have to be persisted using JPA? That is: can I build domain classes that use JavaFX properties instead of regular properties, yet are persistable like regular JPA entity classes?

After a bit of trial and error, I came up with the following answer: it's actually pretty easy, if you understand JPA's property access.


Property access vs. field access

JPA has two ways of accessing persistent attributes:
  1. Field access: reading and writing attributes directly through reflection.
  2. Property access: reading and writing attributes through their accessor (getter and setter) methods.
If you do not explicity set an access type, JPA will figure out what to do:
  • If you annotate your fields, JPA will use field access.
  • If you annotate your getter methods, JPA will use property access.
  • If you annotate both fields and getter methods, you're asking for trouble.
If you want to use property access, do the following:
  1. Do not annotate any fields.
  2. Provide both a getter and a setter for each property you want to persist. These methods have to be either protected or public.
  3. Place all annotations on the getter methods.

A simple example

The following example shows how to map simple Employee and Department classes, including a unidirectional many-to-one relationship between them. The resulting database schema is exactly the same as if you had used regular properties.

@Entity
public class Department {
    
    private final StringProperty name = new SimpleStringProperty();

    @Id
    public String getName() {
        return name.get();
    }

    public void setName(String value) {
        name.set(value);
    }

    public StringProperty nameProperty() {
        return name;
    }
}

@Entity
public class Employee {
    
    private final IntegerProperty id = new SimpleIntegerProperty();

    @Id @GeneratedValue
    public int getId() {
        return id.get();
    }

    public void setId(int value) {
        id.set(value);
    }

    public IntegerProperty idProperty() {
        return id;
    }
    
    private final StringProperty name = new SimpleStringProperty();

    public String getName() {
        return name.get();
    }

    public void setName(String value) {
        name.set(value);
    }

    public StringProperty nameProperty() {
        return name;
    }
    
    private final ObjectProperty<Department> department = new SimpleObjectProperty<>();

    @ManyToOne
    public Department getDepartment() {
        return department.get();
    }

    public void setDepartment(Department value) {
        department.set(value);
    }

    public ObjectProperty<Department> departmentProperty() {
        return department;
    }
}

Mixing access types

If you only want a few JavaFX properties in your entity classes, you could go for mixed access types. For example:

@Entity
@Access(AccessType.FIELD)
public class Employee {
    
    @Id @GeneratedValue
    private int id;
    
    @Transient
    private final StringProperty name = new SimpleStringProperty();

    @Access(AccessType.PROPERTY)
    public String getName() {
        return name.get();
    }

    public void setName(String value) {
        name.set(value);
    }

    public StringProperty nameProperty() {
        return name;
    }
    
    @ManyToOne
    private Department department;

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }
}

Note that:
  • The default access type for this class is explicity set to field access.
  • The access type for the name property is changed from the default to property access.
  • The underlying StringProperty attribute is marked as transient, otherwise JPA would persist both the property and the field.

Feedback

If you try this out, let me know how it's working for you. Feedback on these ideas is greatly appreciated, as I literally just made them up during breakfast.