+1 (315) 557-6473 

Create A Spotify Stats System in Java Assignment Solution.


Instructions

Objective
Write a java assignment program to create a Spotify stats system in programming language.

Requirements and Specifications

Spotify stats system
Assignment brief: You have been given some code that is currently being developed as part of a Spotify application specifically analysing recent Songs streamed on Spotify. Not all the requirements have been implemented. It is your task to implement these and raise the coding standards of all the code.
This should take you around 4-5 hours to complete and it will help with your exam next week. You should complete this on Eclipse IDE. When you finish, record a short video of code walkthrough and demonstrate functionality.
Create a project solution (named SpotifyAssignment). Create a package named spotify. Add StartApp.java to the solution and the spotify_stats_clean.csv. Ensure your name and student number are placed in the Javadoc comments of all the classes you create. The StartApp has been partially written with a menu. The application will run (start) from the StartApp.java, initially reading in the data from the spotify_stats_clean.csv file and then perform a number of menu-driven operations. Data in the file does not correspond directly with the expected attributes of the required object design. (e.g. duration_ms is in millisecsonds whereas the object stores this as seconds.) Implement the class independent of the file content, and deal with any required data translations as part of the reading process.
Part 1 – Data mapping, storage, testing and read-from file - 50%
Using your knowledge of OOP you should add/update the code based on the following:
  1. Analyse the data in the spotify_stats_clean.csv (See the explanation specified in appendix I) and create a class (Song.java) to represent the data in the csv file, include the following …
    1. Store the duration_ms in seconds.
    2. Mode :
    3. map the input for mode to MAJOR from 1 and MINOR from 0.

    4. Genre : map the code for genre to the following…

    1 = POP, 2 = RB, 3 = COUNTRY, 4 = HIPHOP, 5 = DANCE, 6 = ROCK, 7= LATIN, 8 = METAL, 9 = TRAD

     For example : if the Genre read from file was :

    7 : Genre would be mapped to LATIN

    142 : Genre would be mapped to POP HIPHOP RB

  2. Validation rules :
  3. Specified in appendix I. Any records (songs) that break these rules should not be included and stored in the arraylist in the system (see part 4 below).

  4. Conduct a full junit test for the Song class.
  5. In the StartApp.java class read and store the data in this arraylist :
public static ArrayList songs = new ArrayList();
Note : include a summary output message of the number of attempted reads and the number of successful reads.
e.g. Console output:
Attempted to read Spotify songs : 2000
Song data read successfully : 1996
Part 2 – Functions – 50%
Having read the data from the csv file complete the menu driven functions as outlined below. An example of the expected format is shown for each function (note: actual answers based on csv data provided are not necessarily the example output shown).
Display all Songs to screen. Example output…
Display all songs for a selected Artist. Example output…
Display all songs ranked by popularity (descending)
Analyse and display the song genres (order by Genre alphabetically (DESCENDING)). Categorising each genre output a count of the songs that list (in full or part) each genre type. For example, given :
File write - export/write in a separate THREAD a new file (LiveSongs .csv) in the format Title, Artist, Duration (descending order) for each Live song (as determined by liveness i.e. a value above 0.8 which provides a strong likelihood that the track is live). Capitalise the TITLE and ARTIST.
Format of the csv file shown below (include the header).
Output to screen the songs(s) with the highest TEMPO. Output song name, artist and tempo value
Output to screen all songs within a given year. Output the song name and artist.
When complete compress (zip) the entire Eclipse solution and upload. Remember to record and then upload a short commentary walk-through of your code with your solution (upload that too).
Appendix I
artist: Name of the Artist [1-255 characters]
song: Name of the Track. [1-255 characters]
duration_ms: Duration of the track in milliseconds.[1000 – 600000]. Note that when stored in the Song.java class this should be mapped to seconds from milliseconds. You should aim to unit test this as seconds.
explicit: The lyrics or content of a song or a music video contain one or more of the criteria which could be considered offensive or unsuitable for children.
year: Release Year of the track. [2000-current]
popularity: The higher the value the more popular the song is. [0-100]
danceability: Danceability describes how suitable a track is for dancing based on a combination of musical elements including tempo, rhythm stability, beat strength, and overall regularity. A value of 0.0 is least danceable and 1.0 is the most danceable.
energy: Energy is a measure from 0.0 to 1.0 and represents a perceptual measure of intensity and activity.
key: The key the track is in. Integers map to pitches using standard Pitch Class notation.. If no key was detected, the value is -1. [-1 to 11]
loudness: The overall loudness of a track in decibels (dB). Loudness values are averaged across the entire track and are useful for comparing relative loudness of tracks. Loudness is the quality of a sound that is the primary psychological correlate of physical strength (amplitude). Values typically range between [-60 to 0].
mode: Mode indicates the modality (major or minor) of a track, the type of scale from which its melodic content is derived. Major is represented by 1 and minor is 0.
speechiness: Speechiness detects the presence of spoken words in a track. The more exclusively speechlike the recording (e.g. talk show, audio book, poetry), the closer to 1.0 the attribute value. Values above 0.66 describe tracks that are probably made entirely of spoken words. Values between 0.33 and 0.66 describe tracks that may contain both music and speech, either in sections or layered, including such cases as rap music. Values below 0.33 most likely represent music and other non-speech-like tracks. [0.0 to 1.0]
acousticness: A confidence measure from 0.0 to 1.0 of whether the track is acoustic. 1.0 represents high confidence the track is acoustic. [0.0 to 1.0]
instrumentalness: Predicts whether a track contains no vocals. "Ooh" and "aah" sounds are treated as instrumental in this context. Rap or spoken word tracks are clearly "vocal". The closer the instrumentalness value is to 1.0, the greater likelihood the track contains no vocal content. Values above 0.5 are intended to represent instrumental tracks, but confidence is higher as the value approaches 1.0. [0.0 to 1.0]
liveness: Detects the presence of an audience in the recording. Higher liveness values represent an increased probability that the track was performed live. A value above 0.8 provides strong likelihood that the track is live. [0.0 to 1.0]
valence: A measure from 0.0 to 1.0 describing the musical positiveness conveyed by a track. Tracks with high valence sound more positive (e.g. happy, cheerful, euphoric), while tracks with low valence sound more negative (e.g. sad, depressed, angry).
tempo: The overall estimated tempo of a track in beats per minute (BPM). In musical terminology, tempo is the speed or pace of a given piece and derives directly from the average beat duration. [0.0 to 300.0]
genre: Genre of the track.
Source Code
SONG
package spotify;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class Song {
    public enum Mode {
        MINOR(0),
        MAJOR(1);
        private int value;
        Mode(int value) {
            this.value = value;
        }
        public static Mode getMode(int value) {
            for (Mode mode : values()) {
                if (mode.value == value) {
                    return mode;
                }
            }
            throw new IllegalArgumentException("invalid mode value");
        }
    }
    public enum Genre {
        POP(1),
        RB(2),
        COUNTRY(3),
        HIPHOP(4),
        DANCE(5),
        ROCK(6),
        LATIN(7),
        METAL(8),
        TRAD(9);
        private int value;
        Genre(int value) {
            this.value = value;
        }
        public static Genre getGenre(int value) {
            for (Genre genre : values()) {
                if (genre.value == value) {
                    return genre;
                }
            }
            throw new IllegalArgumentException("invalid genre value");
        }
    }
    private String artist;
    private String song;
    private double duration;
    private boolean explicit;
    private int year;
    private int popularity;
    private double danceability;
    private double energy;
    private int key;
    private double loudness;
    private Mode mode;
    private double speechiness;
    private double acousticness;
    private double instrumentalness;
    private double liveness;
    private double valence;
    private double tempo;
    private List genres;
    private String genreOld;
    public Song(String line) {
        String[] parts = line.split(",(?=([^\"]*\"[^\"]*\")*[^\"]*$)");
        if (parts.length != 19) {
            throw new IllegalArgumentException();
        }
        try {
            this.artist = parts[0];
            if (artist.length() < 1 || artist.length() > 255) {
                throw new IllegalArgumentException("invalid artist length");
            }
            this.song = parts[1];
            if (song.length() < 1 || song.length() > 255) {
                throw new IllegalArgumentException("invalid song length");
            }
            this.duration = Long.parseLong(parts[2]) / 1000.0;
            if (duration < 1.000 || duration > 600.000) {
                throw new IllegalArgumentException("invalid duration");
            }
            this.explicit = Boolean.parseBoolean(parts[3].toLowerCase());
            this.year = Integer.parseInt(parts[4]);
            if (year < 2000 || year > LocalDate.now().getYear()) {
                throw new IllegalArgumentException("invalid year");
            }
            this.popularity = Integer.parseInt(parts[5]);
            if (popularity < 0 || popularity > 100) {
                throw new IllegalArgumentException("invalid popularity");
            }
            this.danceability = Double.parseDouble(parts[6]);
            if (danceability < 0.0 || danceability > 1.0) {
                throw new IllegalArgumentException("invalid danceability");
            }
            this.energy = Double.parseDouble(parts[7]);
            if (energy < 0.0 || energy > 1.0) {
                throw new IllegalArgumentException("invalid energy");
            }
            if (parts[8].trim().isEmpty()) {
                this.key = -1;
            }
            this.key = Integer.parseInt(parts[8]);
            if (key < -1 || key > 11) {
                throw new IllegalArgumentException("invalid key");
            }
            this.loudness = Double.parseDouble(parts[9]);
            if (loudness < -60.0 || loudness > 0.0) {
                throw new IllegalArgumentException("invalid loudness");
            }
            this.mode = Mode.getMode(Integer.parseInt(parts[10]));
            this.speechiness = Double.parseDouble(parts[11]);
            if (speechiness < 0.0 || speechiness > 1.0) {
                throw new IllegalArgumentException("invalid sppechiness");
            }
            this.acousticness = Double.parseDouble(parts[12]);
            if (acousticness < 0.0 || acousticness > 1.0) {
                throw new IllegalArgumentException("invalid acousticness");
            }
            this.instrumentalness = Double.parseDouble(parts[13]);
            if (instrumentalness < 0.0 || instrumentalness > 1.0) {
                throw new IllegalArgumentException("invalid instrumentalness");
            }
            this.liveness = Double.parseDouble(parts[14]);
            if (liveness < 0.0 || liveness > 1.0) {
                throw new IllegalArgumentException("invalid liveness");
            }
            this.valence = Double.parseDouble(parts[15]);
            if (valence < 0.0 || valence > 1.0) {
                throw new IllegalArgumentException("invalid valence");
            }
            this.tempo = Double.parseDouble(parts[16]);
            if (tempo < 0.0 || tempo > 300.0) {
                throw new IllegalArgumentException("invalid tempo");
            }
            this.genres = new ArrayList<>();
            for (int i = 0; i
                genres.add(Genre.getGenre(parts[17].charAt(i) - '0'));
            }
            this.genreOld = parts[18];
        }
        catch(Exception e) {
            throw new IllegalArgumentException(e);
        }
    }
    public String getArtist() {
        return artist;
    }
    public String getSong() {
        return song;
    }
    public double getDuration() {
        return duration;
    }
    public boolean isExplicit() {
        return explicit;
    }
    public int getYear() {
        return year;
    }
    public int getPopularity() {
        return popularity;
    }
    public double getDanceability() {
        return danceability;
    }
    public double getEnergy() {
        return energy;
    }
    public int getKey() {
        return key;
    }
    public double getLoudness() {
        return loudness;
    }
    public Mode getMode() {
        return mode;
    }
    public double getSpeechiness() {
        return speechiness;
    }
    public double getAcousticness() {
        return acousticness;
    }
    public double getInstrumentalness() {
        return instrumentalness;
    }
    public double getLiveness() {
        return liveness;
    }
    public double getValence() {
        return valence;
    }
    public double getTempo() {
        return tempo;
    }
    public List getGenres() {
        return genres;
    }
    public String getGenreOld() {
        return genreOld;
    }
    @Override
    public String toString() {
        String builder = "-----------------------------" + System.lineSeparator() +
                String.format("%-35s: %s", "Artist", getArtist()) + System.lineSeparator() +
                String.format("%-35s: %s", "Song title", getSong()) + System.lineSeparator() +
                String.format("%-35s: %d", "Duration (secs)", Math.round(getDuration())) + System.lineSeparator() +
                String.format("%-35s: %s", "Explicit", Boolean.toString(isExplicit())) + System.lineSeparator() +
                String.format("%-35s: %s", "Year", getYear()) + System.lineSeparator() +
                String.format("%-35s: %s", "Popularity", getPopularity()) + System.lineSeparator() +
                String.format("%-35s: %s", "Genre",
                        getGenres().stream().map(Enum::toString).collect(Collectors.joining(" "))) +
                System.lineSeparator() +
                "Other details..." + System.lineSeparator() +
                "Danceability (" + getDanceability() +
                ") Energy (" + getEnergy() +
                ") Key (" + getKey() +
                ") Loudness (" + getLoudness() +
                ") Mode (" + getMode() +
                ") Speechiness (" + getSpeechiness() +
                ") Acousticness (" + getAcousticness() +
                ") Instrumentalness (" + getAcousticness() +
                ") Liveness (" + getLiveness() +
                ") Valence (" + getValence() +
                ") Tempo (" + getTempo() +
                ")";
        return builder;
    }
    public String toStringPopularity() {
        String builder = "-----------------------------" + System.lineSeparator() +
                String.format("%-35s: %s", "Artist", getArtist()) + System.lineSeparator() +
                String.format("%-35s: %s", "Song title", getSong()) + System.lineSeparator() +
                String.format("%-35s: %s", "Popularity", getPopularity());
        return builder;
    }
}
SONG TEST
package spotify;
import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
public class SongTest {
    @Test
    public void songTest() {
        Song song1 = new Song("Angie Martinez,If I Could Go! (feat. Lil' Mo & Sacario),244466,FALSE,2019,40,0.583,0" +
                ".643,9,-7.486,0,0.355,0.171,0,0.0395,0.7,195.685,1,pop R&B");
        Assert.assertEquals("Angie Martinez", song1.getArtist());
        Assert.assertEquals("If I Could Go! (feat. Lil' Mo & Sacario)", song1.getSong());
        Assert.assertEquals(244.466, song1.getDuration(), 0.00001);
        Assert.assertFalse(song1.isExplicit());
        Assert.assertEquals(2019, song1.getYear());
        Assert.assertEquals(40, song1.getPopularity());
        Assert.assertEquals(0.583, song1.getDanceability(), 0.00001);
        Assert.assertEquals(0.643, song1.getEnergy(), 0.00001);
        Assert.assertEquals(9, song1.getKey());
        Assert.assertEquals(-7.486, song1.getLoudness(), 0.00001);
        Assert.assertEquals(Song.Mode.MINOR, song1.getMode());
        Assert.assertEquals(0.355, song1.getSpeechiness(), 0.00001);
        Assert.assertEquals(0.171, song1.getAcousticness(), 0.00001);
        Assert.assertEquals(0.0, song1.getInstrumentalness(), 0.00001);
        Assert.assertEquals(0.0395, song1.getLiveness(), 0.00001);
        Assert.assertEquals(0.7, song1.getValence(), 0.00001);
        Assert.assertEquals(195.685, song1.getTempo(), 0.00001);
        Assert.assertEquals(Arrays.asList(Song.Genre.POP), song1.getGenres());
        Assert.assertEquals("pop R&B", song1.getGenreOld());
    }
}
START APP
package spotify;
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;
/**
 * Start point for the app. Reads in data from csv file and then presents a menu
 * with several functions - includes several searches and a thread based write to file.
 *
 * @author ENTER YOUR NAME AND STUDENT NO. HERE
 */
public class StartApp {
    // container that holds the songs (of type Song)
    public static List songs = new ArrayList();
    /**
     * Start point for app
     *
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("Start");
        readData();
        boolean isOver = false;
        try (Scanner scanner = new Scanner(System.in)) {
            while (!isOver) {
                showMenu();
    try {
     int choice = Integer.parseInt(scanner.nextLine().trim());
     if (choice < 1 || choice > 8) {
      throw new IllegalArgumentException();
     }
     switch (choice) {
      case 1:
       for (Song song : songs) {
        System.out.println(song);
       }
       break;
      case 2:
       System.out.println("Enter city");
       String artist = scanner.nextLine().trim();
       List byArtist =
         songs.stream().filter(s -> s.getArtist().toLowerCase().equals(artist.toLowerCase())).collect(Collectors.toList());
       System.out.println("Songs found for " + artist + " (" + byArtist.size() + ")");
       System.out.println();
       for (Song song : byArtist) {
        System.out.println(song.getSong());
       }
       break;
      case 3:
       System.out.println("By popularity descending");
       List descList = new ArrayList<>(songs);
       descList.sort((o1, o2) -> Integer.compare(o2.getPopularity(), o1.getPopularity()));
       for (Song song : descList) {
        System.out.println(song.toStringPopularity());
       }
       System.out.println("-----------------------------");
       System.out.println();
       System.out.println("By popularity ascending (RECURSIVE)");
       List ascList = new ArrayList<>(songs);
       byPopularityAscending(ascList);
       break;
      case 4:
       Map counts = new HashMap<>();
       for (Song song : songs) {
        for(Song.Genre genre : song.getGenres()) {
         counts.merge(genre, 1, Integer::sum);
        }
       }
       List genres = new ArrayList<>(counts.keySet());
       genres.sort(Comparator.comparing(Enum::toString));
       System.out.println("Genre stats :");
       System.out.println();
       for (Song.Genre genre : genres) {
        System.out.println(String.format("%-35s: %d", genre.toString(), counts.get(genre)));
       }
       break;
      case 5:
       Thread t = new Thread(() -> {
        String liveFilename = "LiveSongs.csv";
        try(PrintWriter printWriter = new PrintWriter(liveFilename)) {
         printWriter.println("TITLE, ARTIST, DURATION");
         printWriter.println();
         for (Song song : songs) {
          if (song.getLiveness() > 0.8) {
           printWriter.println(song.getSong().toUpperCase() + ", " + song.getSong().toUpperCase() + ", " + Math.round(song.getDuration()));
          }
         }
        }
        catch (IOException e) {
         System.out.println("Can not open file " + liveFilename);
        }
       });
       t.start();
       t.join();
       System.out.println("Export completed");
       break;
      case 6:
       System.out.println("Highest Tempo");
       Song song = songs.stream().max(Comparator.comparingDouble(Song::getTempo)).orElse(null);
       if (song != null) {
        System.out.println(song.getSong() + ", " + song.getArtist() + " Tempo (" + song.getTempo() + ")");
       }
       break;
      case 7:
       System.out.println("Enter year");
       try {
        int year = Integer.parseInt(scanner.nextLine());
        List yearSongs = songs.stream().filter(s -> s.getYear() == year).collect(Collectors.toList());
        System.out.println("Songs from " + year);
        for(Song yearSong : yearSongs) {
         System.out.println(yearSong.getSong() + ", " + yearSong.getArtist());
        }
       }
       catch (NumberFormatException e) {
        System.out.println("Invalid year");
       }
       break;
      case 8:
       isOver = true;
       break;
      default:
       throw new IllegalStateException();
     }
    }
    catch (Exception e) {
     System.out.println("Invalid input");
    }
    System.out.println("-----------------------------");
            }
        }
    }
 private static void byPopularityAscending(List songs) {
  if (songs.isEmpty()) {
   return;
  }
  Song song = songs.stream().min(Comparator.comparingInt(Song::getPopularity)).get();
  System.out.println(song.toStringPopularity());
  songs.remove(song);
  byPopularityAscending(songs);
 }
    /**
     * Shows the menu and coordinates the searches and file write
     *
     * @throws Exception
     */
    public static void showMenu() {
        System.out.println("1. Display all songs");
        System.out.println("2. Display all songs by Artist");
        System.out.println(
                "3. Display all songs ranked by popularity (descending) and by popularity (ascending) using RECURSION)");
        System.out.println(
                "4. Display Genre stats - Genre and total number of songs fully or part of each genre");
        System.out.println("5. File write - export/write in a separate THREAD a new file (LiveSongs .csv) ");
        System.out.println("6. Display the highest TEMPO ");
        System.out.println("7. Display all songs within a given year");
        System.out.println("8. Quit");
        System.out.println("Enter option ...");
    }
    /**
     * Reads in the data from the csv and maps to the song class
     */
    public static void readData() {
// String filename = "copy.csv";
        String filename = "spotify_stats_clean.csv";
        int attempted = 0;
        int read = 0;
        try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
   reader.readLine();
   String line;
            while ((line = reader.readLine()) != null) {
    attempted++;
                try {
                    songs.add(new Song(line));
                    read++;
                } catch (IllegalArgumentException e) {
// System.out.println("ERROR: " + e.getLocalizedMessage() + " in line: " + line);
                }
            }
            System.out.println("Attempted to read Spotify songs : " + attempted);
            System.out.println("Song data read successfully : " + read);
        } catch (IOException e) {
            System.out.println("Can not read file " + filename);
        }
    }
}