/** * Class to create objects of a Library, which can hold and report on assets held within * * @George Wilkinson * @2.0 */ // Import all required libraries. Not using .* as it is not good practice due to potential conflicts. import java.util.ArrayList; import java.util.HashSet; import java.util.Scanner; import java.util.Random; import java.io.File; import java.io.IOException; import java.awt.FileDialog; import java.awt.Frame; import java.io.PrintWriter; public class Library { private ArrayList itemList; // Initialise an ArrayList of name itemList to store Library Items private ArrayList userList; // Initialise an ArrayList of name userList to store Library Users private HashSet uuidSet; // Initialise a Hash Set of name uuidSet ( unique user identifier ) to store unique user IDs for efficient O(1) searching /* * Constructor for objects of class Library */ public Library() { itemList = new ArrayList(); userList = new ArrayList(); uuidSet = new HashSet(); } /* * Appends a LibraryItem to the itemList. */ public void storeItem( LibraryItem item ) { itemList.add( item ); } /* * Appends a LibraryUser to the userList. */ public void storeUser( LibraryUser user ) { userList.add( user ); if ( user.getUserID().equals( "unknown" ) ) { user.setUserID( generateUserID( "AB-", 6 ) ); } } /* * Returns a random unique user ID by specifying * @prefix - arbitrary alphanumeric prefix * @length - length of numeric ID * and returning a unique user id * @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) */ public 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 ( uuidSet.size() > ( intLength - 1 ) ) { // No idea why I get an error for equals FIX LATER 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 ( uuidSet.contains( uuid ) ) { generateUserID( prefix, length ); // If the ID generated is already contained in the hashset, the method should be called again. } uuidSet.add( uuid ); // Add the UUID to the hash set so it cannot be returned from this method more than once. return uuid; } /* * A method to output all user data to a file using a fileDialog to specify file location. * * Note: Potentially could implement the HashSet made for GenerateUserID to avoid unnecessary recursion. */ 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() ) ); for ( LibraryUser user : userList ){ 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." ); } } /* * Prints to the terminal, in a human-readable format, all items in the itemList * * Contains a marker at the start and end to visualise in terminal output. */ public void printAll() { System.out.println("\n\nStart Detail Print"); for( LibraryItem item : itemList ) { System.out.println("---------------"); item.printDetails(); } System.out.println("End Detail Print\n"); } /* * 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 typeFlag = "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") ) typeFlag = "book"; else if ( lineItem.toLowerCase().contains("periodical") ) typeFlag = "periodical"; else if ( lineItem.toLowerCase().contains("cd") ) typeFlag = "cd"; else if ( lineItem.toLowerCase().contains("dvd") ) typeFlag = "dvd"; else if ( lineItem.toLowerCase().contains("user") ) typeFlag = "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: "); typeFlag = "generic"; } } // Could be a switch case to be more efficient // Check current flag for data processing. else { if ( typeFlag.equals( "book" ) ) { // Process Book Data Scanner detailScanner = new Scanner ( lineItem ).useDelimiter(","); // Create a new scanner to grab the values in a comma separated list LibraryItem book = new Book(); book.readItemData( detailScanner ); storeItem( book ); // Store the new LibraryItem in the itemList } else if ( typeFlag.equals( "periodical" ) ) { // Process Periodic Data Scanner detailScanner = new Scanner ( lineItem ).useDelimiter(","); LibraryItem periodical = new Periodical(); periodical.readItemData( detailScanner ); storeItem( periodical ); } else if ( typeFlag.equals( "cd" ) ) { //Process CD Data Scanner detailScanner = new Scanner ( lineItem ).useDelimiter(","); LibraryItem cd = new CD(); cd.readItemData( detailScanner ); storeItem( cd ); } else if ( typeFlag.equals( "dvd" ) ) { //Process DVD Data Scanner detailScanner = new Scanner ( lineItem ).useDelimiter(","); LibraryItem dvd = new DVD(); dvd.readItemData( detailScanner ); storeItem( dvd ); } else if ( typeFlag.equals( "user" ) ) { //Process User Data Scanner detailScanner = new Scanner ( lineItem ).useDelimiter(","); LibraryUser user = new LibraryUser(); user.readData( detailScanner ); storeUser( user ); } else if ( typeFlag.equals( "generic" ) ) { // Output unaccepted lines to terminal System.out.println( lineItem ); } } } } 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." ); } } }