diff --git a/src/main/java/com/CDPrintable/Constants.java b/src/main/java/com/CDPrintable/Constants.java index 7714e80..49c923a 100644 --- a/src/main/java/com/CDPrintable/Constants.java +++ b/src/main/java/com/CDPrintable/Constants.java @@ -15,4 +15,7 @@ public class Constants { public static final String VERSION = "1.6.4"; public static final boolean USER_AGENT_EMAIL_CHANGED = false; -} + + public static final int MAX_THREADS = 4; + public static final ThreadManager THREAD_MANAGER = new ThreadManager(); +} \ No newline at end of file diff --git a/src/main/java/com/CDPrintable/MusicBrainzResources/MusicBrainzJSONReader.java b/src/main/java/com/CDPrintable/MusicBrainzResources/MusicBrainzJSONReader.java index 403c85d..f19bc71 100644 --- a/src/main/java/com/CDPrintable/MusicBrainzResources/MusicBrainzJSONReader.java +++ b/src/main/java/com/CDPrintable/MusicBrainzResources/MusicBrainzJSONReader.java @@ -10,12 +10,17 @@ package com.CDPrintable.MusicBrainzResources; +import com.CDPrintable.Constants; import com.google.gson.*; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; +import javax.swing.*; import javax.swing.table.DefaultTableModel; import java.lang.reflect.Array; +import java.util.concurrent.Future; public class MusicBrainzJSONReader { private final JsonObject json; @@ -36,6 +41,28 @@ public MusicBrainzJSONReader(String json) throws IllegalArgumentException { this.json = tempJsonObject; } + /** + * This method splits a JSON array into smaller chunks for multithreading. + * @param jsonArray The JSON array to split. + */ + public JsonArray[] splitJsonArray(JsonArray jsonArray) { + int chunkSize = Constants.MAX_THREADS; + int arraySize = jsonArray.size(); + int numChunks = (int) Math.ceil((double) arraySize / chunkSize); + JsonArray[] chunks = new JsonArray[numChunks]; + + for (int i = 0; i < numChunks; i++) { + int start = i * chunkSize; + int end = Math.min(start + chunkSize, arraySize); + JsonArray chunk = new JsonArray(); + for (int j = start; j < end; j++) { + chunk.add(jsonArray.get(j)); + } + chunks[i] = chunk; + } + return chunks; + } + /** * Parses a JSON array and creates a new array of the same type as the provided array. * @@ -49,22 +76,40 @@ public MusicBrainzJSONReader(String json) throws IllegalArgumentException { */ @SuppressWarnings("unchecked") private T[] parseJsonArray(String key, JsonArrayProcessor processor, T[] array) { - // Make sure the key exists in the JSON object - if (!json.has(key)) { - // Return an empty array if the JSON object does not have the key - array = (T[]) Array.newInstance(array.getClass().getComponentType(), 0); - return array; + if(!json.has(key)) { + // Return an empty array if the key does not exist + return (T[]) Array.newInstance(array.getClass().getComponentType(), 0); } - // Make a JSON array using the provided key + JsonArray jsonArray = json.getAsJsonArray(key); - array = (T[]) Array.newInstance(array.getClass().getComponentType(), jsonArray.size()); + JsonArray[] chunks = splitJsonArray(jsonArray); - // Process each JSON object in the array - for (int i = 0; i < jsonArray.size(); i++) { - JsonObject jsonObject = jsonArray.get(i).getAsJsonObject(); - array[i] = processor.process(jsonObject); // Uses provided processor to process the JSON object + // Use multithreading to process the JSON array + // This is a list of promises that promise to return a list + // of whatever type T is, e.g., MusicBrainzRelease. + List>> futures = new ArrayList<>(); + for (JsonArray chunk : chunks) { + // Submit a task to the thread manager + futures.add(Constants.THREAD_MANAGER.submit(() -> { + List result = new ArrayList<>(); + for (JsonElement element : chunk) { + JsonObject jsonObject = element.getAsJsonObject(); + result.add(processor.process(jsonObject)); + } + return result; + })); } - return array; + + List resultList = new ArrayList<>(); + try { + for (Future> future : futures) { + resultList.addAll(future.get()); + } + } catch (Exception e) { + JOptionPane.showMessageDialog(null, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); + } + + return resultList.toArray((T[]) Array.newInstance(array.getClass().getComponentType(), resultList.size())); } /** diff --git a/src/main/java/com/CDPrintable/ProgramWindow.java b/src/main/java/com/CDPrintable/ProgramWindow.java index cc6bee0..fbb8f7c 100644 --- a/src/main/java/com/CDPrintable/ProgramWindow.java +++ b/src/main/java/com/CDPrintable/ProgramWindow.java @@ -23,6 +23,8 @@ public class ProgramWindow { private final UserAgent userAgent; private JLabel fullUserAgentLabel = new JLabel(); + private final JPanel cdSearchPanel = new JPanel(); + private final JLabel searchStatusLabel = new JLabel("Status: Nothing's going on."); /** * Creates a new ProgramWindow and sets up the GUI. @@ -76,20 +78,20 @@ private JPanel searchPanel() { JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); - // Track List panel set up + // Track List panel set-up JPanel trackListPanel = new JPanel(new BorderLayout()); trackListPanel.setBorder(BorderFactory.createTitledBorder("Search Results")); // Search table set up JTable searchTable = new JTable(getCDStubModel()); JScrollPane trackListScrollPane = new JScrollPane(searchTable); + trackListPanel.add(searchStatusLabel, BorderLayout.NORTH); trackListPanel.add(trackListScrollPane, BorderLayout.CENTER); // Add the Track List panel to the main panel panel.add(trackListPanel, BorderLayout.CENTER); // CD Search Panel set up - JPanel cdSearchPanel = new JPanel(); cdSearchPanel.setBorder(BorderFactory.createTitledBorder("Search")); JTextField searchField = new JTextField(15); @@ -102,26 +104,36 @@ private JPanel searchPanel() { if (searchTypeComboBox.getSelectedItem() == null) { return; } + setSearchStatus("Preforming search...", "blue"); if (searchTypeComboBox.getSelectedItem().equals("CDStub")) { - MusicBrainzJSONReader reader = sendRequest("cdstub", searchField.getText()); - - // Get CDStubs and set the table model - MusicBrainzCDStub[] cdStubs = reader.getCDStubs(); - searchTable.setModel(reader.getCDStubsAsTableModel(cdStubs)); + Constants.THREAD_MANAGER.submit(() -> { + MusicBrainzJSONReader reader = sendRequest("cdstub", searchField.getText()); + + // Get CDStubs and set the table model + MusicBrainzCDStub[] cdStubs = reader.getCDStubs(); + searchTable.setModel(reader.getCDStubsAsTableModel(cdStubs)); + setSearchStatus("All done!", "green"); + }); } else if (searchTypeComboBox.getSelectedItem().equals("Artist")) { - MusicBrainzJSONReader reader = sendRequest("artist", searchField.getText()); - - // Get Artists and set the table model - MusicBrainzArtist[] artists = reader.getArtists(); - searchTable.setModel(reader.getArtistsAsTableModel(artists)); + Constants.THREAD_MANAGER.submit(() -> { + MusicBrainzJSONReader reader = sendRequest("artist", searchField.getText()); + + // Get Artists and set the table model + MusicBrainzArtist[] artists = reader.getArtists(); + searchTable.setModel(reader.getArtistsAsTableModel(artists)); + setSearchStatus("All done!", "green"); + }); } else if (searchTypeComboBox.getSelectedItem().equals("Release")) { - MusicBrainzJSONReader reader = sendRequest("release", searchField.getText()); - - // Get Releases and set the table model - MusicBrainzRelease[] releases = reader.getReleases(); - searchTable.setModel(reader.getReleasesAsTableModel(releases)); + Constants.THREAD_MANAGER.submit(() -> { + MusicBrainzJSONReader reader = sendRequest("release", searchField.getText()); + + // Get Releases and set the table model + MusicBrainzRelease[] releases = reader.getReleases(); + searchTable.setModel(reader.getReleasesAsTableModel(releases)); + setSearchStatus("All done!", "green"); + }); } else { - // how does this even happen + // how does this even happen? JOptionPane.showMessageDialog(panel, "Please select a search type."); } resizeColumnWidths(searchTable); @@ -286,7 +298,7 @@ public void changedUpdate(DocumentEvent e) {} // Not used userAgentPanel.add(fullAgentPanel, BorderLayout.NORTH); userAgentPanel.add(userAgentInputPanel, BorderLayout.CENTER); - // Add sub panels to main panel + // Add subpanels to the main panel panel.add(userAgentPanel); panel.add(fontPanel); @@ -294,7 +306,7 @@ public void changedUpdate(DocumentEvent e) {} // Not used } /** - * Helper method to resize a tables columns to fit the largest element. + * Helper method to resize a table column to fit the largest element. * @param table The table to resize. */ private void resizeColumnWidths(JTable table) { @@ -313,4 +325,8 @@ private void resizeColumnWidths(JTable table) { tableColumn.setPreferredWidth(preferredWidth + 2); // Add padding } } + + public void setSearchStatus(String status, String color) { + searchStatusLabel.setText("Status: " + status + ""); + } } \ No newline at end of file diff --git a/src/main/java/com/CDPrintable/ThreadManager.java b/src/main/java/com/CDPrintable/ThreadManager.java new file mode 100644 index 0000000..0acbf79 --- /dev/null +++ b/src/main/java/com/CDPrintable/ThreadManager.java @@ -0,0 +1,32 @@ +/* + * CDPrintable: A program that prints labels with track listings for your CD cases. + * Copyright (C) 2025 Alexander McLean + * + * This source code is licensed under the GNU General Public License v3.0 + * found in the LICENSE file in the root directory of this source tree. + * + * This class manages threads. + */ + +package com.CDPrintable; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +public class ThreadManager { + private final ExecutorService executorService; + + public ThreadManager() { + executorService = Executors.newFixedThreadPool(Constants.MAX_THREADS); + } + + public Future submit(Callable task) { + return executorService.submit(task); + } + + public void submit(Runnable task) { + executorService.submit(task); + } +}