Model Part 1 - Stock
Interface ImplementationS2C Home « Model Part 1 - Stock
Interface implementation
In our final lesson of the Model Part 1 we write an implementation class for the Stock
Interface.
Implementing The Stock
Interface Top
The final thing to do for Model Part 1 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
Class 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 Charlie
* @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 = 2498052502L;
/**
* 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
.
Lesson 6 Complete
In this lesson we coded the StockImpl
class.
Related Java Tutorials
Beginning Java - Primitive Variables
Beginning Java - Conditional Statements
Beginning Java - 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
Exceptions - Handling Exceptions
Exceptions - Declaring Exceptions
Exceptions - 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.