This is not as easy as it might seem, but, the basic answer comes down to making use of models and observers (something like the "model-view-controller" concept), so you can decouple the workflows in a more meaningful way.
One important note to make is, Swing is NOT thread save. This means that you should not be modifying the UI or any state the UI relies on from out side the context the Event Dispatching Thread. Under your current workflow it's possible for the SwingWorker to modify the state of the array (and other state values) while the UI is been painted, this could cause no end of issues.
The core functionality of a sorter is basically the same, it needs some values, needs to be able to swap those values and needs to deliver notifications to interested parties that some kind of state has changed.
public interface Sorter {
public interface Observer {
public void swap(int from, int to);
public void setCurrent(int current);
public void setTraverse(int traverse);
}
public int[] sort(int[] values, Observer observer);
}
Okay, pretty basic, but the nice idea behind this anything that changes the values can be used, for example, we can shuffle the values through the interface...
public class ShuffleSorter extends AbstractSorter {
@Override
public int[] sort(int[] original, Observer observer) {
int[] values = Arrays.copyOf(original, original.length);
Random random = new Random();
for (int i = 0; i < values.length; i++) {
int swap = random.nextInt(values.length - 1);
fireSetCurrent(observer, i);
fireSetTraverse(observer, i);
Helper.swap(values, i, swap);
fireSwap(observer, i, swap);
try {
Thread.sleep(5);
} catch (InterruptedException ex) {
}
}
return values;
}
}
And the insertion sorter...
public class InsertionSorter extends AbstractSorter {
@Override
public int[] sort(int[] original, Observer observer) {
int[] values = Arrays.copyOf(original, original.length);
for (int current = 1; current < values.length; current++) {
int traverse = current;
fireSetCurrent(observer, current);
fireSetTraverse(observer, traverse);
while (traverse > 0 && values[traverse] < values[traverse - 1]) {
Helper.swap(values, traverse, traverse - 1);
fireSwap(observer, traverse, traverse - 1);
traverse--;
fireSetTraverse(observer, traverse);
try {
Thread.sleep(5);
} catch (InterruptedException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
fireSetCurrent(observer, values.length - 1);
fireSetTraverse(observer, values.length - 1);
return values;
}
}
The idea here is the sorters will generate events telling what's changed, so you can apply those changes to the view independently of the sorters, so you don't risk dirty updates.
And finally, the SwingWorker
public class SortWorker extends SwingWorker<Void, Void> {
private Sorter sorter;
private int[] values;
private Sorter.Observer observer;
public SortWorker(int[] values, Sorter sorter, Sorter.Observer observer) {
this.sorter = sorter;
this.values = values;
this.observer = observer;
}
@Override
protected Void doInBackground() throws Exception {
int[] shuffled = new ShuffleSorter().sort(values, observer);
sorter.sort(shuffled, observer);
return null;
}
}
Now, this will shuffle the values and the re-sort them as a single unit of work.
Runnable example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new SortPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class SortPane extends JPanel {
final int SCREEN_WIDTH = 1280;
final int SCREEN_HEIGHT = 720;
final int BAR_HEIGHT = SCREEN_HEIGHT * 4 / 5;
final int BAR_WIDTH = 5;
final int NUM_BARS = SCREEN_WIDTH / BAR_WIDTH;
private JButton bubbleSort = new JButton();
private JButton insertSort = new JButton();
private int[] values = new int[NUM_BARS];
private int current = 0;
private int traverse = 0;
public SortPane() {
for (int i = 0; i < NUM_BARS; i++) {
values[i] = i * BAR_HEIGHT / NUM_BARS;
}
//InsertionSort Button
insertSort.setText("Insertion Sort");
this.add(insertSort);
insertSort.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
insertSort.setEnabled(false);
bubbleSort.setEnabled(false);
current = 0;
traverse = 0;
SortWorker sortWorker = new SortWorker(values, new InsertionSorter(), new Sorter.Observer() {
@Override
public void swap(int from, int to) {
SwingUtilities.invokeLater(() -> {
Helper.swap(values, from, to);
repaint();
});
}
@Override
public void setCurrent(int current) {
SwingUtilities.invokeLater(() -> {
SortPane.this.current = current;
repaint();
});
}
@Override
public void setTraverse(int traverse) {
SwingUtilities.invokeLater(() -> {
SortPane.this.traverse = traverse;
repaint();
});
}
});
sortWorker.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (sortWorker.getState() == SwingWorker.StateValue.DONE) {
insertSort.setEnabled(true);
bubbleSort.setEnabled(true);
}
}
});
sortWorker.execute();
}
});
//BubbleSort Button
bubbleSort.setText("Bubble Sort");
this.add(bubbleSort);
bubbleSort.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
}
});
}
@Override
public Dimension getPreferredSize() {
return new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g.setColor(Color.white);
for (int i = 0; i < values.length; i++) {
g2d.fillRect(i * BAR_WIDTH, SCREEN_HEIGHT - values[i], BAR_WIDTH, values[i]);
}
g2d.setColor(Color.green);
g2d.fillRect(current * BAR_WIDTH, SCREEN_HEIGHT - values[current], BAR_WIDTH, values[current]);
g2d.setColor(Color.red);
g2d.fillRect(traverse * BAR_WIDTH, SCREEN_HEIGHT - values[traverse], BAR_WIDTH, values[traverse]);
g2d.dispose();
}
}
public class Helper {
public static void swap(int[] values, int indexOne, int indexTwo) {
int temp = values[indexOne];
values[indexOne] = values[indexTwo];
values[indexTwo] = temp;
}
}
public interface Sorter {
public interface Observer {
public void swap(int from, int to);
public void setCurrent(int current);
public void setTraverse(int traverse);
}
public int[] sort(int[] values, Observer observer);
}
public abstract class AbstractSorter implements Sorter {
protected void fireSwap(Observer obserer, int from, int to) {
obserer.swap(from, to);
}
protected void fireSetCurrent(Observer obserer, int current) {
obserer.setCurrent(current);
}
protected void fireSetTraverse(Observer obserer, int traverse) {
obserer.setTraverse(traverse);
}
}
public class ShuffleSorter extends AbstractSorter {
@Override
public int[] sort(int[] original, Observer observer) {
int[] values = Arrays.copyOf(original, original.length);
Random random = new Random();
for (int i = 0; i < values.length; i++) {
int swap = random.nextInt(values.length - 1);
fireSetCurrent(observer, i);
fireSetTraverse(observer, i);
Helper.swap(values, i, swap);
fireSwap(observer, i, swap);
try {
Thread.sleep(5);
} catch (InterruptedException ex) {
}
}
return values;
}
}
public class InsertionSorter extends AbstractSorter {
@Override
public int[] sort(int[] original, Observer observer) {
int[] values = Arrays.copyOf(original, original.length);
for (int current = 1; current < values.length; current++) {
int traverse = current;
fireSetCurrent(observer, current);
fireSetTraverse(observer, traverse);
while (traverse > 0 && values[traverse] < values[traverse - 1]) {
Helper.swap(values, traverse, traverse - 1);
fireSwap(observer, traverse, traverse - 1);
traverse--;
fireSetTraverse(observer, traverse);
try {
Thread.sleep(5);
} catch (InterruptedException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
fireSetCurrent(observer, values.length - 1);
fireSetTraverse(observer, values.length - 1);
return values;
}
}
public class SortWorker extends SwingWorker<Void, Void> {
private Sorter sorter;
private int[] values;
private Sorter.Observer observer;
public SortWorker(int[] values, Sorter sorter, Sorter.Observer observer) {
this.sorter = sorter;
this.values = values;
this.observer = observer;
}
@Override
protected Void doInBackground() throws Exception {
int[] shuffled = new ShuffleSorter().sort(values, observer);
sorter.sort(shuffled, observer);
return null;
}
}
}
Food for thought...
A different approach would be to sort the values and in the process generate a series of "events" which describe what actions are been carried out. This would allow you to take the same starting data and apply those actions in a more controlled manner (using a Swing Timer for example). This would reduce some of the overhead of having to copy the array data each time an update occurs.
If you're interested, I've done an example here