4.3 Tutorial 5
Task 1: Consider project better-ticket-machine from chapter 2 of the book Objects First with Java: A Practical Introduction using BlueJ (Barnes & Kölling). Modify the class TicketMachine
to include pre- and post-conditions for all methods. Add those specifications as part of the documentation and using exceptions instead of assertions. Propose a class invariant, add it as part of the documentation and implement its validation as a repOk
method.
/**
* TicketMachine models a ticket machine that issues
* flat-fare tickets.
* The price of a ticket is specified via the constructor.
* Instances will check to ensure that a user only enters
* sensible amounts of money, and will only print a ticket
* if enough money has been input.
*
* @author David J. Barnes and Michael Kölling
* @version 2016.02.29
*/
public class TicketMachine
{
// The price of a ticket from this machine.
private int price;
// The amount of money entered by a customer so far.
private int balance;
// The total amount of money collected by this machine.
private int total;
/**
* Create a machine that issues tickets of the given price.
* <li> cost must greater than 0 <li/>
*/
public TicketMachine(int cost)
{
if(cost <= 0)
{
throw new IllegalArgumentException("The cost: " + cost + "is not greater than 0");
}
price = cost;
balance = 0;
total = 0;
if(!repOk())
{
throw new IllegalStateException("The value of price or balance or total is invalid");
}
}
/**
* @return The price of a ticket.
*/
public int getPrice()
{
return price;
}
/**
* @return The amount of money already inserted for the
* next ticket.
*/
public int getBalance()
{
return balance;
}
/**
* Receive an amount of money from a customer.
* Check that the amount is sensible.
* <li> Amount should be a positive number <li/>
*/
public void insertMoney(int amount)
{
if(amount <= 0)
{
throw new IllegalArgumentException("Amount: " + amount + "is not a positive number");
}
else
{
balance = balance + amount;
}
if(!repOk())
{
throw new IllegalStateException("Error, the balance becomes negative");
}
}
/**
* Print a ticket if enough money has been inserted, and
* reduce the current balance by the ticket price. Print
* an error message if more money is required.
*/
public void printTicket()
{
if(balance >= price) {
// Simulate the printing of a ticket.
System.out.println("##################");
System.out.println("# The BlueJ Line");
System.out.println("# Ticket");
System.out.println("# " + price + " cents.");
System.out.println("##################");
System.out.println();
// Update the total collected with the price.
total = total + price;
// Reduce the balance by the price.
balance = balance - price;
}
else {
System.out.println("You must insert at least: " +
(price - balance) + " more cents.");
}
if(!repOk())
{
throw new IllegalStateException("The total or balance becomes invalid");
}
}
/**
* @return the money in the balance.
* The balance is cleared.
*/
public int refundBalance()
{
int amountToRefund;
amountToRefund = balance;
balance = 0;
return amountToRefund;
}
/**
* @return the money in the Total.
* The total is cleared.
*/
public int emptyMachine()
{
int originTotal = total;
total = 0;
return originTotal;
}
/**
* Check whether the representation is valid.
* @return (price && balance && total is non-negative)
*/
public boolean repOk()
{
if(price <= 0 || balance < 0 || total < 0)
{
return false;
}
return true;
}
}
Task 2: Consider project clock-display from chapter 3 of the book Objects First with Java: A Practical Introduction using BlueJ (Barnes & Kölling). Modify classes ClockDisplay
and NumberDisplay
with pre- and post-conditions for all methods. Add these specifications as part of the documentation, using exceptions instead of assertions to check them. Propose a class invariant for both classes, add them as part of the documentation and implement its validation as a repOk
method.
/**
* The NumberDisplay class represents a digital number display that can hold
* values from zero to a given limit. The limit can be specified when creating
* the display. The values range from zero (inclusive) to limit-1. If used,
* for example, for the seconds on a digital clock, the limit would be 60,
* resulting in display values from 0 to 59. When incremented, the display
* automatically rolls over to zero when reaching the limit.
*
* @author Michael Kölling and David J. Barnes
* @version 2016.02.29
*/
public class NumberDisplay
{
private int limit;
private int value;
/**
* Constructor for objects of class NumberDisplay.
* Set the limit at which the display rolls over.
* <li> rollOverLimit should be greater than 0 <li/>
*/
public NumberDisplay(int rollOverLimit)
{
if(rollOverLimit <= 0)
{
throw new IllegalArgumentException("rollOverLimit: " + rollOverLimit
+ "should be greater than 0");
}
limit = rollOverLimit;
value = 0;
if(!repOk())
{
throw new IllegalStateException("Error, the limit has been changed to non-positive number");
}
}
/**
* @return the current value.
*/
public int getValue()
{
return value;
}
/**
* @return the display value (that is, the current value as a two-digit
* String. If the value is less than ten, it will be padded with a leading
* zero).
* <li> value should in [0, limit) </li>
*/
public String getDisplayValue()
{
if(value < 0 || value >= limit)
{
throw new IllegalArgumentException("The value: " + value + "should" +
"less than limit and greater or equal than zero");
}
if(value < 10) {
return "0" + value;
}
else {
return "" + value;
}
}
/**
* Set the value of the display to the new specified value. If the new
* value is less than zero or over the limit, do nothing.
* <li> replacementValue should in [0, limit) <li/>
*/
public void setValue(int replacementValue)
{
if((replacementValue < 0) || (replacementValue >= limit)) {
throw new IllegalArgumentException("ReplacementValue should in [0, limit)");
}
else
{
value = replacementValue;
}
if(!repOk())
{
throw new IllegalStateException("The value has been non-positive, error");
}
}
/**
* Increment the display value by one, rolling over to zero if the
* limit is reached.
*/
public void increment()
{
value = (value + 1) % limit;
if(!repOk())
{
throw new IllegalStateException("The value has been non-positive, error");
}
}
/**
* Check whether the representation is valid.
* @return (limit is non-positive && value greater or equal to limit && value is negative)
*/
public boolean repOk()
{
if(limit <= 0)
{
return false;
}
else if(value >= limit || value < 0)
{
return false;
}
return true;
}
}
Task 3: Consider project music-organizer-v2 from chapter 4 of the book Objects First with Java: A Practical Introduction using BlueJ (Barnes & Kölling). Modify classes MusicOrganizer
and Track
with pre- and post-conditions for all methods. Add these specifications as part of the documentation, using exceptions instead of assertions. Propose a class invariant for both classes, add them as part of the documentation and implement its validation as a repOk
method.
/**
* Store the details of a music track,
* such as the artist, title, and file name.
*
* @author David J. Barnes and Michael Kölling
* @version 2016.02.29
*/
public class Track
{
// The artist.
private String artist;
// The track's title.
private String title;
// Where the track is stored.
private String filename;
/**
* Constructor for objects of class Track.
* @param artist The track's artist.
* @param title The track's title.
* @param filename The track file.
* <li>
* artist, title, filename cannot be null
* artist, title, filename cannot be empty
* </li>
*/
public Track(String artist, String title, String filename)
{
if(artist.trim().isEmpty() || title.trim().isEmpty() || filename.trim().isEmpty())
{
throw new IllegalArgumentException("Artist, title, filename cannot be empty!");
}
else if(artist == null || title == null || filename == null)
{
throw new IllegalArgumentException("Artist, title, filename cannot be null!");
}
setDetails(artist, title, filename);
if(!repOk())
{
throw new IllegalStateException("Artisit or title or filename becomes invalid");
}
}
/**
* Constructor for objects of class Track.
* It is assumed that the file name cannot be
* decoded to extract artist and title details.
* @param filename The track file.
* <li>
* filename cannot be null, filename cannot be empty
* </li>
*/
public Track(String filename)
{
if(filename.trim().isEmpty())
{
throw new IllegalArgumentException("filename cannot be empty!");
}
else if(filename == null)
{
throw new IllegalArgumentException("filename cannot be null!");
}
setDetails("unknown", "unknown", filename);
if(!repOk())
{
throw new IllegalStateException("filename becomes invalid");
}
}
/**
* Return the artist.
* @return The artist.
*/
public String getArtist()
{
return artist;
}
/**
* Return the title.
* @return The title.
*/
public String getTitle()
{
return title;
}
/**
* Return the file name.
* @return The file name.
*/
public String getFilename()
{
return filename;
}
/**
* Return details of the track: artist, title and file name.
* @return The track's details.
*/
public String getDetails()
{
return artist + ": " + title + " (file: " + filename + ")";
}
/**
* Set details of the track.
* @param artist The track's artist.
* @param title The track's title.
* @param filename The track file.
* <li>
* artist, title, filename cannot be null
* artist, title, filename cannot be empty
* </li>
*/
public void setDetails(String artist, String title, String filename)
{
if(artist.trim().isEmpty() || title.trim().isEmpty() || filename.trim().isEmpty())
{
throw new IllegalArgumentException("Artist, title, filename cannot be empty!");
}
else if(artist == null || title == null || filename == null)
{
throw new IllegalArgumentException("Artist, title, filename cannot be null!");
}
this.artist = artist;
this.title = title;
this.filename = filename;
if(!repOk())
{
throw new IllegalStateException("Artist, title or filename become invalid");
}
}
/**
* Check whether the representation is valid.
* @return (whether artist && title && filename arenot null && artist && title && filename arenot empty
*/
public boolean repOk()
{
if(artist.trim().isEmpty() || title.trim().isEmpty() || filename.trim().isEmpty())
{
return false;
}
if(artist == null || title == null || filename == null)
{
return false;
}
return true;
}
}
Arrays and loops
Task 4: Consider the accompanying code utils. Complete the implementations for all methods.
Task 5: Consider if the class should be divided to increase cohesion, do it if you consider it necessary.
Task 6: Should methods require the creation of an object? Justify your answer.
Task 7: If the answer for the previous question was negative, made any necessary modifications.
Task 8: Make a new class to test all methods, try difference scenarios for each.
NumberUtils.java
import java.util.ArrayList;
import java.util.Arrays;
public class NumberUtils
{
public boolean isPrime(int n) {
if(n <= 0) {
throw new IllegalArgumentException("The number must be positive");
}
boolean isPrime = true;
if(n == 1) {
return false;
}
else {
for(int divisor = 2; divisor < n; divisor++) {
if(n % divisor == 0) {
isPrime = false;
}
}
}
return isPrime;
}
public int nthFib(int n) {
if(n <= 0) {
throw new IllegalArgumentException("The number must be positive");
}
if(n == 1) {
return 0;
}
if(n == 2) {
return 1;
}
else {
return nthFib(n - 1) + nthFib(n - 2);
}
}
public ArrayList<Integer> digits(int n) {
if(n < 0) {
throw new IllegalArgumentException("The number must be greater or equal than zero");
}
ArrayList<Integer> digits = new ArrayList<>();
if(n == 0) {
digits.add(0);
}
else {
int n2 = n;
int digit;
while(n2 != 0) {
digit = n2 % 10;
digits.add(digit);
n2 /= 10;
}
}
return digits;
}
ArrayUtils.java
import java.util.ArrayList;
/**
* This class offers some basic array routines.
*
* @author N. Aguirre
* @version 0.2
*/
public class ArrayUtils {
/**
* Returns the minimum value in a non-empty array of floats.
* preconditions:
* <ul>
* <li>The array cannot be {@code null}, nor empty.</li>
* </ul>
* @param values the array of floats.
* @return {@code m} such that {@code m in values} and there is no {@code n in values}
* such that {@code n < m}.
*/
public static float minimum(float[] values) {
if(values == null || values.length == 0){
throw new IllegalArgumentException("The array cannot be null, nor empty");
}
float min = values[0];
for(int i = 1; i < values.length; i++){
if(values[i] < min){
min = values[i];
}
}
return min;
}
/**
* Returns the maximum value in a non-empty array of floats.
* preconditions:
* <ul>
* <li>The array cannot be {@code null}, nor empty.</li>
* </ul>
* @param values the array of floats.
* @return {@code m} such that {@code m in values} and there is no {@code n in values}
* such that {@code n > m}.
*/
public static float maximum(float[] arreglo) {
if(arreglo == null || arreglo.length == 0){
throw new IllegalArgumentException("The array cannot be null, nor empty");
}
float max = arreglo[0];
for(int i = 1; i < arreglo.length; i++){
if(arreglo[i] > max){
max = arreglo[i];
}
}
return max;
}
/**
* Returns the first n values of the Fibonacci sequence.
* preconditions:
* <ul>
* <li>n must be greater or equal than zero (0).</li>
* </ul>
* @param n a value that determines the first n values of the Fibonacci sequence to return.
* @return the first {@code n} elements of the Fibonacci sequence.
*/
public static int[] fibSequence(int n) {
if(n < 0){
throw new IllegalArgumentException("n must be greater or equal than zero");
}
//!!!Note the base case
if (n == 0) {
return new int[0];
}
if (n == 1) {
return new int[]{0};
}
int[] fibSeq = new int[n];
fibSeq[0] = 0;
fibSeq[1] = 1;
for(int i = 2; i < n; i++){
fibSeq[i] = fibSeq[i - 1] + fibSeq[i - 2];
}
return fibSeq;
}
/**
* Returns the average value of a non-empty array of floats.
* preconditions:
* <ul>
* <li>The array cannot be {@code null}, nor empty.</li>
* </ul>
* @param values the array of floats.
* @return the average value for {@code values}.
*/
public static float average(float[] values) {
if(values == null || values.length == 0){
throw new IllegalArgumentException("The array cannot be null, nor empty");
}
float avg = 0;
for(int i = 0; i < values.length; i++){
avg += values[i];
}
avg /= values.length;
return avg;
}
/**
* Returns the median value of a non-empty array of floats.
* preconditions:
* <ul>
* <li>The array cannot be {@code null}, nor empty.</li>
* </ul>
* @param values the array of floats.
* @return the median value for {@code values}.
*/
public static float median(float[] values) {
if(values == null || values.length == 0){
throw new IllegalArgumentException("The array cannot be null, nor empty");
}
//Bubble Sort
for(int i = 0; i < values.length; i++){
for(int j = 0; j < values.length - 1; j++){
if(values[j] > values[j + 1]){
float temp = values[j];
values[j] = values[j + 1];
values[j + 1] = temp;
}
}
}
if(values.length % 2 == 0){
return (values[values.length / 2] + values[values.length / 2 - 1]) / 2;
}
else {
return values[(values.length - 1) / 2];
}
}
/**
* Checks if two integer arrays are equal.
* preconditions:
* <ul>
* <li>The arrays cannot be {@code null}</li>
* </ul>
* @param arr1 the first array of integers.
* @param arr2 the second array of integers.
* @return {@code true} if both {@code arr1} and {@code arr2} have the same values in the same order.
*/
public static boolean equals(int[] arr1, int[] arr2) {
if(arr1 == null || arr2 == null){
throw new IllegalArgumentException("The array cannot be null");
}
if(arr1.length != arr2.length){
return false;
}
boolean isEqual = true;
for(int i = 0; i < arr1.length; i++){
if(arr1[i] != arr2[i]){
isEqual = false;
}
}
return isEqual;
}
/**
* Checks if an array has repeated values.
* preconditions:
* <ul>
* <li>The array cannot be {@code null}</li>
* </ul>
* @param values the array to check.
* @return {@code true} if {@code values} contain repeated values.
*/
public static boolean hasRepeatedValues(int[] values) {
if(values == null){
throw new IllegalArgumentException("The array cannot be null");
}
boolean isRepeated = false;
for(int i = 0; i < values.length; i++){
for(int j = i + 1; j < values.length; j++){
if(values[i] == values[j]){
isRepeated = true;
}
}
}
return isRepeated;
}
/**
* Checks if an array has two different values that sum zero (0).
* preconditions:
* <ul>
* <li>The array cannot be {@code null}</li>
* </ul>
* @param values the array to check.
* @return {@code true} if exists {@code n in values} and {@code m in vales}
* such that {@code n != m} and {@code n + m == 0}.
*/
public static boolean hasOpposites(float[] values) {
if(values == null){
throw new IllegalArgumentException("The array cannot be null");
}
boolean isOpposite = false;
for(int i = 0; i < values.length; i++){
for(int j = i + 1; j < values.length; j++){
if(values[i] == -values[j]){
isOpposite = true;
}
}
}
return isOpposite;
}
}