vault backup: 2024-04-12 09:39:31
This commit is contained in:
573
Semester 2/Programming 2/Project/Part 4 Complete/Library.java
Normal file
573
Semester 2/Programming 2/Project/Part 4 Complete/Library.java
Normal file
@@ -0,0 +1,573 @@
|
||||
// Import all required libraries. Not using .* as it is not good practice due to potential conflicts.
|
||||
import java.util.Scanner;
|
||||
import java.util.Random;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Date;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.awt.FileDialog;
|
||||
import java.awt.Frame;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Class to create objects of a Library, which can hold and report on assets held within
|
||||
*
|
||||
* @author George Wilkinson
|
||||
* @version 3.0
|
||||
*/
|
||||
public class Library
|
||||
{
|
||||
// private List<LibraryItem> itemList; // Initialise an ArrayList of name itemList to store Library Items
|
||||
// private List<LibraryUser> userList; // Initialise an ArrayList of name userList to store Library Users
|
||||
|
||||
/* No longer needed, as we can use the keys of customerMap.
|
||||
private HashSet<String> uuidSet; // Initialise a Hash Set of name uuidSet ( unique user identifier ) to store unique user IDs for efficient O(1) searching
|
||||
*/
|
||||
|
||||
private Map<String, LibraryUser> customerMap;
|
||||
private Map<String, LibraryItem> itemsMap;
|
||||
private Map<String, LibraryReservation> libraryReservationMap;
|
||||
|
||||
private Diary diary;
|
||||
/*
|
||||
* Constructor for objects of class Library
|
||||
*/
|
||||
public Library()
|
||||
{
|
||||
// itemList = new ArrayList<LibraryItem>();
|
||||
// userList = new ArrayList<LibraryUser>();
|
||||
|
||||
/* No longer needed, can use keys of customerMap.
|
||||
uuidSet = new HashSet<String>();
|
||||
*/
|
||||
|
||||
customerMap = new HashMap<String, LibraryUser>();
|
||||
itemsMap = new HashMap<String, LibraryItem>();
|
||||
libraryReservationMap = new HashMap<String, LibraryReservation>();
|
||||
diary = new Diary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor Start
|
||||
*
|
||||
* Possible scope creep, but thought it would be useful or even required for top marks to have accessors here.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return the keys of @customerMap as a list.
|
||||
*/
|
||||
public ArrayList getUserIDs()
|
||||
{
|
||||
return new ArrayList<String>( customerMap.keySet() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the keys of @itemsMap as a list.
|
||||
*/
|
||||
public ArrayList getItemCodes()
|
||||
{
|
||||
return new ArrayList<String>( itemsMap.keySet() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the keys of @libraryReservationMap as a list.
|
||||
*/
|
||||
public ArrayList getReservationNos()
|
||||
{
|
||||
return new ArrayList<String>( libraryReservationMap.keySet() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the values of @customerMap as a list.
|
||||
*/
|
||||
public ArrayList getUsers()
|
||||
{
|
||||
return new ArrayList<LibraryUser>( customerMap.values() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the values of @itemsMap as a list.
|
||||
*/
|
||||
public ArrayList getItems()
|
||||
{
|
||||
return new ArrayList<LibraryItem>( itemsMap.values() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the values of @libraryReservationMap as a list.
|
||||
*/
|
||||
public ArrayList getLibraryReservations()
|
||||
{
|
||||
return new ArrayList<LibraryReservation>( libraryReservationMap.values() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor End
|
||||
*/
|
||||
|
||||
/**
|
||||
* Storing Objects Start
|
||||
*/
|
||||
|
||||
/*
|
||||
* Inserts object value item alongside key of @itemCode into @itemsMap.
|
||||
*/
|
||||
private void storeItem( LibraryItem item )
|
||||
{
|
||||
// itemList.add( item );
|
||||
itemsMap.put( item.getItemCode(), item );
|
||||
}
|
||||
|
||||
/*
|
||||
* Inserts object value user alongside key of @userID into @customerMap.
|
||||
* If the userID is set to unknown, it will call @generateUserID.
|
||||
*/
|
||||
private void storeUser( LibraryUser user )
|
||||
{
|
||||
// userList.add( user );
|
||||
if ( user.getUserID().equals( "unknown" ) )
|
||||
{
|
||||
user.setUserID( generateUserID( "AB-", 6 ) );
|
||||
}
|
||||
customerMap.put( user.getUserID(), user ); // Store the user along with the userID.
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts object value reservation alongside key of @reservationNo into @libraryReservationMap.
|
||||
**/
|
||||
private void storeLibraryReservation( LibraryReservation reservation )
|
||||
{
|
||||
libraryReservationMap.put( reservation.getReservationNo(), reservation );
|
||||
diary.addReservation( reservation );
|
||||
}
|
||||
|
||||
/**
|
||||
* Storing Objects End
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Generate IDs Start
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generate a sequential reservation number padded with 0s to a length of 6.
|
||||
*
|
||||
* Since we are not storing in a file at this point, we obtain the size of the Map, and add 1 to determine the current number.
|
||||
* This is due to the event of program restart, if a field variable is used, the first reservation generated will be of ID "000001"
|
||||
* no matter what. This causes an issue if a file is read to load the reservations into the Library, since IDs will conflict and
|
||||
* the value will be overwritten, causing data integrity loss.
|
||||
* Unlike @generateUserID, this has a maximum value hardcoded to the spec.
|
||||
* This likely will not be an issue since we can still have 1,000,000 reservations.
|
||||
*
|
||||
* Returns
|
||||
* String @candidateNo - a proposed lowest number that is checked and interated if conflicting.
|
||||
**/
|
||||
private String generateReservationNo()
|
||||
{
|
||||
/*
|
||||
* Originally, I did not check if the number was already taken, however the last part of step 4 introduced deletion.
|
||||
* When a reservation is deleted that does not have the highest reservation number, conflicting reservation numbers are generated.
|
||||
* e.g. 5 reservations, reservation 3 ID: 000003 is deleted. The next reservation number would be 000004, as the size + 1 = 4
|
||||
*/
|
||||
String candidateNo = String.format("%06d", libraryReservationMap.size() + 1);
|
||||
for ( int i = 1; libraryReservationMap.containsKey( candidateNo ); i++ )
|
||||
{
|
||||
candidateNo = String.format( "%06d", libraryReservationMap.size() + i );
|
||||
}
|
||||
return candidateNo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique user identifier to apply to users when the UUID is unknown.
|
||||
* This generates a number of @length length using the Random class, and prefixes @prefix.
|
||||
*
|
||||
* Takes Parameters
|
||||
* String @prefix - arbitrary alphanumeric prefix
|
||||
* String @length - length of numeric ID
|
||||
* Returns
|
||||
* String @uuid - a unique string starting with @prefix, and concat. with a random number
|
||||
*
|
||||
* Example: length = 6, expected result should be under 999,999 and above 99,999.
|
||||
* If we just use 10^(length), this would generate any number under 1,000,000.
|
||||
* This is an issue since any integers below 100,000 can be used, which would be incorrect.
|
||||
* By using the offset of a factor of 10 below the desired length we can generate between 0 and 899,999.
|
||||
* After this, we can add 100,000 back to the number to ensure a baseline length is maintained.
|
||||
*
|
||||
* Note: I am aware that this is overengineered, and that several random integers of amount @length could be used,
|
||||
* but this is considerably more efficient since there is no iteration involved in the creation of the ID. O(1)
|
||||
**/
|
||||
private String generateUserID( String prefix, int length )
|
||||
{
|
||||
Random random = new Random();
|
||||
final int offset = (int) Math.pow( 10, (length-1) ); // Static integer equal to 10^(length-1) lower than length. This will allow for the factor to be consistent.
|
||||
int intLength = (int) ( Math.pow( 10, length ) ) - offset ; // Integer equal to 10^(length) minus the offset. This creates a ceiling for the ID range.
|
||||
|
||||
if ( customerMap.size() == intLength ) {
|
||||
System.out.println("Too many user IDs delegated for current range, increasing ID range by a factor of 10");
|
||||
return generateUserID( prefix, length+1 );
|
||||
/*
|
||||
* This is essential for eliminating a stack overflow error when the specified range is full. This would be incorrect to catch.
|
||||
* By incrementing the length value when the ID range is full, roughly 2.1 billion users can be created without running into an overflow error.
|
||||
*/
|
||||
}
|
||||
|
||||
String uuid = prefix + ( random.nextInt( intLength ) + offset );
|
||||
/*
|
||||
* Sets the uid to the prefix, concat. with a random integer of specified length
|
||||
* Add the offset to a random number, to create a floor to the ID range.
|
||||
*/
|
||||
|
||||
if ( customerMap.containsKey( uuid ) ) // Previously uuidSet.contains, replaced to de-dupe held data.
|
||||
{
|
||||
generateUserID( prefix, length ); // If the ID generated is already contained in the hashset, the method should be called again.
|
||||
}
|
||||
|
||||
/* No longer needed, as we add the user and the ID to the customerMap when storing.
|
||||
* uuidSet.add( uuid )
|
||||
*/
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate IDs End
|
||||
*/
|
||||
|
||||
/**
|
||||
* Print Object Details Start
|
||||
*/
|
||||
|
||||
/**
|
||||
* Prints to the terminal, in a human-readable format, all LibraryReservation(s)
|
||||
*
|
||||
* Contains a marker at the start and end to visualise in terminal output.
|
||||
**/
|
||||
public void printLibraryReservations() {
|
||||
System.out.println( "Reservation Details Start" );
|
||||
for ( LibraryReservation reservation : libraryReservationMap.values() ){
|
||||
System.out.println("---------------");
|
||||
reservation.printDetails();
|
||||
}
|
||||
System.out.println( "---------------\nReservation Details End\n" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints to the terminal, in a human-readable format, all:
|
||||
* LibraryItem(s)
|
||||
* LibraryUser(s)
|
||||
* LibraryReservation(s)
|
||||
*
|
||||
* Contains a marker at the start and end of each type to visualise in terminal output.
|
||||
* For printing the LibraryReservation(s), the corresponding method is called to reduce
|
||||
* code duplication.
|
||||
**/
|
||||
public void printAll()
|
||||
{
|
||||
System.out.println("\n\nItem Details Start");
|
||||
for( LibraryItem item : itemsMap.values() )
|
||||
{
|
||||
System.out.println("---------------");
|
||||
item.printDetails();
|
||||
}
|
||||
System.out.println("---------------");
|
||||
System.out.println("Items Details End\n\n---------------\n\nUser Details Start\n");
|
||||
for ( LibraryUser user : customerMap.values() )
|
||||
{
|
||||
System.out.println("---------------");
|
||||
user.printDetails();
|
||||
}
|
||||
System.out.println("---------------\nUser Details End\n");
|
||||
printLibraryReservations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Print, to the terminal all reservation entries in the diary.
|
||||
* Takes Parameters
|
||||
* String @start
|
||||
* String @end
|
||||
*
|
||||
* This converts both string inputs to Date formats, and calls the printEntries
|
||||
* method in the diary class.
|
||||
*/
|
||||
public void printDiaryEntries( String start, String end )
|
||||
{
|
||||
diary.printEntries( DateUtil.convertStringToDate( start ), DateUtil.convertStringToDate( end ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Print Object Details End
|
||||
*/
|
||||
|
||||
/**
|
||||
* Write Files Start
|
||||
*/
|
||||
|
||||
/**
|
||||
* Write all current library reservations to a file specified by the user.
|
||||
* For each user, this passes a printWriter object to the writeData method of LibraryReservation.
|
||||
**/
|
||||
public void writeLibraryReservationData()
|
||||
{
|
||||
try {
|
||||
Frame frame = null; // Initialise null frame
|
||||
FileDialog fileBox = new FileDialog( frame, "Save", FileDialog.SAVE ); // Initialise file dialog box to save written data.
|
||||
fileBox.setVisible( true );
|
||||
PrintWriter writer = new PrintWriter( new File( fileBox.getDirectory() + fileBox.getFile() ) );
|
||||
|
||||
for ( LibraryReservation reservation : libraryReservationMap.values() ) {
|
||||
reservation.writeData( writer );
|
||||
}
|
||||
writer.close();
|
||||
}
|
||||
catch( IOException e ) { // Catch any IO Exceptions that may occur from improper file selection.
|
||||
System.err.println( "Caught IOException: " + e.getMessage() + "\nAn I/O Exception has occurred, please check file is readable and correct format." );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method to output all user data to a file using a fileDialog to specify file location.
|
||||
* For each user, this passes a printWriter object to the writeData method of LibraryUser.
|
||||
**/
|
||||
public void writeUserData()
|
||||
{
|
||||
try {
|
||||
Frame frame = null; // Initialise null frame
|
||||
FileDialog fileBox = new FileDialog( frame, "Save", FileDialog.SAVE ); // Initialise file dialog box to save written data.
|
||||
fileBox.setVisible( true );
|
||||
PrintWriter writer = new PrintWriter( new File( fileBox.getDirectory() + fileBox.getFile() ) ); // Create PrintWriter to write user data to.
|
||||
// for ( LibraryUser user : userList ){
|
||||
writer.println("[Library User data]"); // Write the type header into the file.
|
||||
for ( LibraryUser user : customerMap.values() ) {
|
||||
user.writeData( writer );
|
||||
}
|
||||
writer.close();
|
||||
}
|
||||
catch( IOException e ) { // Catch any IO Exceptions that may occur from improper file selection.
|
||||
System.err.println( "Caught IOException: " + e.getMessage() + "\nAn I/O Exception has occurred, please check file is readable and correct format." );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write Files End
|
||||
*/
|
||||
|
||||
/**
|
||||
* Read Files Start
|
||||
*/
|
||||
|
||||
/**
|
||||
* Read Library Reservation Data from a file specified by the user.
|
||||
* This passes a scanner to the readData method in LibraryReservation for each
|
||||
* line of the file.
|
||||
**/
|
||||
public void readLibraryReservationData()
|
||||
{
|
||||
try {
|
||||
Frame frame = null; // Initialise null frame
|
||||
FileDialog fileBox = new FileDialog( frame, "Load", FileDialog.LOAD ); // Initialise file dialog box to save written data.
|
||||
fileBox.setVisible( true );
|
||||
Scanner reservationScanner = new Scanner ( new File( fileBox.getDirectory() + fileBox.getFile() ) );
|
||||
while ( reservationScanner.hasNextLine() )
|
||||
{
|
||||
Scanner detailScanner = new Scanner( reservationScanner.nextLine() ).useDelimiter(",");
|
||||
LibraryReservation reservation = new LibraryReservation();
|
||||
reservation.readData( detailScanner );
|
||||
storeLibraryReservation( reservation );
|
||||
detailScanner.close();
|
||||
}
|
||||
reservationScanner.close(); // Close Scanner
|
||||
}
|
||||
catch( IOException e ) { // Catch any IO Exceptions that may occur from improper file selection.
|
||||
System.err.println( "Caught IOException: " + e.getMessage() + "\nAn I/O Exception has occurred, please check file is readable and correct format." );
|
||||
}
|
||||
catch ( NoSuchElementException e ) { // In the event of incorrect formats from a readable file, this prevents a program crash.
|
||||
System.err.println( "Caught Exception: " + e.getMessage() + "\nEnsure File contains the correct information." );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method to read all data from files using a fileDialog to specify file location.
|
||||
* This will create the corresponding objects depending on flags contained in the file
|
||||
* and populate it's fields.
|
||||
*
|
||||
* Default flag value: "book", to support legacy files. This will not interfere with
|
||||
* files of different flag orders.
|
||||
**/
|
||||
public void readData()
|
||||
{
|
||||
Frame frame = null; // Initialise a null frame
|
||||
FileDialog fileBox = new FileDialog( frame, "Open", FileDialog.LOAD ); // Initialise filebox with the null frame pointer
|
||||
fileBox.setVisible( true ); // Open a file selection dialog to the user.
|
||||
|
||||
try {
|
||||
Scanner fileScanner = new Scanner( new File( fileBox.getDirectory() + fileBox.getFile() ) );
|
||||
String itemType = "book"; // Set a default flag to support legacy files.
|
||||
|
||||
while( fileScanner.hasNextLine() ) {
|
||||
|
||||
String lineItem = fileScanner.nextLine();
|
||||
|
||||
// Ensure no comments or empty lines are included
|
||||
if ( lineItem.contains( "//" ) || lineItem.trim().isEmpty() ){}
|
||||
|
||||
// Check current line is a candidate flag
|
||||
else if ( lineItem.startsWith("[" ) ) {
|
||||
|
||||
if ( lineItem.toLowerCase().contains("book") )
|
||||
itemType = "book";
|
||||
else if ( lineItem.toLowerCase().contains("periodical") )
|
||||
itemType = "periodical";
|
||||
else if ( lineItem.toLowerCase().contains("cd") )
|
||||
itemType = "cd";
|
||||
else if ( lineItem.toLowerCase().contains("dvd") )
|
||||
itemType = "dvd";
|
||||
else if ( lineItem.toLowerCase().contains("user") )
|
||||
itemType = "user";
|
||||
else {
|
||||
System.out.println( "Flag detected, but no accepted format...\n Cannot store item in Library. Changing Flag to generic and skipping the following: ");
|
||||
itemType = "generic";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Unfortunately cannot use switch case with string comparisons in Java 8.
|
||||
// Check current flag for data processing.
|
||||
else {
|
||||
Scanner detailScanner = new Scanner ( lineItem ).useDelimiter(","); // Create a new scanner to grab the values in a comma separated list
|
||||
LibraryItem item = null; // Initialise LibraryItem object. Java compiler was being cautious here, so I have to assign the value null.
|
||||
if ( itemType.equals( "book" ) ) {
|
||||
// Process Book Data
|
||||
item = new Book();
|
||||
}
|
||||
else if ( itemType.equals( "periodical" ) ) {
|
||||
// Process Periodic Data
|
||||
item = new Periodical();
|
||||
}
|
||||
else if ( itemType.equals( "cd" ) ) {
|
||||
//Process CD Data
|
||||
item = new CD();
|
||||
}
|
||||
else if ( itemType.equals( "dvd" ) ) {
|
||||
//Process DVD Data
|
||||
item = new DVD();
|
||||
}
|
||||
else if ( itemType.equals( "user" ) ) {
|
||||
//Process User Data
|
||||
LibraryUser user = new LibraryUser();
|
||||
user.readData( detailScanner );
|
||||
storeUser( user );
|
||||
continue;
|
||||
}
|
||||
else if ( itemType.equals( "generic" ) ) {
|
||||
// Output unaccepted lines to terminal
|
||||
System.out.println( lineItem );
|
||||
continue;
|
||||
}
|
||||
|
||||
item.readItemData( detailScanner ); // Deduped from if statements
|
||||
storeItem( item );
|
||||
detailScanner.close();
|
||||
}
|
||||
}
|
||||
fileScanner.close();
|
||||
}
|
||||
catch( IOException e ) { // Catch any IO Exceptions that may occur from improper file selection.
|
||||
System.err.println( "Caught IOException: " + e.getMessage() + "\nAn I/O Exception has occurred, please check file is readable and correct format." );
|
||||
}
|
||||
catch ( NoSuchElementException e ) { // In the event of incorrect formats from a readable file, this prevents a program crash.
|
||||
System.err.println( "Caught Exception: " + e.getMessage() + "\nEnsure File contains the correct information." );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read Files End
|
||||
*/
|
||||
|
||||
/**
|
||||
* Other Methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a reservation to allow a user to reserve an item from the library. Ensures a reservation is not already
|
||||
* in place for a conflicting itemCode.
|
||||
* Takes parameters
|
||||
* String @userID
|
||||
* String @itemCode
|
||||
* String @startDate
|
||||
* int @noOfDays
|
||||
*
|
||||
* Returns
|
||||
* boolean
|
||||
**/
|
||||
public boolean makeLibraryReservation( String userID, String itemCode, String startDate, int noOfDays )
|
||||
{
|
||||
if ( !customerMap.containsKey( userID ) )
|
||||
{
|
||||
System.out.println( "User does not exist" );
|
||||
return false;
|
||||
}
|
||||
if ( !itemsMap.containsKey( itemCode ) )
|
||||
{
|
||||
System.out.println( "Item does not exist in library" );
|
||||
return false;
|
||||
}
|
||||
if ( !DateUtil.isValidDateString( startDate ) )
|
||||
{
|
||||
System.out.println( "Date is not valid" );
|
||||
return false;
|
||||
}
|
||||
if ( !(noOfDays > 0) )
|
||||
{
|
||||
System.out.println( "Reservation must be more than 0 days" );
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Date start = DateUtil.convertStringToDate( startDate );
|
||||
/*
|
||||
* Unneeded, since addReservations returns any item currently on loan. Dates do not need to be checked for every day in loan.
|
||||
* Date end = DateUtil.incrementDate( start, noOfDays-1 );
|
||||
*/
|
||||
String rID = generateReservationNo(); // Generate a sequential ID for the reservation.
|
||||
LibraryReservation reservation = new LibraryReservation( rID, itemCode, userID, startDate, noOfDays ); // Create new reservation, passing parameters to the constructor.
|
||||
/** Instead of the following For loop, I could've implemented
|
||||
* Arrays.asList( diary.getReservation( start, end ).contains( reservation )
|
||||
* Although this would've been easier, this implementation is more in the scope of what we have learned.
|
||||
**/
|
||||
for ( LibraryReservation diaryReservation : diary.getReservations( start ) )
|
||||
{
|
||||
if ( diaryReservation.toString().equals( reservation.toString() ) )
|
||||
{
|
||||
System.err.println( "This item is already reserved" );
|
||||
return false; // Reservation unsuccessful
|
||||
}
|
||||
}
|
||||
|
||||
storeLibraryReservation( reservation );
|
||||
itemsMap.get( itemCode ).setOnLoan( true ); // Set the loan status for the item when the reservation is made
|
||||
return true; // Reservation successful
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the deletion of a library reservation.
|
||||
*
|
||||
* Takes Parameter
|
||||
* String @reservationNo
|
||||
*
|
||||
* Removes the corresponding value from the hashMap and diary.
|
||||
*/
|
||||
public void deleteLibraryReservation( String reservationNo )
|
||||
{
|
||||
if ( libraryReservationMap.containsKey( reservationNo ) )
|
||||
{
|
||||
// Since the application does not know the time, or use timers, this is the best I can do to remove the loan status.
|
||||
itemsMap.get ( libraryReservationMap.get( reservationNo ).getItemCode() ).setOnLoan( false );
|
||||
diary.deleteReservation( libraryReservationMap.get( reservationNo ) ); // Remove the reservation from the diary
|
||||
libraryReservationMap.remove( reservationNo ); // Remove the reservation from the Map.
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user