Skip to content

Commit 9c0ef3f

Browse files
Merge pull request #41 from EatSleepProgramRepeat/28-multi-thread-program-logic
28-multi-thread-program-logic
2 parents 321ded1 + 8ed161e commit 9c0ef3f

4 files changed

Lines changed: 129 additions & 33 deletions

File tree

src/main/java/com/CDPrintable/Constants.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@ public class Constants {
1515
public static final String VERSION = "1.6.4";
1616

1717
public static final boolean USER_AGENT_EMAIL_CHANGED = false;
18-
}
18+
19+
public static final int MAX_THREADS = 4;
20+
public static final ThreadManager THREAD_MANAGER = new ThreadManager();
21+
}

src/main/java/com/CDPrintable/MusicBrainzResources/MusicBrainzJSONReader.java

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,17 @@
1010

1111
package com.CDPrintable.MusicBrainzResources;
1212

13+
import com.CDPrintable.Constants;
1314
import com.google.gson.*;
1415

16+
import java.util.ArrayList;
17+
import java.util.List;
1518
import java.util.Locale;
1619

20+
import javax.swing.*;
1721
import javax.swing.table.DefaultTableModel;
1822
import java.lang.reflect.Array;
23+
import java.util.concurrent.Future;
1924

2025
public class MusicBrainzJSONReader {
2126
private final JsonObject json;
@@ -36,6 +41,28 @@ public MusicBrainzJSONReader(String json) throws IllegalArgumentException {
3641
this.json = tempJsonObject;
3742
}
3843

44+
/**
45+
* This method splits a JSON array into smaller chunks for multithreading.
46+
* @param jsonArray The JSON array to split.
47+
*/
48+
public JsonArray[] splitJsonArray(JsonArray jsonArray) {
49+
int chunkSize = Constants.MAX_THREADS;
50+
int arraySize = jsonArray.size();
51+
int numChunks = (int) Math.ceil((double) arraySize / chunkSize);
52+
JsonArray[] chunks = new JsonArray[numChunks];
53+
54+
for (int i = 0; i < numChunks; i++) {
55+
int start = i * chunkSize;
56+
int end = Math.min(start + chunkSize, arraySize);
57+
JsonArray chunk = new JsonArray();
58+
for (int j = start; j < end; j++) {
59+
chunk.add(jsonArray.get(j));
60+
}
61+
chunks[i] = chunk;
62+
}
63+
return chunks;
64+
}
65+
3966
/**
4067
* Parses a JSON array and creates a new array of the same type as the provided array.
4168
*
@@ -49,22 +76,40 @@ public MusicBrainzJSONReader(String json) throws IllegalArgumentException {
4976
*/
5077
@SuppressWarnings("unchecked")
5178
private <T> T[] parseJsonArray(String key, JsonArrayProcessor<T> processor, T[] array) {
52-
// Make sure the key exists in the JSON object
53-
if (!json.has(key)) {
54-
// Return an empty array if the JSON object does not have the key
55-
array = (T[]) Array.newInstance(array.getClass().getComponentType(), 0);
56-
return array;
79+
if(!json.has(key)) {
80+
// Return an empty array if the key does not exist
81+
return (T[]) Array.newInstance(array.getClass().getComponentType(), 0);
5782
}
58-
// Make a JSON array using the provided key
83+
5984
JsonArray jsonArray = json.getAsJsonArray(key);
60-
array = (T[]) Array.newInstance(array.getClass().getComponentType(), jsonArray.size());
85+
JsonArray[] chunks = splitJsonArray(jsonArray);
6186

62-
// Process each JSON object in the array
63-
for (int i = 0; i < jsonArray.size(); i++) {
64-
JsonObject jsonObject = jsonArray.get(i).getAsJsonObject();
65-
array[i] = processor.process(jsonObject); // Uses provided processor to process the JSON object
87+
// Use multithreading to process the JSON array
88+
// This is a list of promises that promise to return a list
89+
// of whatever type T is, e.g., MusicBrainzRelease.
90+
List<Future<List<T>>> futures = new ArrayList<>();
91+
for (JsonArray chunk : chunks) {
92+
// Submit a task to the thread manager
93+
futures.add(Constants.THREAD_MANAGER.submit(() -> {
94+
List<T> result = new ArrayList<>();
95+
for (JsonElement element : chunk) {
96+
JsonObject jsonObject = element.getAsJsonObject();
97+
result.add(processor.process(jsonObject));
98+
}
99+
return result;
100+
}));
66101
}
67-
return array;
102+
103+
List<T> resultList = new ArrayList<>();
104+
try {
105+
for (Future<List<T>> future : futures) {
106+
resultList.addAll(future.get());
107+
}
108+
} catch (Exception e) {
109+
JOptionPane.showMessageDialog(null, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
110+
}
111+
112+
return resultList.toArray((T[]) Array.newInstance(array.getClass().getComponentType(), resultList.size()));
68113
}
69114

70115
/**

src/main/java/com/CDPrintable/ProgramWindow.java

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
public class ProgramWindow {
2424
private final UserAgent userAgent;
2525
private JLabel fullUserAgentLabel = new JLabel();
26+
private final JPanel cdSearchPanel = new JPanel();
27+
private final JLabel searchStatusLabel = new JLabel("Status: Nothing's going on.");
2628

2729
/**
2830
* Creates a new ProgramWindow and sets up the GUI.
@@ -76,20 +78,20 @@ private JPanel searchPanel() {
7678
JPanel panel = new JPanel();
7779
panel.setLayout(new BorderLayout());
7880

79-
// Track List panel set up
81+
// Track List panel set-up
8082
JPanel trackListPanel = new JPanel(new BorderLayout());
8183
trackListPanel.setBorder(BorderFactory.createTitledBorder("Search Results"));
8284

8385
// Search table set up
8486
JTable searchTable = new JTable(getCDStubModel());
8587
JScrollPane trackListScrollPane = new JScrollPane(searchTable);
88+
trackListPanel.add(searchStatusLabel, BorderLayout.NORTH);
8689
trackListPanel.add(trackListScrollPane, BorderLayout.CENTER);
8790

8891
// Add the Track List panel to the main panel
8992
panel.add(trackListPanel, BorderLayout.CENTER);
9093

9194
// CD Search Panel set up
92-
JPanel cdSearchPanel = new JPanel();
9395
cdSearchPanel.setBorder(BorderFactory.createTitledBorder("Search"));
9496

9597
JTextField searchField = new JTextField(15);
@@ -102,26 +104,36 @@ private JPanel searchPanel() {
102104
if (searchTypeComboBox.getSelectedItem() == null) {
103105
return;
104106
}
107+
setSearchStatus("Preforming search...", "blue");
105108
if (searchTypeComboBox.getSelectedItem().equals("CDStub")) {
106-
MusicBrainzJSONReader reader = sendRequest("cdstub", searchField.getText());
107-
108-
// Get CDStubs and set the table model
109-
MusicBrainzCDStub[] cdStubs = reader.getCDStubs();
110-
searchTable.setModel(reader.getCDStubsAsTableModel(cdStubs));
109+
Constants.THREAD_MANAGER.submit(() -> {
110+
MusicBrainzJSONReader reader = sendRequest("cdstub", searchField.getText());
111+
112+
// Get CDStubs and set the table model
113+
MusicBrainzCDStub[] cdStubs = reader.getCDStubs();
114+
searchTable.setModel(reader.getCDStubsAsTableModel(cdStubs));
115+
setSearchStatus("All done!", "green");
116+
});
111117
} else if (searchTypeComboBox.getSelectedItem().equals("Artist")) {
112-
MusicBrainzJSONReader reader = sendRequest("artist", searchField.getText());
113-
114-
// Get Artists and set the table model
115-
MusicBrainzArtist[] artists = reader.getArtists();
116-
searchTable.setModel(reader.getArtistsAsTableModel(artists));
118+
Constants.THREAD_MANAGER.submit(() -> {
119+
MusicBrainzJSONReader reader = sendRequest("artist", searchField.getText());
120+
121+
// Get Artists and set the table model
122+
MusicBrainzArtist[] artists = reader.getArtists();
123+
searchTable.setModel(reader.getArtistsAsTableModel(artists));
124+
setSearchStatus("All done!", "green");
125+
});
117126
} else if (searchTypeComboBox.getSelectedItem().equals("Release")) {
118-
MusicBrainzJSONReader reader = sendRequest("release", searchField.getText());
119-
120-
// Get Releases and set the table model
121-
MusicBrainzRelease[] releases = reader.getReleases();
122-
searchTable.setModel(reader.getReleasesAsTableModel(releases));
127+
Constants.THREAD_MANAGER.submit(() -> {
128+
MusicBrainzJSONReader reader = sendRequest("release", searchField.getText());
129+
130+
// Get Releases and set the table model
131+
MusicBrainzRelease[] releases = reader.getReleases();
132+
searchTable.setModel(reader.getReleasesAsTableModel(releases));
133+
setSearchStatus("All done!", "green");
134+
});
123135
} else {
124-
// how does this even happen
136+
// how does this even happen?
125137
JOptionPane.showMessageDialog(panel, "Please select a search type.");
126138
}
127139
resizeColumnWidths(searchTable);
@@ -286,15 +298,15 @@ public void changedUpdate(DocumentEvent e) {} // Not used
286298
userAgentPanel.add(fullAgentPanel, BorderLayout.NORTH);
287299
userAgentPanel.add(userAgentInputPanel, BorderLayout.CENTER);
288300

289-
// Add sub panels to main panel
301+
// Add subpanels to the main panel
290302
panel.add(userAgentPanel);
291303
panel.add(fontPanel);
292304

293305
return panel;
294306
}
295307

296308
/**
297-
* Helper method to resize a tables columns to fit the largest element.
309+
* Helper method to resize a table column to fit the largest element.
298310
* @param table The table to resize.
299311
*/
300312
private void resizeColumnWidths(JTable table) {
@@ -313,4 +325,8 @@ private void resizeColumnWidths(JTable table) {
313325
tableColumn.setPreferredWidth(preferredWidth + 2); // Add padding
314326
}
315327
}
328+
329+
public void setSearchStatus(String status, String color) {
330+
searchStatusLabel.setText("<html><font color='" + color + "'>Status: " + status + "</font></html>");
331+
}
316332
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* CDPrintable: A program that prints labels with track listings for your CD cases.
3+
* Copyright (C) 2025 Alexander McLean
4+
*
5+
* This source code is licensed under the GNU General Public License v3.0
6+
* found in the LICENSE file in the root directory of this source tree.
7+
*
8+
* This class manages threads.
9+
*/
10+
11+
package com.CDPrintable;
12+
13+
import java.util.concurrent.Callable;
14+
import java.util.concurrent.ExecutorService;
15+
import java.util.concurrent.Executors;
16+
import java.util.concurrent.Future;
17+
18+
public class ThreadManager {
19+
private final ExecutorService executorService;
20+
21+
public ThreadManager() {
22+
executorService = Executors.newFixedThreadPool(Constants.MAX_THREADS);
23+
}
24+
25+
public <T> Future<T> submit(Callable<T> task) {
26+
return executorService.submit(task);
27+
}
28+
29+
public void submit(Runnable task) {
30+
executorService.submit(task);
31+
}
32+
}

0 commit comments

Comments
 (0)