Model Part 1S2C Home « Model Part 1

In this section of the case study we look at the model part of the MVC paradigm and code as much of the model as we can from the information provided within the Project Proposal. The stakeholder Stocking Goods Limited have supplied us with a Stock interface that must be implemented and from this we can also derive some exception classes. In our first lesson of the section we code up the Stock interface and exceptions for our model code and compile them. The stakeholder has also requested that we create a class representing the record information within the Manufacturer file called unsurprisingly Manufacturer as they see a lot of future reuse of this file and it will also be helpful when implementing the Stock interface. The final thing we can do with the model at present is implement the Stock interface and create some method stubs we will come back to when we revisit the model code in the Model Part 2 lessonon.

Stock Interface & Exceptionsgo to top of page Top

The stakeholder Stocking Goods Limited have supplied us with an interface that must be implemented and from this we can also derive some exception classes. The exception classes we need to code to honour the contract of the Stock interface are DuplicateKeyException, RecordNotFoundException and SecurityException. So to get a clean compile on the the Stock Interface we will need to code up these exceptions first. All the exception classes are similar and allow us to throw an exception alone, with a message or chained and all of these classes extend the RuntimeException class. All the exception classes can be thrown remotely and so are serialized and use a version number so that serialisation can occur without worrying about the underlying class changing between serialization and deserialization.

We also decided in the Proposal Conclusions that a situation might arise where a user tries to stock goods that have already been stocked by another user and if this happens we will need to throw a StockingException and so we will also create and compile this class.

Compiling The DuplicateKeyException Classgo to top of page Top

The following DuplicateKeyException class will be be thrown when we try to create a record that already exists.

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


package model;

/**
 * Holds any duplicate key exceptions that may occur in the StockImpl class.
 * 
 * @author Kevin 
 * @version 1.0
 * @see StockImpl
 *
 */
public class DuplicateKeyException extends RuntimeException {

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

    /**
     * Create a default DuplicateKeyException instance.
     * 
     */
    public DuplicateKeyException() {
        super();
    }

    /**
     * Create a DuplicateKeyException instance that accepts a String.
     * 
     * @param e The string description of the exception.
     */
    public DuplicateKeyException(String e) {
        super(e);
    }

    /**
     * Create a DuplicateKeyException instance and chain an exception.
     * 
     * @param e The exception to wrap
     */
    public DuplicateKeyException(Throwable e) {
        super(e);
    }
}

Compiling Our Source File With the -d Option

Open your command line editor:

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

Compile DuplicateKeyException.java using the java compiler with the -d option
  javac -d ..\..\classes DuplicateKeyException.java

Because we have used a package (package model;) at the top of the code the compiler has created this directory within the classes directory. When we look in the model directory we can see the compiled DuplicateKeyException.class file.

compile DuplicateKeyException

Compiling The RecordNotFoundException Classgo to top of page Top

The following RecordNotFoundException class will be be thrown when we try to create a record that is not found which can occur when we try to read, update, delete or lock a record.

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


package model;

/**
 * Holds any record not found exceptions that may occur in the StockImpl class.
 * 
 * @author Kevin 
 * @version 1.0
 * @see StockImpl
 *
 */
public class RecordNotFoundException extends RuntimeException {

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

    /**
     * Create a default DuplicateKeyException instance.
     * 
     */
    public RecordNotFoundException() {
        super();
    }

    /**
     * Create a RecordNotFoundException instance that accepts a String.
     * 
     * @param e The string description of the exception.
     */
    public RecordNotFoundException(String e) {
        super(e);
    }

    /**
     * Create a RecordNotFoundException instance and chain an exception.
     * 
     * @param e The exception to wrap
     */
    public RecordNotFoundException(Throwable e) {
        super(e);
    }
}

Compiling Our Source File With the -d Option

Open your command line editor:

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

Compile RecordNotFoundException.java using the java compiler with the -d option
  javac -d ..\..\classes RecordNotFoundException.java

Compiling The SecurityException Classgo to top of page Top

The following SecurityException class will be be thrown when we have a lock record mismatch which can occur when we try to update, delete or unlock a record.

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


package model;

/**
 * Holds any security exceptions that may occur in the StockImpl class.
 * 
 * @author Kevin 
 * @version 1.0
 * @see StockImpl
 *
 */
public class SecurityException extends RuntimeException {

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

    /**
     * Create a default SecurityException instance.
     * 
     */
    public SecurityException() {
        super();
    }

    /**
     * Create a SecurityException instance that accepts a String.
     * 
     * @param e The string description of the exception.
     */
    public SecurityException(String e) {
        super(e);
    }

    /**
     * Create a SecurityException instance and chain an exception.
     * 
     * @param e The exception to wrap
     */
    public SecurityException(Throwable e) {
        super(e);
    }
}

Compiling Our Source File With the -d Option

Open your command line editor:

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

Compile SecurityException.java using the java compiler with the -d option
  javac -d ..\..\classes SecurityException.java

Compiling The StockingException Classgo to top of page Top

The following StockingException class will be be thrown when a user tries to stock goods that have already been stocked by another user.

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


package model;

/**
 * Holds any stocking exceptions that may occur in the StockImpl class.
 * 
 * @author Kevin 
 * @version 1.0
 * @see StockImpl
 *
 */
public class StockingException extends Exception {
    /**
     * A version number for the StockingException class so that serialisation 
     * can occur without worrying about the underlying class changing 
     * between serialisation and deserialisation.
     * 
     */
    private static final long serialVersionUID = 871964L;

    /**
     * Create a default StockingException instance.
     * 
     */
	public StockingException() {
		super();
	}

    /**
     * Create a StockingException instance that accepts a String.
     * 
     * @param e The string description of the exception.
     */
	public StockingException(String e) {
		super(e);
	}

    /**
     * Create a StockingException instance and chain an exception.
     * 
     * @param e The exception to wrap
     */
	public StockingException(Throwable e) {
		super(e);
	}
}

Compiling Our Source File With the -d Option

Open your command line editor:

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

Compile StockingException.java using the java compiler with the -d option
  javac -d ..\..\classes StockingException.java

Compiling The Stock Interfacego to top of page Top

The following Stock interface will be used in other projects and so must be implemented as is. The interface provides the contract for the CRUD operations for the Manufacturer file as well as templates for the search and locking/unlocking mechanisms.

Stocking Goods Limited have also included doc comments for the interface which are written in HTML and can be turned into a document at a later date using the Javadoc tool. Stocking Goods Limited have also requested that we provide doc comments for all class, field, constructor and method declarations.

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


package model;

/**
 * An interface implemented by classes that provide use of the Manufacturer file.
 * 
 */
public interface Stock {
	
	/**
	 * Creates a new record in the Manufacturer file which could be a deleted
	 * entry. Inserts the given Manufacturer data, and returns the record
	 * number of the new record.
	 * 
	 * @param manufacturerData An array of Manufacturer data fields.
	 * 
	 * @return The record number of the Manufacturer record created.
	 * @throws DuplicateKeyException Record already exists.
	 */
	public long createRecord(String[] manufacturerData) throws DuplicateKeyException;
	
	/**
	 * Reads a record from the Manufacturer file and returns a String
	 * array where each element is a Manufacturer field value.
	 * 
	 * @param recNo A record number denoting the byte location in the 
	 * Stock file where this Manufacturer record starts.
	 * 
	 * @return A String array containing the fields of the 
	 * Manufacturer record.
	 * 
	 * @throws RecordNotFoundException Indicates the record was not found.
	 */
	public String[] readRecord(long recNo) throws RecordNotFoundException;
	
	/**
	 * Modifies the fields of a Manufacturer record. The new value for field n 
	 * appears in manufacturerData[n]. Throws a SecurityException if the record 
	 * is locked with a number other than lockRecord.
	 * 
	 * @param recNo The record number of the product record to read.
	 * @param manufacturerData A String array of Manufacturer fields.
	 * @param lockRecord The lock value.
	 * 
	 * @throws RecordNotFoundException Indicates the record was not found.
	 * @throws SecurityException Indicates a lock record mismatch.
	 */
	public void updateRecord(long recNo, String[] manufacturerData, long lockRecord)
			throws RecordNotFoundException, SecurityException;
	
	/**
	 * Deletes a record, making the record number and associated disk storage
	 * available for reuse. Throws SecurityException if the record is locked
	 * with a number other than lockRecord.
	 * 
	 * @param recNo The record number of the product record to read.
	 * @param lockRecord The lock value.
	 * 
	 * @throws RecordNotFoundException Indicates the record was not found.
	 * @throws SecurityException Indicates a lock record mismatch.
	 */
	public void deleteRecord(long recNo, long lockRecord)
			throws RecordNotFoundException, SecurityException;

	/** 
	 * Returns an array of record numbers that match the specified search. Field n 
	 * in the Manufacturer file is described by searchCriteria[n]. A null value in 
	 * searchCriteria[n] matches any field value. A non-null value in 
	 * searchCriteria[n] matches any field value that begins with searchCriteria[n]. 
	 *
	 * @param searchCriteria The search criteria for retrieving records.
	 * 
	 * @return A long array of manufacturer record numbers
	 * matching the search criteria.
	 */
	public long[] findBySearchCriteria(String[] searchCriteria);

	/**
	 * Locks a record so that it can only be updated or deleted by this client.
	 * Returned value is a number that must be used when the record is unlocked,
	 * updated, or deleted. If the specified record is already locked by a
	 * different client, the current thread goes into a wait state until the 
	 * record is unlocked.
	 * 
	 * @param recNo The record number of the Manufacturer record to lock.
	 * 
	 * @return The record number of the locked Manufacturer record.
	 * @throws RecordNotFoundException Indicates the record was not found.
	 */
	public long lockRecord(long recNo) throws RecordNotFoundException;

	/** Releases the lock on a record. Lock number must be the same as the number
	 * returned when the record was locked; otherwise throws SecurityException.
	 * 
	 * @param recNo The record number of the Manufacturer record to unlock.
	 * 
	 * @return A long value denoting the generated lock number.
	 * 
	 * @throws SecurityException Indicates a lock record mismatch.
	 */
	public void unlock(long recNo, long lockRecord) throws SecurityException;
}

Compiling Our Source File With the -d Option

Open your command line editor:

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

Compile Stock.java using the java compiler with the -d option
  javac -d ..\..\classes Stock.java

compile Stock interface

As you can see from the screenshot we get a compiler error because the compiler can't find the exception classes we just compiled. This is because we are now putting our compiled classes into separate folders from our source using the -d option. To find them we need to use another compiler option to point to the directories the class files reside in.

Compiling Our Source File With the -classpath Option

When compiling our source files we have the option of pointing the compiler to directories where other compiled classes are, that we need to cleanly compile the class in question. There are two options available to do this, these being -classpath and the abbreviated version -cp. In this example we will use -classpath and for future compiles we will use -cp.

Open your command line editor:

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

Compile Stock.java using the java compiler with the -classpath and -d options
  javac -classpath ..\..\classes -d ..\..\classes Stock.java

The following screenshot shows that we now get a clean compile and also the classes now compiled into the classes\model directory.

compile Stock interface 2

Manufacturer OO Requestgo to top of page Top

The stakeholder Stocking Goods Limited have also requested that we create a class representing the record information within the Manufacturer file called unsurprisingly Manufacturer as they see a lot of future reuse of this file and it will also be helpful when implementing the Stock interface. In this lesson we create the Manufacturerclass, wherein a Manufacturer object is a representation of the Manufacturer file in an object oriented framework, allowing easy access to fields.

We will be creating a log file using the java.util.Logger class which allows us to put messages into a file that we can view after a run of the application to help with debugging and to store information about events that have occurred and methods that have been invoked. We can also set the granularity of log messages when we need finer or less detailed information to appear in our log files. We will create log files in most of our other top level classes so that we can see the general flow through our coding if problems arise.

Access to the methods of the Manufacturer class can be invoked remotely and so this object can be serialized, therefore we use a version number so that serialisation can occur without worrying about the underlying class changing between serialisation and deserialisation.

We will use statics for the length of each field within the Manufacturer file and private instance variables that can be accessed by public getters and setters.

A default no arguments constructor will be coded as well as a constructor to set initial values for a Manufacturer object.

We will also override the equals(), hashCode() and toString() methods of the Object class.

Another point of interest not covered elsewhere on the site are the annotation used for the Logger, @SuppressWarnings("unused") which supresses compiler warnings for a field not being used and the Object class overrides using @Override which ensures we override correctly and informs browsers of the code that this is an override. Annotations were introduced in Java5 and are a form of metadata that provide data about a program but are not part of the program itself and have no direct effect on the operation of the code annotated.

Compiling The Manufacturer Classgo to top of page Top

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


package model;

import java.util.logging.Logger;

/**
 * A Manufacturer object is a representation of the Manufacturer file
 * in an object oriented framework, allowing easy access to fields.
 * 
 * @author Kevin 
 * @version 1.0
 * @see model.StockImpl
 *
 */
public class Manufacturer {
    /**
     * The Logger instance through which all log messages from this class are routed.
     * Logger namespace is s2cCaseStudy.
     */
    private static Logger log = Logger.getLogger("s2cCaseStudy"); // Log output

    /**
     * A version number for the Manufacturer class so that serialisation 
     * can occur without worrying about the underlying class changing 
     * between serialisation and deserialisation.
     */
    @SuppressWarnings("unused")
	private static final long serialVersionUID = 871964L;

    /* Declared constants for lengths of each field in the Manufacturer record. */
    /**
     * Length of Manufacturer deletedFlag field.
     */
    static final int DELETED_FLAG_LENGTH = 1;

    /**
     * Length of Manufacturer name field.
     */
    static final int NAME_LENGTH = 30;

    /**
     * Length of Manufacturer location field.
     */
    static final int LOCATION_LENGTH = 30;

    /**
     * Length of Manufacturer Product field.
     */
    static final int PRODUCT_LENGTH = 40;

    /**
     * Length of Manufacturer price field.
     */
    static final int PRICE_LENGTH = 8;

    /**
     * Length of Manufacturer stockLevel field.
     */
    static final int STOCK_LEVEL_LENGTH = 3;

    /**
     * Length of Manufacturer stockOrdered field.
     */
    static final int STOCK_ORDERED_LENGTH = 3;
    
    /**
     * Length of a complete Manufacturer data record, calculated 
     * by adding all the fields together. 
     */
    public static final int MANUFACTURER_RECORD_LENGTH = DELETED_FLAG_LENGTH
                               	                       + NAME_LENGTH
                               	                       + LOCATION_LENGTH
                               	                       + PRODUCT_LENGTH
                              	                       + PRICE_LENGTH
                               	                       + STOCK_LEVEL_LENGTH
                              	                       + STOCK_ORDERED_LENGTH;
    
    /* Instance variables. */
    /**
     * Stores the Deleted flag.
     */
    private String deletedFlag = ""; 	// The deleted flag indicator.

    /**
     * Stores the name of the Manufacturer 
     */
    private String name = ""; 			// Manufacturer Name.

    /**
     * Stores the City Location of the Manufacturer.
     */
    private String location = ""; 		// Manufacturer Location.

    /**
     * Stores the product name.
     */
    private String product = "";	    // Product name.

    /**
     * Stores the unit price of the product.
     */
    private String price = ""; 			// Product unit price.

    /**
     * Stores the amount of stock available.
     */
    private String stockLevel = ""; 	// Product available from manufacturer.

    /**
     * Stores the amount of stock ordered.
     */
    private String stockOrdered = ""; 	// Amount of product ordered.

    /* Manufacturer constructors. */
    /**
     * Create an instance of Constructor with default values.
     */
    public Manufacturer() {
        log.entering("Manufacturer", "Manufacturer");
        log.exiting("Manufacturer", "Manufacturer");
    }

    /**
     * Create an instance of Constructor with a list
     * of initial values.
     * 
     * @param deletedFlag The Deleted flag value.
     * @param name The name of the Manufacturer.
     * @param location The City Location of the Manufacturer.
     * @param product Product name.
     * @param price Product unit price.
     * @param stockLevel Amount of product available from manufacturer.
     * @param stockOrdered Amount of product ordered.
     */
     public Manufacturer(String deletedFlag, String name, String location, 
			String product, String price, String stockLevel, String stockOrdered) {
        log.entering("Manufacturer", "Manufacturer", new Object[]{name, location,
	    		product, price, stockLevel, stockOrdered});
        this.deletedFlag = deletedFlag;
        this.name = name;
        this.location = location;
        this.product = product;
        this.price = price;
        this.stockLevel = stockLevel;
        this.stockOrdered = stockOrdered;
        log.exiting("Manufacturer", "Manufacturer");
    }

    /* Manufacturer getter and setter methods. */
    /**
     * Returns the value of the Deleted Flag.
     * 
	 * @returns A String containing the deleted flag value.
     */
    public String getDeletedFlag() {
        log.entering("Manufacturer", "getDeletedFlag");
        log.exiting("Manufacturer", "getDeletedFlag", this.deletedFlag);
        return deletedFlag;
    }

    /**
     * Sets the deleted flag value
     * 
     * @param deletedFlag A String containing the 
     * deleted flag value.
     */
    public void setDeletedFlag(String deletedFlag) {
        log.entering("Manufacturer", "setDeletedFlag");
        this.deletedFlag = deletedFlag;
        log.exiting("Manufacturer", "setDeletedFlag", this.deletedFlag);
    }

    /**
     * Returns the value of the manufacturer name.
     * 
     * @return A String containing the name of the manufacturer.
     */
    public String getName() {
        log.entering("Manufacturer", "getName");
        log.exiting("Manufacturer", "getName", this.name);
        return name;
    }

    /**
     * Sets the manufacturer name value
     * 
     * @param name A String containing the name of the manufacturer.
     */
    public void setName(String name) {
        log.entering("Manufacturer", "setName");
        this.name = name;
        log.exiting("Manufacturer", "setName", this.name);
    }

    /**
     * Returns the value of the manufacturer location.
     * 
     * @return A String containing  the manufacturer location.
     */
    public String getLocation() {
        log.entering("Manufacturer", "getLocation");
        log.exiting("Manufacturer", "getLocation", this.location);
        return location;
    }

    /**
     * Sets the manufacturer location value.
     * 
     * @param location A String containing  the manufacturer location.
     */
    public void setLocation(String location) {
        log.entering("Manufacturer", "setLocation");
        this.location = location;
        log.exiting("Manufacturer", "setLocation", this.location);
    }

    /**
     * Returns the name of the product. 
     *  
     * @return product A String of name of the product.
     */
    public String getProduct() {
        log.entering("Manufacturer", "getProduct");
        log.exiting("Manufacturer", "getProduct", this.product);
        return product;
    }

    /**
     * Sets the types of work performed value.  
     * 	  
     * @param product A single String of name of the product.
     */
    public void setProduct(String product) {
        log.entering("Manufacturer", "setProduct");
        this.product = product;
        log.exiting("Manufacturer", "setProduct", this.product);
    }
	  
    /**
     * Returns the unit price of a product. 
     * 
     * @return price A String containing price of a product. 
     */
    public String getPrice() {
        log.entering("Manufacturer", "getPrice");
        log.exiting("Manufacturer", "getPrice", this.price);
        return price;
    }

    /**
     * Sets the unit price of a product.
     * 
     * @param price A String containing price of a product.
     */
    public void setPrice(String price) {
        log.entering("Manufacturer", "setPrice");
        this.price = price;
        log.exiting("Manufacturer", "setPrice", this.price);
    }

    /**
     * Returns the stock level of a product.
     * 
     * @return stockLevel A String containing product stock level.
     */
    public String getStockLevel() {
        log.entering("Manufacturer", "getStockLevel");
        log.exiting("Manufacturer", "getStockLevel", this.stockLevel);
        return stockLevel;
    }

    /**
     * Sets the stock level of a product.
     * 
     * @param stockLevel A String containing stock level.
     */
    public void setStockLevel(String stockLevel) {
        log.entering("Manufacturer", "setStockLevel");
        this.stockLevel = stockLevel;
        log.exiting("Manufacturer", "setStockLevel", this.stockLevel);
    }

    /**
     * Returns the amount of stock on order.
     * 
     * @return stockOrdered A String containing stock on order.
     */
    public String getStockOrdered() {
        log.entering("Manufacturer", "getStockOrdered");
        log.exiting("Manufacturer", "getStockOrdered", this.stockOrdered);
        return stockOrdered;
    }

    /**
	 * Sets the amount of stock on order.
     * 
     * @param stockOrdered A String containing stock on order.
    */
    public void setStockOrdered(String stockOrdered) {
        log.entering("Manufacturer", "setStockOrdered");
        this.stockOrdered = stockOrdered;
        log.exiting("Manufacturer", "setStockOrdered", this.stockOrdered);
    }

    /* override Object methods equals(), hashCode() and toString(). */
    /**
     * Checks whether two Manufacturer objects are the same by
     * comparing their name and manufacturer fields.
     * 
     * @param obj The Manufacturer to compare with this Manufacturer
     * @return true If this Manufacturer and other manufacturer have 
     * same name and location. 
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
		
        if (!(obj instanceof Manufacturer))
            return false;

        Manufacturer other = (Manufacturer) obj;

        if (location == null) {
            if (other.location != null)
                return false;
        } else if (!location.equals(other.location))
            return false;
		
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
		
        return true;
    }

    /**
     * Returns a hashCode for this Manufacturer object using name and location
     * fields. Although this may not return unique hashCodes it should ensure
     * the objects are spread fairly evenly across the buckets.
     *
     * @return hashKey An int hashCode for this Manufacturer instance.
     */
    @Override
    public int hashCode() {
        log.entering("Manufacturer", "hashCode");

        String hashKey = name + location;
        log.exiting("Manufacturer", "hashCode", hashKey.hashCode());
        return hashKey.hashCode();
    }

    /**
     * A String representation of the Manufacturer class.
     *
     * @return A String representation of the Manufacturer class.
     */
    @Override
    public String toString() {
        log.entering("Manufacturer", "toString");
        String strVal = "["
                      	+ this.deletedFlag + "; "
                      	+ this.name + "; "
                      	+ this.location + "; "
                      	+ this.product + "; "
                      	+ this.price + "; "
                      	+ this.stockLevel + "; "
                      	+ this.stockOrdered
                      	+ "]";

        log.exiting("Manufacturer", "toString", strVal);
        return strVal;
    }
}

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

Open your command line editor:

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

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

Compiling The ManufacturerFileAccessException Classgo to top of page Top

Reading files is risky and so we wrap file access calls in try.. catch blocks to catch any I/O exceptions that may occur. For the case study whenever we access the Manufacturer file we have to code for a possible exception. So we will need to make a new exception to cater for I/O exceptions when trying to access the Manufacturer file, which we will call ManufacturerFileAccessException.

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


package model;

/**
 * Holds any data access exceptions that may occur in the Manufacturer file.
 * 
 * @author Kevin 
 * @version 1.0
 * @see StockImpl
 *
 */
public class ManufacturerFileAccessException extends RuntimeException {

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

    /**
     * Create a default ManufacturerFileAccessException instance.
     * 
     */
    public ManufacturerFileAccessException() {
        super();
    }

    /**
     * Create a ManufacturerFileAccessException instance that accepts a String.
     * 
     * @param e The string description of the exception.
     */
    public ManufacturerFileAccessException(String e) {
        super(e);
    }

    /**
     * Create a ManufacturerFileAccessException instance and chain an exception.
     * 
     * @param e The exception to wrap
     */
    public ManufacturerFileAccessException(Throwable e) {
        super(e);
    }
}

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

Open your command line editor:

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

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

The following screenshot shows that we now get a clean compile and also the Manufacturer and ManufacturerFileAccessException classes are now compiled into the classes\model directory.

compile Manufacturer class

Implementing The Stock Interfacego to top of page Top

The final thing to do in this lesson is to implement the Stock interface using the StockImpl class and create some method stubs we will come back to, when we revisit the model code in the Model Part 2 section.

Before we code up the StockImpl class we can make a few design decisions with regards to use of the class and reading the Manufacturer file. A StockImpl object provides access to all the Manufacturer information and the methods required to modify this information are synchronised in this class. We will use the Singleton pattern so that only one StockImpl object will ever exist and as such all access to the Manufacturer file will be via the StockImpl singleton.

The majority of requests for Manufacturer information will be via searches from users of the GUI to browse records via name or location before doing any stock adjustments required. This will be slow due to the I/O involved accessing the Manufacturer file and so we will create a Map collection of Manufacturer records on startup that we can use as a cache to improve performance. This will cut down on file I/O and from then on we will only need to access the Manufacturer file when we need to physically CRUD a record. We will use statics for locking of the Map collection when required.

We will be creating a log file with a namespace of s2cCaseStudy using the java.util.Logger class.

Access to the methods of the StockImpl class can be invoked remotely and so this object can be serialized, therefore we use a version number so that serialisation can occur without worrying about the underlying class changing between serialization and deserialization.

The StockImpl constructor takes the path for the Manufacturer file as a parameter, will use a private access modifier so users can only access the constructor via a public getter. A point of interest here is that we will create the StockImpl Singleton first time through using double-checked locking to ensure the private constructor is only synchronized once. This is achieved using checks before and after entering the synchronization block as well as using the volatile modifier, which is a special mechanism to guarantee that communication happens between threads.

The methods of the Stock interface are implemented and any locking or synchronization blocks are kept as small as possible to aid efficiency.

The other completed methods of the StockImpl class are used to manage field movement from/to the Manufacturer file.

There are also two inner classes coded where appropriate to aid in reading and writing fields from/to the Manufacturer file.

The uncompleted methods at the end of the class are the stubs we will complete in the Model Part 2 section.

Compiling The StockImpl Classgo to top of page Top

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


package model;

import java.io.*;
import java.util.*;
import java.util.concurrent.locks.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A StockImpl object provides access to all the Manufacturer information
 * and the methods required to modify this information are synchronised 
 * in this class. The class uses the Singleton pattern so that only one
 * StockImpl object will ever exist. This is the model element of the MVC paradigm.
 * 
 * @author Kevin 
 * @version 1.0
 *
 */
public class StockImpl implements Stock {
    /**
     * The Logger instance through which all log messages from this class are
     * routed. Logger namespace is s2cCaseStudy.
     */
    private static Logger log = Logger.getLogger("s2cCaseStudy"); // Log output

    /**
     * A version number for the StockImpl class so that serialisation 
     * can occur without worrying about the underlying class changing 
     * between serialisation and deserialisation.
     */
    @SuppressWarnings("unused")
    private static final long serialVersionUID = 871964L;

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

    /**
     * Starting position of Manufacturer file.
     */
    private static int initialOffset = 0;

    /**
     * The pathname where the Manufacturer file is stored.
     */
    private static String storedManufacturerPathName = null;

    /**
     * A String for use when writing a Manufacturer record.
     */
    private static String emptyManufacturerRecordString = null;

    /**
     * Builds the emptyManufacturerRecordString.
     */
    static {
    	emptyManufacturerRecordString = 
                new String(new byte[Manufacturer.MANUFACTURER_RECORD_LENGTH]);
    }
    
    /**
     * The physical file on disk containing our Manufacturer records.
     */
    private RandomAccessFile manufacturerFile = null;

    /**
     * A map containing our manufacturer record number via file position and
     * a lock number value derived from the system time.
     */
    private static Map<Long, Long> manufacturerRecordNumbers
            = new HashMap<Long, Long>();

    /**
     * Ensures many users can read the manufacturerRecordNumbers
     * collection as long as nobody is updating it.
     */
    private static final Lock manufacturerRecordNumbersLock = new ReentrantLock();

    /**
     * Ensures that many users can read the manufacturerRecordNumbers
     * collection as long as nobody is updating it.
     */
    private static final Condition lockReleased = manufacturerRecordNumbersLock.newCondition();

    /**
     * A cached Map of manufacturer records with a record number
     * key to reduce I/O.
     */
    private static Map<Long, Manufacturer> manufacturerCache = new HashMap<Long, Manufacturer>();

    /**
     * Ensures that many users can read the manufacturerCache
     * collection as long as nobody is updating it.
     */
    private static ReadWriteLock manufacturerCacheLock = new ReentrantReadWriteLock();

    /**
     * StockImpl Constructor that takes the path for the Manufacturer file as a parameter.
     *
     * @param filePathName the path to the manufacturer file directory
     * 
     * @throws ManufacturerFileAccessException Indicates there is a problem 
     *         accessing the Manufacturer's data.
     */
    private StockImpl(String filePathName) throws ManufacturerFileAccessException {
        log.entering("StockImpl", "StockImpl", filePathName);
        if (manufacturerFile == null) {
            try {
            	manufacturerFile = new RandomAccessFile(filePathName, "rw");
                // Process Manufacturer file header information
                processManufacturerHeader();
                // Populate a cached map of manufacturers
                getManufacturersMap(); 
            	storedManufacturerPathName = filePathName;
                log.fine("Manufacturer file opened and file position map populated");
            }
            catch (IOException e) {
                log.log(Level.SEVERE, e.getMessage(), e);
            	throw new ManufacturerFileAccessException(e);
            }
        } else if (storedManufacturerPathName != filePathName) {
            log.warning("Only one manufacturer file location can be specified. "
                        + "Current pathname: " + storedManufacturerPathName + " "
                        + "Ignoring specified pathname: " + filePathName);
        }
        log.exiting("StockImpl", "StockImpl");
    }

    /**
     * Clients of the StockImpl class using a pathname for the Manufacturer file
     * have to call this method to get the unique instance (singleton)
     * for this class as the constructors are private.
     * 
     * @param filePathName the path to the dvd_db directory
     * @param runMode 
     * 
     * @return The singleton for StockImpl class.
     */
    public static StockImpl getStockImplInstance(String filePathName) {
        if (uniqueStockImplInstance == null) {
            synchronized (StockImpl.class) {
                if (uniqueStockImplInstance == null) { 
                    uniqueStockImplInstance = new StockImpl(filePathName);
                }
            }
        }
        return uniqueStockImplInstance;
    }

    /**
     * This method processes Manufacturer header information
     *
     */
    private void processManufacturerHeader() throws ManufacturerFileAccessException {
    	try {
            log.entering("manufacturerData", "processManufacturerHeader");
            int fileNumber = manufacturerFile.readInt();
            log.fine("Manufacturer File number is: " + fileNumber);
            // Add header bytes to static int so we can ascertain
            // starting position of first Manufacturer data record
            initialOffset += 4;
        } catch (IOException e) {
            log.log(Level.SEVERE, e.getMessage(), e);
            throw new ManufacturerFileAccessException(e);
        }
        log.exiting("manufacturerData", "processManufacturerHeader");
    }

    /**
     * Creates a new record in the Manufacturer file which could be a deleted
     * entry. Inserts the given Manufacturer data, and returns the record
     * number of the new record.
     *
     * @param manufacturerData A string array of Manufacturer 
     * data for the Manufacturer to be created.
     * 
     * @return A long value denoting the generated record number.
     * @throws DuplicateKeyException Indicates there is an existing record with 
     * same name/location values that doesn't have deletion flag set to deleted.
     * @throws ManufacturerFileAccessException Indicates there is a problem 
     * accessing the Manufacturer's data.
     */
    public long createRecord(String[] manufacturerData) 
             throws DuplicateKeyException, ManufacturerFileAccessException {
        log.entering("StockImpl", "createRecord", manufacturerData);
    	log.fine("createRecord Run by Thread: " 
                + Thread.currentThread().getName());
        // Populate Manufacturer from the passed string array.
        Manufacturer manufacturer = populateManufacturerFields(manufacturerData);
        Long positionInFile = 0L;
        boolean reusingDeletedRecord = false;
        
        try {
            positionInFile = manufacturerFile.length();
        } 
        catch (IOException e) {
            log.log(Level.SEVERE, e.getMessage(), e);
   	     	throw new ManufacturerFileAccessException(e);
        }
        /*
         * Check name and location field values on the cached map to see if a 
         * record already exists with same  values. If it does and deletion flag 
         * set to deleted, reuse this record, otherwise throw a DuplicateKeyException.
         */
        manufacturerCacheLock.readLock().lock();
        Set<Long> recOffsetSet = new TreeSet<Long>(manufacturerCache.keySet());
        for (Long s : recOffsetSet) {
            Manufacturer manufacturerCheck = manufacturerCache.get(s);
            if (manufacturerData[1].equalsIgnoreCase(manufacturerCheck.getName()) 
                && manufacturerData[2].equalsIgnoreCase(manufacturerCheck.getLocation())) {
                // name and location match, see if deletion flag set to deleted
                if (manufacturerCheck.getDeletedFlag().equals("1")) {
                    manufacturer.setDeletedFlag("0");
                    positionInFile = s;
                    reusingDeletedRecord = true;
                    log.log(Level.INFO, "Reusing record number: " + s 
                            + "Manufacturer: " + manufacturer.getName() 
                            + " , " + manufacturer.getLocation() 
                            + " and resetting deletion flag to 0");
                    break;
                } else {
                    String dke = "Duplicate record found: " + s + 
                            " Update cancelled."; 
				 log.log(Level.SEVERE, dke);
                    throw new DuplicateKeyException(dke);
                }
            }
        }
        manufacturerCacheLock.readLock().unlock();
        manufacturerCacheLock.writeLock().lock();
        if (!reusingDeletedRecord) {
            //Check for the case when another record has been added
            while (manufacturerCache.containsValue(positionInFile)) {
                positionInFile += Manufacturer.MANUFACTURER_RECORD_LENGTH;
            }
        }
       	manufacturerCache.put(positionInFile, manufacturer);
        manufacturerCacheLock.writeLock().unlock();
        log.info("Creating record number: " +  positionInFile 
           		+ ". Manufacturer information = :" + manufacturer);

        StringBuilder manufacturerString = manufacturerToString(manufacturer);

        // Block other users for minimum time possible. 
        synchronized (manufacturerFile) {
            try {
       	     	manufacturerFile.seek(positionInFile);
       	     	manufacturerFile.write(manufacturerString.toString().getBytes());
            }
            catch (IOException e) {
                log.log(Level.SEVERE, e.getMessage(), e);
       	     	throw new ManufacturerFileAccessException(e);
            }
        }
        log.exiting("StockImpl", "createRecord", positionInFile);
        return positionInFile;
    }

    /**
     * This method reads a record from the Manufacturer file and returns a 
     * String array where each element is a Manufacturer field value.
     *
     * @param recOffset The offset into the file where the record starts.
     * 
	 * @return A String array containing the fields of the 
	 * Manufacturer record.
     * 
     * @throws RecordNotFoundException Deletion flag has been set to deleted.
     * @throws ManufacturerFileAccessException Indicates there is a problem 
     *         accessing the Manufacturer's data.
     */
    public String[] readRecord(long recOffset) 
	                throws RecordNotFoundException, ManufacturerFileAccessException {
        log.entering("StockImpl", "readRecord", new Object[]{recOffset});
    	log.fine("readRecord Run by Thread: " 
    			+ Thread.currentThread().getName()
    			+ " for record number: " + recOffset);
        final byte[] manufacturerInput 
        		= new byte[Manufacturer.MANUFACTURER_RECORD_LENGTH];
        // Block other users for minimum time possible. 
        synchronized (manufacturerFile) {
            try {
        	    manufacturerFile.seek(recOffset);
        	    manufacturerFile.readFully(manufacturerInput);
            }
            catch (IOException e) {
                log.log(Level.SEVERE, e.getMessage(), e);
            	throw new ManufacturerFileAccessException(e);
            }
        }
        // Convert bytes into a Manufacturer string array.
        /**
         * Class to assist in converting from one big byte[] into multiple
         * String[] - one String per field. 
         */
        class ManufacturerFieldReader {
            /** field to track the position within the byte array */
            private int fieldOffset = 0;
            /**
             * Converts the required number of bytes into a String.
             *
             * @param length, the length to be converted from current offset.
             * @return the converted String
             * @throws UnsupportedEncodingException if "UTF-8" not known.
             */
            String read(int length) throws UnsupportedEncodingException {
                String str = new String(manufacturerInput, fieldOffset, 
                		length, "US-ASCII");
                fieldOffset += length;
                return str.trim();
            }
        }
        ManufacturerFieldReader readManufacturerRecord = new ManufacturerFieldReader();
      	String returnManufacturer[] = new String[7];
        try {
            returnManufacturer[0] 
                    = readManufacturerRecord.read(Manufacturer.DELETED_FLAG_LENGTH);
            returnManufacturer[1] 
                    = readManufacturerRecord.read(Manufacturer.NAME_LENGTH);
            returnManufacturer[2] 
                    = readManufacturerRecord.read(Manufacturer.LOCATION_LENGTH);
            returnManufacturer[3] 
                    = readManufacturerRecord.read(Manufacturer.PRODUCT_LENGTH);
            returnManufacturer[4] 
                    = readManufacturerRecord.read(Manufacturer.PRICE_LENGTH);
            returnManufacturer[5] 
                    = readManufacturerRecord.read(Manufacturer.STOCK_LEVEL_LENGTH);
            returnManufacturer[6] 
                    = readManufacturerRecord.read(Manufacturer.STOCK_ORDERED_LENGTH);
	    }
	    catch (UnsupportedEncodingException e){
            log.log(Level.SEVERE, e.getMessage(), e);
   	     	throw new ManufacturerFileAccessException(e);
	    }
        log.exiting("StockImpl", "readRecord", returnManufacturer.toString());
        return returnManufacturer;
    }

    /**
     * Modifies the fields of a Manufacturer record. The new value for field n 
     * appears in manufacturerData[n]. Throws a SecurityException if the record 
     * is locked with a number other than lockRecord.
     *
     * @param recOffset long value denoting the generated record number.
     * @param manufacturerData A String array of Manufacturer fields.
     * @param lockRecord long value denoting the generated lock number.
     * 
     * @throws RecordNotFoundException Deletion flag has been set to deleted.
     * @throws SecurityException Indicates there is a mismatch between
     * the passed record number and the record number stored in the locking map.
     */
    public void updateRecord(long recOffset, String[] manufacturerData, long lockRecord)
            throws RecordNotFoundException, SecurityException {
        log.entering("StockImpl", "updateRecord", new Object[]{recOffset, manufacturerData});
        log.fine("updateRecord Run by Thread: " 
                + Thread.currentThread().getName()
                + " Lock number generated: " + lockRecord
                + " for record number: " + recOffset);

        Long lock = 0L;

        // Make sure record is still in locking map.
        if (!manufacturerRecordNumbers.containsKey(recOffset)) {
            String e = "Record number: " + recOffset 
			         + " not found in locking map."; 
            log.log(Level.SEVERE, e);
	            throw new RecordNotFoundException(e); 
        } else {
            lock = manufacturerRecordNumbers.get(recOffset);
        }
        //Ensure lock number supplied matches locking map number
    	if (lock == lockRecord){
            //Lock on cache map next
            manufacturerCacheLock.writeLock().lock();
            // Populate Manufacturer from the passed string array.
            Manufacturer manufacturer = populateManufacturerFields(manufacturerData);
            try {
                //Check whether record already has deletion flag set
                if (manufacturer.getDeletedFlag().equals("1")) {
                    String rnf = "Record number: " + recOffset 
                               + " has deletion flag set"; 
                    log.log(Level.SEVERE, rnf);
                    throw new RecordNotFoundException(rnf); 
                } else {
                    StringBuilder manufacturerString 
                            = manufacturerToString(manufacturer);
                    //Everything fine so physically update Manufacturer file
                    try {
                        synchronized (manufacturerFile) {
                            manufacturerFile.seek(recOffset);
                            manufacturerFile.write(
                            manufacturerString.toString().getBytes());
                        }
                    } catch (IOException e) {
                        log.log(Level.SEVERE, e.getMessage(), e);
                        throw new ManufacturerFileAccessException(e);
                    }
                }    
            } finally {
                manufacturerCache.put(recOffset, manufacturer);
                manufacturerCacheLock.writeLock().unlock();
            }
    	} else {
            String se = "Attempt to update failed due to lock number mismatch. " 
                    + " Thread " + Thread.currentThread().getName() 
                    + " Supplied lock number = :"  + lockRecord 
                    + ". Lock number value on locking map = :" + lock;
            log.log(Level.SEVERE, se);
            throw new SecurityException(se);
    	}
    	log.exiting("StockImpl", "updateRecord");
    }

    /**
     * Deletes a record, making the record number and associated disk storage
     * available for reuse. Throws SecurityException if the record is locked
     * with a number other than lockRecord.
     *
     * @param recOffset the offset into the file where the record starts.
     * @param lockRecord A lock number to be tested against when updating.
     * 
     * @throws RecordNotFoundException Deletion flag has been set to deleted.
     * @throws SecurityException Indicates there is a mismatch between the
     * passed record number and the record number stored in the locking map.
     */
    public void deleteRecord(long recOffset, long lockRecord)
            throws RecordNotFoundException, SecurityException {
        log.entering("StockImpl", "deleteRecord", new Object[]{recOffset, lockRecord});
    	log.fine("deleteRecord Run by Thread: " 
                + Thread.currentThread().getName()
                + " Lock number generated: " + lockRecord
                + " for record number: " + recOffset);
    	Long lock = 0L;

    	// Make sure record is still in locking map.
    	if (!manufacturerRecordNumbers.containsKey(recOffset)) {
            String rnf = "Record number: " + recOffset + " not found in locking map."; 
            log.log(Level.SEVERE, rnf);
            throw new RecordNotFoundException(rnf); 
    	} else {
            lock = manufacturerRecordNumbers.get(recOffset);
    	}
    	// Ensure lock number supplied matches locking map lock number
    	if (lock == lockRecord){
            // Lock on cache map next
            manufacturerCacheLock.writeLock().lock();
            Manufacturer manufacturer = manufacturerCache.get(recOffset);
            try {
                // Check whether record already has deletion flag set
                if (manufacturer.getDeletedFlag().equals("1")) {
                    String rnf = "Record number: " + recOffset + 
                            " already has deleted flag set."; 
                    log.log(Level.SEVERE, rnf);
                    throw new RecordNotFoundException(rnf); 
                } else {
                    manufacturer.setDeletedFlag("1");
                    StringBuilder manufacturerString 
                            = manufacturerToString(manufacturer);
                    // Everything fine so physically delete from Manufacturer file
                    try {
                        synchronized (manufacturerFile) {
                            manufacturerFile.seek(recOffset);
                            manufacturerFile.write(
                                    manufacturerString.toString().getBytes());
        				}
                    } catch (IOException e) {
                        log.log(Level.SEVERE, e.getMessage(), e);
                        throw new ManufacturerFileAccessException(e);
                    }
                    log.fine("Record number: " + recOffset + 
                            " deletion flag set to deleted (1)");
                }    
            } finally {
                manufacturerCache.put(recOffset, manufacturer);
                manufacturerCacheLock.writeLock().unlock();
            }
    	} else {
            String se = "Attempt to delete failed due to lock number mismatch. " 
                    + " Thread " + Thread.currentThread().getName() 
                    + " Supplied lock = : "  + lockRecord 
                    + ". Lock number value on locking map = : " + lock;
            log.log(Level.SEVERE, se);
            throw new SecurityException(se);
    	}
        log.exiting("StockImpl", "deleteRecord");
	}
	
    /**
     * This method searches the cached map of manufacturers for entries
     * that match the selection criteria and returns an array of record numbers 
     * that match the specified search. Field n in the Manufacturer file is 
     * described by searchCriteria[n]. A null value in searchCriteria[n] matches 
     * any field value. A non-null value in searchCriteria[n] matches any field 
     * value that begins with searchCriteria[n]. 
     *
     * @param searchCriteria The search criteria for retrieving records.
     * 
     * @return A long array of manufacturer record numbers
     * matching the search criteria.
     */
    public long[] findBySearchCriteria(String[] searchCriteria) {
        log.entering("StockImpl", "findBySearchCriteria", searchCriteria);
    	log.fine("findBySearchCriteria Run by Thread: " 
                + Thread.currentThread().getName());
        boolean searchName = false;
        boolean searchLocation = false;
        // Create an array to hold returned records numbers (500 should be enough)
        long[] recOffsets = new long[500];
        // Passed name for search
        if (searchCriteria[0] != null) {
            searchName = true;
        }
        // Passed location for search
        if (searchCriteria[1] != null) {
            searchLocation = true;
        }
        manufacturerCacheLock.readLock().lock();
        Set<Long> recOffsetSet = new TreeSet<Long>(manufacturerCache.keySet());
        int recOffsetIdx = 0;
        for (Long s : recOffsetSet) {
            Manufacturer manufacturer = manufacturerCache.get(s);
            // Check if record is deleted
            if (manufacturer.getDeletedFlag().equals("1")) {
                log.log(Level.FINER, "Manufacturer: " + manufacturer.getName() 
                        + " , " + manufacturer.getLocation() + " , "
                        + " has deletion flag set");
            } else if (searchName && searchLocation) {
                // Return an array searched on name and location
                if (manufacturer.getName().startsWith(searchCriteria[0]) 
                    && manufacturer.getLocation().startsWith(searchCriteria[1])) {
                        recOffsets[recOffsetIdx] = s;
                        recOffsetIdx += 1;
                }
            } else if (searchName) {
                // Return  an array searched on name
                if (manufacturer.getName().startsWith(searchCriteria[0])) {
                    recOffsets[recOffsetIdx] = s;
                    recOffsetIdx += 1;
                }
            } else if (searchLocation) {
                // Return  an array searched on location
                if (manufacturer.getLocation().startsWith(searchCriteria[1])) { 
                    recOffsets[recOffsetIdx] = s;
                    recOffsetIdx += 1;
                }
            } else {
                // Return  an array of all name/locations (no search criteria passed)
                recOffsets[recOffsetIdx] = s;
                recOffsetIdx += 1;
            }
        }
        manufacturerCacheLock.readLock().unlock();
        log.exiting("StockImpl", "findBySearchCriteria", recOffsets);
        return recOffsets;
    }

    /**
     * This method will add a record to the locking map. If the key is
     * already in the locking map the thread will wait until the record
     * is removed from map and the lock released.
     * Locks a record in the locking so that it can only be updated or deleted 
     * by this client. Returned value is a number that must be used when the 
     * record is unlocked, updated, or deleted. If the specified record is 
     * already locked by a different client, the current thread goes into a 
     * wait state until the record is unlocked.
     *
     * @param recOffset the offset into the file where the record starts.
     * 
     * @return A long value denoting the generated lock number.
     * 
     * @throws RecordNotFoundException Indicates there is a problem accessing
     * the Manufacturer record in the cahe map.
     */
    public long lockRecord(long recOffset) throws RecordNotFoundException {
        log.entering("StockImpl", "lockRecord", recOffset);
                long lockNumber = System.nanoTime();
        log.fine("lock Run by Thread: " 
                + Thread.currentThread().getName() 
                + " Lock number generated: " + lockNumber
                + " for record number: " + recOffset);
        manufacturerRecordNumbersLock.lock();
        try {
            // Wait for the record to become unlocked.
            while (manufacturerRecordNumbers.containsKey(recOffset)) {
                try {
                    log.fine("Thread: " + Thread.currentThread().getName()
                            + " awaiting for lock to be released"
                            + " for record number: " + recOffset);
                    lockReleased.await();
                } catch (InterruptedException e) {
                    log.log(Level.SEVERE, e.getMessage(), e);
                    throw new ManufacturerFileAccessException(e);
                }
            }
            // Check if manufacturer record exists.
            try {
                manufacturerCacheLock.readLock().lock();

                // Check if the given record is present.
                if (!manufacturerCache.containsKey(recOffset)) {
                    String rnf = "There is no manufacturer record with number: " 
                            + recOffset; 
                    log.log(Level.SEVERE, rnf);
                    throw new RecordNotFoundException(rnf);
                }
            } finally {
                manufacturerCacheLock.readLock().unlock();
            }
            manufacturerRecordNumbers.put(recOffset, lockNumber);
            log.fine("Thread: " + Thread.currentThread().getName() 
                                + " Record number: " + recOffset + " added to locking map");
        } finally {
            manufacturerRecordNumbersLock.unlock();
        }
        log.exiting("StockImpl", "lockRecord", lockNumber);
        return lockNumber;
    }

    /**
     * This method will remove a record from the locking map. If the passed 
     * locking number equals the locking number stored in the locking map then 
     * record is removed from map, lock released and waiting threads signalled.
     *
     * @param recOffset the offset into the file where the record starts.
     * 
     * @throws SecurityException Indicates there is a mismatch between
     * the passed lock number and the lock number stored in the locking map.
     */
    public void unlock(long recOffset, long lockRecord) throws SecurityException {
        log.entering("StockImpl", "unlock", new Object[]{recOffset, lockRecord});
    	log.fine("unlock Run by: " + Thread.currentThread().getName()
                + " Lock lock number supplied: " + lockRecord);
        manufacturerRecordNumbersLock.lock();
        // Compare lock numbers
        try {
            if (manufacturerRecordNumbers.get(recOffset) == lockRecord) {
                manufacturerRecordNumbers.remove(recOffset);
                log.fine("Thread: " + Thread.currentThread().getName()
                        + " Supplied lock number: "  + lockRecord 
                        + " lock released for record number: " + recOffset);
                lockReleased.signal();
            } else {
                String se = " Thread " + Thread.currentThread().getName() 
                        + "Attempt to unlock record number: " + recOffset 
                        + "failed. Supplied lock number: "  + lockRecord 
                        + ". Lock number value on locking map:" 
                        + manufacturerRecordNumbers.get(recOffset); 
                log.log(Level.SEVERE, se);
                throw new SecurityException(se);
            }
        } finally {
            manufacturerRecordNumbersLock.unlock();
        }
        log.exiting("StockImpl", "unlock");
    }

    /**
     * Public method used by other classes to call the private method
     * getManufacturersMap to ensure the Map isn't corrupted. 
     *
     * @throws ManufacturerFileAccessException If there is a problem accessing the manufacturerData.
     */
    public Map<Long, Manufacturer> getManufacturers() throws ManufacturerFileAccessException {
        return getManufacturersMap();
    }

    /**
     * Create a Map collection that we can use as a cache to improve 
     * performance and cut down on file I/O.
     *
     * This method is private so we can call it from the constructor 
     * to initially populate the Map from the Manufacturer file. External 
     * calls to this method are routed through the getManufacturers()
     * method.
     * 
     * @param buildRecordNumbers build internal map of record number to file locations.
     * 
     * @return A Map collection of all Manufacturers.
     * @throws ManufacturerFileAccessException Indicates there is a problem 
     * accessing the Manufacturer's data.
     */
    private Map<Long, Manufacturer> getManufacturersMap() throws ManufacturerFileAccessException {
        log.entering("StockImpl", "getManufacturersMap");
    	long fileLength = 0L;

    	try {
            fileLength = manufacturerFile.length();
    	} 
    	catch (IOException e) {
            log.log(Level.SEVERE, e.getMessage(), e);
            throw new ManufacturerFileAccessException(e);
    	}
        for (long positionInFile = initialOffset;
                positionInFile < fileLength;
                positionInFile += Manufacturer.MANUFACTURER_RECORD_LENGTH) {
            log.fine("Reading record at " + positionInFile);
            String manufacturerArray[] = readRecord(positionInFile);
            log.fine("Found record number: " + positionInFile
                 + ". Manufacturer name is: " + manufacturerArray[1]
                 + ". Manufacturer location is: " + manufacturerArray[2]);
            // Populate Manufacturer from the passed string array.
            Manufacturer manufacturer = populateManufacturerFields(manufacturerArray);
            manufacturerCache.put(positionInFile, manufacturer);
            log.fine("Manufacturer cache map populated. Record number is: " + positionInFile);
        }
        log.exiting("StockImpl", "getManufacturersMap", manufacturerCache);
        return manufacturerCache;
    }

    /**
     * Locates a Manufacturer using the record offset.
     *
     * @param recOffset The record offset of the Manufacturer to locate in the cache map.
     * 
     * @return The Manufacturer object matching the record number.
     * @throws RecordNotFoundException Indicates record not found in cache map.
     */
    public Manufacturer getManufacturer(long recOffset) throws RecordNotFoundException {
        log.entering("StockImpl", "getManufacturer", recOffset);
        // Check if manufacturer record exists.
        try {
            manufacturerCacheLock.readLock().lock();
            // Check if the given record is present.
            if (!manufacturerCache.containsKey(recOffset)) {
                String rnf = "There is no manufacturer record with record " +
                        "offset: " + recOffset; 
                log.log(Level.SEVERE, rnf);
                throw new RecordNotFoundException(rnf);
            } else {
                Manufacturer manufacturer = manufacturerCache.get(recOffset);
                return manufacturer;
            }
        } finally {
            log.exiting("StockImpl", "getManufacturer");
            manufacturerCacheLock.readLock().unlock();
        }
    }

    /**
     * This method will create one long string from manufacturer fields.
     *
     * @param manufacturer The Manufacturer reference to convert to a string
     * @return A String of Manufacturer information
     */
    private StringBuilder manufacturerToString(Manufacturer manufacturer) {
        log.entering("StockImpl", "manufacturerToString", manufacturer);
        final StringBuilder out = new StringBuilder(emptyManufacturerRecordString);

        /** 
         * Add fields to out StringBuilder field 
         * */
        class ManufacturerFieldWriter {
            private int currentPosition = 0;
            /**
             * converts a String of specified length to byte[]
             *
             * @param manufacturerData the String to be converted into part of the byte[].
             * @param length the maximum size of the String
             */
            void write(String manufacturerData, int length) {
                out.replace(currentPosition, currentPosition + manufacturerData.length(), 
                		manufacturerData);
                currentPosition += length;
            }
        }
        ManufacturerFieldWriter writeRecord = new ManufacturerFieldWriter();

        writeRecord.write(manufacturer.getDeletedFlag(), Manufacturer.DELETED_FLAG_LENGTH);
        writeRecord.write(manufacturer.getName(), Manufacturer.NAME_LENGTH);
        writeRecord.write(manufacturer.getLocation(), Manufacturer.LOCATION_LENGTH);
        writeRecord.write(manufacturer.getProduct(), Manufacturer.PRODUCT_LENGTH);
        writeRecord.write(manufacturer.getPrice(), Manufacturer.PRICE_LENGTH);
        writeRecord.write(manufacturer.getStockLevel(), Manufacturer.STOCK_LEVEL_LENGTH);
        writeRecord.write(manufacturer.getStockOrdered(), Manufacturer.STOCK_ORDERED_LENGTH);

        log.exiting("StockImpl", "manufacturerToString", out);
        return out; 
    }

    /**
     * This method populates a Manufacturer object.
     *
     * @param A String array holding Manufacturer information.
     * @return Manufacturer object.
     */
    private Manufacturer populateManufacturerFields(String[] manufacturerArray) {
        log.entering("StockImpl", "populateManufacturerFields", manufacturerArray);
        // Instantiate the Manufacturer object
        Manufacturer manufacturer = new Manufacturer();
        // Populate Manufacturer object
        manufacturer.setDeletedFlag(manufacturerArray[0]);
        manufacturer.setName(manufacturerArray[1]);
        manufacturer.setLocation(manufacturerArray[2]);
        manufacturer.setProduct(manufacturerArray[3]);
        manufacturer.setPrice(manufacturerArray[4]);
        manufacturer.setStockLevel(manufacturerArray[5]);
        manufacturer.setStockOrdered(manufacturerArray[6]);
        log.exiting("StockImpl", "populateManufacturerFields", manufacturer);
        return manufacturer;
    }

    /**
     * Locks the locking map manufacturerRecordNumbersLock so that
     * no other client can modify it. Used when we are shutting down the 
     * Manufacturer file.
     *
     * @param lockingMapLocked locked true on the Manufacturer file.
     */
    public void lockLockingMap (boolean lockingMapLocked) {
        log.entering("StockImpl", "LockLockingMap", lockingMapLocked);

        if (lockingMapLocked) {
        	manufacturerRecordNumbersLock.lock();
        }
        log.exiting("StockImpl", "LockLockingMap");
    }

    /**
     * Stock the requested amount entered by a user in the GUI.
     *
     * @param name The name of the Manufacturer.
     * @param location The location where the Manufacturer is based.
     * @param stockLevel The amount of stock left.
     * @param stockOrdered The amount of stock requested.
     * 
     */
    public void stocking(String name, String location, int stockLevel, int stockOrdered) {
        /*
         * TO DO in Model Part 2
         *
         */
    }

    /**
     * Unstock the requested amount entered by a user in the GUI.
     *
     * @param name The name of the Manufacturer.
     * @param location The location where the Manufacturer is based.
     * @param stockOrdered The amount of unstocking to do.
     * 
     */
    public void unstocking(String name, String location, int stockOrdered) {
        /*
         * TO DO in Model Part 2
         *
         */
    }
		
    /**
     * Search manufacturer file via criteria entered by a user in the GUI.
     *
     * @param name The name of the Manufacturer.
     * @param location The location where the Manufacturer is based.

     * 
     */
    public void search(String name, String location) {
        /*
         * TO DO in Model Part 2
         *
         */
    }
}

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

Open your command line editor:

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

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

The following screenshot shows that we now get a clean compile and also the StockImpl class now compiled into the classes\model directory along with the inner classes ManufacturerFieldReader and ManufacturerFieldWriter.

compile StockImpl class

Related Java6 Tutorials

Beginning Java6 - Primitive Variables
Beginning Java6 - Conditional Statements
Beginning Java6 - Loop Statements
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
OO Concepts - Encapsulation - Getters & Setters
OO Concepts - Inheritance Concepts - Using the super keyword
OO Concepts - Interfaces
OO Concepts - The Object Superclass - Checking Object Equality
OO Concepts - The Object Superclass - The hashCode() Method
OO Concepts - The Object Superclass - The toString() Method
Flow Control - Handling Exceptions
Flow Control - Declaring Exceptions
Flow Control - Creating Our Own Exceptions
API Contents - Inheritance - Using the package keyword
API Contents - Inheritance - Using the import keyword
Concurrency - Synchronization - Synchronized Blocks
Swing - RMI - Serialization

What's Next?

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

go to home page Homepage go to top of page Top