package MusicLandscape.entities;
import MusicLandscape.util.ConsoleScanner;
import MusicLandscape.util.ConsoleScanable;
import java.beans.*;
import java.util.Scanner;package MusicLandscape.entities;
import MusicLandscape.util.ConsoleScanner;
import MusicLandscape.util.ConsoleScanable;
import java.beans.*;
import java.util.Scanner;The Track is the same as before, only the scan() method needs to ask the user
for all fields so they can completely edit/add tracks.
/**
* represents a piece of music that has been released on some kind of media (CD, vinyl, video, ...)
*
* @author Jonas Altrock (ew20b126@technikum-wien.at)
* @version 5
* @since ExerciseSheet01
*/This JavaBean annotation is not necessary. A “bean” in Java-speak is any object that
has some properties with getters and setters. The clue is that the XMLEncoder and
XMLDecoder can read and write any bean object to and from a file.
@JavaBean(description = "A piece of music.", defaultProperty = "title")
public class Track implements ConsoleScanable {
/**
* the duration of this track in seconds
* <p>
* the duration is a non-negative number, duration 0 (zero) represents unknown duration
*/
private int duration = 0;
/**
* the artist who performs this track
* <p>
* the performer cannot be null
*/
private Artist performer = new Artist();
/**
* the title of this track.
*/
private String title;
/**
* the artist who wrote this track
* <p>
* the writer cannot be null
*/
private Artist writer = new Artist();
/**
* the year in which the Track was or will be produced
* <p>
* valid years are between 1900-2999
*/
private int year = 1900;
/**
* creates a default track.
* <p>
* a default track has the following values:
* <ul>
* <li>unknown title
* <li>duration 0
* <li>default writer and performer
* <li>year 1900
* </ul>
*/
public Track() {
}
/**
* creates a track with a certain title
* <p>
* the resulting track has the specified title, all other values are default
*
* @param title the title of this track
*/
public Track(String title) {
this.title = title;
}
/**
* creates a deep copy of a Track
*
* @param other the track to copy
*/
public Track(Track other) {
title = other.getTitle();
duration = other.getDuration();
performer = new Artist(other.getPerformer());
writer = new Artist(other.getWriter());
year = other.getYear();
}
/**
* gets the duration of this track
*
* @return the duration
*/
public int getDuration() {
return duration;
}
/**
* returns the performer of this track
*
* @return the performer
*/
public Artist getPerformer() {
return performer;
}
/**
* gets the title of this track. if the title is not known (null) "unknown title" is returned (without quotes)
*
* @return the title
*/
public String getTitle() {
if (title == null || title.isBlank()) {
return "unknown title";
}
return title;
}
/**
* returns the writer of this track
*
* @return the writer
*/
public Artist getWriter() {
return writer;
}
/**
* gets the production year of this track
*
* @return the year
*/
public int getYear() {
return year;
}
/**
* sets the duration
* <p>
* a negative value is ignored, the object remains unchanged
*
* @param duration the duration to set
*/
public void setDuration(int duration) {
if (duration < 0) {
return;
}
this.duration = duration;
}
/**
* sets the performer of this track
* <p>
* null arguments are ignored
*
* @param performer the performer to set
*/
public void setPerformer(Artist performer) {
if (performer == null) {
return;
}
this.performer = performer;
}
/**
* sets the title of this track.
*
* @param title the title to set
*/
public void setTitle(String title) {
this.title = title;
}
/**
* sets the writer of this track
* <p>
* null arguments are ignored
*
* @param writer the writer to set
*/
public void setWriter(Artist writer) {
if (writer == null) {
return;
}
this.writer = writer;
}
/**
* sets the production year of this track
* <p>
* valid years are between 1900 and 2999
* <p>
* other values are ignored, the object remains unchanged
*
* @param year the year to set
*/
public void setYear(int year) {
if (year < 1900 || year > 2999) {
return;
}
this.year = year;
}
/**
* this getter is used to check if the writer of this Track is known.
*
* @return true if the writer of this track is known (and has a name), false otherwise.
*/
public boolean writerIsKnown() {
return isKnown(writer);
}
/**
* Check whether an artist is known.
*
* @param artist the artist to check
* @return whether the artist is known or not
*/
protected boolean isKnown(Artist artist) {
return artist != null && artist.getName() != null && !artist.getName().isBlank();
}
/**
* returns a formatted String containing all information of this track.
* <p>
* the String representation is (without quotes):
* <pre>
* "title by writer performed by performer (min:sec)"
* </pre>
* <p>
* where
* <ul>
* <li>title stands for the title (exactly 10 chars wide) if not set, return unknown
* <li>writer stands for the writer name (exactly 10 chars wide, right justified)
* <li>performer stands for the performer name (exactly 10 chars wide, right justified)
* <li>min is the duration's amount of full minutes (at least two digits, leading zeros)
* <li>sec is the duration's remaining amount of seconds (at least two digits, leading zeros)
* </ul>
*
* @return a String representation of this track
*/
public String getString() {
String title = ("unknown title").equals(getTitle()) ? "unknown" : getTitle();
String writer = isKnown(getWriter()) ? getWriter().getName() : "unknown";
String performer = isKnown(getPerformer()) ? getPerformer().getName() : "unknown";This changed from ES06: all string parts must be max 10 characters wide.
title = String.format("%-10.10s", title);
writer = String.format("%10.10s", writer);
performer = String.format("%10.10s", performer);
String minutes = String.format("%02d", (duration / 60) % 100);
String seconds = String.format("%02d", duration % 60);
return title + " by " + writer + " performed by " + performer + " (" + minutes + ":" + seconds + ")";
}
/**
* returns a String representation of this track
* <p>
* the string representation of this track is described
* in getString()
*
* @return the string representation
*/
public String toString() {
return getString();The interactive edit feature now supports all five track fields.
/**
* Guides the user through a process that allows scanning/modifying of this track with a text-based user interface.
* <p>
* This method allows modification of the following fields, in the order listed:
*
* <ul>
* <li>title</li>
* <li>duration</li>
* <li>performer</li>
* <li>writer</li>
* <li>year</li>
* </ul>
*
* <p>
* For each modifiable field the process is the following:
* <ul>
* <li>field name and current value are displayed</li>
* <li>new value is read and validated</li>
* </ul>
* <p>
* if input is valid, field is set, otherwise a short message is shown and input of this field is repeated.
* <p>
* Old values can be kept for all fields by entering an empty string. The operation cannot be cancelled, instead
* the user must keep all former values by repeatedly entering empty strings.
*
* @return whether this object was altered or not
*/
@Override
public boolean scan() {
boolean changed = false;
System.out.println("You are modifying Track#" + this.hashCode());
System.out.println("Enter an empty value if you do not want to change a field.");It is important to instantiate Scanner once per scan() call, otherwise the tests fail,
as cached Scanner objects might hold onto old System.in mock objects.
Scanner consoleInput = new Scanner(System.in);I refactored my ConsoleScanner class a bit to contain global objects for the two most common use cases: some arbitrary string or some positive integer.
String newTitle = ConsoleScanner.nonEmptyString.withScanner(consoleInput).scan("Title (" + getTitle() + ")");
if (newTitle != null) {
setTitle(newTitle);
changed = true;
}
Integer newDuration = ConsoleScanner.positiveInteger.withScanner(consoleInput).scan("Duration (" + getDuration() + ")");
if (newDuration != null) {
setDuration(newDuration);
changed = true;
}Here the reason for why the ConsoleScanner class is generic: so that
we can create an Artist object from the user input and let that be
the result of the .scan() function.
ConsoleScanner<Artist> artistScanner = new ConsoleScanner<>(
Artist::new,
(Artist input) -> !input.getName().isBlank(),
consoleInput
);
Artist newPerformer = artistScanner.scan("Performer (" + getPerformer() + ")");
if (newPerformer != null) {
setPerformer(newPerformer);
changed = true;
}
Artist newWriter = artistScanner.scan("Writer (" + getWriter() + ")");
if (newWriter != null) {
setWriter(newWriter);
changed = true;
}
Integer newYear = ConsoleScanner.positiveInteger.withScanner(consoleInput).scan("Year (" + getYear() + ")");
if (newYear != null) {
setYear(newYear);
changed = true;
}
return changed;
}
}