Question

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?

Was it helpful?

Solution

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

OTHER TIPS

I found the answer. It was the same problem I was asking about in this thread, and was thinking the solution was something else: Java program to calculate directory size keeps throwing NPE

The problem is that file.listFiles() was null when run while the file path was C:\Users\user\Documents\My Music. To fix it I simply put in an if statement to check that file.listFiles() != null before the "for" loop at the bottom of the copyDirectory method:

if (file.listFiles() != null){
    for (File f : file.listFiles()) {
        copyDirectory(f, newDir);
    }
}

I'm sorry for posting this here when apparently I already had the same problem that was answered in another thread. Hopefully this helps someone though. Thank you all for your suggestions, they are appreciated.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top