I'm creating a progress bar to monitor an image download. The image download doesn't work — it generates a file measuring 0 bytes in size. If I move my code to a standalone class without a SwingWorker, the image download works. I've played with this for a while and I still don't know what I'm doing wrong. The two code blocks are identical. Any tips would be much appreciated!
Image download with SwingWorker
(take note of doInBackground
):
package download_progress_bar;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
import java.net.*;
import java.io.*;
public class ProgressBar implements ActionListener, PropertyChangeListener {
private JFrame frame;
private JPanel gui;
private JButton button;
private JProgressBar progressBar;
private SwingWorker<Void, Void> worker;
private boolean done;
public ProgressBar() {
done = false;
customizeFrame();
createMainPanel();
createProgressBar();
createButton();
addComponentsToFrame();
}
private void customizeFrame() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private void createMainPanel() {
gui = new JPanel();
gui.setLayout(new BorderLayout());
}
private void createProgressBar() {
progressBar = new JProgressBar(0, 100);
progressBar.setStringPainted(true); // renders a progress string
}
private void createButton() {
button = new JButton("Start download");
button.addActionListener(this);
}
/**
* Invoked when user clicks the button.
*/
public void actionPerformed(ActionEvent evt) {
button.setEnabled(false);
// NOTE: Instances of javax.swing.SwingWorker are not reusable,
// so we create new instances as needed
worker = new Worker();
worker.addPropertyChangeListener(this);
worker.execute();
}
class Worker extends SwingWorker<Void, Void> {
/*
* Main task. Executed in worker thread.
*/
@Override
protected Void doInBackground() throws MalformedURLException {
// Create a URL object for a given URL
String src = "https://lh3.googleusercontent.com/l6JAkhvfxbP61_FWN92j4ulDMXJNH3HT1DR6xrE7MtwW-2AxpZl_WLnBzTpWhCuYkbHihgBQ=s640-h400-e365";
URL url = new URL(src);
// Open connection on the URL object
try {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// Always check response code first
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
System.out.println(responseCode);
// Open input stream from connection
BufferedInputStream in = new BufferedInputStream(connection.getInputStream());
// Open output stream for file writing
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("cat.jpg"));
int bytesRead = - 1;
int totalBytesRead = 0;
int percentCompleted = 0;
while ((bytesRead = in.read()) != -1) {
out.write(bytesRead);
totalBytesRead += bytesRead;
percentCompleted = totalBytesRead * 100 / connection.getContentLength();
System.out.println("..." + percentCompleted);
this.setProgress(percentCompleted);
}
// Close streams
out.close();
in.close();
}
} catch (IOException ex) {
System.out.println(ex);
this.setProgress(0);
cancel(true);
}
return null;
}
/*
* Executed in event dispatching thread
*/
@Override
protected void done() {
button.setEnabled(true);
if (!isCancelled()) {
System.out.println("File has been downloaded successfully!");
} else {
System.out.println("There was an error in downloading the file.");
}
}
}
/**
* Invoked when task's progress property changes.
*/
public void propertyChange(PropertyChangeEvent evt) {
System.out.println(evt);
// NOTE: By default two property states exist: "state" and "progress"
if (evt.getPropertyName().equals("progress")) {
int progress = (Integer) evt.getNewValue();
progressBar.setValue(progress);
System.out.println(String.format(
"Completed %d%% of task.\n", progress));
}
}
private void addComponentsToFrame() {
gui.add(progressBar, BorderLayout.CENTER);
gui.add(button, BorderLayout.SOUTH);
frame.add(gui);
frame.pack();
}
public void activate() {
frame.setVisible(true);
}
}
Image download with standalone Downloader
class (take note of download
):
package download_progress_bar;
import java.net.*;
import java.io.*;
public class Downloader {
public static void main(String[] args) throws IOException {
download();
}
public static void download() throws IOException {
// Create a URL object for a given URL
String src = "https://lh3.googleusercontent.com/l6JAkhvfxbP61_FWN92j4ulDMXJNH3HT1DR6xrE7MtwW-2AxpZl_WLnBzTpWhCuYkbHihgBQ=s640-h400-e365";
URL url = new URL(src);
// Open connection on the URL object
try {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// Always check response code first
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
System.out.println(responseCode);
// Open input stream from connection
BufferedInputStream in = new BufferedInputStream(connection.getInputStream());
// Open output stream for file writing
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("test.jpg"));
int bytesRead = - 1;
int totalBytesRead = 0;
int percentCompleted = 0;
while ((bytesRead = in.read()) != -1) {
out.write(bytesRead);
totalBytesRead += bytesRead;
percentCompleted = totalBytesRead * 100 / connection.getContentLength();
System.out.println("..." + percentCompleted);
}
// Close streams
out.close();
in.close();
}
} catch (IOException ex) {
System.out.println(ex);
}
}
}
You should call get()
in done()
. If doInBackground
throws an exception, then get()
will throw an ExecutionException
whose cause is the exception from doInBackground
.
Something like this:
@Override
protected void done() {
button.setEnabled(true);
try {
if (!isCancelled()) {
get();
System.out.println("File has been downloaded successfully!");
return;
}
} catch (InterruptedException x) {
x.printStackTrace();
} catch (ExecutionException x) {
// This should print an IllegalArgumentException
// if me theory (explained below) is correct.
x.getCause().printStackTrace();
}
System.out.println("There was an error in downloading the file.");
}
My theory is that the issue is related to this line:
totalBytesRead += bytesRead;
Since bytesRead
is the return value of InputStream.read()
, it's actually a byte of data, not the number of bytes read. This doesn't have an obvious effect on the I/O, but it corrupts the value of percentCompleted
. This ends up passing a value which is greater than 100 to setProgress
which throws an exception. The line should be totalBytesRead++;
instead.
You can verify my theory with the aforementioned modification to done()
.