HEAD PREVIOUS

Chapter 5
MyTunes in under 200 Lines

Tunes have become a popular topic. Let's build a music player! Before we get started, bear in mind that we won't have to build features such as Smart Lists for songs; JMatter will give us those things for free. We'll be able to concentrate on the task at hand: modeling our application and implementing the behaviours of our music player, such as importing songs into our song database, and playing songs, of course. Let's get started.

5.1  Creating the Project

We know how to create our project. I'll name my project MyTunes and make it a dependent project. A finished copy of this project is already bundled with JMatter. If you wish, you can simply follow along using the existing demo application.
$ cd jmatter/
$ ant new-project-ui
$ cd ../MyTunes
$ ant -projecthelp
So far so good. Next, let's setup our project in our favorite IDE because we're finally going to be writing code.

5.2  Setup your Project in an IDE

Technically, this step is optional. These days, the state of Integrated Development Environments (IDEs) for writing software in Java is so advanced that it is compelling to use one. These IDEs sport command completion features, intelligent assisted editing, automatic code formatting, useful hints, refactoring support, etc..
At the time of this writing, the three major IDEs for Java are Eclipse http://www.eclipse.org/, IntelliJ IDEA http://www.jetbrains.com/idea/, and NetBeans http://www.netbeans.org/. It doesn't really matter which you use, or whether you use one at all, as long as you're comfortable in your development environment.
Below is a description of the steps that I take as I setup my project in IntelliJ IDEA:
  1. Launch the IDE program
  2. Create New Project (launches a wizard)
    1. specify the project name as MyTunes, and
    2. the base directory path
  3. Pick the version of Java that I wish to use (Java 5)
  4. Specify that we'll be using a single module project
  5. Select the module type: Java module
  6. Specify the module name as MyTunes (accept the defaults)
  7. Make sure the source directory name is src
  8. Make sure the build directory path (where the compiled classes are placed) is build/classes
  9. Click Finish
Nothing special here. Actually, most of the defaults were already correct; that is, I mostly clicked Next Next Next, with one or two deviations from the defaults. My project is almost completely setup. The last thing I need to do is add the jar files in ../jmatter/lib/runtime to my project classpath so that the classes we reference can be resolved. If you created a dependent project as I did, add the directory ../jmatter/build/classes to your classpath as well. Since the specific instructions vary from IDE to IDE, and since this is such a basic operation, I won't hold your hand through this task. :-)
Before getting started with coding in the next section, you might want take a quick peek at section to setup some JMatter-specific file templates and live templates which will make keying the code much simpler.

5.3  Getting Started

Ok, checking out my iTunes application on a nearby powerbook, I see that the main entities in a music player system appear to be Songs, Albums, Artists, and Genres. Pretty straightforward. Let's get started by modeling each of these:
  1. Create a package to hold your classes; mine will be com.u2d.mytunes
  2. Create four classes: Song, Album, Artist, and Genre
Let's take a look at a basic template for implementing the Artist JMatter class:
package com.u2d.mytunes;
import com.u2d.model.AbstractComplexEObject;
import com.u2d.model.Title;
import com.u2d.type.atom.StringEO;
import com.u2d.type.atom.TextEO;
import com.u2d.persist.Persist;
@Persist
public class Artist extends AbstractComplexEObject
{
   private final StringEO _name = new StringEO();
   private final TextEO _bio = new TextEO();
   
   public static final String[] fieldOrder = {"name", "bio"};
   public Artist() {}
   public StringEO getName() { return _name; }
   public TextEO getBio() { return _bio; }
   public Title title() { return _name.title(); }
}
Let's analyze the above:
  1. The @Persist Annotation: automates having to manually edit src/persistClasses.st as we had to do in the last chapter
  2. Artist extends AbstractComplexEObject
    In Java all the classes we write extend Object. When building JMatter applications, the base class for complex entities (ones that have more than a single field) is AbstractComplexEObject. Just like Object gives us toString() for free, AbstractComplexEObject also gives us a few things for free.
  3. Artist has two fields: name, and bio
    It happens to be a personal style choice of the author to name instance variables with the underscore prefix. This, of course, is not necessary.
  4. Funky types: name is a StringEO, bio is a TextEO
    That's right. JMatter gives you a whole slew of atomic types to choose from, including types for phone numbers, social security numbers, time durations, images, email addresses, colors, and much more. It's quite easy to adapt a String as a StringEO and vice versa. There are two differences between a StringEO and a TextEO:
    1. StringEO's are saved to the database as varchar types (limit is typically 255) whereas TextEO's are saved as text types or some other similar large text object18; and
    2. StringEO editors in the GUI are text fields whereas TextEO editors are text areas.
      Think of the EO part of the type name as being an acronym for Enhanced Object.
  5. Name and Bio are marked final!
    Yep. That's part of the ValueHolder contract, these are never null. To set a value on an atomic or aggregate type, call the setValue() method. Consequently, atomic fields and aggregates in JMatter classes have only getters.
  6. public static final String[] fieldOrder = {"name", "bio"}
    This is essentially metadata. You're giving JMatter a hint as to the order that you'd like these fields displayed when viewed using a form view. Name should come first, bio second. Notice that the field names are those derived from the accessor methods according to the JavaBeans convention, not the underlying variable names.
  7. public Title title() { return _name.title(); }
    Every JMatter class you define must specify a title. In this case, the title for an artist object will simply be the artist's name. For the Person class in the previous chapter it was a concatenation of the person's name and the value of their preferred contact method (phone number or email address). The idea for the Title class was shamelessly taken from the NakedObjects framework [1], though you'll find in it a few additional features, such as the appendParens() method which allows you to perform concatenation by wrapping the argument in parentheses.
That's it. The code is not very lengthy, but this first-time explanation certainly is.
One last note: If you're thinking right now that metadata such as the fieldOrder field above might be better modeled using Java 5 annotations, I would agree with you; I may do just that in the next version of JMatter. But this design decision has no bearing on your productivity as a software developer.

5.4  The Album Class

In a fashion similar to the way we wrote Artist, let's now write a simple implementation for an Album:
package com.u2d.mytunes;
import com.u2d.type.atom.StringEO;
import com.u2d.type.atom.ImgEO;
import com.u2d.model.AbstractComplexEObject;
import com.u2d.model.Title;
import com.u2d.list.RelationalList;
import com.u2d.persist.Persist;
@Persist
public class Album extends AbstractComplexEObject
{
   private final StringEO _name = new StringEO();
   private final ImgEO _cover = new ImgEO();
   
   public static final String[] fieldOrder = {"name", "cover"};
   public Album() {}
   public StringEO getName() { return _name; }
   public ImgEO getCover() { return _cover; }
   public Title title() { return _name.title(); }
}
There's really very little new here. It's worth mentioning that we're defining a field of type ImgEO for the album's cover illustration. This basic type represents an image (gif, jpg, or png) that will be saved to database as some kind of binary large object (blob).

5.5  The Genre Class

Let's take a look at Genre next. Genre is a little more interesting. I decided to consider this type more like an enumeration. That is, that there should exist a fairly small, finite list of genres. However, unlike enumerations, we don't want to hard-code the list of genres in Java. We would like the end-user to be able to add entries to the list directly from the user interface. This is the template that JMatter defines for such database-backed enumerations:
package com.u2d.mytunes;
import com.u2d.type.AbstractChoiceEO;
import com.u2d.type.atom.StringEO;
import com.u2d.model.ComplexType;
import com.u2d.persist.Persist;
@Persist
public class Genre extends AbstractChoiceEO
{
   private final StringEO _code = new StringEO();
   private final StringEO _caption = new StringEO();
   public static String[] identities = {"code"};
   
   public Genre() {}
   public Genre(String code, String caption)
   {
      _code.setValue(code);
      _caption.setValue(caption);
   }
   
   public StringEO getCode() { return _code; }
   public StringEO getCaption() { return _caption; }
}
JMatter will make sure to give us a database table to hold genres. Notice that we've extended a different class: AbstractChoiceEO. In the GUI, when we want to specify a genre for a song, we'll be picking the genre from a ComboBox (also called a select or pull-down menu). For each Genre object we'll specify both a code and a caption. The line
public static String[] identities = {"code"};
is metadata telling JMatter that the code column in the table should be unique for all Genres (that is, you can't have two genres with the same code, 'rock' for example).

5.6  The Song Class

Here's a first stab at the Song class:
package com.u2d.mytunes;
[imports collapsed]
@Persist
public class Song extends AbstractComplexEObject
{
   private final StringEO _title = new StringEO();
   private final TimeEO _duration = new TimeEO();
   private Album _album;
   private final Genre _genre = new Genre();
   private Artist _artist;
   public static String[] fieldOrder = 
                    {"title", "duration", "artist", "album", "genre"};
   public Song() {}
   public StringEO getTitle() { return _title; }
   @Fld(format="m:ss")
   public TimeEO getDuration() { return _duration; }
   public Album getAlbum() { return _album; }
   public void setAlbum(Album album)
   {
      Album oldAlbum = _album;
      _album = album;
      firePropertyChange("album", oldAlbum, _album);
   }
   public Genre getGenre() { return _genre; }
   public Artist getArtist() { return _artist; }
   public void setArtist(Artist artist)
   {
      Artist oldValue = _artist;
      _artist = artist;
      firePropertyChange("artist", oldValue, _artist);
   }
   public Title title() { return _title.title().appendBracket(_duration); }
}
So far, this is more of the same, with a few new concepts. Let's review this class:
  1. The song class defines the fields: title, duration, album, genre, and artist. As usual, we define the fieldOrder String array as a means of specifying the order in which these fields are to be displayed.
  2. The duration field is of a type we haven't seen before: TimeEO. This type can be used to specify a certain length of time.
  3. @Fld(format="m:ss")
    We're specifying through a field annotation parameter that in this application we're going to want to format durations using minutes and seconds. The format string complies with the contract specified by java.text.SimpleDateFormat.
  4. Accessors and Mutators
    Finally we see that not all fields are owned by the Song class. The fields album and artist are associations to other entities. The convention for defining and specifying the accessors and mutators for association fields is different. It complies with the JavaBeans convention for bound properties. Notice we:
    1. provide both a getter and a setter, and
    2. fire a PropertyChangeEvent after the assignment.
  5. Here we have a better example of a title() method. First, don't confuse the song title with the song type's title() method. They're two different things. We want songs to display their titles and durations and so we concatenate the two. I use the utility method appendBracket to display the song duration in square brackets.

5.7  Running our Application

We've been a little zealous here and defined all four classes Artist, Album, Genre, and Song at once. This is not an example of good incremental coding practice. However, we made sure to keep these classes really simple. We didn't implement any behavior, we simply defined four types along with some pretty basic properties on each, such as the Artist's name, a Song's title. If you like, feel free to keep the Song class even more simple by just giving it a title for now.

5.7.1  Configuration

Technically there is no configuration left to do. However, it might be simpler for us to specify the contents of the classbar for our application in xml form. Again, this is optional. The file to edit is src/class-list.xml. Here's a sample that'll do nicely:
<?xml version="1.0" encoding="UTF-8"?>
<folder>
   <name>Class List</name>
   <items>
      <folder>
         <name>Model</name>
         <items>
           <type>com.u2d.mytunes.Song</type>
           <type>com.u2d.mytunes.Album</type>
           <type>com.u2d.mytunes.Artist</type>
           <type>com.u2d.mytunes.Genre</type>
           <type>com.u2d.app.User</type>
           <type>com.u2d.type.composite.Folder</type>
           <type>com.u2d.find.CompositeQuery</type>
           <type>com.u2d.type.composite.LoggedEvent</type>
         </items>
      </folder>
   </items>
</folder>

5.7.2  Finally Running the Application

Nothing new here. Let's run our app:
ant schema-export
ant run
After logging in we should see something that looks like figure 5.1.
figures/MyTunes-1.png
Figure 5.1: A first look at MyTunes
This is problematic: we haven't specified any icons for our types. Let me tell you (and I speak from experience) that the choice and quality of your icons is as important to the success of your application than the quality of the code itself. I strongly believe that all aspects of our application must be of high quality, regardless of whether it's the code under the hood or perhaps cosmetic items that many developers often perceive as being outside their jurisdiction, or outside their realm of expertise.
Here's how to specify icons for each type: in the resources/images directory, place 16x16 and 32x32 pixel versions of an icon for each type. The naming of these image files is important. It is via a file naming convention that JMatter knows to associate an image with a type. This idea again was shamelessly taken from the NakedObjects framework. If you haven't guessed it by now, NakedObjects [1] had a strong influence on my thinking.
Ok, images can be of type jpg, png, or gif, or anything that Java Standard Edition supports. Here is a listing of my resources/images folder:
eitan@ubuntu:/projects/ds/MyTunes$ ls -lF resources/images/
total 40
-rwxr-xr-x  1 eitan eitan  855 2005-11-28 21:38 Album16.png*
-rwxr-xr-x  1 eitan eitan 1961 2005-11-28 21:39 Album32.png*
-rwxr-xr-x  1 eitan eitan 1961 2005-11-29 07:56 App32.png*
-rwxr-xr-x  1 eitan eitan  859 2005-11-28 21:39 Artist16.png*
-rwxr-xr-x  1 eitan eitan 2170 2005-11-28 21:40 Artist32.png*
-rwxr-xr-x  1 eitan eitan  925 2005-11-28 21:39 Genre16.png*
-rwxr-xr-x  1 eitan eitan 2596 2005-11-28 21:39 Genre32.png*
-rw-r--r--  1 eitan eitan  753 2005-11-28 21:13 README
-rwxr-xr-x  1 eitan eitan  696 2005-11-28 21:38 Song16.png*
-rwxr-xr-x  1 eitan eitan 1720 2005-11-28 21:38 Song32.png*
eitan@ubuntu:/projects/ds/MyTunes$
The basic convention is quite simple: [ClassName]16.png and [ClassName]32.png for each class. But that's not the whole story on icons. For a complete description of the icon conventions, see section 8.4.3.
If, like me, producing icons is not your forte, then I highly recommend the incors professional icons collection http://www.iconexperience.com/. Ok, now things should look a little bit nicer (see figure 5.2).
figures/MyTunes-2.png
Figure 5.2: MyTunes with Icons
That's much better, don't you agree? TODO: Need to setup Genres first before creating songs. Address this (later).
We can play with the user interface some more and create genres, songs, and so on, but we're not quite finished with our application.

5.8  Back To the Code

Our application's foundation is laid; we have a basic object model. But we're missing something: an album should define and maintain a list of songs. We already have the other side of that association in Song.album. Let's add that one-to-many relationship right now. In Album.java, add these:
private final RelationalList _songs = new RelationalList(Song.class);
public static final Class songsType = Song.class;
public RelationalList getSongs() { return _songs; }
If you're thinking that the above might be better modeled using Java 5 generics, I would agree with you; I may do just that in the next version of JMatter.
We defined a relational list of type song. The reason for the static Class songsType is to allow JMatter to introspect the list type statically. It's not pretty but it will do for now.
One last thing: we have a bidirectional one-to-many relationship between a song and an album. We need to add two more lines of metadata to tell JMatter about it. In Album:
public static String songsInverseFieldName = "album";
And in Song:
public static final String albumInverseFieldName = "songs";
Our model might quickly get of sync with the database tables we generated when we add new fields to types. There are two ways to bring them back in sync. The easy way:
ant schema-update
The harder, possibly more correct way:
  1. Dump your data out of the database
  2. ant schema-export (wipes out the data)
  3. restore the data
The restoration of data usually needs to be broken down into at least these steps:
  1. drop constraints on your schema
  2. restore the data
  3. add the constraints back in
Anyhow, in our case, we don't have any precious data to save and restore yet so we'll go the easy route of either using schema-update or schema-export.

5.9  Validation

You might have also noticed that you can now create albums or songs with no name or title. This is a little disconcerting. There are two ways to address this issue. The first might be to specify that a song's title or album's name should be unique. We've seen this already with Genre: you can specify the field metadata identities to enforce this. So in Album, you can add this:
public static String[] identities = {"name"};
Like fieldOrder, you can specify multiple identity fields if necessary. The field is flagged with the 'unique' database constraint and in addition, JMatter treats identity fields as required.
In this case, it happens to be a coincidence that the fields we wanted to mark as required also happened to be unique (i.e. you should definitely not go about marking fields unique for the purpose of making the user interface treat them as required fields).
To mark fields as required, edit the file resources/model-metadata.properties like so:
#
Song.title.required=true
Album.name.required=true
Artist.name.required=true
#
Now JMatter will give you built-in validation support in the user interface:
  1. Required fields' captions are automatically styled in blue; (you can change the styling of required fields by modifying the rule ".required" in the file resources/styles.css19)
  2. Validation checks will automatically be performed when attempting to create or save instances;
  3. The user interface will automatically display validation error messages in the proper locations on the form
Figure 5.3 shows the behavior of the JMatter user interface after the change.
figures/MyTunes-3.png
Figure 5.3: Validation
I attempted to create a new Artist, leaving the name blank. So JMatter is pre-wired to support validation. This mechanism we just specified is the simple way, for required fields. There's an additional method validate() that we can override in any of our types to specify more complex validation rules that have dependencies on multiple fields. For a more complete treatment of validation in JMatter, see chapter 9.

5.9.1  A word about the model-metadata.properties File

Although the mechanism of specifying model metadata in a text file may continue to exist in the future, it is the goal of the JMatter framework to move all of that work directly into the GUI. Whether a field is required, a field's default value, its caption and more will all be entered in the same way one enters the username for a new User. The intent is for Field types (and meta-types in general) to become full-fledged JMatter objects, with a GUI, editability, and so on.
One can definitely imagine a new role for a JMatter user: the configuration manager, who uses a JMatter application to configure a JMatter application before releasing it to its user population. In fact, I recall reading early versions of the J2EE specification years ago where they outlined a number of roles in application development.

5.10  Behaviour

So far, we seem to have nice, working model for a music application: we can create songs, albums, associate them. We can define artists like the famous Shlomo Artzi for example.
This modeling is important, don't get me wrong. After we've defined a thousand songs, we'll have powerful search features at our disposal to find that one special song. What's missing is behaviour: the ability to play songs! Also, it would be awfully tedious to enter all that information by hand. Can't we just import that stuff from our iTunes library?
So, we've just defined some additional work for ourselves: we'd like to be able to play songs and we'd like to be able to import songs listings.
A short glance at the Java sound API javadocs tells us that we can attempt to play an mp3 file with this bit of code:
try
{
   AudioClip clip = Applet.newAudioClip(_path.fileValue().toURL());
   clip.play();
}
catch (MalformedURLException ex)
{
   System.err.println(ex);
}
Ok, so we need to keep track of the path where our song file is located. So we'll need to add a new field to Song:
private final FileEO _path = new FileEO();
And add the new field to our fieldOrder array:
public static String[] fieldOrder = 
          {"title", "duration", "artist", "album", "genre", "path"};
It sure would be nice to construct a song in a single line, so let's add this constructor:
public Song(File path)
{
   _title.setValue(path.getName());
   _path.setValue(path);
}
Don't forget the accessor method for _path:
public FileEO getPath() { return _path; }
Finally we need to expose a play command to our user interface. Since we're already there, why not also add a pause command?
private AudioClip _clip;
@Cmd(mnemonic='p')
public Object Play(CommandInfo cmdInfo)
{
   try
   {
      _clip = Applet.newAudioClip(_path.fileValue().toURL());
      vmech().message("Playing song.."+this);
      _clip.play();
      return null;
   }
   catch (MalformedURLException ex)
   {
      System.err.println(ex);
      return ex.getMessage();
   }
}
@Cmd
public void Pause(CommandInfo cmdInfo)
{
   if (_clip != null) _clip.stop();
}
It turns out this will work for certain audio files but not for MP3's. A quick google and we discover a nice little utility that gives us an MP3 Service Provider Interface (SPI) for the Java Sound API. We download it. Its README.txt file tells us we just need to add three jar files to our classpath:
cp mp3spi1.9.3.jar /jmatter-complet/demo-apps/MyTunes/lib/src
cp lib/*.jar /jmatter-complet/demo-apps/MyTunes/lib/src
Now we're in business20. Let's give it a shot:
$ ant schema-export
$ ant run
[create a song, specify a path to an mp3 file, save, and click Play]
On my machine, Ubuntu took perhaps 2-3 seconds to load the mp3 song and then started playing Amour Perdu, by Adamo (see figure 5.4).
figures/MyTunes-amoutperdu.png
Figure 5.4: Playing Amour Perdu
Time to slow down and explain some of what we just did..

5.11  Analysis

5.11.1  FileEO

JMatter provides editors for all kinds of basic types including Files. Our path was of type FileEO. This implies to the user interface to use the designated editor for the type FileEO when selecting a song: a File Chooser. Simple enough.

5.11.2  Commands

Exposing commands on types in JMatter is as simple as exposing types. A command in the user interface maps to a method implementation on my class, just as a type in the UI is implemented via a Java class. The convention to follow for commands is to decorate the method with the @Cmd annotation. This annotation accepts a number of options, including the mnemonic character that the user interface will use to make the command keyboard-accessible. There's one additional requirement, that these methods take a CommandInfo argument. We don't actually need that information in this particular case but there exist situations when we do. Anyhow, it's part of the current convention established by JMatter.
The caption that JMatter uses in menu items and buttons representing a command method is derived from the method name. For an annotated method named ProduceHCFA_WithForm, for example, the caption will be Produce HCFA With Form.
Notice that we did not have to worry about launching the song in a separate thread. JMatter does all this for us. Our user interface remains responsive the entire time. Although the UI replaces the mouse cursor with a WAIT_CURSOR (which these days seems to be some kind of spinning wheel on Ubuntu Linux anyway) to indicate that the song has not finished playing, we have full control over the UI and can close our window, open new ones, perform queries, etc.. all while our Song plays in the background.

5.11.3  message()

There's a curious line of code in the middle of the command implementation:
vmech().message("Playing song.."+this);
vmech() returns a reference to the view mechanism. message() signals to the view mechanism to display a message. The Swing view mechanism displays the message in a large font in a semitransparent window that automatically dismisses itself after two second.

5.11.4  Return Type

Our command method returns an Object. Here is yet another simple convention established by JMatter that if for some reason your command returns a bit of text, that bit of text will be displayed to the GUI. Notice that if for some reason we get an exception attempting to play our song, we're returning an error message, which will be displayed to the user.
JMatter tries to do this in good taste. Rather than display some kind of dialog box that one is forced to dismiss, JMatter displays a timed, semi-transparent, borderless dialog with the message which automatically disappears after a few seconds or when clicked on.
Let's proceed and implement our second task: a mechanism for importing songs from our iTunes library.

5.12  Importing Songs

So here's the issue: sitting in my /music/iTunes Music/ folder are a bunch of mp3 files. I don't want to add them to my system one by one. I'd like to basically right-click on Songs and request that MyTunes scan for songs on my disk, from a specific base path. I decided to call my command Scan From Base Path and so will name my command method accordingly:
@Cmd
public static Object ScanFromBasePath(CommandInfo cmdInfo,
                                      @Arg("Base Path") FileEO basePath)
{
   if (!basePath.fileValue().isDirectory())
      return "You must specify a directory as the base path";
   List songFiles = basePath.listRecursive(mp3Filter);
   Set songs = new HashSet();
   for (int i=0; i<songFiles.size(); i++)
   {
      songs.add(new Song((File) songFiles.get(i)));
   }
   HBMSingleSession pmech = (HBMSingleSession)
         Context.getInstance().getPersistenceMechanism();
   pmech.saveMany(songs);
   return "Finished importing MP3s";
}
private static FileFilter mp3Filter = new FileFilter()
{
   public boolean accept(File file)
   {
      return file.getName().toLowerCase().endsWith(".mp3");
   }
};
Let's study this bit of code:
  1. Our method is defined as static to ensure that our command is added to the Song type, and not to song instances. The command will be accessible from the Class Bar (right-click on Song).
  2. Next, we see that we use the same little trick of returning a string if the specified path is not a directory. So the user will get an error message when they pick a base path that is invalid.

    Wait a minute. Where or when did we tell JMatter to give us a base path? Notice that the command method takes an additional argument of type FileEO. When this command is invoked, JMatter will see that this method needs a path and will prompt the user for it.
  3. Look towards the bottom of the listing: we're defining a plain old Java file filter (POJFF) to let through only files with a .mp3 suffix. Then we invoke a method listRecursive() on FileEO, passing in the file filter. Now we have a listing of all the mp3 files nested inside the user's specified base directory.
  4. Now all we need to do is create a Song object for each file and save all these songs. Not bad for 10 lines of code, if I may say so.
Let's try this out. No persistence-related changes have taken place so we can just re-run our app:
$ ant run
Figure 5.5 shows the song listing of the base directory after invoking Scan From Base Path on Song.
figures/MyTunes-SongListing.png
Figure 5.5: Song Listing after Import
The operation created 159 Song objects or eleven pages' worth. The durations are not correct of course. A better way to solve this problem would be to interpret the iTunes xml file, which contains all sorts of song metadata besides the basic song file information.

5.13  Not Completely Finished

If we take a moment to think about our implementation, we'll realize that our work is not exactly done. Although our songs actually play, our implementation is naive. Is the song actually streamed? Are resources disposed of properly?
A little more research into Java sound shows us that the API we're using is really outdated. There is a newer Java sound API in the javax.sound package. Furthermore, even that API has been somewhat deprecated by the Java Media Framework, which is an add-on to the standard Java distribution. Finally, we discover that JMF itself has been in maintenance mode for a number of years now. Some advocate using Apple's QuickTime for Java API (QTJ). I personally have an issue with using QTJ: it doesn't run on my computer. That is, Apple does not write their APIs or software applications to run on GNU/Linux, even though they had no qualms about pillaging open source code for their operating system kernel, or for their Safari web browser, just to name a few situations. I suppose giving is a one-way street.
Here is a list of enhancements we can think of for our MyTunes player:
  1. Replace our audio playing implementation with one that is based on the Java Media Framework
  2. Write a second, alternative import implementation that interprets iTunes' XML metafile
  3. Revise our model to define the notion of a current playlist, add a command to Song to add it to the playlist, and finally, move the play and pause commands from Song to the new PlayList. This revised model seems to make a little more sense.
It turns out that if we discard the Java Sound API and the MP3 provider interface we downloaded, and instead use the JLayer library and its API, we get beautiful mp3 streaming for free in Java. The MyTunes demo application in the JMatter distribution was revised to use JLayer directly and mp3 files of all sizes play immediately without consuming gobs of memory. I leave the study of the final application to you as an exercise.
The point here is that JMatter frees you to work on the important things, like properly importing or playing a song. You don't have to worry about infrastructure services such as persistence, queries, or a user interface.
In very little time, we created a basic music player with Smart Lists. Figure 5.6 shows the quick search feature built into JMatter where you can just type in a song title in a listing's searchbar to filter the list accordingly.
figures/MyTunes-search.png
Figure 5.6: That One Special Song..

5.14  Summary

To say that we covered a lot of ground in this chapter would be an understatement. And yet, if we go through the due diligence of measuring how much code we actually produced:
eitan@ubuntu:/projects/ds/MyTunes/src/com/u2d/mytunes$ wc -l *.java
  28 Album.java
  21 Artist.java
  24 Genre.java
 119 Song.java
 192 total
eitan@ubuntu:/projects/ds/MyTunes/src/com/u2d/mytunes$
We discover that we wrote our music player in under 200 lines of code! That's readable. That's workable. But not just 200 lines of code: a full-fledged music player in under an hour? That's a paradigm shift.
Let's quickly review what we learned in this chapter:
  1. We learned how to setup our project in an IDE.
  2. We've learned the basic class template for our business objects in JMatter: our classes extend AbstractComplexEObject, they have aggregate properties that adhere to the ValueHolder pattern, and they have associations to other objects that follow the JavaBeans accessor-mutator pattern.
  3. We learned how to control the field order in the GUI with the fieldOrder static member; we can specify that a property is unique by including it in the identities static member.
  4. We learned how to associate icons with types by placing our icons in the resources/images folder, and following a specific naming convention.
  5. We learned how to expose both instance and class (static) actions to the UI. We've also seen how to write actions that take arguments.
  6. We've been exposed to some of the atomic types that JMatter provides out of the box, including: StringEO, TextEO, ImgEO, TimeEO, and FileEO.
  7. We've also been exposed to a simple mechanism for specifying field metadata, such as whether a field is required or not.
Although we have begun to dig deeper into this framework, there are many more features that we have yet to discuss and uncover.
A small but useful additional feature that we're going to use in the next chapter is the ability to customize icons on a per-instance basis. That is, we can use the artist's photo as the basis for its icon in the MyTunes user interface; though we'll introduce this feature in the context of a new application, the Conference Manager Sympster.
Some other, more serious features in JMatter include
  1. Integration with JFreeReport for producing PDFs
  2. Calendaring (we'll see this in the next chapter)
  3. Wizards
  4. Polymorphic modeling support including interface-based modeling

HEAD NEXT