/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
package org.opensourcephysics.tools;
import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Insets;
import java.awt.Toolkit;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.rmi.RemoteException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import javax.swing.AbstractButton;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingConstants;
import javax.swing.filechooser.FileSystemView;
import org.opensourcephysics.display.DisplayRes;
import org.opensourcephysics.display.OSPRuntime;
/**
* This provides a simple way to package files in a single JAR or ZIP file
*
* @author Francisco Esquembre (http://fem.um.es)
* @author Doug Brown
* @version 1.0
*/
public class JarTool implements Tool, Runnable {
static public final int YES = 0;
static public final int NO = 1;
static public final int YES_TO_ALL = 2;
static public final int NO_TO_ALL = 3;
static public final int CANCEL = 4;
// ---- Localization
static private final String BUNDLE_NAME = "org.opensourcephysics.resources.tools.tools"; //$NON-NLS-1$
static private ResourceBundle res = ResourceBundle.getBundle(BUNDLE_NAME);
static public void setLocale(Locale locale) {
res = ResourceBundle.getBundle(BUNDLE_NAME, locale);
}
static public String getString(String key) {
try {
return res.getString(key);
} catch(MissingResourceException e) {
return '!'+key+'!';
}
}
// --- End of localization
/**
* The singleton shared translator tool.
*/
private static JarTool TOOL = new JarTool();
private static JFileChooser chooser;
private static int overwritePolicy = NO;
private static Frame ownerFrame = null;
private static Map<String, Map<String, ZipEntry>> jarContents = new HashMap<String, Map<String, ZipEntry>>(); // added by D Brown 2007-10-31
/**
* Gets the shared JarTool.
* @return the shared JarTool
*/
public static JarTool getTool() {
if(TOOL==null) {
TOOL = new JarTool();
}
return TOOL;
}
/**
* Private constructor.
*/
private JarTool() {
String name = "JarTool"; //$NON-NLS-1$
chooser = OSPRuntime.createChooser("JAR, ZIP", new String[] {"zip", "jar", "trz"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
Toolbox.addTool(name, this);
}
private ArrayList<String> instanceSources;
private File instanceParent;
private File instanceTarget;
private Manifest instanceManifest;
private OverwriteValue instancePolicy;
private Frame instanceOwnerFrame;
public void run() {
compressList(instanceSources, instanceParent, instanceTarget, instanceManifest, instancePolicy, instanceOwnerFrame);
}
private JarTool(ArrayList<String> aSources, File aParent, File aTarget, Manifest aManifest, OverwriteValue aPolicy, Frame _anOwner) {
this();
instanceSources = aSources;
instanceParent = aParent;
instanceTarget = aTarget;
instanceManifest = aManifest;
instancePolicy = aPolicy;
instanceOwnerFrame = _anOwner;
}
/**
* Sends a job to this tool and specifies a tool to reply to.
*
* @param job the Job
* @param replyTo the tool to notify when the job is complete (may be null)
* @throws RemoteException
*/
public void send(Job job, Tool replyTo) throws RemoteException {}
// -----------------------------------
// Main user methods
// -----------------------------------
/**
* This method is kept for backwards compatibility. It is equivalent to
* alwaysOverwrite().
*/
static public void disableWarning() {
alwaysOverwrite();
}
/**
* Sets the overwrite feature to false.
* By default, the packaging methods create() and append() will warn the user
* before overwritting a duplicated file. Calling this method before running
* those methods disables this warning and makes sure a new file will NEVER
* overwrite an older one. The methods create() and append() set the warning
* feature back.
* @see alwayOverwrite
*/
static public void neverOverwrite() {
overwritePolicy = NO_TO_ALL;
}
/**
* Sets the overwrite feature to true.
* By default, the packaging methods create() and append() will warn the user
* before overwritting a duplicated file. Calling this method before running
* those methods disables this warning AND makes sure a new file will ALWAYS
* overwrite an older one. The methods create() and append() set the warning
* feature back.
* @see neverOverwrite
*/
static public void alwaysOverwrite() {
overwritePolicy = YES_TO_ALL;
}
/**
* Sets the owner frame for progress dialogs that may appear when creating
* a JAR file.
* @param owner Frame
*/
static public void setOwnerFrame(Frame owner) {
ownerFrame = owner;
}
/**
* Creates a JAR file out of the list of contents provided.
* Each entry in the sources list can be either a single file, a directory,
* or a compressed (ZIP, JAR or TRZ) file.
* Regular files and directories must exist under the given parent directory,
* and are saved with the same relative path as provided.
* As an exception, you can also add files in parent directories, such as,
* "../../../WhateverDir/WhateverFile.xml", but ALL leading "../" will be removed
* when saved.
* Directories are added recursively.
* Compressed files can, on the contrary, be placed anywhere and their contents
* are stored with the same directory structure as in the original jar file, irrespective
* of the original location of the source compressed file.
* If a file appears more than once, succesive files could overwrite previous ones.
* The user will be warned of this, except if either neverOverwrite() or alwaysOverwrite()
* have been invoked immediately before calling this method.
* (Calling this method resets the warning feature back.)
*
* @param sources ArrayList The list of content files to add.
* Each item in the list is a String with the relative name of a
* file or directory under the given parent directory, or of
* a compressed file anywhere in the hard disk.
* @param parent File The parent directory for all relative filenames
* @param target File The target compressed file.
* Its name must end in .zip, .jar or .trz.
* The user will be prompted to confirm the target.
* @param manifest Manifest A manifest for the newly created JAR file.
* @return File The file that will eventually be created. Note that
* the main work is done using a separate thread, hence the
* method returns BEFORE the JAR file is actually created.
* @see java.util.jar.Manifest
*/
public File create(ArrayList<String> sources, File parent, File target, Manifest manifest) {
OverwriteValue policy = new OverwriteValue(overwritePolicy);
overwritePolicy = NO;
if(sources.size()<=0) {
return null;
}
try {
boolean warnBeforeOverwritting = true;
if(target!=null) {
chooser.setCurrentDirectory(target.getParentFile());
chooser.setSelectedFile(target);
} else {
chooser.setSelectedFile(new File("default.jar")); //$NON-NLS-1$
}
String targetName = OSPRuntime.chooseFilename(chooser);
if(targetName==null) {
return null;
}
if(!(targetName.toLowerCase().endsWith(".jar")|| //$NON-NLS-1$
targetName.toLowerCase().endsWith(".trz")|| //$NON-NLS-1$
targetName.toLowerCase().endsWith(".zip"))) { //$NON-NLS-1$
targetName = targetName+".jar"; //$NON-NLS-1$
} else {
warnBeforeOverwritting = false; // the chooser already checked if the target file exists
}
target = new File(targetName);
if(org.opensourcephysics.controls.XML.forwardSlash(target.getAbsolutePath()).equals(OSPRuntime.getLaunchJarPath())) {
String[] message = new String[] {res.getString("JarTool.JarNotCreated"), res.getString("JarTool.FileIsForbidden")+" "+target}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
JOptionPane.showMessageDialog((JFrame) null, message, res.getString("JarTool.Error"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
return create(sources, parent, target, manifest); // added by D Brown June 2007
}
if(warnBeforeOverwritting&&target.exists()) {
int selected = JOptionPane.showConfirmDialog(null, DisplayRes.getString("DrawingFrame.ReplaceExisting_message")+" "+ //$NON-NLS-1$ //$NON-NLS-2$
target.getName()+DisplayRes.getString("DrawingFrame.QuestionMark"), //$NON-NLS-1$
DisplayRes.getString("DrawingFrame.ReplaceFile_option_title"), //$NON-NLS-1$
JOptionPane.YES_NO_CANCEL_OPTION);
if(selected!=JOptionPane.YES_OPTION) {
return null;
}
}
JarTool builder = new JarTool(sources, parent, target, manifest, policy, ownerFrame);
java.lang.Thread thread = new Thread(builder);
thread.setPriority(Thread.NORM_PRIORITY);
thread.start();
return target;
// return compressList(sources,parent,target,manifest,policy,ownerFrame);
} catch(Exception exception) {
exception.printStackTrace();
return null;
}
}
/**
* Appends to an existing compressed file the list of contents provided.
* Works similarly to create(), but uses an existing compressed file
* and respects its manifest (if a JAR file).
* @param sources ArrayList The list of content files to add.
* Each item in the list is a String with the relative name of a
* file or directory under the current parent directory, or of
* a compressed file anywhere in the hard disk.
* @param parent File The parent directory for all relative filenames
* @param target String The name of an existing compressed file, relative
* to the parent directory.
*/
public File append(ArrayList<String> sources, File parent, String target) {
OverwriteValue policy = new OverwriteValue(overwritePolicy);
overwritePolicy = NO;
if(sources.size()<=0) {
return null;
}
try {
File targetFile = new File(parent, target);
if(!targetFile.exists()) {
String[] message = new String[] {res.getString("JarTool.JarNotCreated"), res.getString("JarTool.FileDoesntExist")+" "+target}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
JOptionPane.showMessageDialog((JFrame) null, message, res.getString("JarTool.Error"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
return null;
}
if(!sources.contains(target)) {
sources.add(0, target);
}
return compressList(sources, parent, targetFile, getManifest(targetFile), policy, ownerFrame);
} catch(Exception exception) {
exception.printStackTrace();
return null;
}
}
/**
* Creates a Manifest for a JAR file with the given parameters
* @param classpath String
* @param mainclass String
* @return Manifest
*/
static public Manifest createManifest(String classpath, String mainclass) {
SimpleDateFormat sdf = new SimpleDateFormat("dd MMM yyyy"); //$NON-NLS-1$
Calendar cal = Calendar.getInstance();
String date = sdf.format(cal.getTime());
try {
StringBuffer manifestStr = new StringBuffer();
manifestStr.append("Manifest-Version: 1.0\n"); //$NON-NLS-1$
manifestStr.append("Built-By: Open Source Physics JarTool\n"); //$NON-NLS-1$
manifestStr.append("Build-Date: "+date+"\n"); //$NON-NLS-1$ //$NON-NLS-2$
if(classpath!=null) {
classpath = classpath.replace(';', ' ');
classpath = classpath.replace(',', ' ');
classpath = classpath.replace(':', ' ');
manifestStr.append("Class-Path: "+classpath+"\n"); //$NON-NLS-1$ //$NON-NLS-2$
}
if(mainclass!=null) {
manifestStr.append("Main-Class: "+mainclass+"\n"); //$NON-NLS-1$ //$NON-NLS-2$
}
manifestStr.append("\n"); //$NON-NLS-1$
InputStream mis = new ByteArrayInputStream(manifestStr.toString().getBytes("UTF-8")); //$NON-NLS-1$
return(new Manifest(mis));
} catch(Exception exc) {
exc.printStackTrace();
return null;
}
}
/**
* Gets the Manifest of an existing JAR file
* @param file File the jar file from which to obtain the manifest
* @return Manifest the manifest found, null if failed.
*/
static public Manifest getManifest(File file) {
try {
JarFile jar = new JarFile(file);
Manifest manifest = jar.getManifest();
jar.close();
return manifest;
} catch(Exception exc) {
return null;
} // Do not complain
}
/**
* Extracts a given file from a compressed (ZIP, JAR or TRZ) file
* @param source File The compressed file to extract the file from
* @param filename String The path of the file to extract
* @param destination String The full (or relative to whatever the current
* user directory is) path where to save the extracted file
* @return File The extracted file, null if failed
*/
static public File extract(File source, String filename, String destination) {
return extract(source, filename, new File(destination));
}
/**
* Extracts a given file from a compressed (ZIP, JAR or TRZ) file
* Extensive changes by D Brown 2007-10-31
* @param source File The compressed file to extract the file from
* @param filename String The path of the file to extract
* @param target File The target file for the extracted file
* @return File The extracted file, null if failed
*/
static public File extract(File source, String filename, File target) {
if((source.exists()==false)||(filename==null)||(filename.trim().length()<1)||(target==null)) {
return null;
}
boolean isDirectory = (filename.lastIndexOf("/")==filename.length()-1); //$NON-NLS-1$
try {
// get contents Map of filename to ZipEntry for source jar
Map<String, ZipEntry> contents = jarContents.get(source.getPath());
if(contents==null) {
// create new Map and fill it
contents = new HashMap<String, ZipEntry>();
jarContents.put(source.getPath(), contents);
ZipInputStream input = new ZipInputStream(new FileInputStream(source));
ZipEntry zipEntry = null;
while((zipEntry = input.getNextEntry())!=null) {
if(zipEntry.isDirectory()) {
continue; // don't include directories
}
contents.put(zipEntry.getName(), zipEntry);
}
input.close();
}
if(isDirectory) {
// target is a directory: extract all contained files
Iterator<String> it = contents.keySet().iterator();
while(it.hasNext()) {
String next = it.next();
if(next.startsWith(filename)) {
// next is in the directory, so extract it
ZipEntry zipEntry = contents.get(next);
// construct new target for the file
int n = filename.length();
File newTarget = new File(target, zipEntry.getName().substring(n));
extract(source, next, newTarget);
}
}
return target;
}
// target is a file
ZipEntry entry = contents.get(filename);
ZipFile input = new ZipFile(source);
InputStream in = input.getInputStream(entry); // A stream to read the entry
File parent = target.getParentFile();
if(parent!=null) {
parent.mkdirs();
}
int bytesRead;
byte[] buffer = new byte[1024];
FileOutputStream output = new FileOutputStream(target);
while((bytesRead = in.read(buffer))!=-1) {
output.write(buffer, 0, bytesRead);
}
output.close();
input.close();
return target;
} catch(Exception ex) {
ex.printStackTrace();
}
return null;
}
/**
* Extracts a file using the given class loader
* @param _classLoader ClassLoader The class loader to extract the files from
* @param filename String The path of the file to extract
* @param target File The target file for the extracted file
* @return File The extracted file, null if failed
*/
static public File extract(ClassLoader classLoader, String filename, File target) {
if((filename==null)||(filename.trim().length()<=0)||(target==null)) {
return null;
}
try {
URL url = classLoader.getResource(filename);
if(url==null) {
return null;
}
target.getParentFile().mkdirs();
int bytesRead;
byte[] buffer = new byte[1024];
FileOutputStream output = new FileOutputStream(target);
BufferedInputStream input = new BufferedInputStream(url.openStream());
while((bytesRead = input.read(buffer))!=-1) {
output.write(buffer, 0, bytesRead);
}
output.close();
input.close();
return target;
} catch(Exception exc) {
exc.printStackTrace();
return null;
}
}
/**
* Extracts a file using the ResourceLoader utility
* @param filename String The path of the file to extract
* @param target File The target file for the extracted file
* @return File The extracted file, null if failed
*/
static public File extract(String filename, File target) {
//System.out.println("Extract filename="+filename); //$NON-NLS-1$
if((filename==null)||(filename.trim().length()<=0)||(target==null)) {
return null;
}
try {
InputStream inputStream=null;
if(OSPRuntime.applet!=null) {// added by Wolfgang Christian
//URL url=OSPRuntime.applet.getClass().getResource(filename);
//inputStream=url.openStream();
inputStream=OSPRuntime.applet.getClass().getResourceAsStream(filename);
}
if(inputStream==null) { // use resource loader when not an applet
if(filename.indexOf("http:")>-1) { //$NON-NLS-1$
int n = filename.toLowerCase().indexOf(".zip!/"); //$NON-NLS-1$
if (n==-1) n = filename.toLowerCase().indexOf(".jar!/"); //$NON-NLS-1$
if (n==-1) n = filename.toLowerCase().indexOf(".trz!/"); //$NON-NLS-1$
if (n>-1) {
File extracted = ResourceLoader.extractFileFromZIP(filename, target, false);
if (extracted!=null) return extracted;
}
}
else {
inputStream = ResourceLoader.getResource(filename, false).openInputStream();
}
}
if(inputStream==null) {
return null;
}
BufferedInputStream input = new BufferedInputStream(inputStream);
target.getParentFile().mkdirs();
int bytesRead;
byte[] buffer = new byte[1024];
FileOutputStream output = new FileOutputStream(target);
while((bytesRead = input.read(buffer))!=-1) {
output.write(buffer, 0, bytesRead);
}
output.close();
input.close();
return target;
} catch(Exception exc) {
System.err.println("JarTool extract resource error. Filename="+filename); //$NON-NLS-1$
exc.printStackTrace();
return null;
}
}
/**
* Extract a list of files (given by their relative names) to the given target directory.
* If files exist, the user will be warned.
* @param source Object Either a compressed java.io.File with the given resources,
* a ClassLoader object which will be used to extract the files, or null, in which case,
* the ResourceLoader will be used.
* @param files AbstractList The list of String with the relative names of the files to extract
* @param targetDirectory File The target directory where to extract the files
* @return boolean
*/
static public boolean extract(Object source, java.util.List<?> files, File targetDirectory) {
if(files.size()<=0) {
return true;
}
if(!((source==null)||(source instanceof File)||(source instanceof ClassLoader))) {
String[] message = new String[] {res.getString("JarTool.FileNotExtracted"), res.getString("JarTool.SourceRequirement")+" null, java.io.File, java.lang.ClassLoader."}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
JOptionPane.showMessageDialog((JFrame) null, message, res.getString("JarTool.Error"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
return false;
}
if(!(targetDirectory.exists()&&targetDirectory.isDirectory())) {
String[] message = new String[] {res.getString("JarTool.FileNotExtracted"), res.getString("JarTool.FileDoesntExist")+" "+targetDirectory.getName()}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
JOptionPane.showMessageDialog((JFrame) null, message, res.getString("JarTool.Error"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
return false;
}
int policy = NO;
for(Iterator<?> it = files.iterator(); it.hasNext(); ) { // Copy or uncompress all sources to the temporary directory
String filename = it.next().toString();
File targetFile = new File(targetDirectory, filename);
if(targetFile.exists()) {
switch(policy) {
case NO_TO_ALL :
continue;
case YES_TO_ALL :
break; // will overwrite
default :
switch(policy = confirmOverwrite(filename)) {
case NO_TO_ALL :
case NO :
continue;
// case CANCEL : return false;
default : // Do nothing, i.e., will overwrite the file
}
}
}
File result = null;
if(source==null) {
result = extract(filename, targetFile); // Use the ResourceLoader
} else if(source instanceof File) {
result = extract((File) source, filename, targetFile);
} else if(source instanceof ClassLoader) {
result = extract((ClassLoader) source, filename, targetFile);
}
if(result==null) {
String[] message = new String[] {res.getString("JarTool.FileNotExtracted"), filename+" "+res.getString("JarTool.FileNotExtractedFrom")+" "+source}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
JOptionPane.showMessageDialog((JFrame) null, message, res.getString("JarTool.Error"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
return false;
}
}
return true;
}
// -----------------------------------
// Different public File-based utilities
// -----------------------------------
/**
* Copies a file. If the target file exists, it will be overwritten.
* @param source File The file to copy
* @param target File destination file
* @return boolean true if successful
*/
static public boolean copy(File source, File target) {
try {
if(!source.exists()) {
return false;
}
target.getParentFile().mkdirs();
InputStream input = new FileInputStream(source);
OutputStream output = new FileOutputStream(target);
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while((len = input.read(buf))>0) {
output.write(buf, 0, len);
}
input.close();
output.close();
return true;
} catch(Exception exc) {
exc.printStackTrace();
return false;
}
}
/**
* Compresses a directory into a single JAR or ZIP file.
* If the target file exists it will be overwritten.
* @param source File The directory to compress
* @param target File The output file
* @param manifest Manifest The manifest (in case of a JAR file)
* @return boolean
*/
static public boolean compress(File source, File target, Manifest manifest) {
try {
if(!(source.exists()&source.isDirectory())) {
return false;
}
if(target.exists()) {
target.delete(); // Remove the previous JAR file
}
ZipOutputStream output = null;
boolean isJar = target.getName().toLowerCase().endsWith(".jar"); //$NON-NLS-1$
if(isJar) {
// Remove any existing META-INF directory
File manifestDir = new File(source, "META-INF"); //$NON-NLS-1$
remove(manifestDir);
if(manifest!=null) {
output = new JarOutputStream(new FileOutputStream(target), manifest);
} else {
output = new JarOutputStream(new FileOutputStream(target));
}
} else {
output = new ZipOutputStream(new FileOutputStream(target));
}
// Get the list of files
java.util.Collection<File> list = getContents(source);
String baseDir = source.getAbsolutePath().replace('\\', '/');
if(!baseDir.endsWith("/")) { //$NON-NLS-1$
baseDir = baseDir+"/"; //$NON-NLS-1$
}
int baseDirLength = baseDir.length();
// Create the compressed file
byte[] buffer = new byte[1024]; // Allocate a buffer for reading entry data.
int bytesRead;
for(File file : list) {
FileInputStream f_in = new FileInputStream(file);
// Read the entry and make it relative
String filename = file.getAbsolutePath().replace('\\', '/');
if(filename.startsWith(baseDir)) {
filename = filename.substring(baseDirLength);
}
// Write the entry to the new compressed file.
if(isJar) {
output.putNextEntry(new JarEntry(filename));
} else {
output.putNextEntry(new ZipEntry(filename));
}
while((bytesRead = f_in.read(buffer))!=-1) {
output.write(buffer, 0, bytesRead);
}
f_in.close();
output.closeEntry();
}
output.close();
} catch(Exception exc) {
exc.printStackTrace();
return false;
}
return true;
}
/**
* Compresses a list of files and/or directories into a single JAR or ZIP file.
* All files/dirs must be in the same directory.
* If the target file exists it will be overwritten.
* @param sources ArrayList The list of files or directories to compress
* @param target File The output file
* @param manifest Manifest The manifest (in case of a JAR file)
* @return boolean
*/
static public boolean compress(ArrayList<File> sources, File target, Manifest manifest) {
try {
if((sources==null)||(sources.size()==0)) {
return false;
}
if(target.exists()) {
target.delete(); // Remove the previous JAR file
}
ZipOutputStream output = null;
boolean isJar = target.getName().toLowerCase().endsWith(".jar"); //$NON-NLS-1$
if(isJar) {
if(manifest!=null) {
output = new JarOutputStream(new FileOutputStream(target), manifest);
} else {
output = new JarOutputStream(new FileOutputStream(target));
}
} else {
output = new ZipOutputStream(new FileOutputStream(target));
}
// Recall all files must have the same parent dir
String baseDir = sources.get(0).getParentFile().getAbsolutePath().replace('\\', '/');
if(!baseDir.endsWith("/")) { //$NON-NLS-1$
baseDir = baseDir+"/"; //$NON-NLS-1$
}
int baseDirLength = baseDir.length();
// Get the list of files
ArrayList<File> list = new ArrayList<File>();
for(Iterator<File> it = sources.iterator(); it.hasNext(); ) {
File fileOrDir = it.next();
if(isJar&&(manifest!=null)&&fileOrDir.getName().equals("META-INF")) { //$NON-NLS-1$
continue; // Ignore any existing META-INF directory
}
if(fileOrDir.isDirectory()) {
list.addAll(getContents(fileOrDir));
} else {
list.add(fileOrDir);
}
}
// Create the compressed file
byte[] buffer = new byte[1024]; // Allocate a buffer for reading entry data.
int bytesRead;
for(int i = 0, n = list.size(); i<n; i++) {
File file = list.get(i);
FileInputStream f_in = new FileInputStream(file);
// Read the entry and make it relative
String filename = file.getAbsolutePath().replace('\\', '/');
if(filename.startsWith(baseDir)) {
filename = filename.substring(baseDirLength);
}
// Write the entry to the new compressed file.
if(isJar) {
output.putNextEntry(new JarEntry(filename));
} else {
output.putNextEntry(new ZipEntry(filename));
}
while((bytesRead = f_in.read(buffer))!=-1) {
output.write(buffer, 0, bytesRead);
}
f_in.close();
output.closeEntry();
}
output.close();
} catch(Exception exc) {
exc.printStackTrace();
return false;
}
return true;
}
/**
* Completely removes a directory (without warning!)
* @param directory File The directory to delete
*/
static public boolean remove(File directory) {
if(directory.exists()&&directory.isDirectory()) {
return recursiveClearDirectory(directory, FileSystemView.getFileSystemView());
}
return false;
}
/**
* Returns all the files under a given directory
* @param directory File
* @return ArrayList
*/
static public java.util.Collection<File> getContents(File directory) {
if(directory.exists()&&directory.isDirectory()) {
return recursiveGetDirectory(directory, FileSystemView.getFileSystemView());
}
return new HashSet<File>();
}
/**
* Uncompresses a ZIP or JAR file into a given directory.
* Duplicated files will be overwritten.
* @param source File The compressed file to uncompress
* @param targetDirectory File The target directory
* @return boolean
* @see unzipNoOverwrite
* @see unzipWithAWarning
*/
static public boolean unzip(File source, File targetDirectory) {
return unzipWithWarning(source, targetDirectory, new OverwriteValue(YES_TO_ALL));
}
/**
* Uncompresses a ZIP or JAR file into a given directory.
* Duplicated files will NOT be overwriten.
* @param source File The compressed file to uncompress
* @param targetDirectory File The target directory
* @return boolean
* @see unzip
* @see unzipWithAWarning
*/
static public boolean unzipNoOverwrite(File source, File targetDirectory) {
return unzipWithWarning(source, targetDirectory, new OverwriteValue(NO_TO_ALL));
}
/**
* Uncompresses a ZIP or JAR file into a given directory.
* The system will issue a warning before duplicating existing files.
* @param source File The compressed file to uncompress
* @param targetDirectory File The target directory
* @return boolean
* @see unzip
* @see unzipNoOverwrite
*/
static public boolean unzipWithAWarning(File source, File targetDirectory) {
return unzipWithWarning(source, targetDirectory, new OverwriteValue(NO));
}
// -----------------------------------
// Private methods
// -----------------------------------
static public int confirmOverwrite(String filename) {
return confirmOverwrite(filename, false);
}
/**
* Whether to overwrite an existing file.
* @param file File
* @return boolean
*/
static public int confirmOverwrite(String filename, boolean canCancel) {
final JDialog dialog = new JDialog();
final OverwriteValue returnValue = new OverwriteValue(NO);
java.awt.event.MouseAdapter mouseListener = new java.awt.event.MouseAdapter() {
public void mousePressed(java.awt.event.MouseEvent evt) {
AbstractButton button = (AbstractButton) (evt.getSource());
String aCmd = button.getActionCommand();
if(aCmd.equals("yes")) { //$NON-NLS-1$
returnValue.value = YES;
} else if(aCmd.equals("no")) { //$NON-NLS-1$
returnValue.value = NO;
} else if(aCmd.equals("yesToAll")) { //$NON-NLS-1$
returnValue.value = YES_TO_ALL;
} else if(aCmd.equals("noToAll")) { //$NON-NLS-1$
returnValue.value = NO_TO_ALL;
} else if(aCmd.equals("cancel")) { //$NON-NLS-1$
returnValue.value = CANCEL;
}
dialog.setVisible(false);
}
};
JButton yesButton = new JButton(res.getString("JarTool.Yes")); //$NON-NLS-1$
yesButton.setActionCommand("yes"); //$NON-NLS-1$
yesButton.addMouseListener(mouseListener);
JButton noButton = new JButton(res.getString("JarTool.No")); //$NON-NLS-1$
noButton.setActionCommand("no"); //$NON-NLS-1$
noButton.addMouseListener(mouseListener);
JButton yesToAllButton = new JButton(res.getString("JarTool.YesToAll")); //$NON-NLS-1$
yesToAllButton.setActionCommand("yesToAll"); //$NON-NLS-1$
yesToAllButton.addMouseListener(mouseListener);
JButton noToAllButton = new JButton(res.getString("JarTool.NoToAll")); //$NON-NLS-1$
noToAllButton.setActionCommand("noToAll"); //$NON-NLS-1$
noToAllButton.addMouseListener(mouseListener);
JButton cancelButton = new JButton(res.getString("JarTreeDialog.Button.Cancel")); //$NON-NLS-1$
cancelButton.setActionCommand("cancel"); //$NON-NLS-1$
cancelButton.addMouseListener(mouseListener);
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
buttonPanel.add(yesButton);
buttonPanel.add(yesToAllButton);
buttonPanel.add(noButton);
buttonPanel.add(noToAllButton);
if (canCancel) buttonPanel.add(cancelButton);
JLabel label = new JLabel(DisplayRes.getString("DrawingFrame.ReplaceExisting_message")+" "+ //$NON-NLS-1$ //$NON-NLS-2$
filename+DisplayRes.getString("DrawingFrame.QuestionMark")); //$NON-NLS-1$
label.setHorizontalAlignment(SwingConstants.CENTER);
label.setBorder(new javax.swing.border.EmptyBorder(10, 10, 10, 10));
dialog.setTitle(DisplayRes.getString("DrawingFrame.ReplaceFile_option_title")); //$NON-NLS-1$
dialog.getContentPane().setLayout(new java.awt.BorderLayout(5, 0));
dialog.getContentPane().add(label, java.awt.BorderLayout.CENTER);
dialog.getContentPane().add(buttonPanel, java.awt.BorderLayout.SOUTH);
dialog.addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent event) {
returnValue.value = NO;
}
});
dialog.validate();
dialog.pack();
dialog.setLocationRelativeTo(null);
dialog.setModal(true);
dialog.setVisible(true);
return returnValue.value;
}
/**
* Uncompresses a ZIP or JAR file into a given directory.
* policy.value indicates what to do on duplicated files.
* @param source File The compressed file to uncompress
* @param targetDirectory File The target directory
* @param policy OverwriteValue
* @return boolean
*/
static private boolean unzipWithWarning(File source, File targetDirectory, OverwriteValue policy) {
try {
if(!source.exists()) {
return false;
}
ZipInputStream input = new ZipInputStream(new FileInputStream(source));
ZipEntry zipEntry = null;
byte[] buffer = new byte[1024];
while((zipEntry = input.getNextEntry())!=null) {
if(zipEntry.isDirectory()) {
continue;
}
File newFile = new File(targetDirectory, zipEntry.getName());
if(newFile.exists()) {
switch(policy.value) {
case NO_TO_ALL :
continue;
case YES_TO_ALL :
break; // will overwrite
default :
switch(policy.value = confirmOverwrite(zipEntry.getName())) {
case NO_TO_ALL :
case NO :
continue;
// case CANCEL : return false;
default : // Do nothing, i.e., will overwrite the file
}
}
}
newFile.getParentFile().mkdirs();
int bytesRead;
FileOutputStream output = new FileOutputStream(newFile);
while((bytesRead = input.read(buffer))!=-1) {
output.write(buffer, 0, bytesRead);
}
output.close();
input.closeEntry();
}
input.close();
} catch(Exception exc) {
exc.printStackTrace();
return false;
}
return true;
}
/**
* Uncompresses a ZIP or JAR file into a given directory.
* policy.value indicates what to do on duplicated files.
* @param zipStream The InputStream to read from
* @param targetDirectory File The target directory
* @param label An optional JLabel to display messages
* @param prefix A prefix to add to the extracted file in order to create the message
* @return java.util.List<File> the set of files extracted, null if cancelled
*/
static public java.util.List<File> unzipWithWarning(InputStream zipStream, File targetDirectory, javax.swing.JLabel label, String prefix) {
try {
OverwriteValue policy = new OverwriteValue(NO);
BufferedInputStream bufIn = new BufferedInputStream(zipStream);
ZipInputStream input = new ZipInputStream(bufIn);
ZipEntry zipEntry = null;
ArrayList<File> fileSet = new ArrayList<File>();
byte[] buffer = new byte[1024];
while((zipEntry = input.getNextEntry())!=null) {
if (zipEntry.isDirectory()) continue;
if (label!=null) label.setText(prefix+zipEntry.getName());
File newFile = new File(targetDirectory, zipEntry.getName());
if(newFile.exists()) {
switch(policy.value) {
case NO_TO_ALL :
continue;
case YES_TO_ALL :
break; // will overwrite
default :
switch(policy.value = confirmOverwrite(zipEntry.getName(),true)) {
case NO_TO_ALL :
case NO :
continue;
case CANCEL : return null;
default : // Do nothing, i.e., will overwrite the file
}
}
}
newFile.getParentFile().mkdirs();
int bytesRead;
FileOutputStream output = new FileOutputStream(newFile);
while((bytesRead = input.read(buffer))!=-1) {
output.write(buffer, 0, bytesRead);
}
output.close();
input.closeEntry();
fileSet.add(newFile);
}
input.close();
return fileSet;
} catch(Exception exc) {
exc.printStackTrace();
return null;
}
}
/**
* Compresses a list of sources to a final compressed file.
* policy.value indicates what to do on duplicated files.
* @param sources ArrayList A list of relative filenames
* @param parent File The parent directory to which relative filenames are given
* @param target File The target compressed file
* @param policy OverwriteValue What to do on duplicated entries
* @param manifest Manifest The (optional) manifest for JAR files
* @return File
*/
static private File compressList(ArrayList<String> sources, File parent, File target, Manifest manifest, OverwriteValue policy, Frame owner) {
// Create a temporary directory
File temporaryDirectory = null;
try {
temporaryDirectory = File.createTempFile("JarTool", ".tmp", target.getParentFile()); // Get a unique name for our temporary directory //$NON-NLS-1$ //$NON-NLS-2$
temporaryDirectory.delete(); // remove the created file
} catch(Exception exc) {
temporaryDirectory = null;
}
if((temporaryDirectory==null)||!temporaryDirectory.mkdirs()) { // and re-create it as a directory
String[] message = new String[] {res.getString("JarTool.JarNotCreated"), res.getString("JarTool.CantCreateTemp")}; //$NON-NLS-1$ //$NON-NLS-2$
JOptionPane.showMessageDialog((JFrame) null, message, res.getString("JarTool.Error"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
return null;
}
StringBuffer errorMessage = new StringBuffer();
int steps = sources.size(), interval = 1, counter = 0;
if(steps>10) {
interval = Math.round(steps/10.0f);
steps = 10;
}
ProgressDialog pD = new ProgressDialog(owner, steps+2, "JarTool", new Dimension(350, 150)); //$NON-NLS-1$
String pdMessage = res.getString("JarTool.ProcessingFile"); //$NON-NLS-1$
for(Iterator<String> it = sources.iterator(); it.hasNext(); ) { // Copy or uncompress all sources to the temporary directory
if(counter%interval==0) {
pD.reportProgress(pdMessage);
}
counter++;
String filename = it.next().toString();
if(filename!=null) {
errorMessage.append(processFile(filename, new File(parent, filename), temporaryDirectory, policy));
}
}
boolean success = false;
String error = errorMessage.toString().trim();
if(error.length()>0) {
String[] message = new String[] {res.getString("JarTool.JarNotCreated"), error}; //$NON-NLS-1$
JOptionPane.showMessageDialog((JFrame) null, message, res.getString("JarTool.Error"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
}
// Now pack everything into a single compressed file
else {
pD.reportProgress(res.getString("JarTool.CompressingFile")); //$NON-NLS-1$
if(compress(temporaryDirectory, target, manifest)) {
success = true;
} else {
String[] message = new String[] {res.getString("JarTool.JarNotCreated"), res.getString("JarTool.CantCompress")+" "+target.getAbsolutePath()}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
JOptionPane.showMessageDialog((JFrame) null, message, res.getString("JarTool.Error"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
}
}
pD.reportProgress(res.getString("JarTool.CleaningTempFile")); //$NON-NLS-1$
remove(temporaryDirectory);
pD.dispose();
if(success) {
return target;
}
return null;
}
/**
* Processes a regular file, a compressed file, or (recursively) a directory
*
* @param filename String the relative filename to process
* @param parent File The actual file to process
* @param targetDirectory File The target directory
* @param policy OverwriteValue What to do on duplicated entries
* @return StringBuffer A list of errors
*/
static private StringBuffer processFile(String filename, File file, File targetDirectory, OverwriteValue policy) {
// changed by D Brown June 2007 to handle zip/jar entries
if(!file.exists()&&(filename.indexOf("!")==-1)) { //$NON-NLS-1$
return new StringBuffer(res.getString("JarTool.FileDoesntExist")+" "+file.getAbsolutePath()+".\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
} // end change by D Brown
if(file.isDirectory()) {
// recursively process each file in the directory
StringBuffer errorMessage = new StringBuffer();
FileSystemView fsView = FileSystemView.getFileSystemView();
File filesInDir[] = fsView.getFiles(file, false);
for(int i = 0, n = filesInDir.length; i<n; i++) {
errorMessage.append(processFile(filename+"/"+filesInDir[i].getName(), filesInDir[i], targetDirectory, policy)); //$NON-NLS-1$
}
return errorMessage;
}
// Check for a ZIP or JAR file
String filenameLowerCase = file.getName().toLowerCase();
if(filenameLowerCase.endsWith(".jar")||filenameLowerCase.endsWith(".zip")||filenameLowerCase.endsWith(".trz")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if(unzipWithWarning(file, targetDirectory, policy)) {
return new StringBuffer();
}
return new StringBuffer(res.getString("JarTool.CantUncompress")+" "+file.getAbsolutePath()+".\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
// code added by D Brown June 2007 to handle zip/jar entries
int n = filename.indexOf("!"); //$NON-NLS-1$
if(n>-1) {
String entry = filename.substring(n+2);
// next 2 lines added/modified by W Christian/D Brown for Linux 2007-11-05
String filepath = file.getAbsolutePath();
File zipFile = new File(filepath.substring(0, filepath.indexOf("!"))); //$NON-NLS-1$
File target = new File(targetDirectory, entry);
if(extract(zipFile, entry, target)!=null) {
return new StringBuffer();
}
return new StringBuffer(res.getString("JarTool.CantCopy")+" "+filename+" --> "+targetDirectory.getName()+".\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
} // end code added by D Brown
// File is a normal file. Just copy it
// If the file has leading "../" remove them from the name
while(filename.startsWith("../")) { //$NON-NLS-1$
filename = filename.substring(3);
}
File target = new File(targetDirectory, filename);
if(target.exists()) {
switch(policy.value) {
case NO_TO_ALL :
return new StringBuffer();
case YES_TO_ALL :
break; // will overwrite
default :
switch(policy.value = confirmOverwrite(filename)) {
case NO_TO_ALL :
case NO :
return new StringBuffer();
// case CANCEL : return new StringBuffer();
default : // Do nothing, i.e., will overwrite the file
}
}
}
if(copy(file, target)) {
return new StringBuffer();
}
return new StringBuffer(res.getString("JarTool.CantCopy")+" "+filename+" --> "+targetDirectory.getName()+".\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
/**
* Used by removeDirectory (File)
* @param directory File
* @param fsView FileSystemView
*/
static private boolean recursiveClearDirectory(File directory, FileSystemView fsView) {
File files[] = fsView.getFiles(directory, false);
for(int i = 0; i<files.length; i++) {
if(files[i].isDirectory()) {
if(!recursiveClearDirectory(files[i], fsView)) {
return false;
}
} else if(!files[i].delete()) {
return false;
}
}
return directory.delete();
}
/**
* Used by getFilesUnderDirectory (File)
* @param directory File
* @param fsView FileSystemView
*/
static private java.util.Collection<File> recursiveGetDirectory(File directory, FileSystemView fsView) {
File files[] = fsView.getFiles(directory, false);
java.util.Collection<File> list = new ArrayList<File>();
for(int i = 0; i<files.length; i++) {
if(files[i].isDirectory()) {
list.addAll(recursiveGetDirectory(files[i], fsView));
} else {
list.add(files[i]);
}
}
return list;
}
static private class OverwriteValue {
int value = NO;
OverwriteValue(int val) {
value = val;
}
}
static private class ProgressDialog extends JDialog {
private int totalSteps;
private int currentStep = 0;
private JLabel progressLabel = null;
private JProgressBar progressBar = null;
/**
* Constructor ProgressDialog
* @param _owner
* @param _steps
* @param _title
* @param _size
*/
public ProgressDialog(Frame _owner, int _steps, String _title, Dimension _size) {
super(_owner);
totalSteps = _steps;
setTitle(_title);
setSize(_size);
setModal(false);
getContentPane().setLayout(new java.awt.BorderLayout());
JPanel progressPanel = new JPanel() {
public Insets getInsets() {
return new Insets(15, 10, 5, 10);
}
};
progressPanel.setLayout(new BoxLayout(progressPanel, BoxLayout.Y_AXIS));
getContentPane().add(progressPanel, BorderLayout.CENTER);
Dimension d = new Dimension(_size.width, 20);
progressLabel = new JLabel(_title);
progressLabel.setAlignmentX(CENTER_ALIGNMENT);
progressLabel.setMaximumSize(d);
progressLabel.setPreferredSize(d);
progressPanel.add(progressLabel);
progressPanel.add(Box.createRigidArea(new Dimension(1, 20)));
progressBar = new JProgressBar(0, totalSteps);
progressBar.setStringPainted(true);
progressLabel.setLabelFor(progressBar);
progressBar.setAlignmentX(CENTER_ALIGNMENT);
progressPanel.add(progressBar);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
setLocation((screenSize.width-_size.width)/2, (screenSize.height-_size.width)/2);
getContentPane().add(progressPanel, BorderLayout.CENTER);
setCursor(new Cursor(Cursor.WAIT_CURSOR));
setVisible(true);
}
public void reportProgress(String _process) {
currentStep++;
progressBar.setValue(currentStep);
progressLabel.setText(_process);
}
}
} // End of class
/*
* Open Source Physics software is free software; you can redistribute
* it and/or modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 of the License,
* or(at your option) any later version.
*
* Code that uses any portion of the code in the org.opensourcephysics package
* or any subpackage (subdirectory) of this package must must also be be released
* under the GNU GPL license.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
* or view the license online at http://www.gnu.org/copyleft/gpl.html
*
* Copyright (c) 2007 The Open Source Physics project
* http://www.opensourcephysics.org
*/