package MusicLandscape.entities;
/**
* This class represents an album as a concrete release of a specific artist.<br>
* An album has a list of tracks, which, in this class, is implemented as a (singly) linked lists of tracks.
*
* @author Jonas Altrock (ew20b126@technikum-wien.at)
* @version 1
* @since ExerciseSheet04
*/
public class Album extends Release {
/**
* A single item of a linked list of tracks.<br>
* A single list item consists of the primary data, in our case a track, and a reference to its successor,
* which is another list item.
*/
private static class TrackListItem {
/**
* A reference to the next item in the list.
*/
TrackListItem next;
/**
* The primary data of this list item.
*/
Track track;
/**
* Creates a list item from a track.<br>
* Simply wraps a list item around a track, so it can be inserted into a linked list of tracks.
* <p>
* This list item does NOT maintain its own copy of the original track. It means a track in the list can be
* modified from the caller who might still maintain a reference, however, the list structure is protected.
*
* @param t the track of this list item
*/
public TrackListItem(Track t) {
track = t;
}
}
/**
* The tracks of this album.<br>
* More specifically this is the head of the linked list of tracks of this album.
*/
private TrackListItem trackListHead;
/**
* Creates a default Album.<br>
* A default album is a default release with an empty track list.
*/
public Album() {
}
/**
* Creates a copy of an album.<br>
* All release parts of this album are copied as described in the release copy constructor.
* <p>
* The track list of this album contains (references to) the same tracks as the original, meaning tracks are not
* deeply copied.
*
* @param orig the album to copy
*/
public Album(Album orig) {
super(orig);
TrackListItem it = orig.trackListHead;
if (it == null) {
return;
}
trackListHead = new TrackListItem(it.track);
while (it.next != null) {
trackListHead.next = new TrackListItem(it.next.track);
it = it.next;
}
}
/**
* Create an album with a specific title of a specific artist in a specific year.
*
* @param title the title of the new album
* @param artist the artist of the new album
* @param year the year of the new album
*/
public Album(String title, Artist artist, int year) {
super(title, artist, year);
}
/**
* Adds a track to the list of tracks.<br>
* Tracks are added to the end of the list.
* <p>
* Null tracks are not accepted. The method returns whether the list was modified.
* true means success (track was added) false means no success (track was NOT added).
*
* @param t the track to add
* @return whether the list was modified (added successfully) or not
*/
public boolean addTrack(Track t) {
if (t == null) {
return false;
}
/* no head = create new list */
if (trackListHead == null) {
trackListHead = new TrackListItem(t);
return true;
}
/* iterate to end */
TrackListItem tail = trackListHead;
while (tail.next != null) {
tail = tail.next;
}
/* append new list item */
tail.next = new TrackListItem(t);
return true;
}
/**
* Removes a track from the track from the list of tracks.
* <p>
* Removes and returns the track at position n from the list of tracks. Element numbering starts at 0, such that in
* a list containing a single element the position of that element is 0 (zero). If the requested element does not
* exist in the list null is returned.
*
* @param n the (zero-based) position of the track to be removed.
* @return the removed track or null
*/
public Track removeTrack(int n) {
int i = 0;
TrackListItem it = trackListHead;
Track t;
/* empty list */
if (it == null) {
return null;
}
/* remove head */
if (n == i) {
t = it.track;
trackListHead = it.next;
return t;
}
/* find predecessor of n */
while (it != null && i < (n - 1)) {
i++;
it = it.next;
}
/* has no successor = element at index n does not exist */
if (it == null || it.next == null) {
return null;
}
/* remove element n */
t = it.next.track;
it.next = it.next.next;
return t;
}
/**
* Gets the number of tracks on this album.
*
* @return the number of tracks
*/
public int nrTracks() {
int n = 0;
TrackListItem it = trackListHead;
while (it != null) {
n += 1;
it = it.next;
}
return n;
}
/**
* Gets the tracks of this album.
* <p>
* This method creates an array containing all tracks of this album preserving their current order. If the album
* has no tracks, an array of size zero is returned. The tracks in the returned array are NOT (deep) copies of the
* tracks currently maintained by this album, meaning that the caller can modify the tracks of this album, however
* modification of their ordering in the list is not possible from outside.
*
* @return the tracks of this album in order
*/
public Track[] getTracks() {
Track[] tracks = new Track[nrTracks()];
int i = 0;
TrackListItem it = trackListHead;
while (it != null) {
tracks[i] = it.track;
i += 1;
it = it.next;
}
return tracks;
}
/**
* Gets the total running time of this album.
* <p>
* The running time is the sum of the running times of all tracks in this album. The time is returned in seconds.
*
* @return the total running time in seconds.
*/
public int totalTime() {
int i = 0;
int time = 0;
TrackListItem it = trackListHead;
while (it != null) {
time += it.track.getDuration();
i += 1;
it = it.next;
}
return time;
}
/**
* Gets a String representation of this album.
* <p>
* The String representation of an album adds the titles of all tracks to the release String representation.
* The list of track names is enclosed by opening and closing brackets ([,]).
* Track titles are also enclosed by opening and closing brackets.
*
* @return the string representation
*/
public String toString() {
StringBuilder sb = new StringBuilder("[");
TrackListItem it = trackListHead;
while (it != null) {
sb.append("[").append(it.track.getTitle()).append("]");
it = it.next;
}
sb.append("]");
return super.toString() + "\n" + sb;
}
}