Week 2
Software Development Life Cycle, Steps 4-5
Fraction Class
public class Fraction {
private int numerator;
private int denominator;
public Fraction() {
numerator = 1;
denominator = 1;
}
public String toString() {
return numerator + "/" + denominator;
}
}
Generate a parameterized constructor that has two int parameters, called numerator and denominator. Assign both parameters to the instance variables.
public class Fraction {
// code omitted
public Fraction(int numerator, int denominator) {
setNumerator(numerator);
setDenominator(denominator);
}
public int getNumerator() {
return numerator;
}
public void setNumerator(int numerator) {
this.numerator = numerator;
}
public int getDenominator() {
return denominator;
}
public void setDenominator(int denominator) {
this.denominator = denominator;
}
}
package edu.kirkwood.model;
public class Fraction implements Comparable<Fraction> {
// Code omitted
@Override
public int compareTo(Fraction o) {
return 0;
}
public static int gcd(int a, int b) {
return 0;
}
public static int lcm(int a, int b) {
return 0;
}
public void simplify() {
}
public String toMixedNumber() {
return "";
}
public Fraction add(Fraction other) {
return null;
}
public Fraction subtract(Fraction other) {
return null;
}
public Fraction multiply(Fraction other) {
return null;
}
public Fraction divide(Fraction other) {
return null;
}
}
Static methods are called like this:
Fraction.gcd(15, 6); // 3
Fraction.lcm(15, 6); // 30
lcm(15, 6) // "Fraction" can be omitted if called inside the Fraction class.
Non-static methods are called like this
Fraction f1 = new Fraction(15, 6);
f1.simplify(); // 5/2
f1.toMixedNumber(); // 2 1/2
f1.getNumerator(); // 5
f1.getDenominator(); // 2
Fraction f2 = new Fraction(1, 3);
f1.add(f2); // 2 5/6
package edu.kirkwood.model;
/**
* Represents a fraction with an integer numerator and denominator.
* This class provides methods for fraction arithmetic, simplification,
* and comparison.
*/
public class Fraction implements Comparable<Fraction> {
private int numerator;
private int denominator;
/**
* Default constructor.
* Initializes a new fraction to 1/1.
*/
public Fraction() {
this.numerator = 1;
this.denominator = 1;
}
/**
* Constructs a fraction with a specified numerator and denominator.
*
* @param numerator the numerator of the fraction
* @param denominator the denominator of the fraction
*/
public Fraction(int numerator, int denominator) {
this.numerator = numerator;
this.denominator = denominator;
}
/**
* Gets the numerator of the fraction.
*
* @return the numerator
*/
public int getNumerator() {
return numerator;
}
/**
* Sets the numerator of the fraction.
*
* @param numerator the new numerator
*/
public void setNumerator(int numerator) {
this.numerator = numerator;
}
/**
* Gets the denominator of the fraction.
*
* @return the denominator
*/
public int getDenominator() {
return denominator;
}
/**
* Sets the denominator of the fraction.
*
* @param denominator the new denominator
* @throws ArithmeticException if the denominator is zero
*/
public void setDenominator(int denominator) {
this.denominator = denominator;
}
/**
* Returns a string representation of the fraction in the format "numerator/denominator".
*
* @return a string representation of the fraction
*/
@Override
public String toString() {
// Implementation needed
return "";
}
/**
* Compares this fraction to another fraction.
*
* @param o the other Fraction to be compared.
* @return a negative integer, zero, or a positive integer.
*/
@Override
public int compareTo(Fraction o) {
// Implementation needed
return 0;
}
/**
* Calculates the greatest common divisor (GCD) of two integers.
*
* @param a the first integer
* @param b the second integer
* @return the greatest common divisor of a and b
*/
public static int gcd(int a, int b) {
// Implementation needed
return 0;
}
/**
* Calculates the least common multiple (LCM) of two integers.
*
* @param a the first integer
* @param b the second integer
* @return the least common multiple of a and b
*/
public static int lcm(int a, int b) {
// Implementation needed
return 0;
}
/**
* Simplifies this fraction to its lowest terms by dividing the numerator
* and denominator by their greatest common divisor.
*/
public void simplify() {
// Implementation needed
}
/**
* Converts this fraction to a mixed number string representation (e.g., "1 2/3").
* If the fraction is a proper fraction, it returns the fraction itself.
*
* @return a string representation of the fraction as a mixed number
*/
public String toMixedNumber() {
// Implementation needed
return "";
}
/**
* Adds another fraction to this fraction.
*
* @param other the fraction to add
* @return a new Fraction object representing the sum
*/
public Fraction add(Fraction other) {
// Implementation needed
return null;
}
/**
* Subtracts another fraction from this fraction.
*
* @param other the fraction to subtract
* @return a new Fraction object representing the difference
*/
public Fraction subtract(Fraction other) {
// Implementation needed
return null;
}
/**
* Multiplies this fraction by another fraction.
*
* @param other the fraction to multiply by
* @return a new Fraction object representing the product
*/
public Fraction multiply(Fraction other) {
// Implementation needed
return null;
}
/**
* Divides this fraction by another fraction.
*
* @param other the fraction to divide by (the divisor)
* @return a new Fraction object representing the quotient
* @throws IllegalArgumentException if the divisor is zero
*/
public Fraction divide(Fraction other) {
// Implementation needed
return null;
}
}
In IntelliJ, right-click the Fraction class title and choose "Show Context Options" then "Create Test".
JUnit 5 will be pre-selected, click the "Fix" button if shown.
Check the "setUp/@Before" box.
Click the check boxes next to all of the methods.
Click one method name, press Ctrl + A, press the spacebar
The FractionTest class is successfully added to the tests folder.
Fully-qualified annotations ("@org.junit.jupiter.api.BeforeEach" and "@org.junit.jupiter.api.Test") only need to be @BeforeEach and @Test.
If your annotations are fully-qualified press Ctrl + R to open the find and replace menu.
Replace all references of "@org.junit.jupiter.api." with "@".
Add an import statement for @BeforeEach and @Test.
Some Unit Tests require a default instance of the class.
The setUp method can be used to instantiate that object needed for each test.
Create two new Fraction objects as a private instance variables.
Instantiate the Fraction objects in the setUp method.
We want to keep our code DRY (Don't Repeat Yourself). If we don't use the setUp method we will have to instantiate a Fraction object inside of every single test method.
private Fraction f1;
private Fraction f2;
@BeforeEach
void setUp() {
f1 = new Fraction();
f2 = new Fraction(2, 3);
}
If you run the tests now, it should say all tests passed and you will see green checkmarks next to the method names in the bottom-left corner. We actually want all tests to fail by default.
Highlight a set of curly brackets on one of the tests.
Press Ctrl+Cmd+G (Mac) or Shift+Ctrl+Alt+J (Windows) to select all occurrences.
Use the arrow keys to position the cursors inside the curly brackets and type a fail method.
fail();
This is a static method from the Assertions class of JUnit.
Run the FractionTest class again to see that all tests failed. You will see orange X's next to the method names.
Study the assertEquals methods from JUnit's Assertions class.
Write tests for all of the getters and toString.
You should test all getter methods before setter methods so you can safely use the getter methods to verify that the setter methods work correctly.
When using the assertEquals method the first argument is the expected value and the second argument is the actual value.
The actual value will always come from a getter method.
@Test
void getNumerator() {
assertEquals(1, f1.getNumerator());
assertEquals(2, f2.getNumerator());
}
@Test
void getDenominator() {
assertEquals(1, f1.getDenominator());
assertEquals(3, f2.getDenominator());
}
@Test
void testToString() {
assertEquals("1/1", f1.toString());
assertEquals("2/3", f2.toString());
}
Next, we will write tests for all of the setters
For setNumerator, I am setting a positive, 0, and negative value. I am calling the getNumerator and toString methods to ensure equality.
For setDenominator, I am setting a positive, 0, and negative value. I am calling the getDenominator and toString methods to ensure equality.
An optional third parameter can be used for a message.
@Test
void setNumerator() {
f1.setNumerator(3);
assertEquals(3, f1.getNumerator());
assertEquals("3/1", f1.toString());
f1.setNumerator(0);
assertEquals(0, f1.getNumerator());
assertEquals("0/1", f1.toString());
f1.setNumerator(-3);
assertEquals(-3, f1.getNumerator());
assertEquals("-3/1", f1.toString());
}
@Test
void setDenominator() {
f1.setDenominator(3);
assertEquals(3, f1.getDenominator());
assertEquals("1/3", f1.toString());
f1.setDenominator(-3);
assertEquals(3, f1.getDenominator(), "Setting a negative denominator should change the numerator");
assertEquals(-1, f1.getNumerator());
assertEquals("-1/3", f1.toString(), "1/-3 should be -1/3");
f1.setDenominator(-3);
assertEquals("1/3", f1.toString(), "-1/-3 should be 1/3");
}
Because the denominator cannot be 0, we use assertThrows.
The first argument is the type of Exception you expect to be thrown, in this case ArithmeticException.
The second argument is a lambda expression that calls the abstract method from the Executable functional interface.
assertThrows returns a Throwable object of type T. In this case, a reasonable Throwable object to use is ArithmeticException. You can assert the Exception message.
@Test
void setDenominator() {
f1.setDenominator(3);
assertEquals(3, f1.getDenominator());
assertEquals("1/3", f1.toString());
assertThrows(ArithmeticException.class, () -> f1.setDenominator(0));
ArithmeticException e = assertThrows(ArithmeticException.class, () -> f1.setDenominator(0));
assertEquals("Denominator cannot be zero", e.getMessage());
f1.setDenominator(-3);
assertEquals(3, f1.getDenominator());
assertEquals("-1/3", f1.toString()); // 1/-3 should be -1/3
f1.setDenominator(-3);
assertEquals("1/3", f1.toString()); // -1/-3 should be 1/3
}
After writing the unit tests, go back to the regular class and implement the code that will get the test to pass.
public void setDenominator(int denominator) {
if(denominator == 0){
throw new ArithmeticException("Denominator cannot be zero");
}
if(denominator < 0 && numerator > 0 || denominator < 0 && numerator < 0) {
numerator *= -1;
denominator *= -1;
}
this.denominator = denominator;
}
Update the parameterized constructor to call the setter methods to validate the input, rather than potentially assigning invalid values to the attributes.
public Fraction(int numerator, int denominator) {
setNumerator(numerator);
setDenominator(denominator);
}
The following assertions seem logical, but they don't fully test it.
To correctly test the greatest common denominator of two integers, we must test for positive and negative values.
Call assertTrue by passing a single boolean expression.
@Test
void gcd() {
assertEquals(15, Fraction.gcd(75, 45));
assertEquals(2, Fraction.gcd(2, 4));
assertEquals(1, Fraction.gcd(5, 7));
int result1 = Fraction.gcd(5, 7);
int result2 = Fraction.gcd(-5, 7);
int result3 = Fraction.gcd(5, -7);
int result4 = Fraction.gcd(-5, -7);
assertTrue(result1 == result2 && result2 == result3 && result3 == result4);
}
@Test
void gcd() {
assertEquals(15, Fraction.gcd(75, 45));
assertEquals(2, Fraction.gcd(2, 4));
assertEquals(1, Fraction.gcd(5, 7));
}
We will use this solution.
If you run the unit test now, it will fail.
Update the Fraction class gcd method to return Math.abs(a).
public static int gcd(int a, int b) {
if (b == 0) {
return Math.abs(a);
}
return gcd(b,a % b);
}
To test the lcm
method, we should consider several cases:
Two standard positive integers.
Two prime numbers.
Cases where one number is a multiple of the other.
Cases involving the number 1
.
Edge cases, such as involving the number 0
.
We can break each case into a separate @Test method.
We can add a @DisplayName annotation to explain its purpose.
The fundamental rule of unit testing is "one test tests one thing." In the previous examples, if the first assertEquals
fails, the second test never runs, potentially hiding another bug.
@Test
@DisplayName("Test LCM with two positive integers")
void testLcmWithPositiveIntegers() {
assertEquals(24, Fraction.lcm(6, 8));
}
@Test
@DisplayName("Test LCM where one number is a multiple of the other")
void testLcmWithMultiple() {
assertEquals(12, Fraction.lcm(4, 12));
}
@Test
@DisplayName("Test LCM with two prime numbers")
void testLcmWithPrimes() {
// The lcm of two prime numbers is their product.
assertEquals(77, Fraction.lcm(7, 11));
}
@Test
@DisplayName("Test LCM with the number 1")
void testLcmWithOne() {
assertEquals(9, Fraction.lcm(1, 9));
assertEquals(9, Fraction.lcm(9, 1));
}
@Test
@DisplayName("Test LCM with identical numbers")
void testLcmWithIdenticalNumbers() {
assertEquals(5, Fraction.lcm(5, 5));
}
@Test
@DisplayName("Test LCM where one of the inputs is zero")
void testLcmWithZero() {
assertEquals(0, Fraction.lcm(10, 0));
assertEquals(0, Fraction.lcm(0, 10));
assertEquals(0, Fraction.lcm(0, 0));
}
We will use this solution.
The best approach is reduction by the GCD.
Add a simple if statement to get the test where one of the inputs is zero to pass.
Without it, the test will encounter an ArithmeticException because it cannot divide by 0.
public static int lcm(int a, int b) {
if (a == 0 || b == 0) {
return 0;
}
return a * (b / gcd(a, b));
}
@Test
void simplify() {
f1.setNumerator(75);
f1.setDenominator(45);
f1.simplify();
assertEquals("5/3",f1.toString());
f1.setNumerator(2);
f1.setDenominator(4);
f1.simplify();
assertEquals("1/2",f1.toString());
f1.setNumerator(5);
f1.setDenominator(7);
f1.simplify();
assertEquals("5/7",f1.toString());
}
@Test
void simplify() {
f1.setNumerator(75);
f1.setDenominator(45);
f1.simplify();
assertEquals("5/3",f1.toString());
f1.setNumerator(2);
f1.setDenominator(4);
f1.simplify();
assertEquals("1/2",f1.toString());
f1.setNumerator(5);
f1.setDenominator(7);
f1.simplify();
assertEquals("5/7",f1.toString());
f1.setNumerator(-2);
f1.setDenominator(4);
f1.simplify();
assertEquals("-1/2",f1.toString());
f1.setNumerator(2);
f1.setDenominator(-4);
f1.simplify();
assertEquals("-1/2",f1.toString());
f1.setNumerator(-2);
f1.setDenominator(-4);
f1.simplify();
assertEquals("1/2",f1.toString());
}
Find the greatest common divisor of the Fraction object's numerator and denominator.
Update the numerator to be the current numerator divided by the greatest common divisor
Update the denominator to be the current denominator divided by the greatest common divisor.
public void simplify() {
int gcd = gcd(numerator, denominator);
numerator /= gcd;
denominator /= gcd;
}
Below is a set of assertions to test the toMixedNumber method.
@Test
void toMixedNumber() {
assertEquals("1", f1.toMixedNumber(), "1/1 should be 1");
assertEquals("2/3", f2.toMixedNumber(), "2/3 should be 2/3");
f2.setNumerator(0);
assertEquals("0", f2.toMixedNumber(), "0/3 should be 0");
f1.setNumerator(7);
f1.setDenominator(4);
assertEquals("1 3/4", f1.toMixedNumber(), "7/4 should be 1 3/4");
f1.setNumerator(-7);
assertEquals("-1 3/4", f1.toMixedNumber(), "-7/4 should be -1 3/4");
}
Below is a possible implementation of the toMixedNumber method.
public String toMixedNumber() {
simplify();
if(denominator == 1) {
return numerator + "";
} else if(Math.abs(numerator) > denominator) {
int wholeNumber = Math.abs(numerator) / denominator;
int remainder = Math.abs(numerator) % denominator;
return (numerator < 0 ? "-" : "") + wholeNumber + " " + remainder + "/" + denominator;
} else if(numerator == 0) {
return "0";
} else {
return toString();
}
}
@Test
@DisplayName("Test 1/1 + 2/3 = 5/3")
void addWholeNumberToFraction() {
Fraction result = f1.add(f2);
assertEquals(5, result.getNumerator());
assertEquals(3, result.getDenominator());
}
@Test
@DisplayName("Test -1/4 + 2/3 = 5/12")
void addNegativeFractionToPositive() {
f1 = new Fraction(25, -100); // Represents -1/4
f2 = new Fraction(-10, -15); // Represents 2/3
Fraction result = f1.add(f2);
assertEquals(5, result.getNumerator());
assertEquals(12, result.getDenominator());
}
@Test
@DisplayName("Test 1/4 + 1/4 = 1/2")
void addFractionsThatNeedSimplification() {
f1 = new Fraction(1, 4);
f2 = new Fraction(1, 4);
Fraction result = f1.add(f2);
assertEquals(1, result.getNumerator());
assertEquals(2, result.getDenominator());
}
public Fraction add(Fraction other) {
int newNumerator = this.numerator * other.denominator + this.denominator * other.numerator;
int newDenominator = this.denominator * other.denominator;
Fraction result = new Fraction(newNumerator, newDenominator);
result.simplify();
return result;
}
flowchart TD
A@{ shape: circle, label: "Start" } --> B@{ shape: lean-r, label: "Input Fraction 1 (n1/d1)<br>Input Fraction 2 (n2/d2)" };
B --> C@{ shape: rect, label: "Set new numerator by cross multiplying<br>(n1 * d2 + d1 * n2)" };
C --> D@{ shape: rect, label: "Set new denominator<br>(d1 * d2)" };
D --> E@{ shape: rect, label: "Display result as a simplified mixed number" };
E --> F@{ shape: circle, label: "End" };
@Test
@DisplayName("Test 1/1 is greater than 2/3")
void compareToGreaterThan() {
assertTrue(f1.compareTo(f2) > 0);
assertTrue(f2.compareTo(f1) < 0);
}
@Test
@DisplayName("Test 2/3 and 4/6 are equal")
void compareToEquals() {
f1 = new Fraction(4, 6);
assertEquals(0, f1.compareTo(f2));
assertEquals(0, f1.compareTo(f1), "A fraction compared to itself should be equal");
}
@Test
@DisplayName("Test 1/3 is less than 2/3")
void compareToLessThan() {
f1 = new Fraction(1, 3);
assertTrue(f1.compareTo(f2) < 0);
assertTrue(f2.compareTo(f1) > 0);
}
@Test
@DisplayName("Test comparison with negative fractions (-1/2 vs 1/4)")
void compareToWithNegativeFractions() {
Fraction f1 = new Fraction(-1, 2);
Fraction f2 = new Fraction(1, 4);
Fraction f3 = new Fraction(-3, 4);
assertTrue(f1.compareTo(f2) < 0, "-1/2 is less than 1/4");
assertTrue(f2.compareTo(f1) > 0, "1/4 is greater than -1/2");
assertTrue(f3.compareTo(f1) < 0, "-3/4 is less than -1/2");
}
n1/d1
and n2/d2
) is to use cross-multiplication. n1/d1
vs n2/d2
is equivalent to comparing the integer results of n1 * d2
vs n2 * d1
.@Override
public int compareTo(Fraction o) {
long thisNumerator = (long)this.numerator;
long thisDenominator = (long)this.denominator;
long otherNumerator = (long)o.numerator;
long otherDenominator = (long)o.denominator;
long a = thisNumerator * otherDenominator;
long b = otherNumerator * thisDenominator;
return Long.compare(a, b);
}
Create a new package: "edu.kirkwood.view".
In the "view" package, create three classes that were used in the Java 1 project: "UserInput", "UIUtility", and "Helpers".
The UserInput class contains overloaded methods to get integers, Strings, booleans, doubles, and dates from the user via keyboard input.
package edu.kirkwood.view;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Scanner;
import static edu.kirkwood.view.Helpers.formatDateLong;
import static edu.kirkwood.view.Helpers.isValidString;
import static edu.kirkwood.view.UIUtility.displayError;
public class UserInput {
private static Scanner scanner = new Scanner(System.in);
public static Integer getInt(String prompt) {
return getInt(prompt, true, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
public static Integer getInt(String prompt, boolean required) {
return getInt(prompt, required, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
public static Integer getInt(String prompt, boolean required, int min) {
return getInt(prompt, required, min, Integer.MAX_VALUE);
}
public static int getInt(String prompt, boolean required, int min, int max) {
int value = 0;
String minMax = "";
// if min is set and max is not set
if(min != Integer.MIN_VALUE && max == Integer.MAX_VALUE) {
minMax = String.format(" [minimum %d]", min);
}
// if min and max are both set
if(min != Integer.MIN_VALUE && max != Integer.MAX_VALUE) {
minMax = String.format(" [between %d and %d]", min, max);
}
while(true) {
System.out.print(prompt + minMax + (required ? " (*)" : "") + ": ");
String valueStr = scanner.nextLine();
try {
value = Integer.parseInt(valueStr);
} catch (NumberFormatException e) {
if(!required) {
return Integer.MIN_VALUE;
} else {
displayError("Invalid integer");
continue;
}
}
if(value < min) {
displayError("Value too low");
} else if(value > max) {
displayError("Value too high");
} else {
break;
}
}
return value;
}
public static String getString(String prompt) {
return getString(prompt, true);
}
public static String getString(String prompt, boolean required) {
String value = "";
while(true) {
System.out.print(prompt + (required ? " (*)" : "") + ": ");
value = scanner.nextLine().trim();
if(required && !isValidString(value)) {
displayError("Input required");
} else {
break;
}
}
return value;
}
public static boolean getBoolean(String prompt) {
return getBoolean(prompt, true);
}
public static boolean getBoolean(String prompt, boolean required) {
boolean value = true;
while(true) {
String valueStr = getString(prompt + " [y/n]", required);
if(required && !(valueStr.equalsIgnoreCase("y") ||
valueStr.equalsIgnoreCase("n") ||
valueStr.equalsIgnoreCase("yes") ||
valueStr.equalsIgnoreCase("no"))
) {
displayError("Invalid input");
} else {
value = valueStr.equalsIgnoreCase("y") || valueStr.equalsIgnoreCase("yes");
break;
}
}
return value;
}
public static double getDouble(String prompt) {
return getDouble(prompt, true, -Double.MAX_VALUE, Double.MAX_VALUE);
}
public static double getDouble(String prompt, boolean required) {
return getDouble(prompt, required, -Double.MAX_VALUE, Double.MAX_VALUE);
}
public static double getDouble(String prompt, boolean required, int min) {
return getDouble(prompt, required, min, Double.MAX_VALUE);
}
public static double getDouble(String prompt, boolean required, double min, double max) {
double value = 0;
String minMax = "";
// if min is set and max is not set
if(min != -Double.MAX_VALUE && max == Double.MAX_VALUE) {
minMax = String.format(" [minimum %.1f]", min);
}
// if min and max are both set
if(min != -Double.MAX_VALUE && max != Double.MAX_VALUE) {
minMax = String.format(" [between %.1f and %.1f]", min, max);
}
while(true) {
System.out.print(prompt + minMax + (required ? " (*)" : "") + ": ");
String valueStr = scanner.nextLine();
try {
value = Double.parseDouble(valueStr);
} catch (NumberFormatException e) {
if(!required) {
return -Double.MAX_VALUE;
} else {
displayError("Invalid number");
continue;
}
}
if(value < min) {
displayError("Value too low");
} else if(value > max) {
displayError("Value too high");
} else {
break;
}
}
return value;
}
public static LocalDate getDate(String prompt) {
return getDate(prompt, true);
}
public static LocalDate getDate(String prompt, boolean required) {
LocalDate date = null;
while(true) {
String dateStr = getString(prompt + " [MM/DD/YYYY]", required);
try {
DateTimeFormatter dateFormatInput = DateTimeFormatter.ofPattern("M/d/yyyy");
date = LocalDate.parse(dateStr, dateFormatInput);
break;
} catch(DateTimeParseException e) {
if(!required) {
return LocalDate.MIN;
} else {
displayError("Invalid date");
}
}
}
return date;
}
}
The UIUtility class contains methods to display messages, warnings, print menus, prompt the user to press enter to continue, etc.
package edu.kirkwood.view;
public class UIUtility {
public static void displayMessage(String message) {
displayMessage(message, "");
}
public static void displayMessage(String message, String type) {
System.out.printf("*** %s%s ***\n", (!type.equals("") ? type.toUpperCase() + " - " : ""), message);
}
public static void displayError(String message) {
displayMessage(message, "error");
}
public static void displayWarning(String message) {
displayMessage(message, "warning");
}
public static void displaySuccess(String message) {
displayMessage(message, "success");
}
public static void pressEnterToContinue() {
UserInput.getString("Press enter to continue", false);
}
public static void printLine() {
printLine(40);
}
public static void printLine(int length) {
for (int i = 0; i < length; i++) {
System.out.print("-");
}
System.out.println();
}
public static void printMenu(String title, String[] menuItems) {
System.out.println();
printLine();
displayMessage(title);
for (int i = 0; i < menuItems.length; i++) {
System.out.println((i + 1) + ") " + menuItems[i]);
}
printLine();
}
public static String separator(int[] columnWidths) {
StringBuilder sb = new StringBuilder();
for (int width : columnWidths) {
sb.append("+ ");
for (int i = 0; i < width; i++) {
sb.append('-');
}
sb.append(' ');
}
sb.append("+");
return sb.toString();
}
}
The Helpers class contains methods to perform a variety of tasks.
The only method we technically need for this project is "isValidString" to check that input is neither null or an empty string.
package edu.kirkwood.view;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Helpers {
public static boolean isValidString(String str) {
return str != null && !str.equals("");
}
public static String round(double number, int numDecPlaces) {
BigDecimal bigDecimal = new BigDecimal(Double.toString(number));
bigDecimal = bigDecimal.setScale(numDecPlaces, RoundingMode.HALF_UP).stripTrailingZeros();
return bigDecimal.toString();
}
public static String toCurrency(double amt) {
NumberFormat formatter = NumberFormat.getCurrencyInstance();
return formatter.format(amt);
}
public static String formatDateLong(LocalDate date) {
DateTimeFormatter dateFormatOutput = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);
return dateFormatOutput.format(date);
}
public static String formatDateShort(LocalDate date) {
DateTimeFormatter dateFormatOutput = DateTimeFormatter.ofPattern("M/d/yyyy");
return dateFormatOutput.format(date);
}
public static boolean isDateInThePast(LocalDate date) {
if (date == null) {
throw new IllegalArgumentException("Date cannot be null");
}
return date.isBefore(LocalDate.now()); // Check if the date is before today
}
public static boolean isDateInRange(LocalDate date, LocalDate startDate, LocalDate endDate) {
if (date == null || startDate == null || endDate == null) {
throw new IllegalArgumentException("None of the dates can be null");
}
return (date.isEqual(startDate) || date.isAfter(startDate)) &&
(date.isEqual(endDate) || date.isBefore(endDate));
}
}
Inside the "view" package, create a class called "Messages".
Use this class to write messages to greet and say goodbye to the user.
package edu.kirkwood.view;
import static edu.kirkwood.view.UIUtility.displayMessage;
public class Messages {
public static void hello() {
displayMessage("Welcome to the Kirkwood Calculators Application");
}
public static void goodbye() {
displayMessage("Goodbye");
}
public static void fractionGreet() {
displayMessage("Welcome to Marc's Fraction Calculator");
System.out.println("Enter calculations in the format: [fraction] [operator] [fraction]");
System.out.println("Example: 1 1/2 + 3/4\n");
}
public static void fractionGoodbye() {
displayMessage("Thank you for using Marc's Fraction Calculator");
}
}
Inside the "view" package, create a class called MainMenu.
Add a show method with the following implementation.
In Java 1, we implemented a Menu interface, we won't do that in this example since we will have only one Menu.
package edu.kirkwood.view;
import edu.kirkwood.controller.FractionCalculator;
import static edu.kirkwood.view.UIUtility.printMenu;
import static edu.kirkwood.view.UserInput.getInt;
public class MainMenu {
public static void show() {
String[] menuItems = {"Marc's Fraction Calculator", "Student's Math Calculator", "Quit"};
while(true) {
printMenu("Main Menu", menuItems);
int choice = getInt("Choose an option", false,1, menuItems.length);
switch(choice) {
case 1:
break;
case 2:
break;
default:
return;
} // end switch
} // end loop
} // end show method
}
Inside the "edu.kirkwood" package, create a class called CalculatorApp.
This class will contain the main method that starts our program.
You can run the program to test its functionality.
package edu.kirkwood;
import edu.kirkwood.view.MainMenu;
import static edu.kirkwood.view.Messages.*;
public class CalculatorApp {
public static void main(String[] args) {
hello();
MainMenu.show();
goodbye();
}
}
Create a new package: "edu.kirkwood.controller".
In the "controller" package, create a class called "FractionCalculator".
Add a static start() method.
package edu.kirkwood.controller;
import static edu.kirkwood.view.Messages.*;
public class FractionCalculator {
public static void start() {
fractionGreet();
fractionGoodbye();
}
}
Call the start method in the Main Menu class.
case 1:
FractionCalculator.start();
break;
git init -b main
git checkout -b main
git remote add origin https://github.com/YOUR-USERNAME/java-calculators.git
git add .
git commit -m "Your message"
git config --global user.name "YOUR FULL NAME"
git config --global user.email "YOUR EMAIL ADDRESS"
git config --global credential.helper cache