View Part 1 - Run ModesS2C Home « View Part 1 - Run Modes

In the final lesson of the View Part 1 section of the case study we code classes to create a panel for entry of run mode options and and persist these for later reuse to avoid having to reenter them each time the application is used.

Compiling The SavedRunModeOptions Class Top

The SavedRunModeOptions class provides read/write access to the user's saved run mode parameters on disk, so that next time they connect, we can offer the same run mode parameters as a default.


package view;

import java.io.*;
import java.util.*;
import java.util.logging.Logger;

/**
 * Provides read/write access to the user's saved run mode parameters on  disk, so 
 * that next time they connect, we can offer the same run mode parameters as a default.
 *
 */
public class SavedRunModeOptions {
    /**
     * The Logger instance. All log messages from this class are routed through
     * this member. The Logger namespace is s2cCaseStudy.
     */
    private static Logger log = Logger.getLogger("s2cCaseStudy"); // Log output

    /**
     * Key in Properties indicating that the value will be the manufacturer
     * file location.
     */
    public static final String FILE_LOCATION = "FileLocation";

    /**
     * Key in Properties indicating that the value will be the RMI Registry
     * server address.
     */
    public static final String SERVER_ADDRESS = "ServerAddress";

    /**
     * Key in Properties indicating that the value will be the port the RMI
     * registry listens on.
     */
    public static final String SERVER_PORT = "ServerPort";

    /**
     * The location where our SavedRunModeOptions file will be saved.
     */
    private static final String BASE_DIRECTORY = System.getProperty("user.dir");

    /**
     * the name of our properties file.
     */
    private static final String OPTIONS_FILENAME = "manufacturer.properties";

    /**
     * The file containing our saved run mode options.
     */
    private static File savedRunModeOptionsFile = new File(BASE_DIRECTORY, OPTIONS_FILENAME);

    /**
     * Only create SavedRunModeOptions Singleton first time through using double-checked 
     * locking to ensure private constructor is only synchronised once.
     */
    private volatile static SavedRunModeOptions uniqueSavedRunModeInstance;

    /**
     * The Properties for this application.
     */
    private Properties parameters = null;

    /**
     * Creates a new instance of SavedRunModeOptions. There should only ever be
     * one instance of this class (a Singleton), so we have made it private.
     */
    private SavedRunModeOptions() {
        log.entering("SavedRunModeOptions", "SavedRunModeOptions");
        parameters = loadParametersFromFile();

        if (parameters == null) {
            parameters = new Properties();
            parameters.setProperty(SERVER_ADDRESS, "localhost");
            parameters.setProperty(SERVER_PORT, "" + java.rmi.registry.Registry.REGISTRY_PORT);
        }
        log.exiting("SavedRunModeOptions", "SavedRunModeOptions");
    }

    /**
     * Clients of the SavedRunModeOptions class have to call this method to get the unique
     * instance (singleton) for this class as the constructor is private.
     * 
     * @return The singleton for SavedRunModeOptions class.
     */
    public static SavedRunModeOptions getSavedRunModeInstance() {
        if (uniqueSavedRunModeInstance == null) {
            synchronized (SavedRunModeOptions.class) {
                if (uniqueSavedRunModeInstance == null) {
                    uniqueSavedRunModeInstance = new SavedRunModeOptions();
                }
            }
        }
        return uniqueSavedRunModeInstance;
    }

    /**
     * Returns the value of the named parameter.
     * 
     * @param parameterName The name of the run mode parameter.
     * @return The value of the named run mode parameter.
     */
    public String getParameter(String parameterName) {
        log.entering("SavedRunModeOptions", "getParameter", parameterName);
        log.exiting("SavedRunModeOptions", "getParameter", parameters.getProperty(parameterName));
        return parameters.getProperty(parameterName);
    }

    /**
     * Updates the saved run mode parameters with the new values. 
     * 
     * @param parameterName The name of the run mode parameter.
     * @param parameterValue The value run mode parameter to be stored.
     */
    public void setParameter(String parameterName, String parameterValue) {
        log.entering("SavedRunModeOptions", "setParameter", 
                new Object[] {parameterName, parameterValue});
        parameters.setProperty(parameterName, parameterValue);
        saveParametersToFile();
        log.exiting("SavedRunModeOptions", "setParameter");
    }

    /**
     * Saves the parameters to a file so that they can be used again next time
     * the application starts.
     */
    private void saveParametersToFile() {
         log.entering("SavedRunModeOptions", "saveParametersToFile");

        try {
            synchronized (savedRunModeOptionsFile) {
                if (savedRunModeOptionsFile.exists()) {
                    savedRunModeOptionsFile.delete();
                }
                savedRunModeOptionsFile.createNewFile();
                FileOutputStream fos = new FileOutputStream(savedRunModeOptionsFile);
                parameters.store(fos, "Manufacturer Application run mode options");
                fos.close();
            }
        } catch (IOException e) {
            ManufacturerApplicationStartup.handleException("Unable to save run mode parameters"
                    + " to file. They wont be remembered next time you start.");
        }
    log.exiting("SavedRunModeOptions", "saveParametersToFile");
    }

    /**
     * Attempts to load the saved run mode parameter from the file so that the user does
     * not have to reenter all the information.
     * 
     * @return Properties loaded from file or null.
     */
    private Properties loadParametersFromFile() {
        log.entering("SavedRunModeOptions", "loadParametersFromFile");

        Properties loadedProperties = null;

        if (savedRunModeOptionsFile.exists() && savedRunModeOptionsFile.canRead()) {
            synchronized (savedRunModeOptionsFile) {
                try {
                    FileInputStream fis = new FileInputStream(savedRunModeOptionsFile);
                    loadedProperties = new Properties();
                    loadedProperties.load(fis);
                    fis.close();
               } catch (FileNotFoundException e) {
                    ManufacturerApplicationStartup.handleException("Unable to load run mode "
                            + "parameters. Default values will be used.\n" + e);
               } catch (IOException e) {
                    ManufacturerApplicationStartup.handleException("Unable to load run mode "
	                        + "parameters. Default values will be used.\n" + e);
               }
            }
        }
        log.exiting("SavedRunModeOptions", "loadParametersFromFile", loadedProperties);
        return loadedProperties;
    }
}

Compiling Our Source File With the -cp and -d Options

Open your command line editor:

Change to directory  cd c:\_Case_Study\src\client

Compile SavedRunModeOptions .java using the java compiler with the -cp and -d options
  javac -cp ..\..\classes -d ..\..\classes SavedRunModeOptions .java

Compiling The RunModeOptions Class Top

The RunModeOptions class creates a common panel used by both the client and server applications to specify the run mode options. The panel changes slightly dependent on run mode entered on application startup but has a "common feel" for all three run modes. A point of interest here is that we create an inner class that extends the java.util.Observable class; This class represents an observable object, or "data" in the model-view paradigm. An observable object can have one or more observers, which are objects that implements the Observer interface. What this means for the case study is that the RunModeDialog class which we will code in Validate Run Mode Options will be notified whenever changes are made within the run mode options panel, so they can be validated within that dialog.

Cut and paste the following code into your text editor and save it in the   c:\_Case_Study\src\client directory.


package view;

import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.util.logging.Logger;
import java.util.Observable;
import javax.swing.*;

/**
 * A common panel used by both the client and server applications to specify the
 * run mode options. The panel changes slightly dependent on run mode entered
 * on application startup but has a "common feel" for all three run modes.
 * 
 * @see RunMode
 * 
 */
public class RunModeOptions extends JPanel {
    /**
     * The Logger instance. All log messages from this class are routed through
     * this member. The Logger namespace is s2cCaseStudy.
     */
    private static Logger log = Logger.getLogger("s2cCaseStudy"); // Log output

    /**
     * A version number for the RunModeOptions class so that serialisation can
     * occur without worrying about the underlying class changing between
     * serialisation and deserialisation.
     */
    private static final long serialVersionUID = 2498052502L;

    // Constants.
    private static final String FILE_LOCATION_LABEL = "Manufacturer file location: ";
    private static final String SERVER_PORT_LABEL = "Server port: ";
    private static final String FILE_LOCATION_TOOL_TIP
            = "The location of the Manufacturer file on the hard drive";
    private static final String FILE_IP_LOCATION_TOOL_TIP
            = "The server where the Manufacturer file is located (IP address)";
    private static final String SERVER_PORT_TOOL_TIP
            = "The port number the Server uses to listens for requests";
    private static final String FILE_EXTENSION = "txt";
    private static final String FILE_CHOOSER_DESCRIPTION
            = "Database files (*." + FILE_EXTENSION + ")";

    /**
     * An Observable class so interested users of this class can receive
     * automatic updates whenever run mode options change.
     */
    private RunModeObservable observerRunModeOptions = new RunModeObservable();

    // User modifiable fields and all buttons are defined here.
    private JTextField locationField = new JTextField(40);
    private JButton browseButton = new JButton("...");
    private JTextField portNumber = new PositiveIntVerify(5);

    private String location = null;
    private String port = null;
    private RunMode runMode = RunMode.NON_NETWORK_CLIENT;

    /**
     * Creates a new instance of RunModeOptions - the panel for configuring
     * database connectivity.
     *
     * @param runMode One of NON_NETWORK_CLIENT,
     * NETWORK_CLIENT, or SERVER.
     * 
     * @see RunMode
     */
    public RunModeOptions(RunMode runMode) {
        super();
        log.entering("RunModeOptions", "RunModeOptions");
        this.runMode = runMode;

        GridBagLayout gridbag = new GridBagLayout();
        GridBagConstraints constraints = new GridBagConstraints();
        this.setLayout(gridbag);
        /*
         * Standard options
         * Ensure there is always a gap between components
         */
        constraints.insets = new Insets(3, 3, 3, 3);
        // Build the Manufacturer file location row
        JLabel dbLocationLabel = new JLabel(FILE_LOCATION_LABEL);
        gridbag.setConstraints(dbLocationLabel, constraints);
        this.add(dbLocationLabel);

        if (runMode == RunMode.NETWORK_CLIENT) {
            locationField.setToolTipText(FILE_IP_LOCATION_TOOL_TIP);
            constraints.gridwidth = GridBagConstraints.REMAINDER; // End row
        } else {
            locationField.setToolTipText(FILE_LOCATION_TOOL_TIP); // Next-to-last in row
            constraints.gridwidth = GridBagConstraints.RELATIVE;
        }
        locationField.addFocusListener(new ActionHandler());
        locationField.setName(FILE_LOCATION_LABEL);
        gridbag.setConstraints(locationField, constraints);
        this.add(locationField);
        /*
         *  Add a browse button to allow navigation to the Manufacturer file
         *  in Server and non-network client modes. 
         */
        if ((runMode == RunMode.SERVER)
                || (runMode == RunMode.NON_NETWORK_CLIENT)) {
            browseButton.addActionListener(new BrowseForFile());
            constraints.gridwidth = GridBagConstraints.REMAINDER; // End row
            gridbag.setConstraints(browseButton, constraints);
            this.add(browseButton);
        }
        if ((runMode == RunMode.SERVER)
                || (runMode == RunMode.NETWORK_CLIENT)) {
            // Build the Server port row if applicable
            constraints.weightx = 0.0;
            JLabel serverPortLabel = new JLabel(SERVER_PORT_LABEL);
            constraints.gridwidth = 1;
            constraints.anchor = GridBagConstraints.EAST;
            gridbag.setConstraints(serverPortLabel, constraints);
            this.add(serverPortLabel);
            portNumber.addFocusListener(new ActionHandler());
            portNumber.setToolTipText(SERVER_PORT_TOOL_TIP);
            portNumber.setName(SERVER_PORT_LABEL);
            constraints.gridwidth = GridBagConstraints.REMAINDER;  // End row
            constraints.anchor = GridBagConstraints.WEST;
            gridbag.setConstraints(portNumber, constraints);
            this.add(portNumber);
        }
        log.exiting("RunModeOptions", "RunModeOptions");
    }

    /**
     * Utility method to inform our observers of any changes the user makes to
     * the parameters on screen.
     *
     * @param updateType Enum specify which field has changed
     * @param payLoad the new data the user just entered.
     */
    private void updateObservers(RunModeOptionsUpdate.Update updateType, Object payLoad) {
		log.entering("RunModeOptions", "updateObservers");
		RunModeOptionsUpdate update = new RunModeOptionsUpdate(updateType, payLoad);
        observerRunModeOptions.setChanged();
        observerRunModeOptions.notifyObservers(update);
        log.exiting("RunModeOptions", "updateObservers");
    }

    /**
     * A utility class to handle user interactions with the panel. These are
     * not processed by the user of this panel, rather they are processed
     * internally, and an update is then sent to any observers.
     */
    private class ActionHandler implements FocusListener {
        /** {@inheritDoc} */
        public void focusGained(FocusEvent e) {
            // Ignored as we don't do anything particular when users enter a field
        }

        /** {@inheritDoc} */
        public void focusLost(FocusEvent e) {
            if (FILE_LOCATION_LABEL.equals(e.getComponent().getName())
                    && (!locationField.getText().equals(location))) {
                location = locationField.getText();
                updateObservers(RunModeOptionsUpdate.Update.FILE_LOCATION_MODIFIED,
                                location.trim());
            }
            if (SERVER_PORT_LABEL.equals(e.getComponent().getName())
                    && (!portNumber.getText().equals(port))) {
                port = portNumber.getText();
                updateObservers(RunModeOptionsUpdate.Update.PORT_MODIFIED, port.trim());
            }
        }
    }

    /**
     * A utility class that provides the user with the ability to browse for
     * the Manufacturer file rather than forcing them to remember (and type in) 
     * a fully qualified Manufacturer file location.
     */
    private class BrowseForFile implements ActionListener {
        /** {@inheritDoc} */
        public void actionPerformed(ActionEvent ae) {
        JFileChooser chooser = new JFileChooser(System.getProperty("user.dir"));
            chooser.addChoosableFileFilter(
                new javax.swing.filechooser.FileFilter() {
                    /**
                     * display files ending in ".txt" or any other object
                     * (directory or other selectable device).
                     */
                    public boolean accept(File f) {
                        if (f.isFile()) {
                            return f.getName().endsWith(FILE_EXTENSION);
                        } else {
                            return true;
                        }
                    }
                    /**
                     * provide a description for the types of files we are
                     * allowing to be selected.
                     */
                    public String getDescription() {
                        return FILE_CHOOSER_DESCRIPTION;
                    }
                }
            );

            // if the user selected a file, update the file name on screen
            if (JFileChooser.APPROVE_OPTION == chooser.showOpenDialog(null)) {
                locationField.setText(chooser.getSelectedFile().toString());
                location = locationField.getText();
                updateObservers(RunModeOptionsUpdate.Update.FILE_LOCATION_MODIFIED,
                                location.trim());
            }
        }
    }

    /**
     * Returns the run mode the Manufacturer application will be running in.
     * 
     * @return The run mode the Manufacturer application will be running in.
     * @see RunMode
     */
    public RunMode getApplicationMode() {
        log.entering("RunModeOptions", "getApplicationMode");
        log.exiting("RunModeOptions", "getApplicationMode", this.runMode);
        return this.runMode;
    }

    /**
     * Returns the contents of the Manufacturer location field.
     * 
     * @return the contents of the Manufacturer location field.
     */
    public String getLocationFieldText() {
        log.entering("RunModeOptions", "getLocationFieldText");
        log.exiting("RunModeOptions", "getLocationFieldText", locationField.getText());
        return locationField.getText();
    }

    /**
     * Sets the contents of the Manufacturer location field.
     *
     * @param locationField the contents of the database location field.
     */
    public void setLocationFieldText(String locationField) {
        log.entering("RunModeOptions", "setLocationFieldText", locationField);
        this.locationField.setText(locationField);
        log.exiting("RunModeOptions", "setLocationFieldText");
    }

    /**
     * Configures whether the location field is enabled or not.
     *
     * @param enabled true if the location field is enabled.
     */
    public void setLocationFieldEnabled(boolean enabled) {
        log.entering("RunModeOptions", "setLocationFieldEnabled", enabled);
        this.locationField.setEnabled(enabled);
        log.exiting("RunModeOptions", "setLocationFieldEnabled");
    }

    /**
     * Configures whether the browse button is enabled or not.
     *
     * @param enabled true if the browse button is enabled.
     */
    public void setBrowseButtonEnabled(boolean enabled) {
        log.entering("RunModeOptions", "setBrowseButtonEnabled", enabled);
        this.browseButton.setEnabled(enabled);
        log.exiting("RunModeOptions", "setBrowseButtonEnabled");
    }

    /**
     * Returns the contents of the port number text field.
     *
     * @return the contents of the port number text field.
     */
    public String getPortNumberText() {
        log.entering("RunModeOptions", "getPortNumberText");
        log.exiting("RunModeOptions", "getPortNumberText", portNumber.getText());
        return portNumber.getText();
    }

    /**
     * Sets the contents of the port number text field.
     *
     * @param portNumber the contents of the port number text field.
     */
    public void setPortNumberText(String portNumber) {
        log.entering("RunModeOptions", "setPortNumberText", portNumber);
        this.portNumber.setText(portNumber);
        log.exiting("RunModeOptions", "setPortNumberText");
    }

    /**
     * Configures whether the port number field is enabled or not.
     *
     * @param enabled true if the port number field is enabled.
     */
    public void setPortNumberEnabled(boolean enabled) {
        log.entering("RunModeOptions", "setPortNumberEnabled", enabled);
        this.portNumber.setEnabled(enabled);
        log.exiting("RunModeOptions", "setPortNumberEnabled");
    }

    /**
     * Returns an instance of the Observable class. Observers can
     * register themselves with this class in order to receive updates as
     * things change within this panel.
     *
     * @return an instance of the Observable class.
     */
    public Observable getObservable() {
        log.entering("RunModeOptions", "getObservable");
        log.exiting("RunModeOptions", "getObservable", observerRunModeOptions);
        return observerRunModeOptions;
    }

    /**
     * Our Observable class - a class that Observers can register themselves
     * with in order to receive updates as things change within this panel.
     */
    private class RunModeObservable extends Observable {
        /** {@inheritDoc} */
        public void setChanged() {
            super.setChanged();
        }
    }
}

Compiling Our Source File With the -cp and -d Options

Open your command line editor:

Change to directory  cd c:\_Case_Study\src\client

Compile RunModeOptions.java using the java compiler with the -cp and -d options
  javac -cp ..\..\classes -d ..\..\classes RunModeOptions.java

The following screenshot shows that we get a clean compile on the above classes and also the ManufacturerApplication, SavedRunModeOptions and RunModeOptions classes are now compiled into the classes\client directory.

compile Startup Runmode

Lesson 10 Complete

In this lesson we coded the View elements of the MVC pattern that are concerned with run mode options.

Related Java Tutorials

Beginning Java - Primitive Variables
Beginning Java - Conditional Statements
Beginning Java - Loop Statements
Objects & Classes - Arrays
Objects & Classes - Class Structure and Syntax
Objects & Classes - Reference Variables
Objects & Classes - Methods
Objects & Classes - Instance Variables & Scope
Objects & Classes - Constructors
Objects & Classes - Static Members
Objects & Classes - Enumerations
OO Concepts - Encapsulation
OO Concepts - Inheritance Concepts - Using the super keyword
Swing - RMI - Serialization
Exceptions - Handling Exceptions
API Contents - Inheritance - Using the package keyword
API Contents - Inheritance - Using the import keyword
Concurrency - Synchronization - Synchronized Blocks

What's Next?

In the next section we set up the Controller elements of the MVC pattern that can be derived from the project proposal.