/*
* Copyright (C) 2011 Peransin Nicolas.
* Use is subject to license terms.
*/
package org.mypsycho.swing;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.util.concurrent.ExecutionException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import javax.swing.BoundedRangeModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.mypsycho.util.CompressUtil;
import org.mypsycho.util.ProgressInputStream;
/**
* XXX Doc
* <p>Detail ... </p>
* @author Peransin Nicolas
*/
public class SwingCompressUtil extends SwingWorker<Void,Void> {
public interface CompressionListener {
enum State {
DONE, CANCELED, FAILED
}
void compressionFinished(State s, File zip, File from, Exception e);
void decompressionFinished(State s, File zip, File from, Exception e);
}
/** Boolean expected */
public static final String CANCELED_PROPERTY =
SwingCompressUtil.class.getName() + "@Canceled";
/** Number of octets expected for scale property */
public static final Object SCALE_PROPERTY = Scale.class;
public enum Scale {
octet,
kiloOctet,
megaOctet,
gigaOctet,
teraOctet
;
public final int weight = ((Double) Math.pow(1024, ordinal())).intValue();
public final String shortname;
Scale() {
if (ordinal()>0) {
shortname = name().substring(0, 1).toUpperCase()+"o";
} else {
shortname = "o";
}
}
}
public static final Scale DEFAULT_SCALE = Scale.kiloOctet;
public static final String TOTAL_SIZE_PROPERTY =
SwingCompressUtil.class.getName() + "@TotalSize";
boolean doZip; // false == doUnzip
File zipFile;
File path;
JProgressBar progressTotal = null;
JProgressBar progressFile;
CompressionListener callback;
private SwingCompressUtil(boolean compress, File pZip, File pPath,
JProgressBar monitorSet, JProgressBar monitorFile,
CompressionListener listener) {
doZip = compress;
zipFile = pZip;
path = pPath;
progressFile = monitorFile;
progressTotal = monitorSet;
callback = listener;
resetCancel(progressFile);
resetCancel(progressTotal);
}
@Override
public Void doInBackground() throws Exception { // return null or Exception
if (doZip) {
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));
try {
if (path.isDirectory()) {
if (progressTotal != null) {
if (isTotalSize()) {
progressTotal.setMaximum((int)
(countFilesSize(path)/scale(progressTotal)));
} else {
progressTotal.setMaximum(countFiles(path));
}
progressTotal.setValue(0);
}
zipDir(path, zos, path.getName(), 0);
} else {
zipFile(path, zos, path.getName());
}
} finally {
zos.close();
}
} else {
unZip(zipFile, path);
}
return null;
}
@Override public void done() {
if (callback == null) {
return;
}
CompressionListener.State state;
Exception ex = null;
try {
get();
state = CompressionListener.State.DONE;
} catch (InterruptedException e) {
state = CompressionListener.State.CANCELED;
ex = e;
} catch (ExecutionException e) {
state = CompressionListener.State.FAILED;
ex = e;
}
if (doZip) {
callback.compressionFinished(state, zipFile, path, ex);
} else {
callback.decompressionFinished(state, zipFile, path, ex);
}
}
static public void compressFile(File source, File zipTarget,
JProgressBar pFile, CompressionListener callback) {
assert source.exists() && !source.isDirectory();
new SwingCompressUtil(true, zipTarget, source, null, pFile, callback).execute();
}
static public void compressDirectory(File source, File zipTarget,
JProgressBar pTotal, CompressionListener callback) {
assert source.exists() && source.isDirectory();
new SwingCompressUtil(true, zipTarget, source, pTotal, null, callback).execute();
}
static public void compressDirectory(File source, File zipTarget,
JProgressBar pTotal, JProgressBar pFile, CompressionListener callback) {
assert source.exists() && source.isDirectory();
new SwingCompressUtil(true, zipTarget, source, pTotal, pFile, callback).execute();
}
int countFiles(File source) { // We could count octet instead of file number
if (!source.isDirectory())
return 1;
int result = 0;
for (File child : source.listFiles())
result = result + countFiles(child);
return result;
}
long countFilesSize(File source) { // We could count octet instead of file number
if (!source.isDirectory())
return source.length();
int result = 0;
for (File child : source.listFiles())
result = result + countFiles(child);
return result;
}
public static void cancelOperation(JProgressBar p) {
synchronized(p) {
p.putClientProperty(CANCELED_PROPERTY, Boolean.TRUE);
}
}
void resetCancel(JProgressBar p) {
if (p != null) synchronized(p) {
p.putClientProperty(CANCELED_PROPERTY, Boolean.FALSE);
}
}
static boolean isOperationCanceled(JProgressBar p) {
if (p != null) synchronized(p) {
return Boolean.TRUE.equals(p.getClientProperty(CANCELED_PROPERTY));
}
return false;
}
boolean isTotalSize() {
if (progressTotal != null) synchronized (progressTotal) {
return Boolean.TRUE.equals(progressTotal.getClientProperty(TOTAL_SIZE_PROPERTY));
}
return false;
}
long zipDir(File zipDir, ZipOutputStream zos, String name, long count) throws IOException {
// Create a new File object based on the directory we have to zip
if (name.endsWith(File.separator))
name = name.substring(0, name.length() - File.separator.length());
if (!name.endsWith(CompressUtil.ZIP_FILE_SEPARATOR))
name = name + CompressUtil.ZIP_FILE_SEPARATOR;
final int scale = scale(progressTotal);
// Place the zip entry in the ZipOutputStream object
// Get a listing of the directory content
File[] dirList = zipDir.listFiles();
if (dirList.length == 0) { // empty directory
if (CompressUtil.DEBUG)
System.out.println("Add empty entry for directory : " + name);
ZipEntry anEntry = new ZipEntry(name);
zos.putNextEntry(anEntry);
return count;
}
// Loop through dirList, and zip the files
for (int i=0; i<dirList.length; i++) {
File f = dirList[i];
String fName = name + f.getName();
if (f.isDirectory()) {
// if the File object is a directory, call this
// function again to add its content recursively
count = zipDir(f, zos, fName, count);
} else {
if (isTotalSize()) {
count = count + f.length();
progressTotal.setValue((int) (count/scale));
} else {
count++;
progressTotal.setValue((int)count);
}
zipFile(f, zos, fName);
if (isOperationCanceled(progressTotal))
throw new InterruptedIOException("progress");
if (isOperationCanceled(progressFile))
throw new InterruptedIOException("progress");
}
}
return count;
}
static int scale(JProgressBar p) {
synchronized(p) {
Object scale = p.getClientProperty(SCALE_PROPERTY);
if (!(scale instanceof Scale)) {
return DEFAULT_SCALE.weight;
}
return ((Scale) scale).weight;
}
}
void zipFile(File zipfile, ZipOutputStream zos, String name) throws IOException {
// if we reached here, the File object f was not a directory
// create a FileInputStream on top of f
final int scale = scale(progressFile);
FileInputStream fis = new FileInputStream(zipfile);
if (progressFile != null) {
progressFile.setValue(0);
progressFile.setMaximum((int) (zipfile.length()/scale));
}
try {
// create a new zip entry
ZipEntry anEntry = new ZipEntry(name);
if (CompressUtil.DEBUG)
System.out.println("Add file : " + name);
// place the zip entry in the ZipOutputStream object
zos.putNextEntry(anEntry);
// now write the content of the file to the
// ZipOutputStream
long count = 0;
byte[] readBuffer = new byte[CompressUtil.BUFFER_SIZE];
for (int read=fis.read(readBuffer); read != -1; read=fis.read(readBuffer)) {
if (isOperationCanceled(progressTotal))
throw new InterruptedIOException("progress");
if (isOperationCanceled(progressFile))
throw new InterruptedIOException("progress");
if (progressFile != null) {
count = count + read;
progressFile.setValue((int) (count/scale));
}
zos.write(readBuffer, 0, read);
}
} finally {
// close the Stream
fis.close();
}
}
/**
* The progression is measured by the input stream.
* Note that FileInputStream does not support mark(), so there is no issue to simply
* count the number of read bytes.
*
* @param zipSource
* @param target
* @param pFile
* @throws IOException
*/
static public void uncompressDirectory(File zipSource, File target,
JProgressBar pFile, CompressionListener callback) {
new SwingCompressUtil(false, zipSource, target, null, pFile, callback).execute();
}
protected void unZip(File zipSource, File target) throws IOException {
// Target is the zip
// Source is the directory
// create a ZipInputStream to unzip the data from.
InputStream inputStream = new FileInputStream(zipSource);
ProgressInputStream counter = null;
int scale = -1;
if (progressFile != null) {
scale = scale(progressFile);
progressFile.setValue(0);
progressFile.setMaximum((int) (zipSource.length()/scale));
counter = new ProgressInputStream(inputStream);
inputStream = counter;
}
ZipInputStream zis = new ZipInputStream(inputStream);
// assuming that there is a directory named inFolder
// (If there isn't create one)
// in the same directory as the one the code
// runs from, call the zipDir method
try {
byte[] readBuffer = new byte[CompressUtil.BUFFER_SIZE];
for (ZipEntry entry=zis.getNextEntry(); entry != null;
entry=zis.getNextEntry()) {
File createdFile = extractFile(entry, target);
FileOutputStream fos = new FileOutputStream(createdFile);
try {
for (int bytesIn = zis.read(readBuffer); (bytesIn != -1);
bytesIn = zis.read(readBuffer)) {
if (isOperationCanceled(progressFile))
throw new InterruptedIOException("progress");
fos.write(readBuffer, 0, bytesIn);
if (progressFile != null) {
progressFile.setValue((int) (counter.getCount()/scale));
}
}
// Some division approximation prevent 100% to be displayed
if (progressFile != null) {
progressFile.setValue(progressFile.getMaximum());
}
} finally {
fos.close();
}
if (CompressUtil.DEBUG) {
System.out.println("read file (" + createdFile.length() +
" defined as " + entry.getSize()
+ " ) defined : " + createdFile);
}
}
} finally {
zis.close();
}
}
static File extractFile(ZipEntry entry, File target) {
String relativeName = entry.getName();
if (CompressUtil.DEBUG)
System.out.println("- entry : " + relativeName);
int start = 0;
File dir = target;
for (int index=relativeName.indexOf(CompressUtil.ZIP_FILE_SEPARATOR, start);
index != -1;
index=relativeName.indexOf(CompressUtil.ZIP_FILE_SEPARATOR, start)) {
String pathName = relativeName.substring(start, index);
dir = new File(dir, pathName);
start = index + CompressUtil.ZIP_FILE_SEPARATOR.length();
}
dir.mkdirs();
if (entry.isDirectory()) {
// only a directory,
// entry name ends with ZIP_FILE_SEPARATOR
if (CompressUtil.DEBUG)
System.out.println("read dir ["+dir.exists()+"] : " + dir.getPath());
return dir;
}
String name = relativeName.substring(start);
return new File(dir, name);
}
static String getEntryShortName(ZipEntry entry) {
String fullPath = entry.getName();
if (entry.isDirectory())
fullPath = fullPath.substring(0,
fullPath.length()-CompressUtil.ZIP_FILE_SEPARATOR.length());
int endIndex = fullPath.lastIndexOf(CompressUtil.ZIP_FILE_SEPARATOR);
if (endIndex != -1) {
return fullPath.substring(endIndex+CompressUtil.ZIP_FILE_SEPARATOR.length());
} else {
return fullPath;
}
}
public static void main(String[] args) {
final JFrame f = new JFrame("test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel display = new JPanel(new BorderLayout(5, 5));
JPanel titles = new JPanel(new GridLayout(0, 1, 5, 5));
titles.add(new JLabel("File"));
titles.add(new JLabel("Total"));
JPanel progesses = new JPanel(new GridLayout(0, 1, 5, 5));
final JProgressBar pFile = new JProgressBar();
pFile.putClientProperty(SCALE_PROPERTY, Scale.megaOctet);
pFile.setStringPainted(true);
pFile.setString("-");
final JProgressBar pTotal = new JProgressBar();
pTotal.setStringPainted(true);
pTotal.setString("-");
progesses.add(pFile);
progesses.add(pTotal);
display.add(titles, BorderLayout.LINE_START);
display.add(progesses, BorderLayout.CENTER);
ChangeListener l = new ChangeListener() {
public void stateChanged(ChangeEvent e) {
JProgressBar bar = (JProgressBar) e.getSource();
BoundedRangeModel progress = bar.getModel();
bar.setString("(" + progress.getValue()
+ "/" + progress.getMaximum() + ")");
}
};
pFile.addChangeListener(l);
pTotal.addChangeListener(l);
final JButton cancel = new JButton("Cancel");
final JButton go = new JButton("Go");
cancel.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
cancelOperation(pFile);
}
});
final CompressionListener callback = new CompressionListener() {
public void compressionFinished(CompressionListener.State s, File zip, File from, Exception e) {
f.setTitle(s.name());
if (e != null)
e.printStackTrace();
go.setEnabled(true);
}
public void decompressionFinished(CompressionListener.State s, File zip, File from, Exception e) {
f.setTitle(s.name());
if (e != null)
e.printStackTrace();
go.setEnabled(true);
}
};
go.addActionListener(new ActionListener() {
boolean zip = true;
public void actionPerformed(ActionEvent e) {
go.setEnabled(false);
if (zip) {
compressDirectory(
new File("../common-lib/deploy/JBoss/jboss-4.2.2.GA/server"),
new File("test/test.zip"),
pTotal, pFile, callback);
} else {
pTotal.setMaximum(0);
pTotal.setValue(0);
uncompressDirectory(new File("test/test.zip"), new File("test/unzip"),
pFile, callback);
}
zip = !zip;
}
});
f.getContentPane().add(go, BorderLayout.PAGE_START);
f.getContentPane().add(display, BorderLayout.CENTER);
f.getContentPane().add(cancel, BorderLayout.PAGE_END);
f.pack();
f.setVisible(true);
}
}