The Java Tutorials' Weblog

pageicon Tuesday Jun 02, 2009

Deploying Your Rich Internet Application Nice And Easy!

Learn techniques to deploy rich internet applications that start up quickly and run consistently in all environments. This blog is based (mostly) on Thomas Ng's Java One Presentation - "Deploying Java Technologies To The Masses".[Read More]
pageicon Wednesday May 27, 2009

Just released -- Java Tutorials update

To coincide with the JDK7 Preview Release and JavaOne, we have just published an updated version of the Java Tutorials. Besides fixing many typos and errors that you have reported, this release includes:

  • An entirely reworked File I/O lesson, featuring NIO.2. This functionality is part of JDK7, which is available now through the Open JDK project on java.net.
  • A new specialized trail covering Sockets Direct Protocol, also new in JDK7.
  • A new facility for gathering feedback about the tutorial. At the bottom of each tutorial page, under the "Discuss" heading, you can leave a publicly viewable, blog-style comment. Let us know what you think about the inclusion of this JS-Kit mechanism.

Thanks, as always, for your feedback. It helps us improve the tutorial!

-- Sharon Zakhour

pageicon Wednesday May 13, 2009

Watching A Directory for Changes — File Change Notification in NIO.2

Here is a sneak peek of the NIO.2 file coverage I am writing for the Java Tutorial. This will be available in the web push of the tutorial that coincides with the upcoming J1 Preview Release. (NIO.2 is part of the JDK 7 release.)

-- Sharon Zakhour


Have you ever found yourself editing a file, using an IDE or another editor, and a dialog pops up to inform you that one of the open files has changed on the file system and needs to be reloaded? Or perhaps, like the NetBeans IDE, it just quietly updates the file without making a fuss. The following sample dialog shows how this looks when using the free editor, jEdit:

Sample jEdit Dialog stating: The following files were changed on disk by another
 program.

jEdit Dialog — Modified File is Detected

To implement this functionality, called file change notification, a program needs to be able to detect what is happening to the relevant directory on the file system. One way to do this is to poll the file system looking for changes, but this approach is inefficient — it does not scale to applications that may have hundreds of open files or directories to monitor.

The java.nio.file package provides a file change notification API, called the Watch Service API. This API allows you to register a directory (or directories) with the watch service — when registering you tell the service which types of events you are interested in: file creation, file deletion, or file modification. When the service detects an event of interest, it is forwarded to the registered process. The registered process has a thread (or a pool of threads) dedicated to watching for any events it has registered for. When one comes in, it is handled as needed.

This section covers:

WatchService Overview

The WatchService API is fairly low-level, allowing you to customize it — you can use it as-is, or you may choose to create a high-level API on top of this mechanism suited to your particular needs.

Here are the basic steps required to implement a watch service:

  • Create a WatchService "watcher" for the file system.
  • For each directory that you want monitored, register it with the watcher. When registering a directory, you specify the type of events for which you want notification. You receive a WatchKey instance for each directory that you register.
  • Implement an infinite loop to wait for incoming events.
  • When an event occurs, the key is signaled and placed into the watcher's queue.
  • The key is retrieved from the watcher's queue. You can obtain the file name from the key.
  • Retrieve each pending event for the key (there may be multiple events) and process as needed.
  • Reset the key and resume waiting for events.
  • The watch service exits when either the thread exits or when each directory being watched becomes inaccessible.

WatchKeys are thread safe and can be used with the java.nio.concurrent package. You can dedicate a thread pool to this effort.

Try it Out

Because this is a more advanced API, we encourage you to try it out before proceeding. Save the WatchDir example to your computer and compile it. Create a test directory — this is passed to the example. WatchDir uses a single thread to process all events, so it blocks while waiting for events — either run it in a separate window or in the background, like this:

java WatchDir test &

Play with creating, deleting and editing files in the test directory. When any of these events occurs, a message is printed to the console. When you have finished, delete the test directory and WatchDir exits. Or, if you prefer, you can manually kill the process.

You can also watch an entire file tree by specifying the -r option. When you specify -r, WatchDir walks the file tree, registering each directory with the watch service.

Creating a Watch Service and Registering for Events

The first step is to create a new WatchService, using the newWatchService method in the FileSystem class:

WatchService watcher = FileSystems.getDefault().newWatchService();

Next, register one or more objects with the watch service. Any object that implements the Watchable interface can be registered. The Path class implements the Watchable interface, so each directory to be monitored is registered as a Path object.

As with any Watchable, the Path class implements two register methods. This section uses the two-argument version, register(WatchService, WatchEvent.Kind<?>...). (The three-argument version takes a WatchEvent.Modifier which is not currently implemented.)

When registering an object with the watch service, you specify the types of events you want to monitor. The supported StandardWatchEventKind event types are:

  • ENTRY_CREATE — a directory entry is created.
  • ENTRY_DELETE — a directory entry is deleted.
  • ENTRY_MODIFY — a directory entry is modified;
  • OVERFLOW — indicates that events may have been lost or discarded. You do not have to register for this event to receive it.

The following code snippet shows how to register a Path instance for all three event types:

import static java.nio.file.StandardWatchEventKind.*;

Path dir = ...;
try {
    WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
} catch (IOException x) {
    System.err.println(x);
}

Processing Events

The order of events in an event processing loop are:

  1. Get a watch key. There are three methods provided:
    • poll — returns a queued key, if any is available. Returns immediately, with a null value, if none is present.
    • poll(long, TimeUnit) — returns a queued key, if one is available. If one is not immediately available it waits up until the specified time. The TimeUnit argument determines whether the specified time is nanoseconds, milliseconds, or some other unit of time.
    • take — returns a queued key. If none is available, this method waits.
  2. Process the pending events for the key. You fetch the List of WatchEvents from the pollEvents method.
  3. Retrieve the type of event using the kind method. No matter what events the key has registered for, it is possible to receive an OVERFLOW event. You may choose to handle the overflow or ignore it, but you should test for it.
  4. Retrieve the file name associated with the event. The file name is the context of the event, so the context method is used to retrieve it.
  5. After the events for the key have been processed, you need to put it back into a ready state by invoking reset. If this method returns false, the key is no longer valid and the loop can exit. This step is very important — if you fail to invoke reset, this key will not receive any further events.

A watch key has a state — at any given time, its state might be:

  • Ready indicates that the key is ready to accept events. When first created, a key is in the ready state.
  • Signaled indicates that one or more events are queued. Once the key has been signaled, it is no longer in the ready state until the reset method is invoked.
  • Invalid — indicates that the key is no longer active. This happens when one of the following events occurs:
    • The process explicitly cancels the key using the cancel method.
    • The directory becomes inaccessible.
    • The watch service is closed.

Here is an example of an event processing loop. It is lifted from the Email example which watches a directory, waiting for new files to appear. When a new file becomes avaiable, it is examined to see if it is a text/plain file using the probeContentType method. The intention is that plain text files will be emailed to an alias, but that implementation detail is left to the reader.

The methods specific to the watch service API have been bolded:

for (;;) {

    //wait for key to be signaled
    WatchKey key;
    try {
        key = watcher.take();
    } catch (InterruptedException x) {
        return;
    }

    for (WatchEvent<?> event: key.pollEvents()) {
        WatchEvent.Kind<?> kind = event.kind();

        //This key is registered only for ENTRY_CREATE events,
        //but an OVERFLOW event can occur regardless if events are
        //lost or discarded.
        if (kind == OVERFLOW) {
            continue;
        }

        //The filename is the context of the event.
        WatchEvent<Path> ev = (WatchEvent<Path>)event;
        Path filename = ev.context();

        //Verify that the new file is a text file.
        try {
            //Resolve the filename against the directory.
            //If the filename is "test" and the directory is "foo",
            //the resolved name is "test/foo".
            Path child = dir.resolve(filename);
            if (!Files.probeContentType(child).equals("text/plain")) {
                System.err.format("New file '%s' is not a plain text file.%n", filename);
                continue;
            }
        } catch (IOException x) {
            System.err.println(x);
            continue;
        }

        //Email the file to the specified email alias.
        System.out.format("Emailing file %s%n", filename);
        //Details left to reader....
    }

    //Reset the key -- this step is critical if you want to receive
    //further watch events. If the key is no longer valid, the directory
    //is inaccessible so exit the loop.
    boolean valid = key.reset();
    if (!valid) {
        break;
    }
}

Getting the Filename

The file name is retrieved from the event context. The Email example retrieves the file name with this code:

WatchEvent<Path> ev = (WatchEvent<Path>)event;
Path filename = ev.context();

When you compile the Email example, it generates the following error:

Note: Email.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

This is a result of the line of code that casts the WatchEvent<T> to a WatchEvent<Path>. The WatchDir example avoids this error by creating a utility cast method that suppresses the unchecked warning:

@SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
    return (WatchEvent<Path>)event;
}
If you are unfamiliar with the @SuppressWarnings syntax, see the Annotations section.

When Not to Use this API

The Watch Service API is designed for applications that need to be notified about file change events. It is well suited for any application, like an editor or IDE, that potentially has many open files and needs to ensure that the files are in sync with the file system. It is also well suited for the app server that watches a directory, perhaps waiting for .jsp or .jar files to drop, in order to deploy them.

This API is not designed for indexing a hard drive. Most file system implementations have native support for file change notification — the Watch Service API takes advantage of this where available. But when a file system does not support this mechanism, the Watch Service will poll the file system, waiting for events.

pageicon Thursday Apr 02, 2009

Deployment Toolkit 101

The Deployment Toolkit is a set of JavaScript functions that can help developers easily deploy rich internet applications. [Read More]
pageicon Wednesday Apr 01, 2009

Converting Pre-JDK7 File I/O Code

Prior to JDK7, the java.io.File class was the mechanism used for file I/O, but it had several drawbacks:

  • Many methods didn't throw exceptions when they failed, so it was impossible to get a useful error message. For example, if a file delete failed, the program would receive a "delete fail" but wouldn't know if it was because the file didn't exist, the user didn't have permissions, or if there was some other problem.

  • The rename method didn't work consistently across platforms.

  • There was no real support for symbolic links.

  • More support for metadata was desired, such as file permissions, file owner and other security attributes.

  • Accessing file metadata was inefficient.

  • Many of the File methods didn't scale. Requesting a large directory listing over a server could result in a hang. Large directories could also cause memory resource problems, resulting in a denial of service.

Perhaps you have legacy code that uses java.io.File and would like to take advantage of the java.nio.file.Path functionality with minimal impact on your code.

The java.io.File class provides the toPath method that converts an oldstyle File instance to a java.nio.file.Path instance:

Path input = file.toPath();

You can then take advantage of the rich feature set available to the Path class.

For example, say you had a some code that deleted a file:

file.delete();

You can modify this code to use the Path.delete method like this:

Path fp = file.toPath();
fp.delete();

That's all you need to do!

However, if you want to take advantage of the new Path feature set, you can examine the exception to learn why the delete failed:

Path fp = file.toPath();
try {
    fp.delete();
} catch (NoSuchFileException x) {
    System.out.format("%s: no such file or directory%n", fp);
} catch (DirectoryNotEmptyException x) {
    System.out.format("%s not empty%n", fp);
} catch (IOException x) {
    //File permission problems will be caught here.
    System.out.format("%s%n", x);
}

-- Sharon Zakhour

pageicon Tuesday Mar 31, 2009

Deploying An Applet In Under 10 Minutes

Want to learn how to quickly deploy a Java applet?[Read More]
pageicon Friday Mar 27, 2009

PathMatcher in NIO.2

In my last blog entry I show how to walk a file tree using a FileVisitor in a very simple Find example. Alan Bateman (the NIO.2 czar) suggested that, rather than use java.lang.String to match the file, I use the new PathMatcher API.

Good idea.

You can get the path matcher from the file system using this code:

PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
where pattern is, in this case, supplied at the command line.

At least two syntaxes are supported (others may be supported by a particular file system implementation): glob and regex. The getPathMatcher javadoc has more information.

Also note that the previous Find example only matched files, not directories. This is because visitFile is called once per file, but not for a directory: preVisitDirectory is used to take care of that omission.

To use this version of Find you might try:

java Find . "*java"     Finds all files ending in "java".
java Find . "*[0-9]*"   Finds all files containing a numeric value.
java Find . "???"       Finds all files with exactly three letters or digits.

Here is the modified Find.java:

import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import static java.nio.file.FileVisitResult.*;
import java.io.IOException;
import java.util.*;

/**
 * Sample code that finds files that match the specified glob pattern.
 * For more information on what constitutes a glob pattern, see
 * http://openjdk.java.net/projects/nio/javadoc/java/nio/file/FileSystem.html#getPathMatcher(java.lang.String)
 */

public class Find {

    /**
     * A {@code FileVisitor} that finds all files that match the specified pattern.
     */
    public static class Finder implements FileVisitor {
        private final PathMatcher matcher;

        Finder(String pattern) {
            matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
        }

        void find(Path file) {
            if (matcher.matches(file.getName())) {
                System.out.format("%s%n", file);
            }
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
            find(file);
            return CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
            return CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) {
            return CONTINUE;
        }

        @Override
        public FileVisitResult preVisitDirectory(Path dir) {
            find(dir);
            return CONTINUE;
        }

        @Override
        public FileVisitResult preVisitDirectoryFailed(Path dir, IOException exc) {
            return CONTINUE;
        }
    }

    static void usage() {
        System.err.println("java Find path glob_pattern");
        System.exit(-1);
    }

    public static void main(String[] args) throws IOException {

        if (args.length < 2)
            usage();

        Path searchDir = Paths.get(args[0]);
        String pattern = args[1];

        // follow links when searching for files
        EnumSet<FileVisitOption> opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
        Finder finder = new Finder(pattern);
        Files.walkFileTree(searchDir, opts, Integer.MAX_VALUE, finder);
    }
}

-- Sharon Zakhour

pageicon Tuesday Mar 24, 2009

Traversing a file tree in NIO.2

Have you downloaded JDK7 and played with NIO.2 yet? NIO.2 offers many new I/O features, particularly in the area of files and file system APIs. (To those of you who think, I can't use JDK7 because I am on a Mac: So am I. What I did was to download VirtualBox for my Mac (it's free). This allows you to set up a virtual environment on your machine. You can then download OpenSolaris, Ubuntu, or even Windows (but OpenSolaris and Ubuntu are free).

I wrote a simple example which you can use to find a file on your file system. (Think of a very simplified version of the Unix find.) The new java.nio.file.Files class provides a factory method, walkFileTree, you can use to traverse a tree of directories and files. When you invoke this method, you specify the root and how many levels deep you want to go. You can also set an attribute to indicate that it should follow links.

The meat of the work occurs in a class you create that implements the java.nio.file.FileVisitor interface. This interface has hooks for before, during, and after a file is "visited", as well as for when failure occurs. In my simple Find example, when the file is visited, the file name is compared to the user-specified name. If it matches, the full path is printed to stdout.

You create an instance of this class and pass it to the walkFileTree method. For each file or directory in the tree, the instance is invoked. You will notice that in this example, CONTINUE is returned in each of the FileVisitor methods. You can also return SKIP_SUBTREE or SKIP_SIBLINGS to terminate progress when necessary.

That's it.

If you download the NIO.2 JDK7 binaries, you will find other NIO.2 examples. Three of them also use the walkFileTree method: Chmod.java, Copy.java and WatchDir.java.

To call Find: java Find path file.

Here is the code for Find.java:

/**
 * Sample code that finds files.  Similar to the find(1) program, but simplified.
 */

public class Find {
    /**
     * A {@code FileVisitor} that finds a file.
     */
    static class Finder implements FileVisitor {
        private final Path findFile;

        Finder(Path findFile) {
            this.findFile = findFile;
        }

        void find(Path file) {
            if (file.getName().equals(findFile)) {
                System.out.format("%s%n", findFile);
            }
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
            find(file);
            return CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
            return CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) {
            return CONTINUE;
        }

        @Override
        public FileVisitResult preVisitDirectory(Path dir) {
            find(dir);
            return CONTINUE;
        }

        @Override
        public FileVisitResult preVisitDirectoryFailed(Path dir, IOException exc) {
            return CONTINUE;
        }
    }

    static void usage() {
        System.err.println("java Find path file");
        System.exit(-1);
    }

    public static void main(String[] args) throws IOException {

        if (args.length < 2)
            usage();

        Path searchDir = Paths.get(args[0]);
        Path findFile = Paths.get(args[1]);

        // follow links when searching for files
        EnumSet<FileVisitOption> opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
        Finder finder = new Finder(findFile);
        Files.walkFileTree(searchDir, opts, Integer.MAX_VALUE, finder);
    }
}

Update: This code has been slightly modified from when it was originally posted a few days ago. Originally, it compared the file names using the String class, but this may not provide the right result on some file systems. The comparison has been changed to the Path.equals method, which takes the type of file system into account.

Also, the example originally would only match files and not directories. By adding the comparison to the preVisitDirectory method, both files and directories will be examined.

-- Sharon Zakhour

pageicon Wednesday Feb 04, 2009

JavaBat -- Java Practice Programs

Wow, a very cool website was just brought to my attention. Nick Parlante, of Stanford, has pulled together a website where you can hone your Java skills.

Created with the theory that you learn by doing and the more you do it, the better you get, JavaBat provides a set of programming examples that give you immediate feedback. You can work through several examples in an hour — it compiles and runs your code using a variety of parameters so you can see where it fails. At the end, you can hop over to the "done" page and view your stats. Want to test your skills in a friendly, good-natured competition? Here ya go. :)

JavaBat is one of the neatest, free Java resources I've seen in a long time!

-- Sharon Zakhour

pageicon Monday Feb 02, 2009

NIO and JDK7

You may be familiar with the NIO (New I/O) api. This API, introduced in release 1.4, extended the previous I/O package and included support for pattern matching with Regular Expressions, Buffer support for primitive types, Character-set encoders and decoders, and Channels — an abstraction for devices capable of performing I/O operations (to name just some of the functionality).

You may be familiar with NIO, but have you heard of NIO.2? NIO.2, part of the JDK7 effort, completes some of the work begun in the NIO api and goes much further: It includes an improved filesystem interface, multicast support, asynchronous I/O support, and complete socket-channel functionality (which was begun in release 1.4).

Thanks to OpenJDK, you can download and play with NIO.2 now. The NIO Project Page contains links to the latest binaries, javadoc, JSR 203, and links to bloggers involved in this effort.

-- Sharon Zakhour

pageicon Thursday Jan 15, 2009

Inverse Kinematics Demo

Sun engineer Michael Heinrichs recently wrote a great animation demo that uses inverse kinematics to animate JavaFX objects. Check it out on his blog and feel free to leave a comment!

-- Scott Hommel

pageicon Tuesday Jan 06, 2009

JavaFX Rated as Top 10 for 2008

As you know, JavaFX 1.0 was released in early December and has been receiving some exciting attention among developers and the press. For example, eweek has ranked JavaFX as number 5 in its Top 10 Application Development Products for 2008.

If you haven't yet checked it out, I would refer you to the last several entries on this blog. :)

Also, if you would like to run JavaFX applications and applets while not connected to the internet, check out this article by Thomas Ng.

Happy 2009!!

-- Sharon Zakhour

pageicon Friday Dec 12, 2008

JavaFX Coffee Cup

So the JavaFX™ SDK 1.0 has shipped. I thought that now would be a fun time to try out some of its graphics capabilities. I know that its graphics are really slick... but how easy are they to program? Do you need to be a graphics expert to figure it all out, or can anyone just pick it up and learn?

As for my background, I'm a technical writer and general-purpose computer programmer. I am not a software engineer, graphic designer, or GUI expert. Because of that I'm probably the ideal person to test drive the usability of GUI coding with the SDK. The challenge to myself was to take a single afternoon and code up something "impressive"; it didn't matter what, I just wanted it to have a 3D feel with modern visual/lighting effects like what I see in the SDK demos.

So the first thing I did was to read the GUI tutorial, which quickly brought me up to speed on the basics. (Those completely new to the JavaFX Script programming language will want to first read the core tutorial as well.) I then went to the web, looking for information on drawing 3D shapes in general.

I came across this article, which builds a 3D coffee cup using a freeware vector graphics drawing program called inkscape. Having a limited GUI background, this was exactly the kind of breakdown that I needed. It explained how to make "3D" looking objects from basic 2D shapes, filled in with various color gradients. The challenge to myself was to basically "port" their tool instructions to equivalent calls in the API. By the end of the afternoon, I'd made this cool looking cup:

Here's how I did it...

Making the Frame

Setting up the basic application frame was super easy to do. JavaFX technology lets you "group" multiple objects together, so I made one group for the plate, and another for the cup. These two groups will contain the graphical objects to be rendered on screen:

import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.Group;

def plateGroup = Group {
     // The group of objects forming the "plate" will go here
}

def cupGroup = Group {
    // The group of objects forming the "cup" will go here
}

Stage {
    title: "Coffee Cup"
    visible: true
    scene: Scene {
         width: 500
         height: 500
         content: [plateGroup,cupGroup]
     }
}

There's nothing to display yet, but the code is organized, and that's an important first step. I've used the def keyword to assign these groups to individual variables; this makes it easier to "unplug" a finished group (i.e. the plate) to work on the other (the cup) in isolation, if need be.

Making the Plate

The first step in making the plate was to draw an Ellipse, filled with a RadialGradient that spans three colors (Color.WHITESMOKE, Color.LIGHTGRAY, and Color.DARKGREY):

import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;

def plateGroup = Group {

     translateX: 250
     translateY: 300

     content: [
          Ellipse {centerX: 0 centerY: 0  radiusX: 170 radiusY: 50
               fill: RadialGradient{
                         centerX: 0.5 centerY: 0.75
                         stops: [Stop {offset: 0.0 color: Color.WHITESMOKE},
                                 Stop {offset: 0.5 color: Color.LIGHTGRAY},
                                 Stop {offset: 1.0 color: Color.DARKGRAY}]
               } 
          } 
     ] 
}

def cupGroup = Group {
    // The group of objects forming the "cup" will go here
}

Stage {
    title: "Coffee Cup"
    visible: true
    scene: Scene {
         width: 500
         height: 500
         content: [plateGroup,cupGroup]
     }
}

The translateX and translateY variables of the Group object proved useful for moving the entire group around on screen. I found that getting the exact values for the gradient was just a matter of trial and error. There are a number of different settings that all looked good; finding something that looked "right" was just a matter of plugging in different values and recompiling the program.

Next I added a dark gray ellipse below the plate, and another thin ellipse to make the lip (giving it a 3D feel):

import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;

def plateGroup = Group {

translateX: 250
translateY: 300

 content: [ 

          // The gray ellipse under the plate
          Ellipse{centerX: 0 centerY: 10 radiusX: 160 radiusY: 50 fill: Color.DIMGRAY},

          // The thin "lip" of the plate (provides a sense of 3D)
          Ellipse{centerX: 0 centerY: 3  radiusX: 170 radiusY: 50 fill: Color.LAVENDER}, 

          // The large plate ellipse
          Ellipse{centerX: 0 centerY: 0  radiusX: 170 radiusY: 50
          fill: RadialGradient{
                    centerX: 0.5 centerY: 0.75
                    stops:[Stop {offset: 0.0 color: Color.WHITESMOKE},
                           Stop {offset: 0.5 color: Color.LIGHTGRAY},
                           Stop {offset: 1.0 color: Color.DARKGRAY}]
               }
          }
     ]
}

def cupGroup = Group {
    // The group of objects forming the "cup" will go here
}

Stage {
    title: "Coffee Cup"
    visible: true
    scene: Scene {
         width: 500
         height: 500
         content: [plateGroup,cupGroup]
     }
}

Since GUI objects are rendered in the order they appear in the source code, I put the main plate ellipse last so that it would cover the tops of all the others. Again, finding the exact values was just a matter of experimentation.

Finally, I recessed the center of the plate:

import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;

def plateGroup = Group {

translateX: 250
translateY: 300

 content: [

          // The gray ellipse under the plate
          Ellipse{centerX:0 centerY:10 radiusX:160 radiusY:50 fill:Color.DIMGRAY},

          // The thin "lip" of the plate (provides a sense of 3D)
          Ellipse{centerX:0 centerY:3  radiusX:170 radiusY:50 fill:Color.LAVENDER},

          // The large plate ellipse
          Ellipse{centerX:0 centerY:0  radiusX:170 radiusY:50
          fill:RadialGradient{
                    centerX:0.5 centerY:0.75
                    stops:[Stop {offset: 0.0 color: Color.WHITESMOKE},
                           Stop {offset: 0.5 color: Color.LIGHTGRAY},
                           Stop {offset: 1.0 color: Color.DARKGRAY}]
               }
          },

         // Recessed plate center
           Ellipse{centerX: 0 centerY: 5 radiusX: 90 radiusY: 22
                fill: RadialGradient{
                          centerX: 0.5 centerY: 0.75
                          stops:[Stop {offset: 0.0 color: Color.BLACK},
                                 Stop {offset: 0.4 color: Color.LIGHTGRAY},
                                 Stop {offset: 1.0 color: Color.GHOSTWHITE}]
                     }
          }
     ]
}

def cupGroup = Group {
    // The group of objects forming the "cup" will go here
}

Stage {
    title: "Coffee Cup"
    visible: true
    scene: Scene {
         width: 500
         height: 500
         content: [plateGroup,cupGroup]
     }
}

Without this dark shadow, the cup bottom would be hard to see when it's finally in place. In the end this darkened area will be more subtle because the cup will be hiding most of it.

Making the Cup

To make the cup, I just started with a Circle, giving it the the same kind of gradient as used in the plate. My approach here was to treat the cup like a blob of clay. The API contains powerful built-in transforms that I can later use to "stretch" the cup into shape. How cool is that?

import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;

def plateGroup = Group {

translateX: 250
translateY: 300

 content: [

          // The gray ellipse under the plate
          Ellipse{centerX:0 centerY:10 radiusX:160 radiusY:50 fill:Color.DIMGRAY},

          // The thin "lip" of the plate (provides a sense of 3D)
          Ellipse{centerX:0 centerY:3  radiusX:170 radiusY:50 fill:Color.LAVENDER},

          // The large plate ellipse
          Ellipse{centerX:0 centerY:0  radiusX:170 radiusY:50
          fill:RadialGradient{
                    centerX:0.5 centerY:0.75
                    stops:[Stop {offset: 0.0 color: Color.WHITESMOKE},
                           Stop {offset: 0.5 color: Color.LIGHTGRAY},
                           Stop {offset: 1.0 color: Color.DARKGRAY}]
               }
          },

         // Recessed plate center
           Ellipse{centerX: 0 centerY: 5 radiusX: 90 radiusY: 22
                fill: RadialGradient{
                          centerX: 0.5 centerY: 0.75
                          stops:[Stop {offset: 0.0 color: Color.BLACK},
                                 Stop {offset: 0.4 color: Color.LIGHTGRAY},
                                 Stop {offset: 1.0 color: Color.GHOSTWHITE}]
                     }
          }
     ]
}

def cupGroup = Group {

     translateX: 152
     translateY: 20

     content: [
               // Cup body
               Circle {centerX: 100  centerY: 100 radius: 50
                    fill: RadialGradient {
                               centerX: 0.4 centerY: 0.0 focusX: 0.5 focusY: .65, proportional:true
                               stops: [Stop {offset: 0.0 color: Color.GHOSTWHITE},
                                       Stop {offset: 1.0 color: Color.SILVER}]
               }
          }
     ]
}


Stage {
    title: "Coffee Cup"
    visible: true
    scene: Scene {
         width: 500
         height: 500
         content: [plateGroup,cupGroup]
     }
}

To make this ball more "cup-like", I next placed a white rectangle over the top of the circle to flatten it out:

import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;

def plateGroup = Group {

translateX: 250
translateY: 300

 content: [

          // The gray ellipse under the plate
          Ellipse{centerX:0 centerY:10 radiusX:160 radiusY:50 fill:Color.DIMGRAY},

          // The thin "lip" of the plate (provides a sense of 3D)
          Ellipse{centerX:0 centerY:3  radiusX:170 radiusY:50 fill:Color.LAVENDER},

          // The large plate ellipse
          Ellipse{centerX:0 centerY:0  radiusX:170 radiusY:50
          fill:RadialGradient{
                    centerX:0.5 centerY:0.75
                    stops:[Stop {offset: 0.0 color: Color.WHITESMOKE},
                           Stop {offset: 0.5 color: Color.LIGHTGRAY},
                           Stop {offset: 1.0 color: Color.DARKGRAY}]
               }
          },

         // Recessed plate center
           Ellipse{centerX: 0 centerY: 5 radiusX: 90 radiusY: 22
                fill: RadialGradient{
                          centerX: 0.5 centerY: 0.75
                          stops:[Stop {offset: 0.0 color: Color.BLACK},
                                 Stop {offset: 0.4 color: Color.LIGHTGRAY},
                                 Stop {offset: 1.0 color: Color.GHOSTWHITE}]
                     }
          }
     ]
}

def cupGroup = Group {

     translateX: 152
     translateY: 20

     content: [
               // Cup body
               Circle {centerX: 100  centerY: 100 radius: 50
                    fill: RadialGradient {
                               centerX: 0.4 centerY: 0.0 focusX: 0.5 focusY: .65, proportional:true
                               stops: [Stop {offset: 0.0 color: Color.GHOSTWHITE},
                                       Stop {offset: 1.0 color: Color.SILVER}]
               }
          },


               // Cut top of cup
               Rectangle{stroke: Color.WHITE fill: Color.WHITE 
                         x: 25 y: 50 width: 150 height: 50} 
     ]
}

Stage {
    title: "Coffee Cup"
    visible: true
    scene: Scene {
         width: 500
         height: 500
         content: [plateGroup,cupGroup]
     }
}

After that, I just added a couple more ellipses to make the outer and inner rims:

import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;

def plateGroup = Group {

translateX: 250
translateY: 300

 content: [

          // The gray ellipse under the plate
          Ellipse{centerX:0 centerY:10 radiusX:160 radiusY:50 fill:Color.DIMGRAY},

          // The thin "lip" of the plate (provides a sense of 3D)
          Ellipse{centerX:0 centerY:3  radiusX:170 radiusY:50 fill:Color.LAVENDER},

          // The large plate ellipse
          Ellipse{centerX:0 centerY:0  radiusX:170 radiusY:50
          fill:RadialGradient{
                    centerX:0.5 centerY:0.75
                    stops:[Stop {offset: 0.0 color: Color.WHITESMOKE},
                           Stop {offset: 0.5 color: Color.LIGHTGRAY},
                           Stop {offset: 1.0 color: Color.DARKGRAY}]
               }
          },

         // Recessed plate center
           Ellipse{centerX: 0 centerY: 5 radiusX: 90 radiusY: 22
                fill: RadialGradient{
                          centerX: 0.5 centerY: 0.75
                          stops:[Stop {offset: 0.0 color: Color.BLACK},
                                 Stop {offset: 0.4 color: Color.LIGHTGRAY},
                                 Stop {offset: 1.0 color: Color.GHOSTWHITE}]
                     }
          }
     ]
}

def cupGroup = Group {

     translateX: 152
     translateY: 20

     content: [
               // Cup body
               Circle {centerX: 100  centerY: 100 radius: 50
                    fill: RadialGradient {
                               centerX: 0.4 centerY: 0.0 focusX: 0.5 focusY: .65, proportional:true
                               stops: [Stop {offset: 0.0 color: Color.GHOSTWHITE},
                                       Stop {offset: 1.0 color: Color.SILVER}]
               }
          },

               // Cut top of cup
               Rectangle{stroke: Color.WHITE fill: Color.WHITE 
                         x: 25 y: 50 width: 150 height: 50},

               // Outer rim
               Ellipse{fill: Color.WHITE centerX: 100 centerY: 100 radiusX: 50 radiusY: 8},

               // Inner rim
               Ellipse{centerX: 100 centerY: 100 radiusX: 48 radiusY: 7 //inner rim
                       fill: RadialGradient {
                                             centerX: 0.4 centerY: 0.0 focusX: 0.5 focusY:.4, proportional:true
                                             stops: [Stop {offset: 0.0 color: Color.GHOSTWHITE},
                                                     Stop {offset: 1.0 color: Color.SILVER}]
                       }
               }
     ]
}

Stage {
    title: "Coffee Cup"
    visible: true
    scene: Scene {
         width: 500
         height: 500
         content: [plateGroup,cupGroup]
     }
}

Things are looking good! But the cup is still too small. So to make the cup "grow", I just scaled the entire cup group upwards. I actually scaled the y axis slightly more, so as to squish the cup inwards a little to shape it more like an egg:

import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;

def plateGroup = Group {

translateX: 250
translateY: 300

 content: [

          // The gray ellipse under the plate
          Ellipse{centerX:0 centerY:10 radiusX:160 radiusY:50 fill:Color.DIMGRAY},

          // The thin "lip" of the plate (provides a sense of 3D)
          Ellipse{centerX:0 centerY:3  radiusX:170 radiusY:50 fill:Color.LAVENDER},

          // The large plate ellipse
          Ellipse{centerX:0 centerY:0  radiusX:170 radiusY:50
          fill:RadialGradient{
                    centerX:0.5 centerY:0.75
                    stops:[Stop {offset: 0.0 color: Color.WHITESMOKE},
                           Stop {offset: 0.5 color: Color.LIGHTGRAY},
                           Stop {offset: 1.0 color: Color.DARKGRAY}]
               }
          },

         // Recessed plate center
           Ellipse{centerX: 0 centerY: 5 radiusX: 90 radiusY: 22
                fill: RadialGradient{
                          centerX: 0.5 centerY: 0.75
                          stops:[Stop {offset: 0.0 color: Color.BLACK},
                                 Stop {offset: 0.4 color: Color.LIGHTGRAY},
                                 Stop {offset: 1.0 color: Color.GHOSTWHITE}]
                     }
          }
     ]
}

def cupGroup = Group {

     translateX: 152
     translateY: 20
     scaleX: 3.0
     scaleY: 4.0

     content: [
               // Cup body
               Circle {centerX: 100  centerY: 100 radius: 50
                    fill: RadialGradient {
                               centerX: 0.4 centerY: 0.0 focusX: 0.5 focusY: .65, proportional:true
                               stops: [Stop {offset: 0.0 color: Color.GHOSTWHITE},
                                       Stop {offset: 1.0 color: Color.SILVER}]
               }
          },

               // Cut top of cup
               Rectangle{stroke: Color.WHITE fill: Color.WHITE 
                         x: 25 y: 50 width: 150 height: 50},

               // Outer rim
               Ellipse{fill: Color.WHITE centerX: 100 centerY: 100 radiusX: 50 radiusY: 8},

               // Inner rim
               Ellipse{centerX: 100 centerY: 100 radiusX: 48 radiusY: 7 //inner rim
                       fill: RadialGradient {
                                             centerX: 0.4 centerY: 0.0 focusX: 0.5 focusY:.4, proportional:true
                                             stops: [Stop {offset: 0.0 color: Color.GHOSTWHITE},
                                                     Stop {offset: 1.0 color: Color.SILVER}]
                       }
               },
     ]
}

Stage {
    title: "Coffee Cup"
    visible: true
    scene: Scene {
         width: 500
         height: 500
         content: [plateGroup,cupGroup]
     }
}

Filling the Cup

Now it's time to fill the cup! For this I made a custom shape from the intersection of the inner rim and a new coffee-colored ellipse. The built-in ShapeIntersect class makes this a trivial matter:

import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;

def plateGroup = Group {

translateX: 250
translateY: 300

 content: [

          // The gray ellipse under the plate
          Ellipse{centerX:0 centerY:10 radiusX:160 radiusY:50 fill:Color.DIMGRAY},

          // The thin "lip" of the plate (provides a sense of 3D)
          Ellipse{centerX:0 centerY:3  radiusX:170 radiusY:50 fill:Color.LAVENDER},

          // The large plate ellipse
          Ellipse{centerX:0 centerY:0  radiusX:170 radiusY:50
          fill:RadialGradient{
                    centerX:0.5 centerY:0.75
                    stops:[Stop {offset: 0.0 color: Color.WHITESMOKE},
                           Stop {offset: 0.5 color: Color.LIGHTGRAY},
                           Stop {offset: 1.0 color: Color.DARKGRAY}]
               }
          },

         // Recessed plate center
           Ellipse{centerX: 0 centerY: 5 radiusX: 90 radiusY: 22
                fill: RadialGradient{
                          centerX: 0.5 centerY: 0.75
                          stops:[Stop {offset: 0.0 color: Color.BLACK},
                                 Stop {offset: 0.4 color: Color.LIGHTGRAY},
                                 Stop {offset: 1.0 color: Color.GHOSTWHITE}]
                     }
          }
     ]
}

def cupGroup = Group {

     translateX: 152
     translateY: 20
     scaleX: 3.0
     scaleY: 4.0

     content: [
               // Cup body
               Circle {centerX: 100  centerY: 100 radius: 50
                    fill: RadialGradient {
                               centerX: 0.4 centerY: 0.0 focusX: 0.5 focusY: .65, proportional:true
                               stops: [Stop {offset: 0.0 color: Color.GHOSTWHITE},
                                       Stop {offset: 1.0 color: Color.SILVER}]
               }
          },

          // Cut top of cup
          Rectangle{stroke: Color.WHITE fill: Color.WHITE 
                    x: 25 y: 50 width: 150 height: 50},

          // Outer rim
          Ellipse{fill: Color.WHITE centerX: 100 centerY: 100 radiusX: 50 radiusY: 8},

          // Inner rim
          Ellipse{centerX: 100 centerY: 100 radiusX: 48 radiusY: 7 //inner rim
                  fill: RadialGradient {
                                        centerX: 0.4 centerY: 0.0 focusX: 0.5 focusY:.4, proportional:true
                                        stops: [Stop {offset: 0.0 color: Color.GHOSTWHITE},
                                                Stop {offset: 1.0 color: Color.SILVER}]
                  }
          },
          
         // Create "coffee in cup" by intersecting coffee ellipse with inner rim ellipse
         ShapeIntersect {

              fill: Color.SADDLEBROWN

              //Inner Rim
              a: Ellipse{centerX:100 centerY:100 radiusX:48 radiusY:7} // inner rim dimensions

              //Coffee
              b: Ellipse{centerX:100 centerY:102 radiusX:46 radiusY:6} // coffee dimensions
         }
     ]
}

Stage {
    title: "Coffee Cup"
    visible: true
    scene: Scene {
         width: 500
         height: 500
         content: [plateGroup,cupGroup]
     }
}

And finally, I gave the whole thing a little extra class by setting the background to black:

import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.paint.Color;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.paint.Stop;
import javafx.scene.paint.RadialGradient;

def BGCOLOR = Color.BLACK;

def plateGroup = Group {

     translateX: 250
     translateY: 300

     content: [

          // The gray ellipse under the plate
          Ellipse{centerX:0 centerY:10 radiusX:160 radiusY:50 fill:Color.DIMGRAY},

          // The thin "lip" of the plate (provides a sense of 3D)
          Ellipse{centerX:0 centerY:3  radiusX:170 radiusY:50 fill:Color.LAVENDER},

          // The large plate ellipse
          Ellipse{centerX:0 centerY:0  radiusX:170 radiusY:50
          fill:RadialGradient{
                    centerX:0.5 centerY:0.75
                    stops:[Stop {offset: 0.0 color: Color.WHITESMOKE},
                           Stop {offset: 0.5 color: Color.LIGHTGRAY},
                           Stop {offset: 1.0 color: Color.DARKGRAY}]
               } 
          },

          // Recessed plate center
           Ellipse{
                centerX:0 centerY:5 radiusX:90 radiusY:22
                fill:RadialGradient{
                          centerX:0.5 centerY:0.75
                          stops:[Stop {offset: 0.0 color: Color.BLACK},
                                 Stop {offset: 0.4 color: Color.LIGHTGRAY},
                                 Stop {offset: 1.0 color: Color.GHOSTWHITE}]
                     }
          }
     ] 
} 

def cupGroup = Group {
  
     translateX: 152
     translateY: 20
     scaleX: 3.0
     scaleY: 4.0

     content: [
               // Cup body
               Circle {centerX: 100  centerY: 100 radius: 50
                    fill: RadialGradient {
                               centerX: 0.4 centerY: 0.0 focusX: 0.5 focusY:.65, proportional:true
                               stops: [Stop {offset: 0.0 color: Color.GHOSTWHITE},
                                       Stop {offset: 1.0 color: Color.SILVER}]
                             }
                      },

               // Cut top of cup
               Rectangle{stroke: BGCOLOR fill: BGCOLOR
                         x:25 y:50 width:150 height:50},

               // Outer rim
               Ellipse{fill: Color.WHITE centerX:100 centerY:100 radiusX:50 radiusY:8},

               // Inner rim
               Ellipse{centerX:100 centerY:100 radiusX:48 radiusY:7 //inner rim
                       fill: RadialGradient {
                                             centerX: 0.4 centerY: 0.0 focusX: 0.5 focusY:.4, proportional:true
                                             stops: [Stop {offset: 0.0 color: Color.GHOSTWHITE},
                                                     Stop {offset: 1.0 color: Color.SILVER}]
                       }
               },

               // Create "coffee in cup" by intersecting coffee ellipse with inner rim ellipse
               ShapeIntersect {

                    fill:Color.SADDLEBROWN

                    //Inner Rim
                    a: Ellipse{centerX:100 centerY:100 radiusX:48 radiusY:7} // inner rim dimensions

                    //Coffee
                    b: Ellipse{centerX:100 centerY:102 radiusX:46 radiusY:6} // coffee dimensions
               }
     ]
}

Stage {
    title: "Coffee Cup"
    width: 500
    height: 500
    visible: true
    scene: Scene {
         fill: BGCOLOR
         content: [plateGroup,cupGroup]
     }
}

Conclusion and Future Steps

In conclusion, I found my first attempt at GUI programming with the 1.0 SDK to be quite successful, and ultimately a lot of fun. The declarative programming model is easy to use. Creating sophisticated visual effects becomes almost effortless. The most time-consuming part of the entire project was just tweaking the different gradient values to get the lighting the way I wanted. But the declarative nature of this language really made the actual programming a breeze. I'm really looking forward to what this language can do when it's placed into the hands of real graphic designers!

I'm stopping here because this is what I was able to accomplish in a single afternoon. From here the cup needs a handle and some extra decorations on its face. It would also be great to use key frame animation to add in a steam effect. If you're reading this and would like to add them in, feel free to post your solutions!

-- Scott Hommel

pageicon Tuesday Dec 09, 2008

JavaFX on Facebook

Some folks at Sun have created the first JavaFX application to run on Facebook. If you have a Facebook account, please try it out! (And if you don't have a Facebook account, it's free.)

The picture puzzle app runs on any browser supported by JavaFX, although you get more functionality if you are running JRE 6u10 or better.

To play with the jigsaw puzzle:

  1. Login to Facebook.
  2. Search on JavaFX and from the results select JavaFXPicturePuzzle — it should be the second item listed. (It's located at http://apps.facebook.com/javafxpicturepuzzle/Puzzle.)
  3. Select Go to Application and accept the security questions when prompted.
  4. Bookmark the app so that you can easily find it again — the Bookmark JavaFXPicturePuzzle button is at the bottom of the page.
  5. Please share with your friends by selecting the Share with Friends tab near the top of the page.

Let us know what you think!

-- Sharon Zakhour

pageicon Monday Dec 08, 2008

JavaFX is here!!

Released late last week, JavaFX 1.0 is here!!!

My team created two of the tutorials for JavaFX:

Please check it out and let us know what you think!

Sharon Zakhour


« June 2009
SunMonTueWedThuFriSat
 
1
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
    
       
Today

Feeds

Search this blog

Links

Weblog menu

Today's referrers

Today's Page Hits: 226