View Part 1 - GUI ClassesJ8 Home « View Part 1 - GUI Classes
In the second lesson of the View Part 1 section of the case study we will begin coding the view part of the MVC pattern, starting with the classes that are independent of other classes within the Manufacturer application.
These classes have no reliance on other classes and so will compile cleanly and make compilation of the rest of the view
part of the Manufacturer application easier.
Creating The ExitManufacturerFileCleanly
Class Top
The ExitManufacturerFileCleanly
class gets registered with the JVM in the ManufacturerServerStartupWindow
class (yet to be written) to run when the Manufacturer application
is shutting down by calling Runtime.addShutdownHook
with an instance of this class as a parameter. The run
method will be executed when the Manufacturer application is
shut down by the user, ensuring that we are not in the process of writing to the Manufacturer file, which might result in a corrupted file.
Create the ExitManufacturerFileCleanly
class in the client
package and cut and paste the following code into it.
package client;
import java.util.logging.Level;
import java.util.logging.Logger;
import model.StockImpl;
import model.ManufacturerFileAccessException;
/**
* This class is registered with the JVM in the ManufacturerServerStartupWindow
* class to run when the application is shutting down by calling
* Runtime.addShutdownHook
with an instance of this class as a
* parameter.
*
* The run
method will be executed when the application is shut
* down by the user, ensuring that we are not in the process of writing to the
* Manufacturer file, which might result in a corrupted file.
*
* @see java.lang.Runtime#addShutdownHook
*
* @author Charlie
* @version 1.0
*/
public class ExitManufacturerFileCleanly extends Thread {
/**
* The Logger instance through which all log messages from this class are
* routed. Logger namespace is J8CaseStudy
.
*/
private Logger log = Logger.getLogger("J8CaseStudy"); // Log output
/**
* The location of our Manufacturer file, so that we can connect to
* it correctly prior to locking it from updates.
*/
private String manufacturerPathName = null;
/**
* Create an instance of this class on server startup so that it can be run
* later.
*
* @param manufacturerPathName The location of the Manufacturer file
* on the disk.
*/
public ExitManufacturerFileCleanly(String manufacturerPathName) {
this.manufacturerPathName = manufacturerPathName;
}
/**
* This method is executed by the JVM when the application is shutdown,
* ensuring the Manufacturer file is in a clean state for shutdown by
* gaining an exclusive lock on the locking map.
*/
public void run() {
log.info("Ensuring a clean Manufacturer file shutdown");
try {
StockImpl stock = model.StockImpl.getStockImplInstance(manufacturerPathName);
stock.lockLockingMap(true);
} catch (ManufacturerFileAccessException e) {
log.log(Level.SEVERE, "Failed to lock locking map before exiting", e);
}
}
}
Creating The ManufacturerTableModel
Class Top
The ManufacturerTableModel
class is a custom table model used by the ManufacturerWindow
instance, which we will write in the View Part 2
section. This custom table model allows us to get and set table entries along with some other useful utilities that are documented within the code. There are a lot of methods to implement within the
TableModel
interface so we are extending the AbstractTableModel
interface which allows us to only implement those methods we need.
Create the ManufacturerTableModel
class in the client
package and cut and paste the following code into it.
package client;
import java.util.ArrayList;
import java.util.logging.*;
import javax.swing.table.AbstractTableModel;
import model.Manufacturer;
/**
* The custom table model used by the ManufacturerWindow
instance.
*
* @author Charlie
* @version 1.0
* @see model.StockImpl
*
*/
public class ManufacturerTableModel extends AbstractTableModel {
/**
* The Logger instance through which all log messages from this class are routed.
* Logger namespace is J8CaseStudy
.
*/
private static Logger log = Logger.getLogger("J8CaseStudy"); // Log output
/**
* A version number for the ManufacturerTableModel class so that serialisation
* can occur without worrying about the underlying class changing
* between serialisation and deserialisation.
*/
private static final long serialVersionUID = 2498052502L;
/**
* An array of String
objects representing the table headers.
*/
private String [] headerNames = {"Name", "Location", "Product", "Product Price",
"Stock Level", "Stock Ordered"};
/**
* Holds all Manufacturer instances displayed in the main table.
*/
private ArrayList<String[]> manufacturerRecords = new ArrayList<>(2);
/**
* Returns the column count of the table.
*
* @return An integer indicating the number or columns in the table.
*/
public int getColumnCount() {
log.entering("ManufacturerTableModel", "getColumnCount");
log.exiting("ManufacturerTableModel", "getColumnCount", this.headerNames.length);
return this.headerNames.length;
}
/**
* Returns the number of rows in the table.
*
* @return An integer indicating the number of rows in the table.
*/
public int getRowCount() {
log.entering("ManufacturerTableModel", "getRowCount");
log.exiting("ManufacturerTableModel", "getRowCount", this.manufacturerRecords.size());
return this.manufacturerRecords.size();
}
/**
* Gets a value from a specified index in the table.
*
* @param row An integer representing the row index.
* @param column An integer representing the column index.
* @return The object located at the specified row and column.
*/
public Object getValueAt(int row, int column) {
log.entering("ManufacturerTableModel", "getValueAt", new Object[]{row, column});
String[] rowValues = this.manufacturerRecords.get(row);
log.exiting("ManufacturerTableModel", "getValueAt", rowValues[column]);
return rowValues[column];
}
/**
* Sets the cell value at a specified index.
*
* @param obj The object that is placed in the table cell.
* @param row The row index.
* @param column The column index.
*/
public void setValueAt(Object obj, int row, int column) {
log.entering("ManufacturerTableModel", "setValueAt", new Object[]{row, column});
Object[] rowValues = this.manufacturerRecords.get(row);
rowValues[column] = obj;
log.exiting("ManufacturerTableModel", "setValueAt");
}
/**
* Returns the name of a column at a given column index.
*
* @param column The specified column index.
* @return A String containing the column name.
*/
public String getColumnName(int column) {
return headerNames[column];
}
/**
* Adds a row of Manufacturer data to the table.
*
* @param name The Manufacturers name.
* @param location The location where the Manufacturer is based.
* @param product Name of the product.
* @param price Price of a product.
* @param stockLevel Product stock level.
* @param stockOrdered Stock on order.
*/
private void addManufacturerRecord(String name, String location,
String product, String price, String stockLevel, String stockOrdered) {
log.entering("ManufacturerTableModel", "addManufacturerRecord",
new Object[]{name, location, product, price, stockLevel, stockOrdered});
String[] manufacturerData = {name, location, product, price, stockLevel, stockOrdered};
this.manufacturerRecords.add(manufacturerData);
log.exiting("ManufacturerTableModel", "addManufacturerRecord");
}
/**
* Splits the Manufacturer into its components.
*
* @param manufacturer A Manufacturer object.
*/
public void addManufacturerRecord(Manufacturer manufacturer) {
log.entering("ManufacturerTableModel", "addManufacturerRecord");
addManufacturerRecord(manufacturer.getName(), manufacturer.getLocation(),
manufacturer.getProduct(), manufacturer.getPrice(),
manufacturer.getStockLevel(), manufacturer.getStockOrdered());
log.exiting("ManufacturerTableModel", "addManufacturerRecord");
}
}
Creating The PositiveIntVerify
Class Top
The PositiveIntVerify
class is an extension of JTextField
which only allows positive integer numbers to be entered. This reduces the validation required on the field after
entry, and gives a nicer entry field to the end user, as they can only enter digits.
Create the PositiveIntVerify
class in the client
package and cut and paste the following code into it.
package client;
import java.util.logging.Logger;
import javax.swing.JTextField;
import javax.swing.text.*;
/**
* Extension of JTextField
which only allows positive int numbers to be
* entered.
*
* This reduces the validation required on the field after entry, and gives a
* nicer entry field to the end user - as they can only enter digits.
*/
public class PositiveIntVerify extends JTextField {
/**
* The Logger instance. All log messages from this class are routed through
* this member. The Logger namespace is J8CaseStudy
.
*/
private Logger log = Logger.getLogger("J8CaseStudy");
/**
* A version number for the PositiveIntVerify class so that serialisation
* can occur without worrying about the underlying class changing between
* serialisation and deserialisation.
*/
private static final long serialVersionUID = 2498052502L;
/**
* Constructs a new empty PositiveIntVerify with zero columns.
* The default document will be a NumericDocument which only allows
* positive integer numbers to be entered.
*/
public PositiveIntVerify() {
super();
log.entering("PositiveIntVerify", "PositiveIntVerify");
log.exiting("PositiveIntVerify", "PositiveIntVerify");
}
/**
* Constructs a new empty PositiveIntVerify with the specified number of
* columns. The default document will be a NumericDocument which only allows
* positive integer numbers to be entered.
*
* @param columns Number of columns to use to calculate preferred width.
* if columns is set to zero, the preferred width will be whatever
* naturally results from the component implementation
*/
public PositiveIntVerify(int columns) {
super(columns);
log.entering("PositiveIntVerify", "PositiveIntVerify");
log.exiting("PositiveIntVerify", "PositiveIntVerify");
}
/**
* Creates default implementation of the model to be used at construction.
*
* @return NumericDocument a document which only allows positive integers
*/
protected Document createDefaultModel() {
return new NumericDocument();
}
/**
* A document that only allows positive integer numbers to be entered.
*/
class NumericDocument extends PlainDocument {
/**
* A version number for the NumericDocument class so that serialisation
* can occur without worrying about the underlying class changing
* between serialisation and deserialisation.
*/
private static final long serialVersionUID = 2498052502L;
/**
* Inserts some content into the document. Inserting content causes a
* write lock to be held while the actual changes are taking place,
* followed by notification to the observers on the thread that grabbed
* the write lock.
*
* @param offset The starting offset >= 0.
* @param str The string to insert; does nothing with null/empty strings.
* @param a The attributes for the inserted content.
*
* @throws BadLocationException The given insert position is not a
* valid position within the document.
*/
public void insertString(int offset, String str, AttributeSet a)
throws BadLocationException {
if (str == null) {
return;
}
char[] input = str.toCharArray();
int result = 0;
boolean valid = false;
for (char c : input) {
if (Character.isDigit(c)) {
valid = true;
result = result * 10 + Character.digit(c, 10);
}
}
if (valid) {
super.insertString(offset, new String("" + result), a);
}
}
}
}
Creating The RunMode
Enumeration Top
The RunMode
Enumeration specifies the mode the Manufacturer application is running in..
Create the RunMode
Enumeration in the client
package and cut and paste the following code into it.
package client;
/**
* Specifies the mode the Manufacturer application is running in.
*
* NON_NETWORK_CLIENT - Application running in non-networked client mode.
* NETWORK_CLIENT - Application running in network client mode.
* SERVER - Application running in server mode.
*/
public enum RunMode {
/**
* User started Manufacturer application with a mode of "client" so this will be
* a non-networked client - no network access.
*/
NON_NETWORK_CLIENT,
/**
* User didn't enter an application mode when starting Manufacturer application
* so this will be a networked client via RMI.
*/
NETWORK_CLIENT,
/**
* User started Manufacturer application with a mode of "server" so we need to
* setup a server connection.
*/
SERVER
}
Creating The RunModeOptionsUpdate
Class Top
The RunModeOptionsUpdate
class is a Value object used to transfer information about changes to our RunModeOptions
panel to any interested observers.
Create the RunModeOptionsUpdate
class in the client
package and cut and paste the following code into it.
package client;
import java.util.logging.Logger;
/**
* Value object used to transfer information about changes to our
* RunModeOptions
panel to any interested observers.
*
*/
public class RunModeOptionsUpdate {
/**
* The Logger instance. All log messages from this class are routed through
* this member. The Logger namespace is J8CaseStudy
.
*/
private static Logger log = Logger.getLogger("J8CaseStudy"); // Log output
/**
* The enumerated list of possible updates that can be sent from the
* RunModeOptions panel. Only one of the following options can possibly
* be passed with this value object.
*/
public enum Update {
/**
* The user has specified the location of the Manufacturer file
* or the address of the server.
*/
FILE_LOCATION_MODIFIED,
/**
* The user has changed the port number the server is expected to be
* listening on.
*/
PORT_MODIFIED;
}
/*
* The values that will be transfered.
*/
private Update updateType = null;
private Object payload = null;
/**
* Empty constructor to conform with JavaBean requirements.
*/
public RunModeOptionsUpdate() {
log.entering("RunModeOptionsUpdate", "RunModeOptionsUpdate");
log.exiting("RunModeOptionsUpdate", "RunModeOptionsUpdate");
}
/**
* Constructor that allows us to specify the update type and any
* relevant information.
*
* @param updateType The type of run mode update that has occurred.
* @param payload Any relevant information that we would like to pass at
* the same time.
*/
public RunModeOptionsUpdate(Update updateType, Object payload) {
log.entering("RunModeOptionsUpdate", "RunModeOptionsUpdate",
new Object[] {updateType, payload});
this.updateType = updateType;
this.payload = payload;
log.exiting("RunModeOptionsUpdate", "RunModeOptionsUpdate");
}
/*
* Getter and setter methods for JavaBean requirements
*/
/**
* Gets the type of run mode update that has occurred.
*
* @return The type of update that has occurred.
*/
public Update getUpdateType() {
log.entering("RunModeOptionsUpdate", "getUpdateType");
log.exiting("RunModeOptionsUpdate", "getUpdateType", this.updateType);
return this.updateType;
}
/**
* Sets the type of run mode update that has occurred.
*
* @param updateType The type of update that has occurred.
*/
public void setUpdateType(Update updateType) {
log.entering("RunModeOptionsUpdate", "setUpdateType");
this.updateType = updateType;
log.exiting("RunModeOptionsUpdate", "setUpdateType");
}
/**
* Gets any information considered relevant to this update.
*
* @return Any relevant information that we would like to pass at
* the same time.
*/
public Object getPayload() {
log.entering("RunModeOptionsUpdate", "getPayload");
log.exiting("RunModeOptionsUpdate", "getPayload", this.payload);
return this.payload;
}
/**
* Sets any information considered relevant to this update.
*
* @param payload Any relevant information that we would like to pass at
* the same time.
*/
public void setPayload(Object payload) {
log.entering("RunModeOptionsUpdate", "setPayload");
this.payload = payload;
log.exiting("RunModeOptionsUpdate", "setPayload");
}
}
After adding independent GUI classes your client
package should look similar to that in the screenshot below.
Lesson 8 Complete
In this lesson we coded the View elements of the MVC starting with the classes that can be coded independently.
Related Java Tutorials
Fundamentals - Primitive Variables
Fundamentals - if
Construct
Fundamentals - enhanced for
Construct
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
Exceptions - Handling Exceptions
API Contents - Inheritance - Using the package
keyword
API Contents - Inheritance - Using the import
keyword
What's Next?
In the next lesson we code the View elements of the MVC pattern that are used on application startup.