Dienstag, 21. August 2012

Building, Signing and Deploying your JavaFX Application with Maven

Building and deploying a JavaFX with Maven can be cumbersome as I recently had to experience myself.

Oracle's documentation only deals with ANT and only uses one single jar in their examples.

Here's a more complex scenario: Your JavaFX application is managed by Maven and has about 40 dependencies.

First you should make sure to copy all those dependencies to a separate folder. This folder will be later used as our classpath.

You can use the maven-dependency-plugin for this:
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <executions>
        <execution>
            <id>copy-dependencies</id>
            <phase>install</phase>
            <goals>
                <goal>copy-dependencies</goal>
            </goals>
            <configuration>
                <includeScope>runtime</includeScope>
                <outputDirectory>${application.dist}/lib</outputDirectory>
                <overWriteReleases>false</overWriteReleases>
                <overWriteSnapshots>true</overWriteSnapshots>
                <overWriteIfNewer>true</overWriteIfNewer>
            </configuration>
        </execution>
    </executions>
</plugin>
In order to build the JavaFX application, we use ANT. We kind of have to, because this is currently the only official and supported way to build a JavaFX application (besides the command line tool).
Using only Maven is complicated (especially when it comes to web deployment) and needs some more setup.

However, we can use ANT out of Maven, using the maven-antrun-plugin.

Here's my plugin configuration:
<plugin>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.7</version>
    <executions>
        <execution>
            <phase>install</phase>
            <configuration>
                <target xmlns:fx="javafx:com.sun.javafx.tools.ant">
                    <property name="applet.width" value="330"/>
                    <property name="applet.height" value="620"/>
                    <property name="application.title" value="Title"/>
                    <property name="application.vendor" value="Vendor"/>
                    <!-- Copy the class path to the manifest. The lib folder is generated by maven-dependency-plugin. -->
                    <manifestclasspath property="manifest.classpath"
                                       jarfile="${application.dist}/MyApp.jar">
                        <classpath>
                            <path id="build.classpath">
                                <fileset dir="${application.dist}/lib">
                                    <include name="*.jar"/>
                                </fileset>
                            </path>
                        </classpath>
                    </manifestclasspath>

                    <taskdef resource="com/sun/javafx/tools/ant/antlib.xml"
                             uri="javafx:com.sun.javafx.tools.ant"
                             classpath="${javafx.lib.ant-javafx.jar}"/>
                    <fx:application id="myApp"
                                    name="MyApp"
                                    mainClass="package.MainClass"/>
                    <fx:jar destfile="${application.dist}/MyApp.jar">
                        <fx:application refid="myApp"/>

                        <manifest>
                            <attribute name="Class-Path" value="${manifest.classpath}"/>
                        </manifest>

                        <!-- The target/classes folder which contains all resources and class files -->
                        <fileset dir="${project.build.outputDirectory}"/>
                    </fx:jar>
                    <fx:resources id="appRes">
                        <fx:fileset dir="${application.dist}" includes="*.jar"/>
                        <fx:fileset dir="${application.dist}" includes="lib/*.jar"/>
                    </fx:resources>
                            
                    <signjar keyStore="${basedir}\keystore.jks"
                             destdir="${application.dist}"
                             alias="certificatekey" storePass="cshcsh" keyPass="cshcsh">
                        <fileset dir="${application.dist}" includes="*.jar"/>
                        <fileset dir="${application.dist}" includes="lib/*.jar"/>
                    </signjar>
                    <fx:deploy width="${applet.width}" height="${applet.height}"
                               outdir="${application.dist}" embedJNLP="true"
                               outfile="${application.title}">

                        <fx:application refId="myApp"/>

                        <fx:resources refid="appRes"/>
                        <fx:info title="Sample app: ${application.title}"
                                 vendor="${application.vendor}"/>

                        <!-- Request elevated permissions -->
                        <fx:permissions elevated="true"/>
                    </fx:deploy>
                </target>
            </configuration>
            <goals>
                <goal>run</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Building the jar file

The first important part is to build the class-path for the manifest file. Here we use the class-path, which we created with Maven and write it to the manifest.

The rest of the configuration is mostly taken from the official JavaFX documentation (see there for more information).

The fx:jar task creates the jar file and adds some JavaFX properties to the manifest in order to be able to launch your application.
As fileset we reference our target/classes folder which was generated by Maven.

For just building the jar, this would already be sufficient.

Signing the jar files

If you want to also deploy it in the web you can sign the application using the signjar or the fx:signjar task.
(I had some problems signing a jar with fx:signjar, if that jar doesn't have a META-INF folder).

First you should generate a keystore.
Here's a useful tutorial on how to do that.

My configuration signs each jar in the class-path.

Be careful, if one of your Jars is already signed. You might end up with a runtime error "JAR resources in JNLP file are not signed by the same certificate".

Deploying the application

Finally you use the fx:deploy task to generate the JNLP and HTML file for web deployment.

I defined two properties which you need to run the plugins:
<properties>
        <javafx.lib.ant-javafx.jar>C:\Program Files (x86)\Java\jdk1.7.0_06\lib\ant-javafx.jar
        </javafx.lib.ant-javafx.jar>
        <application.dist>${project.build.directory}/dist</application.dist>
</properties>
The first one is the location of the ant-javafx.jar.
The second one is the target folder for all generated files.

Now, have a look at your JNLP file. It should contain all your dependencies from your Maven configuration.

PS: For those who are interested: I updated the code the TreeView control in my previous post.

Dienstag, 6. März 2012

TreeView with a data source

When working with JavaFX controls like ListView or TableView, you can populate these controls easily with a setItems method.

However, when working with the TreeView control, you don't have that method and you have to build up your tree directly in the UI layer.

That means, every time, you want to sort the tree or add or remove elements, you have to alter the control directly instead of doing that on an underlying data source.

I wrote some convenient code, which makes this easier. Let's go...

In order to define hierarchical data, we need an interface, which represents the recursive nature of the tree data:

import javafx.collections.ObservableList;

/**
 * Used to mark an object as hierarchical data.
 * This object can then be used as data source for an hierarchical control, like the {@link javafx.scene.control.TreeView}.
 *
 * @author Christian Schudt
 */
public interface HierarchyData<T extends HierarchyData> {
    /**
     * The children collection, which represents the recursive nature of the hierarchy.
     * Each child is again a {@link HierarchyData}.
     *
     * @return A list of children.
     */
    ObservableList<T> getChildren();
}

This is used to mark an object as hierarchical data. (By the way, the name was inspired by .NET's HierarchyData.)

The second step is to provide a TreeView control with a setItems method. We just derive from the JavaFX TreeView and add one public method. The data of the TreeView must implement our above mentioned interface:

import extfx.util.HierarchyData;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.WeakListChangeListener;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;

import java.util.HashMap;
import java.util.Map;

/**
 * This class extends the {@link TreeView} to use items as a data source.
 * <p/>
 * This allows you to treat a {@link TreeView} in a similar way as a {@link javafx.scene.control.ListView} or {@link javafx.scene.control.TableView}.
 * <p/>
 * Each item in the list must implement the {@link HierarchyData} interface, in order to map the recursive nature of the tree data to the tree view.
 * <p/>
 * Each change in the underlying data (adding, removing, sorting) will then be automatically reflected in the UI.
 *
 * @author Christian Schudt
 */
public class TreeViewWithItems<T extends HierarchyData<T>> extends TreeView<T> {

    /**
     * Keep hard references for each listener, so that they don't get garbage collected too soon.
     */
    private final Map<TreeItem<T>, ListChangeListener<T>> hardReferences = new HashMap<TreeItem<T>, ListChangeListener<T>>();

    /**
     * Also store a reference from each tree item to its weak listeners, so that the listener can be removed, when the tree item gets removed.
     */
    private final Map<TreeItem<T>, WeakListChangeListener<T>> weakListeners = new HashMap<TreeItem<T>, WeakListChangeListener<T>>();

    private ObjectProperty<ObservableList<? extends T>> items = new SimpleObjectProperty<ObservableList<? extends T>>(this, "items");

    public TreeViewWithItems() {
        super();
        init();
    }

    /**
     * Creates the tree view.
     *
     * @param root The root tree item.
     * @see TreeView#TreeView(javafx.scene.control.TreeItem)
     */
    public TreeViewWithItems(TreeItem<T> root) {
        super(root);
        init();
    }

    /**
     * Initializes the tree view.
     */
    private void init() {
        rootProperty().addListener(new ChangeListener<TreeItem<T>>() {
            @Override
            public void changed(ObservableValue<? extends TreeItem<T>> observableValue, TreeItem<T> oldRoot, TreeItem<T> newRoot) {
                clear(oldRoot);
                updateItems();
            }
        });

        setItems(FXCollections.<T>observableArrayList());

        // Do not use ChangeListener, because it won't trigger if old list equals new list (but in fact different references).
        items.addListener(new InvalidationListener() {
            @Override
            public void invalidated(Observable observable) {
                clear(getRoot());
                updateItems();
            }
        });
    }

    /**
     * Removes all listener from a root.
     *
     * @param root The root.
     */
    private void clear(TreeItem<T> root) {
        if (root != null) {
            for (TreeItem<T> treeItem : root.getChildren()) {
                removeRecursively(treeItem);
            }

            removeRecursively(root);
            root.getChildren().clear();
        }
    }

    /**
     * Updates the items.
     */
    private void updateItems() {

        if (getItems() != null) {
            for (T value : getItems()) {
                getRoot().getChildren().add(addRecursively(value));
            }

            ListChangeListener<T> rootListener = getListChangeListener(getRoot().getChildren());
            WeakListChangeListener<T> weakListChangeListener = new WeakListChangeListener<T>(rootListener);
            hardReferences.put(getRoot(), rootListener);
            weakListeners.put(getRoot(), weakListChangeListener);
            getItems().addListener(weakListChangeListener);
        }
    }

    /**
     * Gets a {@link javafx.collections.ListChangeListener} for a  {@link TreeItem}. It listens to changes on the underlying list and updates the UI accordingly.
     *
     * @param treeItemChildren The associated tree item's children list.
     * @return The listener.
     */
    private ListChangeListener<T> getListChangeListener(final ObservableList<TreeItem<T>> treeItemChildren) {
        return new ListChangeListener<T>() {
            @Override
            public void onChanged(final Change<? extends T> change) {
                while (change.next()) {
                    if (change.wasUpdated()) {
                        // http://javafx-jira.kenai.com/browse/RT-23434
                        continue;
                    }
                    if (change.wasRemoved()) {
                        for (int i = change.getRemovedSize() - 1; i >= 0; i--) {
                            removeRecursively(treeItemChildren.remove(change.getFrom() + i));
                        }
                    }
                    // If items have been added
                    if (change.wasAdded()) {
                        // Get the new items
                        for (int i = change.getFrom(); i < change.getTo(); i++) {
                            treeItemChildren.add(i, addRecursively(change.getList().get(i)));
                        }
                    }
                    // If the list was sorted.
                    if (change.wasPermutated()) {
                        // Store the new order.
                        Map<Integer, TreeItem<T>> tempMap = new HashMap<Integer, TreeItem<T>>();

                        for (int i = change.getTo() - 1; i >= change.getFrom(); i--) {
                            int a = change.getPermutation(i);
                            tempMap.put(a, treeItemChildren.remove(i));
                        }

                        getSelectionModel().clearSelection();

                        // Add the items in the new order.
                        for (int i = change.getFrom(); i < change.getTo(); i++) {
                            treeItemChildren.add(tempMap.remove(i));
                        }
                    }
                }
            }
        };
    }

    /**
     * Removes the listener recursively.
     *
     * @param item The tree item.
     */
    private TreeItem<T> removeRecursively(TreeItem<T> item) {
        if (item.getValue() != null && item.getValue().getChildren() != null) {

            if (weakListeners.containsKey(item)) {
                item.getValue().getChildren().removeListener(weakListeners.remove(item));
                hardReferences.remove(item);
            }
            for (TreeItem<T> treeItem : item.getChildren()) {
                removeRecursively(treeItem);
            }
        }
        return item;
    }

    /**
     * Adds the children to the tree recursively.
     *
     * @param value The initial value.
     * @return The tree item.
     */
    private TreeItem<T> addRecursively(T value) {

        TreeItem<T> treeItem = new TreeItem<T>();
        treeItem.setValue(value);
        treeItem.setExpanded(true);

        if (value != null && value.getChildren() != null) {
            ListChangeListener<T> listChangeListener = getListChangeListener(treeItem.getChildren());
            WeakListChangeListener<T> weakListener = new WeakListChangeListener<T>(listChangeListener);
            value.getChildren().addListener(weakListener);

            hardReferences.put(treeItem, listChangeListener);
            weakListeners.put(treeItem, weakListener);
            for (T child : value.getChildren()) {
                treeItem.getChildren().add(addRecursively(child));
            }
        }
        return treeItem;
    }

    public ObservableList<? extends T> getItems() {
        return items.get();
    }

    /**
     * Sets items for the tree.
     *
     * @param items The list.
     */
    public void setItems(ObservableList<? extends T> items) {
        this.items.set(items);
    }
}

What's the benefit?

Clearly, the benefit is, that you only need to make modifications in the data source rather doing it on the control directly. I found it quite convenient, if you have a lot of hierarchical data, which often changes.

E.g. you can just call FXCollections.sort() and the TreeView updates automatically (of course only the corresponding TreeItem), just like you are used it from ListView.

Theoretically the same could also be applied to a Menu structure.

Hope you find it useful ;)

Samstag, 21. Januar 2012

JavaFX Calendar Control

I've had much fun with JavaFX 2.0 recently and decided to develop a DatePicker / Calendar control, because it doesn't exist yet, and it would be a funny little project which would made me learn a lot about JavaFX.

So here it is (here are the sources):


The control consists of three views: A month view (which shows the current month), a year view (which shows the current year with its months) and a decades view (which shows two decades):


The behavior and navigation is similar to the JavaScript MooTools Calendar.

Features:
  • Localization
  • Easy navigation through months, years and decades.
  • CSS Styling
  • Disable certain week days or dates
  • Show/hide the "Today" Button
  • Show/hide the week numbers
  • Set custom date format (by default it is taken from the locale)
  • Set custom prompt text (by default it is taken from the date format pattern)
  • Auto-parsing the text field, when focus is lost and while typing (invalid input results in a red-bordered text field)
  • Cool animations :)
Usage:

You can just instantiate the Date Picker in a usual way and treat it as a normal control.

DatePicker datePicker = new DatePicker();

There are some properties directly on the date picker control, and some more on the calendar view. E.g. if you want to enable the today button, you have to do it on the calendar view:

datePicker.getCalendarView().setShowTodayButton(true);



Setting the locale:

You can either pass a locale in the constructor or you can use the locale property to change the locale later.

The locale is directly used to determine the calendar, e.g. whether the first day of the week is Sunday or Monday, or if the Gregorian calendar or the Buddhist calendar should be used. (Java only knows these two).


Setting the calendar:

If you want to use your custom calendar (must be derived from java.util.Calendar or treat language and calendar independently, you can do it by setting the calendar explicitly like:

datePicker.setLocale(Locale.GERMAN);
datePicker.getCalendarView().setCalendar(new BuddhistCalendar());


Which would look like:


How do I get the selected date?

Probably the most important part. There's are property selectedDate, which you can use. If the field is left without a valid date it becomes null otherwise it gives you the date.

Styling

The control uses only JavaFX css properties for styles. That means you can easily change the -fx-base to some other color and you get the following result:


Download