TOC PREV NEXT INDEX

Put your logo here!

The Essence of Object Oriented Programming with Java and UML


Chapter 6

A Case Study in Java

 

The goal of this chapter is to put everything from the previous chapters together to develop a small Java application. We will start with a specification for the program, go through a simplified OOAD process, and finally build the program with Java and Swing.

For this case study, we will develop a small home video movie catalog. Here is the specification for the application.

The application, which we will call MovieCat, is intended to allow a collector to create a catalog of a home movie collection. The movies can be collected from several sources in different media formats. The movies can be either original commercial copies, or recorded at home. The movies can be on VHS tape, DVD, VCD, SVCD, Hi8, or miniDV. The user should be able to categorize the movies into classes such as action, comedy, family, and the like. MovieCat should include the rating of the movie (G, PG-13, etc.), as well as a personal evaluation by the user. The information included in the catalog should include the title, director, and year the movie was made. Because people often record multiple movies on one tape, MovieCat should be able to track which tape a movie is recorded on. Finally, MovieCat should have a general comment field where the user can enter any other information they want to about the movie. To help simplify the program, it won't have to keep track of the actors or awards, or other specific information. The application should use Java and have a GUI that is simple and easy to use. The user will want to browse for movies in the collection (but, at least for now, not search for specific titles or other parts), add new movies, and edit or delete existing movies.

This is a simple application, so the specification can itself be simple. Because MovieCat will perform a function that is familiar to everyone, maintaining a list of a movie collection, there is not a lot of specialized expertise required to build this application. It might be useful to talk to a real movie buff to find out what information is most interesting to track, but most of us have seen movie reviews and books with movies listed and rated, so this is really common knowledge.

Analysis of MovieCat

Use Cases

MovieCat is a small, simple application, and so it won't require an extensive analysis. First, we will present the use cases. There aren't very many, and in fact, the last sentence of the specification reveals the likely use cases: browse for movies, add new movies, edit existing movies, and delete a movie. Figure 6-1 shows the UML Use Case diagrams.

Figure 6-1.
MovieCat use cases

The next step is to produce a written description of each use case with the accompanying scenarios. MovieCat is simple. There is only one actor, the Collector. There is not much that can go wrong when using this program, so the scenarios are short. We won't bother with a formal use case write up, and will just show the scenarios that go with each use case.

Scenario for Lookup Movie
1. Collector chooses movie catalog.
2. Collector browses list of movies.
3. When the collector is at the beginning or end of the list, browsing will wrap around to bottom or top.
Scenario for Add Movie
1. Collector chooses movie catalog.
2. Collector selects add movie.
3. Collector edits new movie entry.
4. Collector commits or abandons new movie.
5. On commit, movie is added in alphabetical order of the title to the list of movies.
Scenario for Edit Movie
1. Collector chooses movie catalog.
2. Collector browses for movie to edit.
3. Collector edits movie.
4. Collector commits or abandons edits.
5. On commit, the entry is replaced in the correct alphabetical order.
Scenario for Delete Movie
1. Collector chooses movie catalog.
2. Collector browses for movie to delete.
3. Collector deletes movie.

These scenarios reflect some decisions made while writing them. For example, the original specification did not say anything about the order the movie list would be kept in. The scenarios indicate that the movie catalog will be kept in alphabetical order. The specification of the steps required to edit or delete a movie reveal that editing and deleting movies involves many of the same steps required to simply browse.

Even though there are only four simple use cases, they do reveal important aspects of what needs to be done. Larger applications would have many more use cases, some more complicated, some just as simple. The use cases lead to the scenarios. Scenarios will often be much more complicated than this example, and will represent a more detailed contract between the customer and the developer for certain features or functionality. In a more critical application, for example, the Edit Movie scenario would need to be much more detailed. We simply specify "edits movie." If we were editing a record in a financial application, for instance, then the scenario would have to cover each field of the record. What are the legal values of the field? What fields are required? What if the user makes an entry error? A complete scenario would cover each possible situation. The level of detail required for each scenario will depend on many things, but writing scenarios helps to ensure that everyone understands what the system is supposed to do.

We've been informal in our presentation of the MovieCat use cases and scenarios. Sometimes this informal approach and a few pieces of paper or a white board is all that is needed. More formal OO methodologies will have more formal rules for just how to specify use cases and their associated scenarios, and use specific software tools to create and track them.

Now that we have the MovieCat use cases and their scenarios, we can continue with the OOA. The next step is to analyze the problem to discover objects, attributes, and operations.

Object, Attribute, and Operation Discovery

In Chapter 4, we gave some of the basic steps of analysis, including object discovery, attribute discovery, and operation discovery. Because the MovieCat problem is so simple, we will combine these steps. In a larger problem, the steps would more likely be done as separate tasks, although there will always be some overlap.

Examining the problem specification for nouns and verbs is one of the most effective ways to begin object, attribute, and operation discovery. We will list the nouns and verbs in the MovieCat problem description.

Nouns

Reading the problem description gives the following list of nouns: application, collector, catalog, movie collection, movie, formats, classes, rating, evaluation, user, title, director, year, tape, comment field, information, Java, and GUI. The following table discusses each noun and presents candidate classes and attributes.

Analysis of Nouns in the MovieCat problem description
Nouns
Discussion
Candidate Classes and Attributes
application
The application is the main thing we are talking about. Since this application is a GUI app, we will use the Wmvc framework we presented in Chapter 5. Thus, our application will derive from WmvcApp.
MovieCat
collector
The collector is the user of the system, and is outside of it. If this system were intended to be used by several collectors, this might represent a useful candidate object, but not in our case.

catalog
Creating a catalog is one of the primary goals of this application. However, catalog and movie collection are really the same thing.

movie
collection

This is the real thing - what we will be modelling. The concept of a movie collection fits well with the MVC notion of a model, so we will call it the model.
MovieModel
movie
A movie collection is made up of movies. So, we have discovered an object, Movie, and a hierarchy, part of a collection.
Movie
format
This is the format of the tapes. It seems this is simply an attribute of a movie.
Movie.format
classes
This is the category of a movie. A better name is genre, and it is an attribute of a Movie.
Movie.genre
rating
A Movie attribute.
Movie.rating
evaluation
A Movie attribute.
Movie.
evaluation
user
Another name for the collector. Outside the system.

title
A Movie attribute.
Movie.title
director
A Movie attribute.
Movie.
director
year
A Movie attribute.
Movie.year
tape
A Movie attribute. It will be an ID or label on a tape.
Movie.label
comment
field

A Movie attribute.
Movie.
comments
information
In this context, the content of the comment field.

Java
Sometimes the customer will specify the environment or language. This is a design constraint, but not an object or attribute.

GUI
We already discussed the GUI as part of the application. We will use the Wmvc framework and Swing to build the GUI. Because of this decision, we know there will also be some views which we still have to identify.

The analysis of the nouns from the problem statement has given us three candidate objects or classes: MovieCat, MovieModel, and Movie. We discovered several attributes of a Movie. We identified a whole/part hierarchy between the MovieModel and Movie. Finally, we have some views to identify.

We also decided to use the Wmvc framework. This level of decision is usually more of a design decision, but sometimes candidates for libraries or support data bases come up in the analysis because such decisions can affect the ultimate design. Next, we will examine the verbs from the problem description.

Verbs

Reading the problem description gives the following list of verbs: create catalog, collected, categorize, track, browse, add new, edit, and delete. Because this list is short, we will just discuss the verbs instead of creating a table.

The first four verbs, create catalog, collected, categorize, and track, involve how the collector will use MovieCat. They don't really add any hints about the objects involved. The remaining verbs; browse, add new, edit, and delete; are the same actions we've already discussed in the use cases and scenarios.

We know that browsing, adding, editing (which is really just replacing an existing movie with a "new" edited version), and deleting movies are important activities, and these will no doubt show up as operations on the MovieModel and individual Movies. We noted in the discussion of the noun GUI that we still need to identify the views we will need. Taking the verbs browse and edit yields probable views: a view of the movie collection to browse with an associated detailed view of an individual movie, and an edit view to edit existing movies or add information for new movies. So, the new candidate views would be a MovieListView, a MovieItemView, and a MovieEditView.

Evaluation

Now that we have some candidate objects, attributes, and operations, we can evaluate what we have before going on to the design phase. In our analysis, the MovieModel and Movie classes were covered in the most detail. Figure 6-2 is a UML diagram to summarize MovieModel and Movie. All the operations and attributes included in the diagrams come directly from the analysis we just did.

Figure 6-2.
MovieModel and Movie class diagrams - analysis

Note that the UML class diagrams in Figure 6-2 represent analysis diagrams, and not design or implementation class diagrams. The level of detail we are looking at is different, and specific implementation details such as public attributes instead of setters and getters for the Movie class are not relevant in analysis. We have not yet specified types for the attributes, either. This is a detail best left for design and implementation.

Design of MovieCat

The results of our analysis for MovieCat can lead directly to a specific design. The goal of the design phase is to cover the details, and come up with a design that can be turned into the final implementation in code. This means that not only do we consider what the program will do, and what its main organization is, but that we specify the details - what programming language, what hardware, what libraries we will use, and all the other details needed to get a working implementation.

As part of the analysis, we decided to use the MVC design to build MovieCat. One of the advantages of MVC is that it allows you to design a model that is completely independent of the rest of the application. In this section, we will focus mainly on designing the model, which is made up of the MovieModel and Movie classes. We already have the analysis class diagrams for those two classes in Figure 6-2 on page 169.

Movie Class

We will start by refining the Movie class. The analysis diagram for Movie has all the attributes as public values. In the actual implementation, these public attributes should all be made private, with appropriate methods supplied to get and set their values as needed.

Attributes can be variables of simple Java primitive type, or they can be variables that reference objects of some class. Most of the attributes associated with a Movie seem to be simple Java Strings. This would include comments, director, label, and title. Year could be either a String or an int, but we will use String.

The other attributes, evaluation, format, genre, and rating, all represent a list of possible values. For example, rating would be one of "G", "PG", "PG-13", "R", "NC-17", "X", or "NR". One obvious way to represent this would be as an array of Strings with all the different values. But any given move will have only one of these values. So an alternative representation could be to define integer constants that represent a given rating. In fact, partly because of the different ways we will view a Movie, we'd like to be able to use either representation - an int or a String.

One way around this dilemma is to use small helper classes for the evaluation, format, genre, and rating attributes. These helper classes would all be similar, and allow us to represent the value of the attribute as an int, but easily convert between the int and a String. And we'd like to know all the possible values (which might change over time - perhaps there will be a new rating added) for use in a list or combo box. The helper classes won't have much behavior, and can be implemented as class (i.e., static) variables and methods. Each class will provide methods to map between the int and String representations, and will return the full String list of possible values. To keep them static, a separate class will be used for each attribute (this is a Java limitation). One of the main advantages of designing these attributes this way is that it isolates all the representational information in the class definition. We could easily add new genres, or modify the letter codes for the ratings without affecting how the rest of the program works.

What other responsibilities should the Movie class have? Who will be using a Movie object? We know that we will want to edit a Movie object. An editor should work with a copy of a Movie, so the Movie class should be cloneable. And it is common practice for objects that will have their values saved in a file to able to read and write themselves, so we will add readMovie and writeMovie methods. Finally, we will define a constructor.

The Movie class is somewhat atypical because it is so data dominated. It has more setters and getters than is typical of most classes, but that is not out of the ordinary for a data-oriented class. Movie still has significant responsibilities such as reading and writing Movie objects. Data-oriented classes are not necessarily bad, and many applications will require one or several such classes.

Figure 6-3 is the final detailed UML object diagram for the Movie class.

Figure 6-3.
Movie Class

This class diagram shows the list-like attributes (genre, etc.) as ints. Movie will save the representations as ints, and then use the helper classes to get the String values.

The UML for the helper classes is shown in Figure 6-4. Note that the diagrams show the values for the Strings. The UML uses underlines to indicate class attributes and methods.

Figure 6-4.
Movie Helper Classes

MovieModel Class

The analysis diagram of the MovieModel class in Figure 6-2 on page 169 shows five methods for the views to use to interact with the model. In the design phase, we need to decide if those five are all the methods we will really need. We need to consider just what all information about a list of movies the views will require to be able to completely display their views.

We will have at least three views: a list view, a view of the current movie, and an edit view. First, note that the phrase "current movie" implies that there will be a single movie that is the one to be displayed in the current movie view. This is a new attribute for MovieModel. The current movie view needs access to the current Movie object, which will then be used by the view to get all the values that it needs to display. The list view will require access to the full list of movies, the ability to get or set which movie is the current movie, and to find out if the list has changed. The edit view will require access to either a blank movie object, or a copy of the current movie. It will then add a new movie, replace the current movie, or delete the current movie.

The user will want to be able to open an existing movie list file or save the current movie list to a file. MovieCat should only save the movie list if it has changed, so MovieModel needs to track if it has changed. Since the MovieModel is part of the MVC design, it must implement the notifyViews method. These responsibilities belong in the MovieModel object.

Figure 6-5 is the UML for the MovieModel class that includes all the responsibilities we just discussed. Note the FILE_ID attribute. This is a constant that will be used to identify MovieCat files.

The private Vector theList is the list of the Movie objects, and represents the implementation of the aggregation relationship between MovieModel and Movie.

Figure 6-5.
MovieModel UML

View Classes

In the analysis, we identified three candidate view classes: list, item, and edit. In the Thermometer example in Chapter 5, we used another view we called the MainView to implement the controllers for the menu and tool bar commands. We will also use a MainView for MovieCat.

Before designing the view classes, it would be a good idea to know what the GUI should look like. Good GUI design is somewhat of an art form, and a full treatment is beyond the scope of this book, but we will briefly cover the MovieCat GUI design. One of the first steps of any design is to simply use paper and pencil to sketch out a rough design. Depending on what software tools you have available, you might then generate some trial GUIs using a GUI designer tool. These rapid prototyping tools can quickly give you a good idea of what might work for your application.

The MovieCat app has simple GUI requirements. The fact we are using the Wmvc framework means we won't use any kind of GUI prototyping tool. The GUI for MovieCat was initially designed by using pencil and paper, and then mapped directly to the corresponding Swing components. Figure 6-6 shows the final version of the MovieCat GUI. We will use it instead of the original pencil layouts to discuss the design of the MovieCat view classes.

Figure 6-6.
MovieCat App

The screen shot of MovieCat show three views - the main view is the view/controller for the menu and tool bars, and doesn't show any movie information. The JPanel provided by the Wmvc framework is used to hold a JSplitPane. The list view is in the left pane, and the item view is in the right pane. The user browses the movie list by using the scrolling functions of the list view.

What about the edit view? One choice would have been to use the item view for the editor as well. One problem with this approach is that it is too easy for the user to accidentally make changes. A better design is to have the editor as a separate popup dialog. The layout of the dialog is similar to the item view, but uses editable components so the user can change or create a movie entry. The editor dialog is invoked by the Edit button in the item view, or by the Add Movie button on the tool bar. There are synonyms for these commands in the Menu.

Figure 6-7 shows the MovieCat movie editor dialog.

Figure 6-7.
MovieCat Edit Dialog

Because we are using Wmvc and Swing, the design of the view classes is a matter of selecting the appropriate Swing components to implement the required functionality that has been specified. The MVC design architecture will dictate the logical structure of the views. We won't show detailed UML diagrams of the view classes.

The Wmvc framework requires one more class, the main app class. We will call the class MovieCat, and its main purpose is to create the model and the views. Its design is really dictated by the Wmvc framework.

One of the advantages of using a framework, even a small one like Wmvc, is that it can really simplify the design. Wmvc defines a specific framework based on MVC, and provides some basic Swing building blocks. The basic Wmvc architecture makes the design of the views, main app, and even the model much simpler than it might have been with a design from scratch. Once we designed the MovieModel class, it was not difficult to design the views.

Putting It All Together

We now have all the parts of MovieCat designed. Figure 6-8 has a UML class diagram for the full MovieCat application, but without the Wmvc details. The UML shows how all the parts fit and interact with each other.

Figure 6-8.
MovieCat UML for design

Implementation of MovieCat

Now that we have the design of MovieCat, we can write the code. The following listings1 show the complete implementation of the MovieCat application. Each listing is introduced by a short description of the class.

MovieCat Class

We will start with the main MovieCat class. It isn't always easy to know what order to examine source code in, but it is often good to start with the main application class.

MovieCat.java is the top level app class derived from WmvcApp. This class has the main method that gets things rolling. It creates the parts of the app in the MovieCat constructor.

After creating the MovieModel object, the constructor creates the three views. Finally, it creates a JSplitPane with two JScrollPanes to hold the MovieListView and MovieItemView views. The JSplitPane is then added to the MainPane provided by Wmvc.

The last thing the MovieCat class does is to call showApp, which causes the GUI to be displayed, and the Java event loop thread to start.

Listing 6-1. MovieCat.java
MovieCat main app class
 /* MovieCat - A simple Movie Catalog Application
  * Copyright (c) 2001, Bruce E. Wampler
  */
  
 import java.awt.*;
 import javax.swing.*;
  
 public class MovieCat extends WmvcApp
 {
     private MainView mainView;
     private MovieListView listView;
     private MovieItemView itemView;
    
     public MovieCat(String name)        // constructor
     {
         super(name, true, true);    // Create with menu, toolbar
  
         // **** First, create the model
         setModel( (WmvcModel) new MovieModel() );
  
         // **** Next, create the view/controllers
         mainView = new MainView();      // won't use any panels
         listView = new MovieListView(); // list view for left
         itemView = new MovieItemView(); // item view for right
  
         // ****  Create a split pane, add list and item views
         JScrollPane listPane = 
             new JScrollPane(listView.getPanel());
         JScrollPane itemPane = 
             new JScrollPane(itemView.getPanel());
         JSplitPane splitPane = 
             new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                 listPane, itemPane);
         splitPane.setOneTouchExpandable(true);  // details
         splitPane.setDividerLocation(200);
         Dimension minimumSize = new Dimension(100, 50);
         listPane.setMinimumSize(minimumSize);
         itemPane.setMinimumSize(minimumSize);
  
         WmvcApp.addMainPane(splitPane); // add splitter
     }
     
     public boolean appClosing()
     {
         return mainView.closingCurrentMovie(true);
     }
     
     public static void main(String[] args)
     {
         final MovieCat movieCat = 
            new MovieCat("MovieCat Movie Catalog Application");
  
         WmvcApp.getContentPanel().setPreferredSize(
            new Dimension(640, 300)); // make bigger than default
         movieCat.showApp();          // pop it up
     }
 }

Movie Class

Since the Movie class is used by almost all the other classes, it is the next class we will examine. The Movie class follows the design shown in the UML in Figure 6-3 on page 172. Each attribute is implemented as a protected value, and setters and getters are provided for each attribute. There is a clone method to support cloning.

The readMovie and writeMovie classes are the most interesting. Java provides a rich library for doing I/O. One of the alternatives to consider when writing and reading objects to a file is to use Java's Serializable feature, which allows you to read or write an entire object with a single statement. This is very convenient and easy to use. A problem with Serializable objects, however, is that the saved object represents the exact form of a given object. If you change the class definition of the object in any way, such as adding a new attribute variable, the serialized form also changes, and you will no longer be able to read previous versions of the object.

Since it is likely that the definition of the Movie class will change, readMovie and writeMovie use Java's DataInputStream and DataOutputStream. Each attribute is handled separately. If the definition of Movie ever changes, it will be simple to write a program to convert the format of an old saved movie data base to that of the latest version.

Listing 6-2. Movie.java
Movie class
 /* Movie - Defines and manipulates a Movie object for MovieCat
  * Copyright (c) 2001, Bruce E. Wampler
  */
  
 import java.io.*;
  
 public class Movie
     implements  Cloneable
 {
     protected String title;
     protected String director;
     protected String year;
     protected int genre;
     protected int rating;
     protected int format;
     protected int evaluation;
     protected String label;
     protected String comments;
  
     public void setTitle(String name) { title = name; }
     public String getTitle() { return title; }
     public void setDirector(String dir) { director = dir; }
     public String getDirector() { return director; }
     public void setYear(String yr) { year = yr; }
     public String getYear() { return year; }
     public void setGenre(int g) { genre = g; }
     public int getGenre() { return genre; }
     public void setRating(int r) { rating = r; }
     public int getRating() { return rating; }
     public void setFormat(int f) { format = f; }
     public int getFormat() {return format;}
     public void setEvaluation(int e) { evaluation = e; }
     public int getEvaluation() { return evaluation; }
     public void setLabel(String l) { label = l; }
     public String getLabel() { return label; }
     public void setComments(String c) { comments = c; }
     public String getComments() { return comments; }
  
     public Movie()
     {
         title = new String(""); director = new String("");
         year = new String("");          genre = 0;
         rating = 0;                     format = 0;
         label = new String("");         evaluation = 0;
         comments = new String("");
     }
  
     // override Object.clone()
     public Object clone() 
     {
         Movie c = null;
         try
         {
             c = (Movie)super.clone();          // copy ints
             c.title = new String(title);       // String doesn't
             c.director = new String(director); // have clone so
             c.label = new String(label);       // make copy of
             c.comments = new String(comments); // each String
         }
         catch (CloneNotSupportedException e)
         {
             System.out.println(
                     "Should never happen: Movie clone failed.");
         }
         return c;
     }
  
     public boolean readMovie(DataInputStream in)
         throws IOException
     {
         try             // read one MovieCat record
         {
             title = new String(in.readUTF());
             director = new String(in.readUTF());
             year = new String(in.readUTF());
             genre = in.readInt();
             rating = in.readInt();
             format = in.readInt();
             evaluation = in.readInt();
             label = new String(in.readUTF());
             comments = new String(in.readUTF());
             return true;
         }
         catch (EOFException e)  // all records read
         {
             in.close();
             return false;
         }      
     }
  
     public void writeMovie(DataOutputStream out)
         throws IOException
     {
         out.writeUTF(title);
         out.writeUTF(director);
         out.writeUTF(year);
         out.writeInt(genre);
         out.writeInt(rating);
         out.writeInt(format);
         out.writeInt(evaluation);
         out.writeUTF(label);
         out.writeUTF(comments);
         
     }    
 }

MovieModel Class

The MovieModel class follows the UML design shown in Figure 6-5 on page 175 closely. There are some details worth covering.

The attributes listChanged and editsMade serve slightly different purposes. The listChanged is needed to interact with the MovieListView when the list of movies changes in any way. The editsMade tracks if any changes have been made to any of the movies in the movie list, and is used to determine if the movie list needs to be saved when the application closes.

The movie list aggregation is implemented using a Java Vector. A Vector is a standard collection class provided by the Java library, and makes handling the list almost trivial. One of the features of a Vector is the iterator2 it provides. The iterator is used twice in the MovieModel class.

MovieModel provides the high level methods needed to read and write an entire movie list. These methods, in turn, rely on the Movie methods readMovie and writeMovie. MovieModel adds an ID tag to the beginning of the movie list file so that the format of the file can be checked and identified.

Listing 6-3. MovieModel.java
The MovieModel class
 /* MovieModel - implements the movie model. Handles changes.
  * Copyright 2001, Bruce E. Wampler
  */
  
 import java.io.*;
 import java.util.*;
 import java.awt.*;
 import java.awt.event.*;
 import javax.swing.*;
  
 public class MovieModel
     extends     WmvcModel
 {
     private int currentMovieIndex;
     private Vector theList;
     private final int FILE_ID = 48879;  // 0xBEEF
  
     // need two changed flags - one if a new entry has been
     // added that is true only until the views update, and a
     // global one that remains true if anything has changed
     // until the list is saved
     private boolean listChanged;   // true until views updated
     private boolean editsMade;          // true until saved
     private File myFile;
  
     public ListIterator getMovieListIterator()
        { return theList.listIterator();}
     public boolean getListChanged() { return listChanged; }
     public boolean getEditsMade() { return editsMade; }
     public int getCurrentMovieIndex()
        { return currentMovieIndex; }
     public int getNumberOfMovies() { return theList.size(); }
     public File getFile() { return myFile; }
     
     public MovieModel()
     {
         editsMade = false;
         listChanged = false;
         theList = new Vector();
         myFile = null;
     }
  
     public void setCurrentMovieIndex(int movieNumber)
     {
         if (theList == null || theList.size() == 0) // valid?
             return;
         // Validate number passed in, wrap appropriately
         if (movieNumber < 0)
             movieNumber = theList.size() - 1;
         if (movieNumber >= theList.size())
             movieNumber = 0;
  
         currentMovieIndex = movieNumber;  // change the movie
         notifyViews();                    // update
     }
     
     public void addMovie(Movie movie)
     {
         if (movie == null)              // some validation
             return;
         editsMade = true;          // we've made some changes
         listChanged = true;
  
         ListIterator it = getMovieListIterator();
         int nextI = 0;
  
         while (it.hasNext())
         {
             // Assume list is sorted, so as soon as we find the
             // first entry that is > than this one, we insert
             // it before that one.
  
             nextI = it.nextIndex();     // index of next entry
             Movie m = (Movie) it.next();
             String adding = movie.getTitle();
             String curName = m.getTitle();
             if (adding.compareToIgnoreCase(curName) <= 0)
                 break;                  // curName > adding
         }
  
         if (!it.hasNext())      // add at end (also if 1st time)
         {
             theList.add(movie);
             // make it current movie
             setCurrentMovieIndex(theList.size() - 1);
         }
         else                    // add it before nextI
         {
             theList.add(nextI,movie);
             setCurrentMovieIndex(nextI);
         }
     }
     
     public void deleteCurrentMovie()
     {
         if (theList.size() <= 0)
             return;
  
         editsMade = true;       // we've made some changes
         listChanged = true;
  
         theList.removeElementAt(currentMovieIndex);
         setCurrentMovieIndex(currentMovieIndex);
     }
     
     public void replaceCurrentMovie(Movie movie)
     {
         if (movie == null)
             return;
  
         theList.setElementAt(movie,currentMovieIndex);
         editsMade = true;       // we've made some changes
         listChanged = true;
         notifyViews();
     }
     
     public boolean saveMovies()
     {
         return saveMoviesAs(myFile);
     }
     
     public boolean saveMoviesAs(File file)
     {
         if (file != null)
         {
             try
             {
                 DataOutputStream out = new DataOutputStream(
                         new BufferedOutputStream(
                           new FileOutputStream(file)));
                 out.writeInt(FILE_ID);
                 ListIterator it = getMovieListIterator();
                 while (it.hasNext())
                 {
                     Movie m = (Movie) it.next();
                     m.writeMovie(out);
                 }
                 out.flush(); out.close();
                 myFile = file;          // remember name
             }
             catch (IOException e)
             {
                 JOptionPane.showMessageDialog(
                     WmvcApp.getFrame(),
                     "Error opening file: " + e,
                          "MovieCat Error",
                     JOptionPane.ERROR_MESSAGE);
                 return false;
             }
         }
         else
             return false;
         
         editsMade = false;              // no edits now!
         return true;
     }
  
     public boolean openMovies(File file)
     {
         if (file != null)
         {
             myFile = file;              // remember the name
             try
             {
                 DataInputStream in = new DataInputStream(
                         new BufferedInputStream(
                           new FileInputStream(file)));
                 // check if file was made by us
                 if (in.readInt() != FILE_ID)
                 {
                     in.close();
                     myFile = null;
                     JOptionPane.showMessageDialog(
                         WmvcApp.getFrame(),
                         file.getName() +
                          " is not a valid MovieCat file.",
                          "MovieCat Error",
                         JOptionPane.ERROR_MESSAGE);
                     return false;
                 }
                 for ( ; ; )     // do until catch EOF Exception
                 {
                     Movie m = new Movie();
                     if (!m.readMovie(in))
                         break;
                     theList.add(m);
                 }
             }
             catch (IOException e)
             {
                 JOptionPane.showMessageDialog(
                     WmvcApp.getFrame(),
                     "Error reading file: " + e,
                          "MovieCat Error",
                     JOptionPane.ERROR_MESSAGE);
                 myFile = null;
                 return false;
             }
  
             editsMade = false;          // no edits to start
             listChanged = true;
             notifyViews();
             return true;
         }
         else
             return false;
     }
     
     public boolean closeMovies()
     {
         // Just close - Views responsible to save before closing
         myFile = null;                  // reset to empty values
         theList.clear();
         editsMade = false;              // no edits now!
         listChanged = true;
         notifyViews();
         return true;
     }
     
     public Movie getCurrentMovie()
     {
         if (currentMovieIndex < 0 
                      && currentMovieIndex >= theList.size())
             return null;
         else if (theList.size() == 0)
             return null;
         else
             return (Movie)theList.elementAt(currentMovieIndex);
     }
     
     public void notifyViews()
     {
         super.notifyViews();
         // updating views makes list correct
         listChanged = false;
     }
 }

MainView Class

The MainView class handles defining and implementing the commands on the main menu and tool bars. Because we are using Wmvc, this process is greatly simplified.

To handle some of the commands, MainView uses dialog boxes. Some are standard Java dialogs (e.g., JOptionPane, JFileChooser). It also uses the MovieEditor dialog class.

MainView does not display any part of the movie list. Because it only handles control functions, it does not need to provide the update method used by the MVC design.

Listing 6-4. MainView.java
MainView handles menu and tool bars
 /* MainView - Top level view/controller for the MovieCat
  *
  * This is the main view/controller.
  * The main view/controller interacts with the model for
  * the global commands like open and save list in a file.
  * Copyright (c) 2001, Bruce E. Wampler
  */
  
 import java.io.*;
 import java.util.*;
 import java.awt.*;
 import java.awt.event.*;
 import javax.swing.*;
 import javax.swing.filechooser.*;
  
 public class MainView extends WmvcView
 {
     private JFileChooser fc;    // instance of a file chooser
     private MovieEditor theMovieEditor; // instance of editor
     private MovieModel myModel; // local copy of model reference
  
     public MainView()
     {
         myModel = (MovieModel) WmvcApp.getModel();
         myModel.addView(this);
  
         // Create file chooser dialog. We will tell it to open
         // in the "user.dir" directory, which will usually be
         // the "current directory" when the program was started.
         // This will let the user use the "Start In" setting 
         // on Windows, for example.
  
         fc = new JFileChooser(  // file chooser in current dir.
                    new File(System.getProperty("user.dir")));
  
         createControls(); // Create controls - menus,toolbar
  
         theMovieEditor = MovieEditor.getInstance();  // editor
         theMovieEditor.initialize();
     }
  
     public boolean closingCurrentMovie(boolean ask)
     {
         // Check if current movie has changed, ask if want to
         // save. Returns true if saved or didn't want to save,
         // false if save fails or user cancels.
         if (myModel.getEditsMade())
         {
             if (ask)            // interactive closing
             {
                 switch (JOptionPane.showConfirmDialog(
                         WmvcApp.getFrame(),
                         "The movie list has changed since you "
                         + "last saved it.\n"
                         + "Save the current movie list?",
                         "Movie List Has Changed",
                     JOptionPane.YES_NO_CANCEL_OPTION))
                 {
                     case JOptionPane.NO_OPTION:
                         return true;   // don't save, but done
                     case JOptionPane.CANCEL_OPTION:
                     case JOptionPane.CLOSED_OPTION:
                         return false;
                     default:
                         break;          // YES
                 }
                 if (myModel.getFile() == null)
                 {
                     int retV = 
                          fc.showSaveDialog(WmvcApp.getFrame());
                     if (retV == JFileChooser.APPROVE_OPTION)
                     {
                         File file = fc.getSelectedFile();
                         if (!myModel.saveMoviesAs(file))
                             return false;
                         else
                         {
                             myModel.closeMovies();
                             return true;
                         }
                     }
                     else
                         return false;
                 }
             }
             myModel.saveMovies();
             myModel.closeMovies();
         }
         return true;
     }
  
     private void createControls()
     {
         // This is the Controller for this view. It creates the
         // menu & toolbar, and implements all the control code, 
         // mostly in anonymous WmvcExecutor classes.
  
         // MenuBar: File
         JMenu fileMenu = new JMenu("File");
  
         // File->Open Movie List
         WmvcMenuItemCtl fileOpen = new WmvcMenuItemCtl(fileMenu,
             "Open Movie List","images/open-16.gif", 'O',
             null /* no accel */, new WmvcExecutor()
             {
                 public void execute(ActionEvent event)
                 {
                     if (!closingCurrentMovie(true))
                         return;
  
                     int retV = 
                           fc.showOpenDialog(WmvcApp.getFrame());
                     if (retV == JFileChooser.APPROVE_OPTION)
                     {
                         File file = fc.getSelectedFile();
                         myModel.openMovies(file);
                     }
                 }
             });
  
         // File->Save Movie List
         WmvcMenuItemCtl fileSave = new WmvcMenuItemCtl(fileMenu,
             "Save Movie List","images/save-16.gif", 'S',
             null /* no accel */, new WmvcExecutor()
             {
                 public void execute(ActionEvent event)
                 {
                     if (myModel.getFile() == null)
                     {
                         JOptionPane.showMessageDialog(
                          WmvcApp.getFrame(),
                          "No movie file name specified.\n"
                          + "Use \"Save MovieList As\" instead.",
                         "No file name specified",
                         JOptionPane.ERROR_MESSAGE);
                     }
                     else
                         myModel.saveMovies();
                 }
             });
  
         // File->Save Movie List
         WmvcMenuItemCtl fileSaveAs = new WmvcMenuItemCtl(
             fileMenu,
             "Save Movie List As","images/gray.gif",
             'A', null /* no accel */, new WmvcExecutor()
             {
                 public void execute(ActionEvent event)
                 {
                     int retV = 
                           fc.showSaveDialog(WmvcApp.getFrame());
                     if (retV == JFileChooser.APPROVE_OPTION)
                     {
                         File file = fc.getSelectedFile();
                         myModel.saveMoviesAs(file);
                     }
                 }
             });
  
         WmvcApp.addMenu(fileMenu);      // Add to app menu
  
         // MenuBar: Edit
         JMenu editMenu = new JMenu("Edit");
  
         // Edit->Edit Current Movie
         WmvcMenuItemCtl editEdit = new WmvcMenuItemCtl(editMenu,
             "Edit Current Movie","images/gray.gif", 'E',
             null /* no accel */,  new WmvcExecutor()
             {
                 public void execute(ActionEvent event)
                 {
                     Movie edited = theMovieEditor.showDialog(
                        WmvcApp.getFrame(), 
                        myModel.getCurrentMovie());
                     myModel.replaceCurrentMovie(edited);
                 }
             });
  
         // Edit->Add New Movie
         WmvcMenuItemCtl editNew = new WmvcMenuItemCtl(editMenu,
             "Add New Movie","images/addmovie-16.gif", 'A',
             null /* no accel */, new WmvcExecutor()
             {
                 public void execute(ActionEvent event)
                 {
                     Movie blank = new Movie();
                     Movie newMovie = theMovieEditor.showDialog(
                        WmvcApp.getFrame(), blank);
                     myModel.addMovie(newMovie);
                 }
             });
  
         // Edit->Remove Current Movie
         WmvcMenuItemCtl editRemove = new WmvcMenuItemCtl(
             editMenu,
             "Remove Current Movie","images/delx.gif", 'R',
              null /* no accel */, new WmvcExecutor()
             {
                 public void execute(ActionEvent event)
                 {
                     myModel.deleteCurrentMovie();
                 }
             });
  
         WmvcApp.addMenu(editMenu);      // Add to app menu
  
         // ToolBar: Open
         WmvcTBButtonCtl toolOpen = new WmvcTBButtonCtl(
             "Open","images/open-16.gif",
             "Open an Existing Movie List",
             fileOpen.getWmvcExecutor()); // reuse fileopen exec
  
         // ToolBar: Add
         WmvcTBButtonCtl toolAdd = new WmvcTBButtonCtl(
             "Add Movie", "images/addmovie-16.gif",
             "Add a new movie",
             editNew.getWmvcExecutor()); // reuse editNew exec
     }
 }

MovieListView Class

The MovieListView class implements the movie list browser shown in the left side of the split pane. It uses a Java JList in a JPanel to do this. The JList is created in the constructor, and handled by the valueChanged method of the ListSelectionListener implemented by the class. When the user selects a different movie from the list, MovieListView will send a message to the MovieModel to change the current movie, which then causes the MVC notifyViews to be called to update all the views.

There is some code that uses the updating attribute. This was needed to avoid some interaction between MVC update messages and list change messages coming from the JList object.

Listing 6-5. MovieListView.java
MovieListView implements the movie list browser
 /*
  * MovieListView - the list View of the MovieCat model.
  *  This view implements the view of the movie list
  * Copyright (c) 2001, Bruce E. Wampler
  */
  
 import java.util.*;
 import java.awt.*;
 import java.awt.event.*;
 import javax.swing.*;
 import javax.swing.event.*;
  
 public class MovieListView 
     extends WmvcView
     implements  ListSelectionListener   // for JList
 {
     // need updating to avoid interaction between update and
     // valueChanged listener
     private static boolean updating = false;
     private JPanel listPanel;
     private JList jlist;
  
     private MovieModel myModel;
     private DefaultListModel movieList;
  
     public JPanel getPanel() { return listPanel; }
  
     public MovieListView()
     {
         // Build list view which is simply a JList in a JPanel
         myModel = (MovieModel)WmvcApp.getModel();
         myModel.addView(this);  // add view to model list
  
         movieList = new DefaultListModel(); // first allocation
         movieList.addElement("No Movie List Opened");
  
         listPanel = new JPanel();
         listPanel.setLayout(new BorderLayout());
  
         jlist = new JList(movieList);
         jlist.setSelectionMode(
                         ListSelectionModel.SINGLE_SELECTION);       
         jlist.setSelectedIndex(0);
         jlist.addListSelectionListener(this); // valueChanged
  
         listPanel.add(jlist,BorderLayout.CENTER);
     }
  
     public void updateView()
     {
         // Called when model changes
         updating = true;
         // if list changed, don't need to refresh here
         if (myModel.getListChanged())
         {
             movieList.ensureCapacity(
                               myModel.getNumberOfMovies() + 8);
             movieList.clear();
  
             // See if just the selection changed
             // copy titles from movie list to view list
             ListIterator it = myModel.getMovieListIterator();
             while (it.hasNext())
               {
                 Movie m = (Movie) it.next();
                 movieList.addElement(m.getTitle());
               }
         }
         // Always update selected item
         // Note that by using the DefaultListModel, these will
         // trigger valueChanged, so we need the updating value
         jlist.setSelectedIndex(myModel.getCurrentMovieIndex());
         jlist.ensureIndexIsVisible(
                                myModel.getCurrentMovieIndex());
         updating = false;
     }
  
     // Implement ListSelectionListener
     public void valueChanged(ListSelectionEvent e)
     {
         if (e.getValueIsAdjusting()) // Still adjusting?
             return;
  
         JList theList = (JList)e.getSource();
         if (! theList.isSelectionEmpty()) 
         {
             int index = theList.getSelectedIndex();
             // now set the model to use the selected movie name
             if (!updating)
                 myModel.setCurrentMovieIndex(index);
         }
     }
 }

MovieItemView Class

While the MovieItemView class seems long, it is not complex. Most of its bulk comes from the code needed to setup the Swing component layout. The view consists of JLabel, JTextArea, and JButton components laid out in a GridBagLayout.

It turns out that the helper classes for the list-like Movie attributes are the most useful in this MovieItemView class and the MovieEditor class. By providing the helper classes, MovieItemView and MovieEditor can use the String values provided by the helper classes rather than defining their own String arrays or using long switch statements to determine the values of those attributes for display in the GUI.

MovieItemView also defines the protected setAndAdd method to help simplify defining the various components added to the view panel.

Listing 6-6. MovieItemView.java
MovieItemView shows details of each Movie
 /*
  * MovieItemView - the item View for the MovieCat model.
  * This implements a view of a single item - the current movie.
  * Copyright (c) 2001, Bruce E. Wampler
  */
  
 import java.util.*;
 import java.awt.*;
 import java.awt.event.*;
 import javax.swing.*;
  
 public class MovieItemView
         extends         WmvcView
         implements      ActionListener  // for buttons
 {
     protected GridBagLayout gridbag;
     protected GridBagConstraints c;
  
     private JPanel itemPanel;
     private MovieModel myModel;         // local copy
  
     // Various components needed for constructing the view
     // We use private statics of each of these since there will
     // be only one instance of the itemView
  
     private static JLabel lblTitle=new JLabel("Movie Title: ");
     private static JLabel fldTitle = new JLabel(" ");
     private static JLabel lblDirector=new JLabel("Director: ");
     private static JLabel fldDirector = new JLabel(" ");
     private static JLabel lblYear = new JLabel("Year: ");
     private static JLabel fldYear = new JLabel(" ");
     private static JLabel lblRating = new JLabel("Rating: ");
     private static JLabel fldRating = new JLabel(" ");
     private static JLabel lblGenre = new JLabel("   Genre: ");
     private static JLabel fldGenre = new JLabel(" ");
     private static JLabel lblFormat = new JLabel("   Format: ");
     private static JLabel fldFormat = new JLabel(" ");
     private static JLabel lblLabel = new JLabel("   Label: ");
     private static JLabel fldLabel = new JLabel(" ");
     private static JLabel lblEvaluation = 
                                 new JLabel("My Rating: ");
     private static JLabel fldEvaluation = new JLabel(" ");
     private static JLabel lblComments=new JLabel("Comments: ");
  
     private static JTextArea textArea;
     private static JButton bPrevious;
     private static JButton bNext;
     private static JButton bEdit;
  
     public JPanel getPanel() { return itemPanel; }
  
     protected void setAndAdd(JComponent comp,
         int gx, int gy, int gheight, int gwidth, double wx)
     {
         // to simplify laying out the gridbag
         c.anchor = c.WEST;
         c.gridx = gx; c.gridy = gy;
         c.gridheight = gheight; c.gridwidth = gwidth;
         c.weightx = wx;
         gridbag.setConstraints(comp,c);
         itemPanel.add(comp);
     }
  
     public MovieItemView()
     {
         myModel = (MovieModel)WmvcApp.getModel();
         myModel.addView(this);          // adds update
  
         // We will use a GridBag for simple layout of itemView
  
         itemPanel = new JPanel();       // surrounding Panel
         gridbag = new GridBagLayout();
         c = new GridBagConstraints();
         itemPanel.setLayout(gridbag);
         itemPanel.setBorder(BorderFactory.createEmptyBorder(
                                                 5, 5, 5, 5));
  
         // Set data fields to black foreground
         fldTitle.setForeground(Color.black);
         fldDirector.setForeground(Color.black);
         fldYear.setForeground(Color.black);
         fldRating.setForeground(Color.black);
         fldGenre.setForeground(Color.black);
         fldFormat.setForeground(Color.black);
         fldLabel.setForeground(Color.black);
         fldEvaluation.setForeground(Color.black);
  
         // Movie Title: ________________________
         setAndAdd(lblTitle, 0, 0, 1, 1, 1.0);
         setAndAdd(fldTitle, 1, 0, 1, c.REMAINDER, 0.);
  
         // Director: _____________________
         setAndAdd(lblDirector, 0, 1, 1, 1, 1.0);
         setAndAdd(fldDirector, 1, 1, 1, c.REMAINDER, 0.0);
  
         // Year: _______      Genre: _________
         setAndAdd(lblYear,  0, 2, 1, 1, 1.0);
         setAndAdd(fldYear,  1, 2, 1, 1, 0.0);
         setAndAdd(lblGenre, 2, 2, 1, c.RELATIVE,0.0);
         setAndAdd(fldGenre, 3, 2, 1, c.REMAINDER,0.0);
  
         // Rating: _______    Format: __________
         setAndAdd(lblRating,  0, 3, 1, 1, 1.0);
         setAndAdd(fldRating,  1, 3, 1, 1, 0.0);
         setAndAdd(lblFormat,  2, 3, 1, c.RELATIVE, 0.0);
         setAndAdd(fldFormat,  3, 3, 1, c.REMAINDER, 0.);
  
         // My Rating: ______   Label: _________
         setAndAdd(lblEvaluation,  0, 4, 1, 1, 1.0);
         setAndAdd(fldEvaluation,  1, 4, 1, 1, 0.);
         setAndAdd(lblLabel,     2, 4, 1, c.RELATIVE, 0.0);
         setAndAdd(fldLabel,     3, 4, 1, c.REMAINDER, 0.);
  
         // Comment box:
         setAndAdd(lblComments,  0,5,1,1, 0.0);
         textArea = new JTextArea(4,30);
         JScrollPane textScroll = new JScrollPane(textArea);
         setAndAdd(textScroll,  1,5,4,c.REMAINDER, 0.0);
  
         // Command Buttons
         bEdit =   new JButton("  Edit  ");
         bEdit.setActionCommand("edit");
         bEdit.addActionListener(this);
         bEdit.setToolTipText("Edit current movie");
         setAndAdd(bEdit, 1,9,1,1, 0.0);
  
         bPrevious = new JButton("Previous");
         bPrevious.addActionListener(this);
         bPrevious.setActionCommand("previous");
         bPrevious.setIcon(new ImageIcon("images/left-16.gif"));
         bPrevious.setToolTipText("Go to previous movie");
         setAndAdd(bPrevious, 2,9,1,1, 0.0);
  
         bNext =     new JButton("Next");
         bNext.setActionCommand("next");
         bNext.addActionListener(this);
         bNext.setIcon(new ImageIcon("images/right-16.gif"));
         bNext.setToolTipText("Go to next movie");
         setAndAdd(bNext, 3,9,1,1,0.0);
     }
  
     public void updateView()
     {
         // When model changes - update each fld componenet
         Movie m = myModel.getCurrentMovie();
  
         fldTitle.setText(m.getTitle());
         fldDirector.setText(m.getDirector());
         fldYear.setText(m.getYear());
         fldLabel.setText(m.getLabel());
         textArea.setText(m.getComments());
  
         fldRating.setText(MovieRating.stringAt(m.getRating()));
         fldGenre.setText(MovieGenre.stringAt(m.getGenre()));
         fldFormat.setText(MovieFormat.stringAt(m.getFormat()));
         fldEvaluation.setText(
                 MovieEvaluation.stringAt(m.getEvaluation()));
     }
  
     // Implement ActionListener
     public void actionPerformed(ActionEvent e)
     {
         // Since only three buttons, easier to use one listener
         if (e.getActionCommand().equals("edit"))
         {
             Movie edited = 
                 MovieEditor.getInstance().showDialog(
                    WmvcApp.getFrame(),
                    myModel.getCurrentMovie());
             myModel.replaceCurrentMovie(edited);
         }
         else if (e.getActionCommand().equals("previous"))
         {
             myModel.setCurrentMovieIndex(
                  myModel.getCurrentMovieIndex()-1);
         }
         else if (e.getActionCommand().equals("next"))
         {
             myModel.setCurrentMovieIndex(
                  myModel.getCurrentMovieIndex()+1);
         }
     }
 }

MovieEditor Class

The MovieEditor class extends the Swing JDialog class to implement a modal dialog to edit Movies. Much of its code is very similar to the MovieItemView since it follows the same layout of Movie attributes. It uses JComboBox components to allow the user to select from a list of attributes, and JTextField components to allow the String values to be edited.

There is no interaction between the MovieModel and the MovieEditor. MovieEditor dialogs are created by the MainView and MovieItemView classes in response to user commands. The edited Movie object from the MovieEditor is handled appropriately by those classes3.

Listing 6-7. MovieEditor.java
MovieEditor lets user edit a movie
 /* MovieEditor - edits a Movie object for MovieCat
  * Copyright (c) 2001, Bruce E. Wampler
  */
  
 import java.awt.*;
 import java.awt.event.*;
 import javax.swing.*;
  
 public class MovieEditor
     extends     JDialog
     implements  ActionListener
 {
     // Implement MovieEditor with Singleton pattern
     private static MovieEditor theMovieEditor = null;
     private Movie editedMovie = null;
     
     protected GridBagLayout gridbag;
     protected GridBagConstraints c;
  
     private JPanel itemPanel;
  
     // Various components needed for constructing the view
     // We use private statics of each of these since there will
     // be only one instance of the editor
  
     private static JLabel lblTitle=new JLabel("Movie Title: ");
     private static JTextField fldTitle = new JTextField(30);
     private static JLabel lblDirector=new JLabel("Director: ");
     private static JTextField fldDirector = new JTextField(30);
     private static JLabel lblYear = new JLabel("Year: ");
     private static JTextField fldYear = new JTextField(6);
     private static JLabel lblRating = new JLabel("Rating: ");
     private static JComboBox fldRating;
     private static JLabel lblGenre = new JLabel("   Genre: ");
     private static JComboBox fldGenre;
     private static JLabel lblFormat = new JLabel("   Format: ");
     private static JComboBox fldFormat;
     private static JLabel lblLabel = new JLabel("   Label: ");
     private static JTextField fldLabel = new JTextField(8);
     private static JLabel lblEvaluation =
                                     new JLabel("My Rating: ");
     private static JComboBox fldEvaluation;
     private static JLabel lblComments=new JLabel("Comments: ");
  
     private static JTextArea textArea;
     private static JButton bPrevious;
     private static JButton bNext;
     private static JButton bUpdate;
     private static JButton bRevert;
  
     private Movie movie;        // for a local copy
  
     protected void setAndAdd(JComponent comp,
     int gx, int gy, int gheight, int gwidth)
     {
         // to simplify laying out the gridbag
         c.anchor = c.WEST;
         c.gridx = gx; c.gridy = gy;
         c.gridheight = gheight; c.gridwidth = gwidth;
         gridbag.setConstraints(comp,c);
         itemPanel.add(comp);
     }
  
     static public MovieEditor getInstance()
     {
         if (theMovieEditor == null)
         {
             theMovieEditor = new MovieEditor();
         }
         return theMovieEditor;
     }
     
     public MovieEditor()
     {
         // call JDialog constructor
         super(WmvcApp.getFrame(), "Edit Movie", true);
     }
         
     public void initialize()
     {
         // We will use a GridBag for simple layout of itemView
         itemPanel = new JPanel();       // surrounding Panel
  
         gridbag = new GridBagLayout();
         c = new GridBagConstraints();
         itemPanel.setLayout(gridbag);
  
         itemPanel.setBorder(BorderFactory.createEmptyBorder(
                                                   5, 5, 5, 5));
  
         // Movie Title: ________________________
         setAndAdd(lblTitle, 0, 0, 1, 1);
         setAndAdd(fldTitle, 1, 0, 1, c.REMAINDER);
  
         // Director: _____________________
         setAndAdd(lblDirector, 0, 1, 1, 1);
         setAndAdd(fldDirector, 1, 1, 1, c.REMAINDER);
  
         // Year: _______      Genre: _________
         setAndAdd(lblYear,  0, 2, 1, 1);
         setAndAdd(fldYear,  1, 2, 1, 1);
         setAndAdd(lblGenre, 2, 2, 1, c.RELATIVE);
         fldGenre = new JComboBox(MovieGenre.getNames());
         setAndAdd(fldGenre, 3, 2, 1, c.REMAINDER);
  
         // Rating: _______    Format: __________
         setAndAdd(lblRating,  0, 3, 1, 1);
         fldRating = new JComboBox(MovieRating.getNames());
         setAndAdd(fldRating,  1, 3, 1, 1);
         setAndAdd(lblFormat,  2, 3, 1, c.RELATIVE);
         fldFormat = new JComboBox(MovieFormat.getNames());
         setAndAdd(fldFormat,  3, 3, 1, c.REMAINDER);
  
         // My Rating: ______   Label: _________
         setAndAdd(lblEvaluation,  0, 4, 1, 1);
         fldEvaluation = new JComboBox(
                                 MovieEvaluation.getNames());
         setAndAdd(fldEvaluation,  1, 4, 1, 1);
         setAndAdd(lblLabel,     2, 4, 1, c.RELATIVE);
         setAndAdd(fldLabel,     3, 4, 1, c.REMAINDER);
  
         // Comment box:
         setAndAdd(lblComments,  0,5,1,1);
         textArea = new JTextArea(4,30);
         JScrollPane textScroll = new JScrollPane(textArea);
         setAndAdd(textScroll,  1,5,4,c.REMAINDER);
  
         // Command Buttons
  
         bRevert =   new JButton(" Cancel ");
         bRevert.setActionCommand("revert");
         bRevert.addActionListener(this);
         setAndAdd(bRevert, 2,9,1,1);
  
         bUpdate =   new JButton("   OK   ");
         bUpdate.setActionCommand("update");
         bUpdate.addActionListener(this);
         setAndAdd(bUpdate, 3,9,1,1);
  
         Container contentPane = getContentPane();
         contentPane.add(itemPanel,BorderLayout.CENTER);
         pack();
     }
     
     public Movie showDialog(Component comp, Movie m)
     {
         if (theMovieEditor == null || m == null)
             return null;
         editedMovie = null;
  
         movie = (Movie)m.clone();// make a copy to work with
         
         // Set box to current fields
         fldTitle.setText(movie.getTitle());
         fldDirector.setText(movie.getDirector());
         fldYear.setText(movie.getYear());
         fldLabel.setText(movie.getLabel());
         fldRating.setSelectedIndex(movie.getRating());
         fldGenre.setSelectedIndex(movie.getGenre());
         fldFormat.setSelectedIndex(movie.getFormat());
         fldEvaluation.setSelectedIndex(movie.getEvaluation());
         textArea.setText(movie.getComments());
  
         setLocationRelativeTo(comp);
         setVisible(true);
         
         // will now wait here until actionPerformed
         // calls setVisible(false)
         
         return editedMovie;
     }
  
     // Implement ActionListener
     public void actionPerformed(ActionEvent e)
     {
         if (e.getActionCommand().equals("update"))
         {
             movie.setTitle(fldTitle.getText());
             movie.setDirector(fldDirector.getText());
             movie.setYear(fldYear.getText());
             movie.setLabel(fldLabel.getText());
             movie.setRating(fldRating.getSelectedIndex());
             movie.setGenre(fldGenre.getSelectedIndex());
             movie.setFormat(fldFormat.getSelectedIndex());
             movie.setEvaluation(
                               fldEvaluation.getSelectedIndex());
             movie.setComments(textArea.getText());
             editedMovie = movie;
             setVisible(false);
         }
         else if (e.getActionCommand().equals("revert"))
         {
             editedMovie = null;
             setVisible(false);
         }
     }
 }

Movie Helper Classes

The helper classes are all nearly identical. Logically, the classes could have been derived from a single superclass. However, to maximize the ease of using these helper classes (specifically, making them completely static with no need of constructors), they were implemented as individual classes.

Listing 6-8. MovieEvaluation.java
Helper class for evaluation attribute
 /* MovieEvaluation -  very simple helper class
  * Copyright 2001, Bruce E. Wampler
  */
 public class MovieEvaluation
 {
     private static String[] values = 
       {
         "*", "**", "***", "****", "*****"
       };
  
     public static int indexOf(String str)
     {
         for (int ix = 0 ; ix < values.length ; ++ix)
             if (values[ix].equals(str))
                 return ix;
         return 0;
     }
     public static String stringAt(int at)
     {
         if (at < 0 || at >= values.length)
             return "-";
         return values[at];
     }
     public static String[] getNames()
     {
         return values;
     }
 }

Listing 6-9. MovieFormat.java
Helper class for format attribute
 /* MovieFormat -  very simple helper class
  * Copyright 2001, Bruce E. Wampler
  */
 public class MovieFormat
 {
     private static String[] values = 
       {
         "VHS", "DVD", "VCD", "SVCD", "Hi8", "DV", "Other"
       };
  
     public static int indexOf(String str)
     {
         for (int ix = 0 ; ix < values.length ; ++ix)
             if (values[ix].equals(str))
                 return ix;
         return 0;
     }
     public static String stringAt(int at)
     {
         if (at < 0 || at >= values.length)
             return "Other";
         return values[at];
     }
     public static String[] getNames()
     {
         return values;
     }
 }

Listing 6-10. MovieGenre.java
Helper class for genre attribute
 /* MovieGenre -  very simple helper class
  * Copyright 2001, Bruce E. Wampler
  */
 public class MovieGenre
 {
     private static String[] values = 
       {
        "Drama", "Comedy", "Children", "Family", "Action",
        "Sci-Fi", "Documentary", "Other"
       };
  
     public static int indexOf(String str)
     {
         for (int ix = 0 ; ix < values.length ; ++ix)
             if (values[ix].equals(str))
                 return ix;
         return 0;
     }
     public static String stringAt(int at)
     {
         if (at < 0 || at >= values.length)
             return "Other";
         return values[at];
     }
     public static String[] getNames()
     {
         return values;
     }
 }

Listing 6-11. MovieRating.java
Helper class for rating attribute
 /* MovieRating -  very simple helper class
  * Copyright 2001, Bruce E. Wampler
  */
 public class MovieRating
 {
     private static String[] values = 
       {
         "G", "PG", "PG-13", "R", "NC-17", "X", "NR", "Unknown"
       };
  
     public static int indexOf(String str)
     {
         for (int ix = 0 ; ix < values.length ; ++ix)
             if (values[ix].equals(str))
                 return ix;
         return 0;
     }
     public static String stringAt(int at)
     {
         if (at < 0 || at >= values.length)
             return "Unknown";
         return values[at];
     }
     public static String[] getNames()
     {
         return values;
     }
 }

Review

At the end of the development of any software, one of the most important steps is to review the project, and to apply lessons learned to the next round of development. The MovieCat application is not a completely typical case. While it does have a useful purpose, it was designed mostly as a good, short case study for this book, and it meets that goal. It lent itself to a good, yet simple analysis. It had some non-trivial design issues. It provided a good example for using MVC to build a real application. And it could be implemented with a reasonable amount of code to include in a book.

There are still some aspects of MovieCat we can review. For one thing, the program is not really complete. There are several features that could be added for the next release. In fact, the MovieCat app presented in this chapter is not unlike a real world app; now that we have an initial release version, we need to see what should be added for the second release.

What is missing from MovieCat? The following list gives just some features that might be added.

1. More attributes, including actors, writers, awards, language, aspect ratio, and others.
2. Multiple categories for genre.
3. Printing.
4. Searching.
5. IMDb (Internet Movie DataBase, gives complete information about most existing movies) connectivity.
6. Import/Export of movie list.
7. Movie images and clips.

How hard would these be to add? Evaluating how easy it would be to modify an existing program is a good measure of how well designed it is. Let's examine a couple of items from the list.

How about adding more attributes? Adding them to the Movie class should be simple. All the attributes we've discussed would easily map to either a String or a new helper class. There would be some problems associated with adding new Movie attributes.

First, we would have to provide a conversion program to convert from the original file format to a new one. This might mean that we would want to revisit the whole issue of file format. Perhaps we would be better off with a format that would not require conversion from one version of MovieCat to the next.

Second, we would have to modify the code for both the MovieItemView and MovieEditor classes to account for the new attributes. However, the Swing GridBagLayout we've used lends itself to easily modifying component layout, and adding the new code would not be much more difficult than writing the original. The fact that all the new attributes will be isolated in the Movie class will make the process easier.

What about adding searching capability? This should be easy. There are two approaches we could use. In either case, the user interface would be most easily added to the MainView class. First, we could add a search method to the MovieModel class, and then use it from the MainView interface. A second approach would be to use the iterator provided by the MovieModel, and implement the search within MainView. Keeping the search within the MovieModel would make the system less sensitive to future changes, but it would limit the searches to whatever the MovieModel class provides. Building a search based on using the iterator would allow the most flexibility on which fields could be used for the search, but would be sensitive to changes. Either way will easily be added to the current MovieCat code.

Some of the other changes, such as adding images or IMDb connectivity would require a significant effort to understand what image formats are available, or how to connect and use the IMDb. The effort to add either could equal or exceed the original effort, but would still fit within the existing design.

The same seems true for the other features. The isolation of the model from the GUI, and the use of MVC will make it easy to add features to MovieCat.

Chapter Summary

1
Note that some of these listing have a few awkward line breaks caused by the need to fit the page size of this book. The full source code is also included on the book CD.

2
The iterator is another design pattern used for accessing every item in a collection of objects.

3
Only one copy of the MovieEditor will exist. Thus, MovieEditor was implemented using the Singleton design pattern.


TOC PREV NEXT INDEX

The Essence of Object-Oriented Programming with Java and UML (DRAFT)

Copyright © 2001 by Addison-Wesley

All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form, or by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior consent of the publisher. You may view this document on a web browser, but you must not mirror or make local copies.