views:

489

answers:

3

So, I'm feeding file data to an API that takes a Reader, and I'd like a way to report progress.

It seems like it should be straightforward to write a FilterInputStream implementation that wraps the FileInputStream, keeps track of the number of bytes read vs. the total file size, and fires some event (or, calls some update() method) to report fractional progress.

(Alternatively, it could report absolute bytes read, and somebody else could do the math -- maybe more generally useful in the case of other streaming situations.)

I know I've seen this before and I may even have done it before, but I can't find the code and I'm lazy. Has anyone got it laying around? Or can someone suggest a better approach?

+1  A: 

If you’re building a GUI application there’s always ProgressMonitorInputStream. If there’s no GUI involved wrapping an InputStream in the way you describe is a no-brainer and takes less time than posting a question here.

Bombe
Yeah, I looked at that. It's a GUI application but it's quite a complex one and progress reporting isn't just a matter of popping up a standard `ProgressMonitor`.Wrapping the `InputStream` would take less time than posting a question, but then I'd never know if someone out there has a better idea.
David Moles
+2  A: 

Here's a fairly basic implementation that fires PropertyChangeEvents when additional bytes are read. Some caveats:

  • The class does not support mark or reset operations, although these would be easy to add.
  • The class does not check whether the total number bytes read ever exceeds the maximum number of bytes anticipated, although this could always be dealt with by client code when displaying progress.
  • I haven't test the code.

Code:

public class ProgressInputStream extends FilterInputStream {
    private final PropertyChangeSupport propertyChangeSupport;
    private final long maxNumBytes;
    private volatile long totalNumBytesRead;

    public ProgressInputStream(InputStream in, long maxNumBytes) {
        super(in);
        this.propertyChangeSupport = new PropertyChangeSupport(this);
        this.maxNumBytes = maxNumBytes;
    }

    public long getMaxNumBytes() {
        return maxNumBytes;
    }

    public long getTotalNumBytesRead() {
        return totalNumBytesRead;
    }

    public void addPropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.addPropertyChangeListener(l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.removePropertyChangeListener(l);
    }

    @Override
    public int read() throws IOException {
        return (int)updateProgress(super.read());
    }

    @Override
    public int read(byte[] b) throws IOException {
        return (int)updateProgress(super.read(b));
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        return (int)updateProgress(super.read(b, off, len));
    }

    @Override
    public long skip(long n) throws IOException {
        return updateProgress(super.skip(n));
    }

    @Override
    public void mark(int readlimit) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void reset() throws IOException {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean markSupported() {
        return false;
    }

    private long updateProgress(long numBytesRead) {
        if (numBytesRead > 0) {
            long oldTotalNumBytesRead = this.totalNumBytesRead;
            this.totalNumBytesRead += numBytesRead;
            propertyChangeSupport.firePropertyChange("totalNumBytesRead", oldTotalNumBytesRead, this.totalNumBytesRead);
        }

        return numBytesRead;
    }
}
Adamski
Not bad. I probably would have forgotten `skip()`. :)
David Moles
@David: To be honest implementing skip meant introducing nasty (int) casts so if you know you don't need it I'd throw an UnsupportedOperationException here too.
Adamski
+2  A: 

Guava's com.google.common.io package can help you a little. The following is uncompiled and untested but should put you on the right track.

long total = file1.length();
long progress = 0;
final OutputStream out = new FileOutputStream(file2);
boolean success = false;
try {
  ByteStreams.readBytes(Files.newInputStreamSupplier(file1),
      new ByteProcessor<Void>() {
        public boolean processBytes(byte[] buffer, int offset, int length)
            throws IOException {
          out.write(buffer, offset, length);
          progress += length;
          updateProgressBar((double) progress / total);
          // or only update it periodically, if you prefer
        }
        public Void getResult() {
          return null;
        }
      });
  success = true;
} finally {
  Closeables.close(out, !success);
}

This may look like a lot of code, but I believe it's the least you'll get away with. (note other answers to this question don't give complete code examples, so it's hard to compare them that way.)

Kevin Bourrillion