I came across a very strange problem in my program I wrote to copy Pictures, Documents, Videos, and Music (from the Windows file system) to a backup drive. I set a string array directories[]
equal to {picturesDirectory, documentsDirectory, videosDirectory, musicDirectory}
and use a for loop to loop through each one to copy the files. I use a GUI so I have a SwingWorker that runs the actual copy methods. However, when I run the program, it only loops through the "for" loop twice, only copying pictures and documents. I don't think I can really post a SSCCE that will help, so I'm just posting my entire class. inDrive
is the main Windows drive ("C" by default), outDrive
is the backup drive letter, username
is the windows username, and space
is the total disk space that the files/directories will take up.
package diana;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.text.DefaultCaret;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JButton;
import javax.swing.JProgressBar;
import javax.swing.JLabel;
import javax.swing.SwingWorker;
import org.apache.commons.io.FileUtils;
@SuppressWarnings("serial")
public class BasicCopy extends JFrame {
private JPanel contentPane;
private JPanel bottomPane;
private JTextArea txtCopiedDirs;
private JScrollPane scrollPane;
private JButton btnCancel;
private JProgressBar progressBar;
private JLabel lblCopying;
private JLabel lblProgress;
private static String mainDrive;
private static String backupDrive;
private static String username;
private static String backupDir;
double totalSize = 0; //total size of directories/files
double currentMB = 0; //size of already-copied files
static double currentSize = 0; //current size of files counting up to ONE_PERCENT
static int currentPercent = 0; //current progress in %
static double ONE_PERCENT; //totalSize / 100
private ManipulateDirectories md;
private Task task;
public BasicCopy() {
}
public BasicCopy(String inDrive, String outDrive, String username, long space) {
mainDrive = inDrive;
backupDrive = outDrive;
BasicCopy.username = username;
totalSize = space;
ONE_PERCENT = totalSize / 100;
createGUI();
}
public void createGUI() {
// Create frame
setTitle("Backup Progress");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 500, 350);
// Create panel for text area/scroll pane
contentPane = new JPanel(new BorderLayout());
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
// Create panel for progress bar/cancel button
bottomPane = new JPanel();
bottomPane.setLayout(new BoxLayout(bottomPane, BoxLayout.Y_AXIS));
lblCopying = new JLabel("Now backing up your files....\n");
lblCopying.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
contentPane.add(lblCopying, BorderLayout.NORTH);
// Create text area/scroll pane
txtCopiedDirs = new JTextArea(10, 50);
txtCopiedDirs.setEditable(false);
DefaultCaret caret = (DefaultCaret) txtCopiedDirs.getCaret();
caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
scrollPane = new JScrollPane(txtCopiedDirs);
contentPane.add(scrollPane, BorderLayout.CENTER);
lblProgress = new JLabel("Progress:");
progressBar = new JProgressBar(0, 100);
progressBar.setStringPainted(true);
progressBar.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
progressBar.setIndeterminate(true);
btnCancel = new JButton("Cancel");
btnCancel.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "Backup Cancelled!");
closeWindow();
}
});
bottomPane.add(lblProgress, Component.LEFT_ALIGNMENT);
bottomPane.add(progressBar, Component.CENTER_ALIGNMENT);
bottomPane.add(btnCancel, Component.RIGHT_ALIGNMENT);
contentPane.add(bottomPane, BorderLayout.SOUTH);
PropertyChangeListener listener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
if ("progress".equals(event.getPropertyName())) {
currentPercent = (int) event.getNewValue();
progressBar.setValue((int) currentPercent);
progressBar.setString(Integer.toString(currentPercent) + "% ("
+ String.format("%.2f", (currentMB / 1048576))
+ "MB of " + String.format("%.2f", (totalSize / 1048576)) + "MB)");
}
}
};
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
this.setLocation(dim.width / 2 - this.getSize().width / 2, dim.height / 2 - this.getSize().height / 2);
setVisible(true);
task = new Task();
task.addPropertyChangeListener(listener);
task.execute();
}
/**
* Swing Worker class
*/
class Task extends SwingWorker<Void, String> {
public Task() {
md = new ManipulateDirectories(this);
}
@Override
public Void doInBackground() {
md.makeBackupDirectory();
// Directories to be copied
String pics = mainDrive + ":\\Users\\" + username + "\\Pictures\\";
String docs = mainDrive + ":\\Users\\" + username + "\\Documents\\";
String vids = mainDrive + ":\\Users\\" + username + "\\Videos\\";
String musc = mainDrive + ":\\Users\\" + username + "\\Music\\";
File[] directories = { new File(pics), new File(docs), new File(vids), new File(musc) };
/********** THIS ONLY LOOPS THROUGH PICS AND DOCS **********/
for (int i = 0; i < directories.length; i++) {
md.copyDirectory(directories[i], new File(backupDir));
}
/***********************************************************/
return null;
}
@Override
public void process(List<String> chunks) {
for (String path : chunks) {
txtCopiedDirs.append(path);
}
}
@Override
public void done() {
JOptionPane.showMessageDialog(null, "Backup complete!");
closeWindow();
}
public void updateProgress(int tick) {
if (progressBar.isIndeterminate())
progressBar.setIndeterminate(false);
progressBar.setString(Integer.toString(currentPercent) + "% ("
+ String.format("%.2f", (currentMB / 1048576)) + "MB of "
+ String.format("%.2f", (totalSize / 1048576)) + "MB)");
}
public void publishText(String filename) {
publish(filename + "\n");
}
}
public class ManipulateDirectories {
Task task;
public ManipulateDirectories(Task task) {
this.task = task;
}
public void makeBackupDirectory() {
// Create Backup Directory
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("MM-dd-yyyy_HHmmss");
String timestamp = sdf.format(date);
backupDir = backupDrive + ":\\" + "Backup_" + timestamp;
File backupDirectory = new File(backupDir);
backupDirectory.mkdir();
task.updateProgress(0);
}
// Recursive function to loop through and copy individual files
public void copyDirectory(File file, File dest) {
if (file.isFile()) {
try {
FileUtils.copyFileToDirectory(file, dest);
currentMB = currentMB + getDirSize(file);
currentSize = currentSize + getDirSize(file);
if (currentSize >= ONE_PERCENT) {
currentPercent = currentPercent + (int) (currentSize / ONE_PERCENT);
currentSize = currentSize % ONE_PERCENT;
}
task.updateProgress(currentPercent);
task.publishText(file.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
} else if (file.isDirectory()) {
File newDir = new File(String.format("%s\\%s", dest.getAbsolutePath(), file.getName()));
if (!newDir.exists()) {
newDir.mkdir();
for (File f : file.listFiles()) {
copyDirectory(f, newDir);
}
}
}
}
public Long getDirSize(File file) {
long size = 0L;
if (file.isFile() && file != null) {
size += file.isDirectory() ? getDirSize(file) : file.length();
} else if (file.isDirectory()) {
for (File f : file.listFiles()) {
size += f.isDirectory() ? getDirSize(f) : file.length();
}
}
return size;
}
}
/* Close current window */
public void closeWindow() {
WindowEvent close = new WindowEvent(this, WindowEvent.WINDOW_CLOSING);
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(close);
System.exit(0);
}
}
I added a comment around the for loop that is only being executed twice. I placed a breakpoint and it never gets there after copying documents, so it must call the SwingWorker's done()
method before then.
Does anyone see what might cause this problem? I really hate posting all my code here, as I understand it's often a pain to read through it all. I'm hoping someone can find why it's ending prematurely though. Many thanks!
EDIT:
After further debugging (based on some of the suggestions from the comments), I have discovered there is a NPE due to the program looking for "My Music" within the Documents folder--a "link" that doesn't exist as a directory (hence the null value).
java.util.concurrent.ExecutionException: java.lang.NullPointerException
at java.util.concurrent.FutureTask$Sync.innerGet(Unknown Source)
at java.util.concurrent.FutureTask.get(Unknown Source)
at javax.swing.SwingWorker.get(Unknown Source)
at diana.BasicCopy$Task.done(BasicCopy.java:181)
at javax.swing.SwingWorker$5.run(Unknown Source)
at javax.swing.SwingWorker$DoSubmitAccumulativeRunnable.run(Unknown Source)
at sun.swing.AccumulativeRunnable.run(Unknown Source)
at javax.swing.SwingWorker$DoSubmitAccumulativeRunnable.actionPerformed(Unknown Source)
at javax.swing.Timer.fireActionPerformed(Unknown Source)
at javax.swing.Timer$DoPostEvent.run(Unknown Source)
at java.awt.event.InvocationEvent.dispatch(Unknown Source)
at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
at java.awt.EventQueue.access$200(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)
Caused by: java.lang.NullPointerException
at diana.BasicCopy$ManipulateDirectories.copyDirectory(BasicCopy.java:243)
at diana.BasicCopy$ManipulateDirectories.copyDirectory(BasicCopy.java:244)
at diana.BasicCopy$Task.doInBackground(BasicCopy.java:166)
at diana.BasicCopy$Task.doInBackground(BasicCopy.java:1)
at javax.swing.SwingWorker$1.call(Unknown Source)
at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at javax.swing.SwingWorker.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
I was under the impression I wouldn't have to worry about this since I use the file.isFile() and file.isDirectory() checks. According to the javadocs, these only return true if the file is a file/directory AND if it exists. I assumed (probably stupidly) that it should skip over anything like My Music since it doesn't exist as a directory?
The problem is that the existing java.io.File
API can't handle symbolic links (or what ever they call them in Windows). Directories like "My Music" aren't actually a File/directory in the traditional sense, but are a special marker pointing to another file/directory.
In order to overcome this, you'll need to take a look at the new Paths/Files API available in Java 7
For more details, try taking a look at Links, Symbolic or Otherwise