aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/net/sowgro/npehero/levelapi
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/net/sowgro/npehero/levelapi')
-rw-r--r--src/main/java/net/sowgro/npehero/levelapi/Difficulties.java127
-rwxr-xr-xsrc/main/java/net/sowgro/npehero/levelapi/Difficulty.java95
-rw-r--r--src/main/java/net/sowgro/npehero/levelapi/Leaderboard.java81
-rwxr-xr-xsrc/main/java/net/sowgro/npehero/levelapi/LeaderboardEntry.java24
-rwxr-xr-xsrc/main/java/net/sowgro/npehero/levelapi/Level.java152
-rwxr-xr-xsrc/main/java/net/sowgro/npehero/levelapi/Levels.java97
-rw-r--r--src/main/java/net/sowgro/npehero/levelapi/Note.java34
-rw-r--r--src/main/java/net/sowgro/npehero/levelapi/Notes.java107
8 files changed, 717 insertions, 0 deletions
diff --git a/src/main/java/net/sowgro/npehero/levelapi/Difficulties.java b/src/main/java/net/sowgro/npehero/levelapi/Difficulties.java
new file mode 100644
index 0000000..cffd95e
--- /dev/null
+++ b/src/main/java/net/sowgro/npehero/levelapi/Difficulties.java
@@ -0,0 +1,127 @@
+package net.sowgro.npehero.levelapi;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Responsible for the list of difficulties in a level
+ */
+public class Difficulties {
+
+ public final ObservableList<Difficulty> list = FXCollections.observableArrayList();
+ public final HashMap<String, Exception> problems = new HashMap<>();
+
+ private final Level level;
+
+ /**
+ * Creates a new Difficulties object
+ * @param level the file path of the level
+ * @throws IOException If there is a problem reading in the difficulties
+ */
+ public Difficulties(Level level) throws IOException {
+ this.level = level;
+ read();
+ }
+
+ /**
+ * Loads difficulties
+ * <p>
+ * Creates difficulty objects out of each subfolder in the level and adds it to the list.
+ * @throws IOException If there is a problem reading in the difficulties
+ */
+ public void read() throws IOException {
+ list.clear();
+ File[] fileList = level.dir.listFiles();
+ if (fileList == null) {
+ throw new FileNotFoundException();
+ }
+ for(File cur: fileList) {
+ if (cur.isDirectory()) {
+ try {
+ Difficulty diff = new Difficulty(cur, level);
+ list.add(diff);
+ } catch (IOException e) {
+ problems.put("", e);
+ e.printStackTrace();
+ }
+ }
+ }
+ list.sort(Comparator.naturalOrder());
+ }
+
+ /**
+ * Removes a difficulty
+ * <p>
+ * Recursively deletes the folder and removes it from the list
+ * @param diff: The difficulty to remove.
+ * @throws IOException If there is a problem removing the difficulty.
+ */
+ public void remove(Difficulty diff) throws IOException {
+ File hold = diff.thisDir;
+ Files.walk(hold.toPath())
+ .sorted(Comparator.reverseOrder())
+ .map(Path::toFile)
+ .forEach(File::delete);
+ list.remove(diff);
+ }
+
+ /**
+ * Adds a difficulty
+ * <p>
+ * Creates the directory and required files
+ * @param text The name of the directory
+ * @throws IOException If there is a problem adding the level
+ */
+ public void add(String text) throws IOException {
+ File diffDir = new File(level.dir, text.toLowerCase().replaceAll("\\W+", "-"));
+ if (diffDir.exists()) {
+ throw new FileAlreadyExistsException(diffDir.getName());
+ }
+ if (diffDir.mkdirs()) {
+ Difficulty temp = new Difficulty(diffDir, level);
+ temp.title = text;
+ list.add(temp);
+ list.sort(Comparator.naturalOrder());
+ }
+ else {
+ throw new IOException();
+ }
+ }
+
+ /**
+ * Saves the order of the difficulties in the list
+ * <p>
+ * Updates the order variable of each difficulty in the list to match their index in the list
+ * @throws IOException If there is a problem saving the difficulty's metadata file
+ */
+ public void saveOrder() throws IOException {
+ for (Difficulty d : list) {
+ d.order = list.indexOf(d);
+ d.writeMetadata();
+ }
+ }
+
+ /**
+ * Get a list of only the valid difficulties in the level.
+ * @return A list of the valid difficulties.
+ */
+ public List<Difficulty> getValidList() {
+ ObservableList<Difficulty> validList = FXCollections.observableArrayList();
+ for (Difficulty difficulty : list) {
+ if (difficulty.isValid()) {
+ validList.add(difficulty);
+ }
+ }
+ return validList;
+ }
+}
diff --git a/src/main/java/net/sowgro/npehero/levelapi/Difficulty.java b/src/main/java/net/sowgro/npehero/levelapi/Difficulty.java
new file mode 100755
index 0000000..2e99a7a
--- /dev/null
+++ b/src/main/java/net/sowgro/npehero/levelapi/Difficulty.java
@@ -0,0 +1,95 @@
+package net.sowgro.npehero.levelapi;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.ToNumberPolicy;
+
+import java.io.*;
+import java.util.Map;
+
+/**
+ * Represents a difficulty
+ * Responsible for the data in metadata.yml
+ */
+public class Difficulty implements Comparable<Difficulty>
+{
+ public final File thisDir;
+ public final Level level;
+
+ public String title = "Unnamed";
+ public Double bpm = 0.0;
+ public double endTime = 0;
+ public int order = 0;
+
+ public final Leaderboard leaderboard;
+ public final Notes notes;
+
+ private final Gson jsonParser = new GsonBuilder().serializeNulls().setPrettyPrinting().setNumberToNumberStrategy(ToNumberPolicy.DOUBLE).create();
+ private final File jsonFile;
+
+ /**
+ * Creates a new Difficulty
+ * @param newDir: The file path of the Difficulty
+ * @throws IOException If there are any problems reading the metadata or leaderboard files
+ */
+ public Difficulty(File newDir, Level level) throws IOException {
+ thisDir = newDir;
+ this.level = level;
+ jsonFile = new File(thisDir, "metadata.json");
+ readMetadata();
+ notes = new Notes(new File(thisDir, "notes.txt"), this); // needs metadata first
+ leaderboard = new Leaderboard(new File(thisDir, "leaderboard.json"));
+ }
+
+ /**
+ * Read in the data from metadata.json
+ * @throws IOException If there are any problems loading the file.
+ */
+ public void readMetadata() throws IOException {
+ if (!jsonFile.exists()) {
+ return;
+ }
+ Map<String, Object> data = jsonParser.fromJson(new FileReader(jsonFile), Map.class);
+
+ title = (String) data.getOrDefault("title", title);
+ bpm = (Double) data.getOrDefault("bpm", bpm);
+ endTime = (double) data.getOrDefault("endTime", endTime);
+ if (endTime == 0) {
+ int tmp = (int) (double) data.getOrDefault("numBeats", 0.0);
+ if (tmp != 0) {
+ endTime = Notes.beatToSecond(tmp, bpm);
+ }
+ }
+ order = (int) (double) data.getOrDefault("priority", (double) order);
+ }
+
+ /**
+ * Checks the validity of the difficulty
+ * <p>
+ * A valid difficulty has at least one note
+ * @return True if the difficulty is valid
+ */
+ public boolean isValid() {
+ return !notes.list.isEmpty();
+ }
+
+ /**
+ * Writes metadata to json file
+ * @throws IOException If there is a problem writing to the file
+ */
+ public void writeMetadata() throws IOException {
+ jsonFile.createNewFile();
+ Map<String, Object> data = jsonParser.fromJson(new FileReader(jsonFile), Map.class); // start with previous values
+ data.put("title", title);
+ data.put("endTime", endTime);
+ data.put("priority", order);
+ FileWriter fileWriter = new FileWriter(jsonFile);
+ jsonParser.toJson(data, fileWriter);
+ fileWriter.close();
+ }
+
+ @Override
+ public int compareTo(Difficulty d) {
+ return order - d.order;
+ }
+}
diff --git a/src/main/java/net/sowgro/npehero/levelapi/Leaderboard.java b/src/main/java/net/sowgro/npehero/levelapi/Leaderboard.java
new file mode 100644
index 0000000..bb1f30c
--- /dev/null
+++ b/src/main/java/net/sowgro/npehero/levelapi/Leaderboard.java
@@ -0,0 +1,81 @@
+package net.sowgro.npehero.levelapi;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.time.LocalDate;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Leaderboard {
+
+ public final ObservableList<LeaderboardEntry> entries = FXCollections.observableArrayList();
+ private final Gson json = new GsonBuilder().serializeNulls().setPrettyPrinting().create();
+ private final File file;
+
+ public Leaderboard(File file) throws IOException{
+ this.file = file;
+ read();
+ }
+
+ /**
+ * Adds new leaderboardEntry to list and updates json file
+ * @param name: The players name
+ * @param score The players score
+ * @throws IOException If there is a problem updating the leaderboard file.
+ */
+ public void add(String name, int score) throws IOException {
+ entries.add(new LeaderboardEntry(name, score, LocalDate.now().toString()));
+ save();
+ }
+
+ /**
+ * Writes leaderboard to json file
+ * @throws IOException If there are problems writing to the file.
+ */
+ public void save() throws IOException {
+ file.createNewFile();
+ List<Map<String, Object>> data = json.fromJson(new FileReader(file), List.class);
+ for (LeaderboardEntry cur : entries) {
+ Map<String, Object> obj = new HashMap<>();
+ obj.put("name", cur.name);
+ obj.put("score", cur.score);
+ obj.put("date", cur.date);
+ data.add(obj);
+ }
+ FileWriter fileWriter = new FileWriter(file);
+ json.toJson(data, fileWriter);
+ fileWriter.close();
+ }
+
+ /**
+ * Reads in json leaderboard and assigns populates list with leaderboardEntries
+ * @throws IOException If there are problems reading the file
+ */
+ public void read() throws IOException {
+ if (!file.exists()) {
+ return;
+ }
+ List<Map<String, Object>> data = json.fromJson(new FileReader(file), List.class);
+ if (data == null) {
+ return;
+ }
+ for (Map<String, Object> cur: data) {
+ String name = (String) cur.getOrDefault("name", null);
+ int score = (int) (double) cur.getOrDefault("score", -1);
+ String date = (String) cur.getOrDefault("date", null);
+ if (name == null || score == -1 || date == null) {
+ System.out.println("dbg: bad entry skipped");
+ continue; // discard invalid entries
+ }
+ entries.add(new LeaderboardEntry(name, score, date));
+ }
+ }
+}
diff --git a/src/main/java/net/sowgro/npehero/levelapi/LeaderboardEntry.java b/src/main/java/net/sowgro/npehero/levelapi/LeaderboardEntry.java
new file mode 100755
index 0000000..2b98a29
--- /dev/null
+++ b/src/main/java/net/sowgro/npehero/levelapi/LeaderboardEntry.java
@@ -0,0 +1,24 @@
+package net.sowgro.npehero.levelapi;
+
+/**
+ * Represents one players score in the leaderboard
+ */
+public class LeaderboardEntry
+{
+ public final int score;
+ public final String name;
+ public final String date;
+
+ /**
+ * Create a new LeaderboardEntry
+ * @param name The name the player input after completing the level
+ * @param score The score the player earned
+ * @param date The date the player earned this score
+ */
+ public LeaderboardEntry(String name, int score, String date)
+ {
+ this.name = name;
+ this.score = score;
+ this.date = date;
+ }
+}
diff --git a/src/main/java/net/sowgro/npehero/levelapi/Level.java b/src/main/java/net/sowgro/npehero/levelapi/Level.java
new file mode 100755
index 0000000..218779f
--- /dev/null
+++ b/src/main/java/net/sowgro/npehero/levelapi/Level.java
@@ -0,0 +1,152 @@
+package net.sowgro.npehero.levelapi;
+
+import java.io.*;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import javafx.scene.image.Image;
+import javafx.scene.media.Media;
+import javafx.scene.paint.Color;
+
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.Map;
+
+public class Level implements Comparable<Level>{
+
+ public final File dir;
+
+ public String title = "Unnamed";
+ public String artist = "Unknown";
+ public String desc;
+ public Color[] colors = {Color.RED,Color.BLUE,Color.GREEN,Color.PURPLE,Color.YELLOW};
+ public Image preview;
+ public Image background;
+ public Media song;
+
+ public Difficulties difficulties;
+
+ private final File jsonFile;
+ private final Gson jsonParser = new GsonBuilder().serializeNulls().setPrettyPrinting().create();
+
+ /**
+ * Creates a new level
+ * @param newDir The path of the Level
+ * @throws IOException If there is a problem reading the metadata file or loading the difficulties
+ */
+ public Level(File newDir) throws IOException
+ {
+ dir = newDir;
+ jsonFile = new File(dir, "metadata.json");
+ readFiles();
+ readMetadata();
+ difficulties = new Difficulties(this);
+ }
+
+ /**
+ * Check for a song file, background file and preview image file
+ */
+ public void readFiles() {
+
+ File[] fileList = dir.listFiles();
+ if (fileList == null) {
+ return;
+ }
+ for (File file : fileList) {
+ String fileName = file.getName();
+ if (fileName.contains("song")) {
+ song = new Media(file.toURI().toString());
+ }
+ else if (fileName.contains("background")) {
+ background = new Image(file.toURI().toString());
+ }
+ else if (fileName.contains("preview")) {
+ preview = new Image(file.toURI().toString());
+ }
+ }
+
+ }
+
+ /**
+ * Read in metadata file
+ * @throws IOException If there is a problem reading the file
+ */
+ public void readMetadata() throws IOException {
+ if (!jsonFile.exists()) {
+ return;
+ }
+ Map<String, Object> data = jsonParser.fromJson(new FileReader(jsonFile), Map.class);
+ title = (String) data.getOrDefault("title", title);
+ artist = (String) data.getOrDefault("artist", artist);
+ desc = (String) data.getOrDefault("desc", desc);
+ colors[0] = Color.web((String) data.getOrDefault("color1", colors[0].toString()));
+ colors[1] = Color.web((String) data.getOrDefault("color2", colors[1].toString()));
+ colors[2] = Color.web((String) data.getOrDefault("color3", colors[2].toString()));
+ colors[3] = Color.web((String) data.getOrDefault("color4", colors[3].toString()));
+ colors[4] = Color.web((String) data.getOrDefault("color5", colors[4].toString()));
+ }
+
+ /**
+ * Checks if the level is valid.
+ * <p>
+ * A valid level has a song file and 1 or more valid difficulties
+ * @return True if the level is valid
+ */
+ public boolean isValid() {
+ if (song == null) {
+ return false;
+ }
+
+ if (difficulties.getValidList().isEmpty()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Writes metadata to json file
+ * @throws IOException If there is a problem writing to the file.
+ */
+ public void writeMetadata() throws IOException {
+ jsonFile.createNewFile();
+ Map<String, Object> data = jsonParser.fromJson(new FileReader(jsonFile), Map.class);
+ data.put("title", title);
+ data.put("artist", artist);
+ data.put("desc", desc);
+ data.put("color1",colors[0].toString());
+ data.put("color2",colors[1].toString());
+ data.put("color3",colors[2].toString());
+ data.put("color4",colors[3].toString());
+ data.put("color5",colors[4].toString());
+ FileWriter fileWriter = new FileWriter(jsonFile);
+ jsonParser.toJson(data, fileWriter);
+ fileWriter.close();
+ }
+
+
+ /**
+ * Copies a file into the level directory with the name provided. The extension will be inherited from the source file
+ * @param source: the file to be copied
+ * @param name: the new file name EXCLUDING the extension.
+ * @throws IOException If there is a problem adding the file
+ */
+ public void addFile(File source, String name) throws IOException {
+ name = name + "." + getFileExtension(source);
+ Files.copy(source.toPath(), new File(dir, name).toPath(), StandardCopyOption.REPLACE_EXISTING);
+ readFiles();
+ }
+
+ @Override
+ public int compareTo(Level other) {
+ return title.compareTo(other.title);
+ }
+
+ /**
+ * Get the extension of a file.
+ * @param file The file to return the extension of
+ * @return The extension of the file in the format "*.ext"
+ */
+ public String getFileExtension(File file) {
+ return file.getName().substring(file.getName().lastIndexOf('.') + 1);
+ }
+}
diff --git a/src/main/java/net/sowgro/npehero/levelapi/Levels.java b/src/main/java/net/sowgro/npehero/levelapi/Levels.java
new file mode 100755
index 0000000..84ffe51
--- /dev/null
+++ b/src/main/java/net/sowgro/npehero/levelapi/Levels.java
@@ -0,0 +1,97 @@
+package net.sowgro.npehero.levelapi;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.HashMap;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
+/**
+ * Stores a list of all the levels
+ */
+public class Levels {
+
+ public static final ObservableList<Level> list = FXCollections.observableArrayList();
+ public static final HashMap<String, Exception> problems = new HashMap<>();
+
+ private static final File dir = new File("levels");
+
+ /**
+ * Reads contents of the levels folder and creates a level form each subfolder
+ * <p>
+ * All subfolders in the levels folder are assumed to be levels
+ * @throws FileNotFoundException If the levels folder is missing.
+ * @throws IOException If there is a problem reading in the levels.
+ */
+ public static void readData() throws IOException {
+ list.clear();
+ File[] fileList = dir.listFiles();
+ if (fileList == null) {
+ throw new FileNotFoundException();
+ }
+ for (File file: fileList) {
+ try {
+ Level level = new Level(file);
+ list.add(level);
+ } catch (IOException e) {
+ problems.put("", e);
+ e.printStackTrace();
+ }
+ }
+ list.sort(Comparator.naturalOrder());
+ }
+
+ /**
+ * Creates a subfolder in the levels folder for the new level then creates the level with it
+ * @param text: the name of the directory and default title
+ * @throws IOException if there was an error adding the level
+ */
+ public static void add(String text) throws IOException {
+ File levelDir = new File(dir, text.toLowerCase().replaceAll("\\W+", "-"));
+ if (levelDir.exists()) {
+ throw new FileAlreadyExistsException(levelDir.getName());
+ }
+ if (levelDir.mkdirs()) {
+ Level temp = new Level(levelDir);
+ temp.title = text;
+ list.add(temp);
+ }
+ else {
+ throw new IOException();
+ }
+ }
+
+ /**
+ * Removes level from the filesystem then reloads this levelController
+ * @param level: the level to be removed
+ * @throws IOException If there is a problem deleting the level
+ */
+ public static void remove(Level level) throws IOException {
+ File hold = level.dir;
+ Files.walk(hold.toPath())
+ .sorted(Comparator.reverseOrder())
+ .map(Path::toFile)
+ .forEach(File::delete);
+ list.remove(level);
+ }
+
+ /**
+ * Gets a list of only the valid levels.
+ * @return A list of the valid levels.
+ */
+ public static ObservableList<Level> getValidList() {
+ ObservableList<Level> validList = FXCollections.observableArrayList();
+ for (Level level : list) {
+ if (level.isValid()) {
+ validList.add(level);
+ }
+ }
+ return validList;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/net/sowgro/npehero/levelapi/Note.java b/src/main/java/net/sowgro/npehero/levelapi/Note.java
new file mode 100644
index 0000000..ab93885
--- /dev/null
+++ b/src/main/java/net/sowgro/npehero/levelapi/Note.java
@@ -0,0 +1,34 @@
+package net.sowgro.npehero.levelapi;
+
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.SimpleDoubleProperty;
+
+/**
+ * A note represents a moment in the song when the player should hit a key
+ * <p>
+ * The key corresponding to the lane the note is in should be pressed
+ */
+public class Note {
+
+ public final DoubleProperty time = new SimpleDoubleProperty();
+ public final int lane;
+
+ /**
+ * Creates a new note
+ * @param time The time the player should hit the note.
+ * @param lane The lane the note belongs to.
+ */
+ public Note(double time, int lane) {
+ this.time.set(time);
+ this.lane = lane;
+ }
+
+ /**
+ * Copy constructor
+ * @param other the note to copy from
+ */
+ public Note(Note other) {
+ this.lane = other.lane;
+ this.time.set(other.time.get());
+ }
+}
diff --git a/src/main/java/net/sowgro/npehero/levelapi/Notes.java b/src/main/java/net/sowgro/npehero/levelapi/Notes.java
new file mode 100644
index 0000000..1df0248
--- /dev/null
+++ b/src/main/java/net/sowgro/npehero/levelapi/Notes.java
@@ -0,0 +1,107 @@
+package net.sowgro.npehero.levelapi;
+
+import javafx.beans.property.ListProperty;
+import javafx.beans.property.SimpleListProperty;
+import javafx.collections.FXCollections;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.util.Scanner;
+
+/**
+ * Stores all the notes for a difficulty.
+ */
+public class Notes {
+
+ private final File file;
+ private final Difficulty diff;
+
+ public ListProperty<Note> list = new SimpleListProperty<>(FXCollections.observableArrayList());
+
+ /**
+ * Create a new Notes object
+ * @param file The notes.txt file
+ * @param diff The difficulty these notes belong to
+ * @throws IOException If there is a problem reading the notes file
+ */
+ public Notes(File file, Difficulty diff) throws IOException {
+ this.file = file;
+ this.diff = diff;
+ readFile();
+ }
+
+ /**
+ * Read notes.txt and add the notes to the list
+ * @throws IOException if there is a problem reading the file.
+ */
+ public void readFile() throws IOException {
+ if (!file.exists()) {
+ return;
+ }
+ Scanner scan = new Scanner(file);
+ while (scan.hasNext()) {
+ String input = scan.next();
+ int lane = switch (input.charAt(0)) {
+ case 'd' -> 0;
+ case 'f' -> 1;
+ case 's' -> 2;
+ case 'j' -> 3;
+ case 'k' -> 4;
+ default -> -1;
+ };
+ if (lane == -1) {
+ continue;
+ }
+ double time = Double.parseDouble(input.substring(1));
+
+ if (diff.bpm != 0.0) {
+ time = beatToSecond(time, diff.bpm);
+ }
+ list.add(new Note(time, lane));
+ }
+ }
+
+ /**
+ * Writes the notes to notes.txt
+ * @throws IOException If there is a problem writing to the file.
+ */
+ public void writeFile() throws IOException{
+ var _ = file.createNewFile();
+ PrintWriter writer = new PrintWriter(file, StandardCharsets.UTF_8);
+ for (Note note : list) {
+ Character lane = switch (note.lane) {
+ case 0 -> 'd';
+ case 1 -> 'f';
+ case 2 -> 's';
+ case 3 -> 'j';
+ case 4 -> 'k';
+ default -> null;
+ };
+ if (lane == null) {
+ continue;
+ }
+ writer.println(lane + "" + note.time.get());
+ }
+ writer.close();
+ }
+
+ /**
+ * Converts a beat to a second using the levels bpm
+ * @param beat The beat to convert to seconds
+ * @param bpm The beats per minute to use for conversion
+ * @return The time in seconds the beat was at
+ */
+ public static double beatToSecond(double beat, double bpm) {
+ return beat/(bpm/60);
+ }
+
+ /**
+ * Performs a deep copy of the notes list.
+ * @return a new list of notes with the same notes.
+ */
+ public ListProperty<Note> deepCopyList() {
+ ListProperty<Note> ret = new SimpleListProperty<>(FXCollections.observableArrayList());
+ list.forEach(e -> ret.add(new Note(e)));
+ return ret;
+ }
+}