Musings about Java and Swing

Amortization Using A POJO

A couple of months ago, I ran across a Stack Overflow question about some problems a student was having with an amortization program. The program was unusual in that instead of calculating the monthly payment from a term of 36 months or 60 months, the program calculated the term from the monthly payments.

Let me show you an example output from the program so you see what I mean.

What is your principal amount? 5000
What is your yearly interest rate percentage? 6.5
What is your monthly payment? 500
What is your first payment date (mm/dd/yyyy)? 12/5/2016

Payment Date                  Interest         Principal           Payment       New Balance
--------------------   ---------------   ---------------   ---------------   ---------------
December 5, 2016                $27.09           $472.91           $500.00         $4,527.09

January 5, 2017                 $24.53           $475.47           $500.00         $4,051.62
February 3, 2017                $21.95           $478.05           $500.00         $3,573.57
March 3, 2017                   $19.36           $480.64           $500.00         $3,092.93
April 5, 2017                   $16.76           $483.24           $500.00         $2,609.69
May 5, 2017                     $14.14           $485.86           $500.00         $2,123.83
June 5, 2017                    $11.51           $488.49           $500.00         $1,635.34
July 5, 2017                     $8.86           $491.14           $500.00         $1,144.20
August 4, 2017                   $6.20           $493.80           $500.00           $650.40
September 5, 2017                $3.53           $496.47           $500.00           $153.93
October 5, 2017                  $0.84           $153.93           $154.77             $0.00

This could be useful to a used car lot, where the person can name their payment, and as long as the term is reasonable, the owner of the used car lot can finance the car purchase.

Now, the student program didn’t print the amortization in a nice table. It didn’t calculate the payment dates. I added those features.

The student program was one monolithic block of code in the main method. The problem with breaking the code into methods was that there were 6 values that are calculated for each monthly payment. A Java method returns one value. So, what do you do?

You put the 6 values in a plain old Java object. Then an instance of the Java object can be returned from a method.

Here are the changes I made to the student amortization program.

  1. I changed the output to a table to save space. I wrote some methods to create the padding for the dates and the amounts.
  2. I added the payment dates. I changed the date to a Friday if the payment falls on a Saturday or a Sunday.
  3. I created a model object to hold the initial amortization values and the calculated values. This allowed me to put the Scanner input and the table output into methods.
  4. I moved everything out of the main method except the instantiation of the main class.

Here’s my version of the Amortization class. I put the AmortizationModel class inside the Amortization class to make it easier to paste in this article.

package com.ggl.testing;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Scanner;

public class Amortization implements Runnable {

	public static void main(String[] args) {
		new Amortization().run();
	}

	private boolean firstTimeSwitch = true;

	@Override
	public void run() {
		AmortizationModel model;
		try {
			model = getInputValues();
		} catch (ParseException e) {
			e.printStackTrace();
			return;
		}

		while (model.getBalance() > 0) {
			model.calculateMonthlyInterest();

			if (model.getActualPayment() <= model.getCalculatedInterest()) {
				System.err.println("The interest "
						+ format(model.getCalculatedInterest())
						+ " is greater than or equal to the payment "
						+ format(model.getActualPayment()));
				return;
			}

			Calendar paymentDate = model.getAdjustedPaymentDate();
			displayMonthlyValues(model, paymentDate);
			model.incrementPaymentDate();
		}

	}

	private AmortizationModel getInputValues() throws ParseException {
		SimpleDateFormat sdfInput = new SimpleDateFormat("M/d/yyyy");

		Scanner input = new Scanner(System.in);

		System.out.print("What is your principal amount? ");
		double principal = input.nextDouble();
		System.out.print("What is your yearly interest rate percentage? ");
		double rate = input.nextDouble() / 1200D;
		System.out.print("What is your monthly payment? ");
		double payment = input.nextDouble();
		System.out.print("What is your first payment date (mm/dd/yyyy)? ");
		input.nextLine();
		String dateString = input.nextLine().trim();
		Calendar paymentDate = Calendar.getInstance();
		paymentDate.setTime(sdfInput.parse(dateString));
		System.out.println();

		input.close();

		AmortizationModel model = new AmortizationModel(principal, rate,
				payment);
		model.setPaymentDate(paymentDate);

		return model;
	}

	private void displayMonthlyValues(AmortizationModel model,
			Calendar paymentDate) {
		SimpleDateFormat sdfOutput = new SimpleDateFormat("MMMM d, yyyy");

		if (firstTimeSwitch) {
			System.out.println(leftJustify("Payment Date", 20)
					+ rightJustify("Interest", 18)
					+ rightJustify("Principal", 18)
					+ rightJustify("Payment", 18)
					+ rightJustify("New Balance", 18));
			String dashes = "   " + expand("-", 15);
			System.out.println(expand("-", 20) + dashes + dashes + dashes
					+ dashes);
			firstTimeSwitch = false;
		}

		System.out.println(leftJustify(sdfOutput.format(paymentDate.getTime()),
				20)
				+ rightJustify(format(model.getCalculatedInterest()), 18)
				+ rightJustify(format(model.getPrincipalAmount()), 18)
				+ rightJustify(format(model.getActualPayment()), 18)
				+ rightJustify(format(model.getBalance()), 18));

		if (paymentDate.get(Calendar.MONTH) == Calendar.DECEMBER) {
			System.out.println();
		}
	}

	private String format(int cents) {
		double amount = 0.01D * cents;
		return "$" + String.format("%,.2f", amount);
	}

	private String leftJustify(String text, int length) {
		StringBuilder builder = new StringBuilder(length);
		builder.append(text);

		for (int i = text.length(); i < length; i++) {
			builder.append(" ");
		}

		return builder.toString();
	}

	private String rightJustify(String text, int length) {
		StringBuilder builder = new StringBuilder(length);

		for (int i = text.length(); i < length; i++) {
			builder.append(" ");
		}

		builder.append(text);
		return builder.toString();
	}

	private String expand(String character, int length) {
		StringBuilder builder = new StringBuilder(length);

		for (int i = 0; i < length; i++) {
			builder.append(character);
		}

		return builder.toString();
	}

	public class AmortizationModel {
		private final double principal;
		private final double rate;
		private final double payment;

		private int balance;
		private int paymentCents;
		private int calculatedInterest;
		private int principalAmount;
		private int actualPayment;

		private Calendar paymentDate;

		public AmortizationModel(double principal, double rate, double payment) {
			this.principal = principal;
			this.rate = rate;
			this.payment = payment;

			this.balance = (int) Math.round(principal * 100D);
			this.paymentCents = (int) Math.round(payment * 100D);
		}

		public void calculateMonthlyInterest() {
			calculatedInterest = (int) Math.ceil(rate * balance);
			principalAmount = paymentCents - calculatedInterest;
			actualPayment = calculatedInterest + principalAmount;
			int newBalance = balance - principalAmount;

			if (newBalance < 0) {
				principalAmount = balance;
				actualPayment = calculatedInterest + principalAmount;
				balance = 0;
			} else {
				balance = newBalance;
			}
		}

		public int getBalance() {
			return balance;
		}

		public int getPaymentCents() {
			return paymentCents;
		}

		public int getCalculatedInterest() {
			return calculatedInterest;
		}

		public int getPrincipalAmount() {
			return principalAmount;
		}

		public int getActualPayment() {
			return actualPayment;
		}

		public Calendar getAdjustedPaymentDate() {
			Calendar newPaymentDate = (Calendar) paymentDate.clone();

			if (newPaymentDate.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY) {
				newPaymentDate.add(Calendar.DAY_OF_MONTH, -1);
			} else if (newPaymentDate.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
				newPaymentDate.add(Calendar.DAY_OF_MONTH, -2);
			}

			return newPaymentDate;
		}

		public Calendar getPaymentDate() {
			return paymentDate;
		}

		public void setPaymentDate(Calendar paymentDate) {
			this.paymentDate = paymentDate;
		}

		public void incrementPaymentDate() {
			paymentDate.add(Calendar.MONTH, 1);
		}

		public double getPrincipal() {
			return principal;
		}

		public double getPayment() {
			return payment;
		}

	}

}

By using a model getter / setter POJO class, you can break up any monolithic code into nice size modules.

Post a Comment

Your email is kept private. Required fields are marked *