Software Design and Development :: CS 246

01 Prove

Learning Java with Three Examples

Objectives

The three programs that you will write in this first assignment are intended to be a tutorial of the Java language. All the steps for coding the solutions can be found in this assignment. You should follow all the steps and create all three programs as described. You will need to submit the code that you write.

Future assignments will not provide all of this specific detail. You will need to learn to find additional resources online and ask questions to your teacher and peers. You should also refer to the additional examples and notes provided as support each week. You can find these examples in the weekly modules within I-Learn.

Before starting this assignment, you should complete the 01 Teach: Team Activity individually and (if teams are established already) as a team to ensure you have IntelliJ working and can do "Hello World".

If you need a good and simple reference manual for Java, consider using the W3Schools Java Tutorial.

Problem 1: Simple Calculator

The purpose of this program is to write a simple four function calculator using two classes. The first class will be called Calculator and the second class will be called TestCalculator. The TestCalculator class will have the main function and will be used to test the Calculator.

  1. Create a new project in ItelliJ called Calculator.

  2. Create a new package inside the src folder of your project. Use your last name for the package name.

  3. Create two new Java Classes in your package called Calculator and TestCalculator. Notice that IntelliJ will put the package statement and the empty class definition in both of the files for you.

  4. Our calculator will maintain a current result and do basic math operations. Here is the summary of what you want in the class:

  5. Member Data / Attributes Data Type Scope Purpose
    result float private The current result stored in the calculator
    Member Functions / Methods Parameters Return Type Scope Purpose
    add float value void (don't return anything) public Add a value to the current result
    subtract float value void public Subtract a value from the current result
    multiply float value void public Multiply a value with the current result
    divide float value void public Divide a value with the current result. If the value is 0, then set the result to 0.
    reset None void public Reset the result back to 0
    getResult None float public Return a copy of the result
  6. In the Calculator class, first write the member data:

  7. 	
    public class Calculator {
        
        private float result;
        
    }
    
  8. If there is member data, then you need a constructor to initialize the member data. Constructors in Java are named the same as the class and should be public. Create a constructor for the Calculator class after the member data. Note that 0.0f means float and 0.0 means double.

  9. 
        public Calculator() {
            result = 0.0f;
        }
    
  10. Implement the add, subtract, multiply, and divide functions described in the table after the constructor. Note that each parameter includes the data type. Also note that the return type of the function is also specified. If the function does not return anything then you use void. The divide function must use an if statement to ensure you do not try division by zero.

  11. 
        public void add(float value) {
            result += value;
        }
        
        public void subtract(float value) {
            result -= value;
        }
        
        public void multiply(float value) {
            result *= value;
        }
        
        public void divide(float value) {
            /* Check for divide by zero */
            if (value == 0.0f) {
                result = 0.0f;
            } else {
                result /= value;
            }
        }
    
  12. Implement the reset function. This function has no inputs.

  13. 
        public void reset() {
            result = 0.0f;
        }
    
  14. Implement the getResult function. This is called a getter function and will provide a copy of the private data. In IntelliJ, you can automatically create this function by going to menu option Code, select Generate, and select Getter.

  15. 
        public float getResult() {
            return result;
        }
    
  16. Now that you have the Calculator class written, you will write some test code in the TestCalculator class. Create a main function in the TestCalculator class.

  17. 
    public class TestCalculator {
        
        public static void main(String[] args) {
            
        } 
    }
    
  18. In your main function, create a Calculator object using the new keyword. All objects are created dynamically on the heap in Java. You don't have to delete them when you are done. Java will use "garbage collection" to delete unused objects. The Calculator object variable that you create is holding a reference or pointer to the object.

  19. 
        public static void main(String[] args) {
            Calculator calc = new Calculator();
        }
    
  20. Print out the current result of the calculator by calling the getResult function.

  21. 
        public static void main(String[] args) {
            Calculator calc = new Calculator();
            System.out.println("Result = " + calc.getResult());
        }
    
  22. Run the program by pressing the green play button in IntelliJ to the left of the main function code. It should display "Result = 0.0".

  23. Change the code to add 5, multiply by 3, subtract 1, and then divide by 2. Run it again and you should get "Result = 7.0".

  24. 
        public static void main(String[] args) {
            Calculator calc = new Calculator();
            System.out.println("Result = " + calc.getResult());
            calc.add(5.0f);
            calc.multiply(3.0f);
            calc.subtract(1.0f);
            calc.divide(2.0f);
            System.out.println("Result = " + calc.getResult());
        }
    
  25. Create a do while loop in your main function (after the code you have already written) to allow the user to select an operation. The do while loop will keep running until the user selects the exit option. Reading from the keyboard requires the use of the Scanner class provided by Java. Scanner allows you to read a line of text. Notice that when you create the Scanner object, IntelliJ will show an error until you provide the import statement. Java requires that you specify which libraries you are using. You only have to create the Scanner object once before the loop. When you ask for values from the user, we need to convert the String to a float using Float.parseFloat. Note that parseFloat is a static function which is why you don't need a Float object to call it. Also note that you can not use == to compare strings in Java. You must use the equals function.

  26. 
    import java.util.Scanner;
    
    public class TestCalculator {
    
        public static void main(String[] args) {
            Calculator calc = new Calculator();
            System.out.println("Result = " + calc.getResult());
            calc.add(5.0f);
            calc.multiply(3.0f);
            calc.subtract(1.0f);
            calc.divide(2.0f);
            System.out.println("Result = " + calc.getResult());
    
            Scanner scanner = new Scanner(System.in);
            String choice;
            do {
                System.out.println();
                System.out.println("Result = " + calc.getResult());
                System.out.print("Operation (add, subtract, multiply, divide, reset, exit): ");
                choice = scanner.nextLine();
                if (choice.equals("add")) {
                    System.out.print("Enter value: ");
                    float value = Float.parseFloat(scanner.nextLine());
                    calc.add(value);
                } else if (choice.equals("subtract")) {
                    System.out.print("Enter value: ");
                    float value = Float.parseFloat(scanner.nextLine());
                    calc.subtract(value);
                } else if (choice.equals("multiply")) {
                    System.out.print("Enter value: ");
                    float value = Float.parseFloat(scanner.nextLine());
                    calc.multiply(value);
                } else if (choice.equals("divide")) {
                    System.out.print("Enter value: ");
                    float value = Float.parseFloat(scanner.nextLine());
                    calc.divide(value);
                } else if (choice.equals("reset")) {
                    calc.reset();
                } else if (choice.equals("exit")) {
                    System.out.println("Goodbye!");
                } else {
                    System.out.println("Invalid choice.");
                }
            } while (!choice.equals("exit"));
        }
    }
    
  27. Test your code with different inputs to make sure everything works. Remember to test divide by 0.

Problem 2: Form Letter

The purpose of this program is to learn how to use a simple data structure called the ArrayList. The ArrayList is like the Vector in C++ or the list in Python. When you create an ArrayList (like other data structures in Java) you have to specify what it will hold. You are going to hold an ArrayList of Account objects. You will use these Account objects to populate form letters. This will allow you to demonstrate loops.

  1. Create a new project in ItelliJ called FormLetter.

  2. Create a new package inside the src folder of your project. Use your last name for the package name.

  3. Create two new Java Classes in your package called Account and LetterGenerator. Notice that IntelliJ will put the package statement and the empty class definition in both of the files for you.

  4. The Account Class will contain the following member data.

  5. Member Data / Attributes Data Type Scope Purpose
    name String private The account holder name
    lastPurchase String private The name of the last thing purchased
    daysSincePurchase int private The number of days since the last purchase
  6. Write the member data in the Account class and write a construtor to intialize all the member data. This time you want the constructor to accept the three pieces of member data as parameters. Just like the getters, you can use IntelliJ to auto-generate a constructor for the class. Specify that you want all three parameters name, lastPurchase, and daysSincePurchase to be included in the constructor. Note the use of the keyword this. The keyword this is used when you want to refer to your object. Python used the keyword self.

  7. 
    public class Account {
    
        private String name;
        private String lastPurchase;
        private int daysSincePurchase;
    
        public Account(String name, String lastPurchase, int daysSincePurchase) {
            this.name = name;
            this.lastPurchase = lastPurchase;
            this.daysSincePurchase = daysSincePurchase;
        }
    }
    
  8. Using IntelliJ, auto-generate the getters and the setters for the Account class.

  9. 
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getLastPurchase() {
            return lastPurchase;
        }
    
        public void setLastPurchase(String lastPurchase) {
            this.lastPurchase = lastPurchase;
        }
    
        public int getDaysSincePurchase() {
            return daysSincePurchase;
        }
    
        public void setDaysSincePurchase(int daysSincePurchase) {
            this.daysSincePurchase = daysSincePurchase;
        }
    
  10. The LetterGenerator class will have the following member data and member functions:

  11. Member Data / Attributes Data Type Scope Purpose
    accounts ArrayList<Account> private A collection of account objects.
    formLetter String private A form letter than contains replaceable tags
    Member Functions / Methods Parameters Return Type Scope Purpose
    addAccount nothing void public Prompt the user to enter in account information and then add it to the accounts list.
    generateLetters nothing void public Generate the letters for each account in the list
  12. Add the member data and provide a constructor that initializes the ArrayList to an empty list and the formLetter to a example letter. Notice that when you create the ArrayList, IntelliJ will show an error until you provide the import statement. Java requires that you specify which libraries you are using.

  13. 
    import java.util.ArrayList;
    
    public class LetterGenerator {
    
        private ArrayList<Account> accounts;
        private String formLetter;
    
        public LetterGenerator() {
            accounts = new ArrayList<Account>();
            formLetter = "Dear [name], its been [days] since your last purchase " +
                    "of the [purchase].  We hope to see you again soon!";
        }
    }
    
  14. Implement the addAcccount function. This function will use a Scanner to read in the information and create an Account object. You will add the Account object to our accounts list by using the add function.

  15. 
        public void addAccount() {
            Scanner scanner = new Scanner(System.in);
            System.out.print("Enter name: ");
            String name = scanner.nextLine();
            System.out.print("Enter last item purchased: ");
            String item = scanner.nextLine();
            System.out.print("Enter days since last purchase: ");
            int days = Integer.parseInt(scanner.nextLine());
    
            Account account = new Account(name, item, days);
            accounts.add(account);
        }
    
  16. Start the implementation of the generateLetters function. This function will loop through each account in the accounts list. The for loop defines a variable called currAccount which will be populated with the individual Account objects in the list.

  17. 
        public void generateLetters() {
            for (Account currAccount : accounts) {
    
            }
        }
    
  18. In the loop, replace "[name]" with the name from currAccount. To get the name from the currAccount, call the getName function that you created in the Account class. Do the same for "[days]" and "[purchase]". Notice that you need to convert the days int to a String using String.valueof. The String class in Java has lots of functions available to use. Look in the JavaDoc to see more available functions (good place to bookmark).

  19. 
        public void generateLetters() {
            for (Account currAccount : accounts) {
                String letter = formLetter.replace("[name]", currAccount.getName());
                letter = letter.replace("[days]", String.valueOf(currAccount.getDaysSincePurchase()));
                letter = letter.replace("[purchase]", currAccount.getLastPurchase());
                System.out.println(letter);
                System.out.println();
            }
        }
    
  20. Create a main function in the LetterGenerator to test out your code. Call addAccount three times with different information and then call generateLetters. Check to make sure the form letter is populated properly each time.

  21. 
        public static void main(String [] args) {
            LetterGenerator letterGenerator = new LetterGenerator();
            letterGenerator.addAccount();
            letterGenerator.addAccount();
            letterGenerator.addAccount();
            letterGenerator.generateLetters();
        }
    

Problem 3: Enryption Code

The purpose of this problem is to practice using existing code. The existing code is a class called NSALoginController which will encrypt and validate passwords. Your first step will be to copy the code into your project. In the second step, you will write some test code to make sure the NSALoginController is working. In the third step, you will modify the NSALoginController to provide for more strict password requirements.

Part I - Import Existing Code

  1. Start by creating a new Project in IntelliJ called NSALogin

  2. Create a package using your last name.

  3. Download the files for this project. After unzipping this file, look for NSALoginController.java. You can drag and drop this file into your package within IntelliJ. When you do this, it will change the package statement in NSALoginController.

  4. Browse through the code in the NSALoginController class to see what functions are available. Consider making a UML diagram to better understand the class. You don't need to understand how all this code works to do encryption. Take special note of the hashUserPassword and verifyPassword functions. You will be testing those in the next section. Also note that these functions are static. A static function does not require an object to call. Also note that each of these functions has throws Exception in the function declaration line. You will be required to catch these potential exceptions in your test code.

  5. The purpose of hashUserPassword is to take a User object (you will need to create that class later), read its password, and generate a salt and encrypted hashed password. The generated salt is used to encrypt the password. It will then delete the password for security. The salted and hashed password are the only things we need to save.

  6. The purpose of verifyPassword is to take a User object (with the salt and hashed password still populated along with the password we are testing) and return a boolean indicating whether or not the user's password is correct.

Part II - Test the EXISTING CODE

  1. Create a new class called Test. You will put your main function in here.

  2. 
    public class Test {
    
        public static void main(String[] args) {
        }
    
  3. Create a new class called User. The NSALoginController requires that you have a User object with the member data shown in the table below. Note that the NSALoginController expets these variables (and their getters and setters) to be correctly named.

  4. Member Data / Attributes Data Type Scope Purpose
    password String private The raw password from the user
    salt String private A value generated for the user that provides variation in the hashedpassword generation. This is done for security.
    hashedPassword String private The encrypted version of the password generated with the salt.
  5. Create a constructor that passes in the password and sets the salt and hashedPassword to empty strings. Use IntelliJ to auto-generate the getters and setters.

  6. 
    public class User {
        private String password;
        private String salt;
        private String hashedPassword;
    
        public User(String password) {
            this.password = password;
            this.salt = "";
            this.hashedPassword = "";
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getSalt() {
            return salt;
        }
    
        public void setSalt(String salt) {
            this.salt = salt;
        }
    
        public String getHashedPassword() {
            return hashedPassword;
        }
    
        public void setHashedPassword(String hashedPassword) {
            this.hashedPassword = hashedPassword;
        }
    }
    
  7. In the main function of the Test class, you will first test the hashUserPassword function in NSALoginController. You will first need create a User object since both functions require a User object. You should add some debug code to make sure our user object was created correctly.

  8. 
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter password: ");
        String password = scanner.nextLine();
        User user = new User(password);
    
        // Only Password should be set at this point
        System.out.println("Password: " + user.getPassword());
        System.out.println("Salt: " + user.getSalt());
        System.out.println("Hashed Password: " + user.getHashedPassword());
    }
    
  9. Pass the User object to the hashUserPassword. Since the function might throw an Exception, we are required to catch that potential exception. If something fails in the try block (e.g. hashUserPassword throws an exception) then execution will be immediately diverted to the catch block. Again, we should add some debug code to see if the hash operation worked. Take note that the password should be cleared after the hashUserPassword function is called.

  10. 
    public static void main(String[] args) {
            Scanner scanner = new Scanner(System.in);
            System.out.print("Enter password: ");
            String password = scanner.nextLine();
            User user = new User(password);
    
            // Only Password should be set at this point
            System.out.println("Password: " + user.getPassword());
            System.out.println("Salt: " + user.getSalt());
            System.out.println("Hashed Password: " + user.getHashedPassword());
            try {
                // hashUserPassword is a static function (no NSALoginController object needed)
                // This function will set the salt and hashedPassword in the user object
                NSALoginController.hashUserPassword(user);
    
                // Only salt and hashed password set at this point
                System.out.println("Password: " + user.getPassword());
                System.out.println("Salt: " + user.getSalt());
                System.out.println("Hashed Password: " + user.getHashedPassword());
            }
            catch(Exception e) {
                System.out.println("General Exception: " + e.toString());
            }
    }
    
  11. To test the verifyPassword, you will ask the user for their password. We will add the password into our previously created User object. That object still has the salt and hashedUserPassword populated. The verifyPassword function will compare the password with the salt and hashedUserPassword values. This function can throw an exception too so we will keep it in the try block. The function also returns a boolean. If it returns true, then the password matched.

  12. 
    try {
        // hashUserPassword is a static function (no NSALoginController object needed)
        // This function will set the salt and hashedPassword in the user object
        NSALoginController.hashUserPassword(user);
    
        // Only salt and hashed password set at this point
        System.out.println("Password: " + user.getPassword());
        System.out.println("Salt: " + user.getSalt());
        System.out.println("Hashed Password: " + user.getHashedPassword());
    
        // Run a test of the hashed password.
        // Ask the user for the password and set it in the user object
        System.out.print("Enter password: ");
        password = scanner.nextLine();
        user.setPassword(password);
    
        // verifyPassword will check the password in the user object against the salt
        // and hashed password.
        if (NSALoginController.verifyPassword(user))
        {
            System.out.println("Verified!");
        }
        else
        {
            System.out.println("Not Verified!");
        }
    }
    catch(Exception e) {
        System.out.println("General Exception: " + e.toString());
    }
    
  13. Run your code several times to prove that NSALoginController is working properly.

Part III - Modify the EXISTING CODE

  1. Once you have proven that the NSALoginController is working, you can now modify it. You need to change it to require a password length of at least 8 and a requirement to have at least one digit in the password. We can use the .length property on a String to determine the length. To determine if there is a digit, we can loop through each letter of the password and use the Character.isDigit function.

  2. 
    public static void hashUserPassword(User user) throws Exception {
    		
        // Get the next random salt value to use for this password
        byte[] salt = getNextSalt();
        char[] password = user.getPassword().toCharArray();
    
        // Verify password rules:
        // 1) Length at least 8 characters
        // 2) Contain at least one digit
        if (password.length < 8) {
            // Do Something!
        }
        boolean hasDigit = false;
        for (Character c : password) {
            if (Character.isDigit(c)) {
                hasDigit = true;
            }
        }
        if (!hasDigit) {
            // Do Something!
        }
    
  3. If password checking has failed, then we have to exit the function and notify the calling function. However, recall that this function is throwing exceptions (which we had to catch in our main function). One solution is to throw a new Exception object.

  4. 
    public static void hashUserPassword(User user) throws Exception {
    		
        // Get the next random salt value to use for this password
        byte[] salt = getNextSalt();
        char[] password = user.getPassword().toCharArray();
    
        // Verify password rules:
        // 1) Length at least 8 characters
        // 2) Contain at least one digit
        if (password.length < 8) {
            throw new Exception("Password must be at least 8 characters long.");
        }
        boolean hasDigit = false;
        for (Character c : password) {
            if (Character.isDigit(c)) {
                hasDigit = true;
            }
        }
        if (!hasDigit) {
            throw new Exception("Password must contain at least 1 digit.");
        }
    
  5. In Java, it can be useful to create new exception classes for special scenarios like this one. Creating your own exception class provides for better readability of the code and allows you to respond to errors less generically. To create your own exception class, you have to inherit the base Exception class.

  6. Create a new class called WeakPasswordException. When the class is created, modify it to extends the Exception class. This is how you inherit a base class.

  7. 
    public class WeakPasswordException extends Exception {
    
    }
    
  8. This class will be very simple. It will not have any member data within it. You are allowed, but in this case you don't have any need. The only additional requirement is that you provide a constructor that will pass the customized error message up to the constructor of the Exception base class. You can call the parent constructor by using super.

  9. 
    public class WeakPasswordException extends Exception {
    
        public WeakPasswordException(String errorMessage)
        {
            super(errorMessage);
        }
    }
    
  10. With the creation of WeakPasswordException, we can now use it in our hashUserPassword function. Note you also need to state that you can throws Exception, WeakPasswordException.

  11. 
    public static void hashUserPassword(User user) throws Exception, WeakPasswordException {
    		
        // Get the next random salt value to use for this password
        byte[] salt = getNextSalt();
        char[] password = user.getPassword().toCharArray();
    
        // Verify password rules:
        // 1) Length at least 8 characters
        // 2) Contain at least one digit
        if (password.length < 8) {
            throw new WeakPasswordException("Password must be at least 8 characters long.");
        }
        boolean hasDigit = false;
        for (Character c : password) {
            if (Character.isDigit(c)) {
                hasDigit = true;
            }
        }
        if (!hasDigit) {
            throw new WeakPasswordException("Password must contain at least 1 digit.");
        }
    
  12. Since this function can throw multiple types of exceptions now, our main function code can list them out separately. You can have multiple catch blocks in your code for a single try block

  13. 
    try {
        // Only Password should be set at this point
        System.out.println("Password: " + user.getPassword());
        System.out.println("Salt: " + user.getSalt());
        System.out.println("Hashed Password: " + user.getHashedPassword());
    
        // hashUserPassword is a static function (no NSALoginController object needed)
        // This function will set the salt and hashedPassword in the user object
        // We expect that the password will be cleared.
        NSALoginController.hashUserPassword(user);
    
        // Only salt and hashed password set at this point
        System.out.println("Password: " + user.getPassword());
        System.out.println("Salt: " + user.getSalt());
        System.out.println("Hashed Password: " + user.getHashedPassword());
    
        // Run a test of the hashed password.
        // Ask the user for the password and set it in the user object
        System.out.print("Enter password: ");
        password = scanner.nextLine();
        user.setPassword(password);
    
        // verifyPassword will check the password in the user object against the salt
        // and hashed password.
        if (NSALoginController.verifyPassword(user)) {
            System.out.println("Verified!");
        }
        else {
            System.out.println("Not Verified!");
        }
    }
    catch (WeakPasswordException e) {
        System.out.println("Weak Password Error: " + e.getMessage());
    }
    catch(Exception e) {
        System.out.println("General Exception: " + e.toString());
    }
    
  14. Test out your code to make sure that the "Weak Password Error" message is displayed if a weak password is provided by the user.

Submission

To submit this assignment (and future individual assignments), you need to use the following procedure:

  1. Upload your code to the "Code Submission" assignment in I-Learn. You only need to submit your Java files. You can zip them up or submit them individually.

  2. Take a multiple choice, self-evaluation "Assessment" of the functionality in I-Learn.

  3. Once you have submitted the assessment, a link to the instructor's solution to this assignment becomes available. You are expected to look through the instructor's code, which is heavily commented to explain why certain choices were made. Keep in mind that this is not the only way to complete the assignment.

    If desired, you can use the instructor's solution to help guide you through completing anything you were not able to finish. Then, you can re-submit the code and multiple choice assessment above, and an average of the two scores will be used. Thus, if you got 80% the first time, but after looking at the instructor's solution, you were able to complete everything for a 100%, your final score will be 90% for that portion.

Please note that the "Code Submission" element does not receive a score itself in the gradebook. It is provided so the instructor can refer to it, but the "score" is attached to the multiple choice self assessment.

Future assignments will follow this same procedure.