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:
- Launch the IDE program
- Create New Project (launches a wizard)
- specify the project name as MyTunes, and
- the base directory path
- Pick the version of Java that I wish to use (Java 5)
- Specify that we'll be using a single module project
- Select the module type: Java module
- Specify the module name as MyTunes (accept the defaults)
- Make sure the source directory name is src
- Make sure the build directory path (where the compiled classes are
placed) is build/classes
- 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:
- Create a package to hold your classes; mine will be com.u2d.mytunes
- 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:
- The @Persist Annotation: automates having to manually edit
src/persistClasses.st as we had to do in the last chapter
- 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.
- 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.
- 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:
- 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
- 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.
- 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.
- 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.
- 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:
- 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.
- 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.
- @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.
- 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:
- provide both a getter and a setter, and
- fire a PropertyChangeEvent after the assignment.
- 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.
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).
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:
- Dump your data out of the database
- ant schema-export (wipes out the data)
- restore the data
The restoration of data usually needs to be broken down into at least
these steps:
- drop constraints on your schema
- restore the data
- 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:
- 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)
- Validation checks will automatically be performed when attempting
to create or save instances;
- 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.
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).
Figure 5.4:
Playing Amour Perdu
Time to slow down and explain some of what we just did..
5.11 Analysis
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.
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.
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.
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:
- 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).
- 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.
- 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.
- 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.
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:
- Replace our audio playing implementation with one that is based on
the Java Media Framework
- Write a second, alternative import implementation that interprets
iTunes' XML metafile
- 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.
Figure 5.6:
That One Special Song..
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:
- We learned how to setup our project in an IDE.
- 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.
- 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.
- We learned how to associate icons with types by placing our icons
in the resources/images folder, and following a specific naming
convention.
- 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.
- We've been exposed to some of the atomic types that JMatter provides
out of the box, including: StringEO, TextEO, ImgEO, TimeEO, and FileEO.
- 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
- Integration with JFreeReport for producing PDFs
- Calendaring (we'll see this in the next chapter)
- Wizards
- Polymorphic modeling support including interface-based modeling