Search This Blog

Tuesday, 11 December 2012

JavaFX - some later thoughts

I've been using JavaFX for a while now, in a couple of projects - for my PhD work, and on the side for Quelea (whose interface has been migrated entirely from Swing over to JavaFX.) As promised, I thought I'd post a few follow up thoughts - good points / bad points now that I've been using it a while.

So, without further ado, the positives!

  • The API is nice and clean - and not just because it's not stuffed with deprecated methods like Swing is, it's just designed in a fundamentally much more sensible way which makes it easier to follow. The concept of properties on elements, with these properties having a common interface, means I can jump right in with a component and see what properties are available to me without having to dig in the documentation to find out exactly how to add a listener for the width of the bottom half of my split pane (for instance.)
  • Layout is vastly improved - a lot of the swing inconsistencies have gone, and the model is now a specific component is tied to a specific layout (HBox, VBox, GridPane and so on) rather than having a separate content pane which can have a layout applied to it. This again ties in with the first point, making for a much easier, nicer API without having to wonder exactly what, if any parameters I need to shove on the end to get the layout manager which I've selected (and what one was it, anyway?) to behave.
  • You can actually do animations with keyframes without resorting to horrible graphics2d hacks - and it's all GPU accelerated.
  • Multimedia support comes as standard, no need to play around with the buggy as hell Javasound or JMF.
  • UI work on the platform thread is enforced most of the time - a runtime exception is thrown if you don't do this, which makes it much easier to find and solve odd annoying concurrency bugs that would often crop up otherwise.
  • The default cross platform skin doesn't make me want to vomit. On the contrary, it actually does a good job of looking rather nice across a range of platforms. It's visually appealing and nicely animated too.
  • Nice native deployment options - they only work on the current platform (i.e. you can't build a deb package on a windows box) but still, I like the change of thought that users are generally much more comfortable with a custom built package for their OS rather than a generic jar / jnlp file.
Despite the above, it's not flawless:
  • The multimedia support is great - when it works. But it supports in reality a very limited range of formats and file types (no mkv at all for instance, some of my mp4 encoded videos still didn't work either.)
  • There's still a few annoying bugs I've come across, such as this one. Nothing that can't be sorted, but these sorts of things are annoying.
  • Some features just aren't there yet - I wanted a pop up panel similar to the ColorPane, but those components just don't exist yet (or at least aren't part of the public API.)
  • No rich text on controls, at least not yet. This gets really annoying if you want to (for instance) bold part of a label. Can't be done, you have to butt multiple labels together in a HBox to get that effect (which, let's face it, is nasty.)
  • No native skins - you can write your own, but at the moment (as far as I know) none are provided to make it look like the native platform you're working on. Some would argue this is a good thing, but sometimes it's nice to have this option.
  • No damn font metrics in the public API - again, something I have to use an internal class for at present (which is really rather annoying and means code I write at present potentially isn't backwards compatible.)
Overall, I must admit I still like it, and while I've uncovered more things I don't like from my initial positive reaction, most of those things are slated for inclusion in Java 8 (so in a year or so at the time of writing.) I still don't think it's going to take off on the mobile or web front - but as a replacement for Swing on the desktop, it's a very welcome (and arguably long overdue) change.

Thursday, 16 February 2012

Dropbox Java API

As well as being useful as general cloud storage, dropbox also has an API that lets you access its contents programmatically. It's a straightforward REST API with a number of language specific libraries to make the going a bit easier. Java is included on the list of SDKs, but at present only Android is included on the list of tutorials. This can be somewhat frustrating because simple examples using Java are lacking.

Fortunately, the process was described by Josh here. So I've taken it and implemented it in Java, and it seems to work. It's a very basic example that authenticates with dropbox then uploads a file called "testing.txt" containing "hello world."

Of course, more functionality is available than this, but this is the hard part (at least I found working this bit out the hard part.) Once you've got your DropboxAPI object, you can work most things from there using the supplied Javadoc. (You can find the Java libraries that you'll need to download here.)

/**
 * A very basic dropbox example.
 * @author mjrb5
 */
public class DropboxTest {

    private static final String APP_KEY = "APP KEY";
    private static final String APP_SECRET = "SECRET KEY";
    private static final AccessType ACCESS_TYPE = AccessType.APP_FOLDER;
    private static DropboxAPI<WebAuthSession> mDBApi;

    public static void main(String[] args) throws Exception {
        AppKeyPair appKeys = new AppKeyPair(APP_KEY, APP_SECRET);
        WebAuthSession session = new WebAuthSession(appKeys, ACCESS_TYPE);
        WebAuthInfo authInfo = session.getAuthInfo();

        RequestTokenPair pair = authInfo.requestTokenPair;
        String url = authInfo.url;

        Desktop.getDesktop().browse(new URL(url).toURI());
        JOptionPane.showMessageDialog(null, "Press ok to continue once you have authenticated.");
        session.retrieveWebAccessToken(pair);

        AccessTokenPair tokens = session.getAccessTokenPair();
        System.out.println("Use this token pair in future so you don't have to re-authenticate each time:");
        System.out.println("Key token: " + tokens.key);
        System.out.println("Secret token: " + tokens.secret);

        mDBApi = new DropboxAPI<WebAuthSession>(session);
        System.out.println();
        System.out.print("Uploading file...");
        String fileContents = "Hello World!";
        ByteArrayInputStream inputStream = new ByteArrayInputStream(fileContents.getBytes());
        Entry newEntry = mDBApi.putFile("/testing.txt", inputStream, fileContents.length(), null, null);
        System.out.println("Done. \nRevision of file: " + newEntry.rev);
        
    }
}

To use the token pair in future, instead of doing:
WebAuthSession session = new WebAuthSession(appKeys, ACCESS_TYPE);

You would do:
WebAuthSession session = new WebAuthSession(appKeys, ACCESS_TYPE, new AccessTokenPair(key, secret));

...where key and secret are the ones printed in the example above. You can then skip the rest of the auth code (up until you create the DropboxAPI object.)

Wednesday, 15 February 2012

Sometimes, older is better...

When I first started Quelea, I wanted to use it as a kind of testing ground for features and ideas that hadn't already been incorporated into church presentation software. Both in terms of the UI, features and functionality. A lot of these features have worked really well - the ability to import from survivor songbooks for instance, and the instant search that's further improved in the next release.

On the UI side of things I tried to have a go with the ribbon - it's still got a love/hate relationship with people, but I wondered if it could do any good in Quelea. So I had a go, implemented it and left it as such for a few releases.

Thing is, it just didn't work - and I think this was a 50:50 split between it not being suitable for Quelea and the flamingo implementation.

On the suitable for Quelea side:
  • For something that's often run on laptops with small screens, it took up a huge amount of space it didn't need to.
  • There weren't enough controls to make it viable. It works (ish) for office because it replaced a hugely complex menu system, but Quelea just doesn't have that complex menu system, and it won't for the foreseeable future. So it really just acted like a huge toolbar.
And on the flamingo side:
  • It was pretty unmaintained, which doesn't exactly add to my confidence.
  • It only integrated well with the substance look and feel, which users might not want to use.
  • It only integrated at all well with Windows, and Quelea is targetted at cross platform use.
  • It looked - well, odd. Most bits are there, but odd bits like the tight integration with the Window aren't, and that just makes it feel like something's not quite there with it. Combine this with the first point and it isn't going away any time soon either.
Perhaps there are some things I could've chosen to make it look better - like designing the UI in SWT rather than Swing (which has a much nicer looking ribbon.) But I've now replaced the ribbon with a standard set of toolbars and menus, and personally I think this looks much nicer. We'll see how it pans out in practice, but sometimes the older, traditional way is definitely the better one!

Wednesday, 11 January 2012

Java puzzler

You'll need an understanding of weak references to have a meaningful stab at this one.


public class Test {

public static void main(String[] args) {
List<byte[]> list = new ArrayList<byte[]>();

Object thing = new Object();
WeakReference<Object> ref = new WeakReference<Object>(thing);

while(ref.get()!=null) {
list.add(new byte[10000]);
}
System.out.println("bam");
}

}


Before reading any further give it a stab, see what you come up with. Then run it and see. Oh and yes, it compiles and runs.






...







Well, there's two possible options that you could have sensibly come up with. The first is what most people assume will always happen - the byte array will fill up to cause an OutOfMemoryError and the weak reference will never be garbage collected because there also exists a strong reference (thing) that remains in scope throughout the application and thus doesn't "let go" of the object. And indeed if you run it with early versions of Java, this is exactly what will happen.

However, with the latest version of JDK7 (update 2 at the time of writing) the program doesn't do the above, it prints out "bam" and exits cleanly. This initially seems weird - there's still a strong reference in scope so why on earth is it garbage collected?

It boils down to improvements in the JIT. It realises that the hard reference isn't actually used again, so sets it to null thus making it eligible for garbage collection. Put something like "thing.getClass();" as the last line of the program and it always forces the OutOfMemoryError because if that's the case the JIT can't make this optimisation. It's a behaviour that's actually explicitly covered in the JLS:

Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable. For example, a compiler or code generator may choose to set a variable or parameter that will no longer be used to null to cause the storage for such an object to be potentially reclaimable sooner.


http://java.sun.com/docs/books/jls/third_edition/html/execution.html#12.6.1

The lesson? Objects can be garbage collected when the JIT decides they're no longer in use, and this may be earlier when it seems - and this in turn means that finalizers can be called when the object might trivially seem in-scope and thus ineligible for collection.