Archive compression and modification code snippets
Index
The are two slightly different APIs for creating new archives:
- archive format specific API
- archive format independent API
The first API is designed to work with one particular archive format, like Zip. The Second API
allows archive format independent programming.
Some archive formats like GZip only support compression of a single file, while other archive formats
allow multiple files and folders to be compressed.
In order to demonstrate how those archives can be created, some test file and folder structure is required.
The following snippets use a static structure defined by the CompressArchiveStructure class:
public class CompressArchiveStructure {
public static Item[] create() {
Item[] items = new Item[5];
items[0] = new Item("info.txt", "This is the info");
byte[] content = new byte[100];
new Random().nextBytes(content);
items[1] = new Item("random-100-bytes.dump", content);
items[2] = new Item("dir1" + File.separator + "file1.txt",
"This file located in a directory 'dir'");
items[3] = new Item("dir2" + File.separator, (byte[]) null);
items[4] = new Item("dir2" + File.separator + "file2.txt",
"This file located in a directory 'dir'");
return items;
}
static class Item {
private String path;
private byte[] content;
Item(String path, String content) {
this(path, content.getBytes());
}
Item(String path, byte[] content) {
this.path= path;
this.content= content;
}
String getPath() {
return path;
}
byte[] getContent() {
return content;
}
}
}
The archive format specific API provides easy access to the archive configuration
methods (e.g. for setting the compression level).
Also it uses archive format specific item description interfaces (like IOutItemZip for Zip).
Different archive formats support different archive item properties.
Those interfaces provide access to the properties supported by the corresponding
archive format, whether the unsupported properties remain hidden.
Lets see how different archives can be created using archive format specific API
Creating Zip archive using archive format specific API was already presented in the "first steps".
The key parts of the code are:
-
Implementation of the IOutCreateCallback<IOutItemCallbackZip> interface specifying the structure of
the new archive. Also the progress of the compression/update operation get reported here.
-
Creating an instance of the IOutArchive interface and calling createArchive() method.
import java.io.IOException;
import java.io.RandomAccessFile;
import net.sf.sevenzipjbinding.IOutCreateArchiveZip;
import net.sf.sevenzipjbinding.IOutCreateCallback;
import net.sf.sevenzipjbinding.IOutItemZip;
import net.sf.sevenzipjbinding.ISequentialInStream;
import net.sf.sevenzipjbinding.PropID;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.impl.OutItemFactory;
import net.sf.sevenzipjbinding.impl.RandomAccessFileOutStream;
import net.sf.sevenzipjbinding.junit.snippets.CompressArchiveStructure.Item;
import net.sf.sevenzipjbinding.util.ByteArrayStream;
public class CompressNonGenericZip {
/**
* The callback provides information about archive items.
*/
private final class MyCreateCallback
implements IOutCreateCallback<IOutItemZip> {
public void setOperationResult(boolean operationResultOk)
throws SevenZipException {
}
public void setTotal(long total) throws SevenZipException {
}
public void setCompleted(long complete) throws SevenZipException {
}
public IOutItemZip getItemInformation(int index,
OutItemFactory<IOutItemZip> outItemFactory) {
int attr = PropID.AttributesBitMask.FILE_ATTRIBUTE_UNIX_EXTENSION;
IOutItemZip item = outItemFactory.createOutItem();
if (items[index].getContent() == null) {
item.setPropertyIsDir(true);
attr |= PropID.AttributesBitMask.FILE_ATTRIBUTE_DIRECTORY;
attr |= 0x81ED << 16;
} else {
item.setDataSize((long) items[index].getContent().length);
attr |= 0x81a4 << 16;
}
item.setPropertyPath(items[index].getPath());
item.setPropertyAttributes(attr);
return item;
}
public ISequentialInStream getStream(int i) throws SevenZipException {
if (items[i].getContent() == null) {
return null;
}
return new ByteArrayStream(items[i].getContent(), true);
}
}
private Item[] items;
public static void main(String[] args) {
if (args.length == 1) {
new CompressNonGenericZip().compress(args[0]);
return;
}
System.out.println("Usage: java CompressNonGenericZip <archive>");
}
private void compress(String filename) {
items = CompressArchiveStructure.create();
boolean success = false;
RandomAccessFile raf = null;
IOutCreateArchiveZip outArchive = null;
try {
raf = new RandomAccessFile(filename, "rw");
outArchive = SevenZip.openOutArchiveZip();
outArchive.setLevel(5);
outArchive.createArchive(new RandomAccessFileOutStream(raf),
items.length, new MyCreateCallback());
success = true;
} catch (SevenZipException e) {
System.err.println("7z-Error occurs:");
e.printStackTraceExtended();
} catch (Exception e) {
System.err.println("Error occurs: " + e);
} finally {
if (outArchive != null) {
try {
outArchive.close();
} catch (IOException e) {
System.err.println("Error closing archive: " + e);
success = false;
}
}
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
System.err.println("Error closing file: " + e);
success = false;
}
}
}
if (success) {
System.out.println("Compression operation succeeded");
}
}
}
If you run this program with (on Linux)
$ java -cp ‹path-to-lib›\sevenzipjbinding.jar; \
‹path-to-lib›\sevenzipjbinding-Windows-x86.jar;. \
CompressNonGenericZip compressed.zip
you will get the output
Compression operation succeeded
The archive file compressed.zip should be created. It contains files and folders specified in the CompressArchiveStructure class.
$ 7z l compressed.zip
Listing archive: compressed.zip
--
Path = compressed.zip
Type = zip
Physical Size = 718
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2015-09-09 08:56:42 ..... 16 16 info.txt
2015-09-09 08:56:42 ..... 100 100 random-100-bytes.dump
2015-09-09 08:56:42 ..... 38 38 dir1/file1.txt
2015-09-09 08:56:42 D.... 0 0 dir2
2015-09-09 08:56:42 ..... 38 38 dir2/file2.txt
------------------- ----- ------------ ------------ ------------------------
192 192 4 files, 1 folders
Creating 7z archive is a little bit easer that creating Zip archives. The main difference is the implementation
of the MyCreateCallback.getItemInformation() method. 7z doesn't need relative complex calculation of the attribute
property providing a nice default behavior.
import java.io.IOException;
import java.io.RandomAccessFile;
import net.sf.sevenzipjbinding.IOutCreateArchive7z;
import net.sf.sevenzipjbinding.IOutCreateCallback;
import net.sf.sevenzipjbinding.IOutItem7z;
import net.sf.sevenzipjbinding.ISequentialInStream;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.impl.OutItemFactory;
import net.sf.sevenzipjbinding.impl.RandomAccessFileOutStream;
import net.sf.sevenzipjbinding.junit.snippets.CompressArchiveStructure.Item;
import net.sf.sevenzipjbinding.util.ByteArrayStream;
public class CompressNonGeneric7z {
/**
* The callback provides information about archive items.
*/
private final class MyCreateCallback
implements IOutCreateCallback<IOutItem7z> {
public void setOperationResult(boolean operationResultOk)
throws SevenZipException {
}
public void setTotal(long total) throws SevenZipException {
}
public void setCompleted(long complete) throws SevenZipException {
}
public IOutItem7z getItemInformation(int index,
OutItemFactory<IOutItem7z> outItemFactory) {
IOutItem7z item = outItemFactory.createOutItem();
if (items[index].getContent() == null) {
item.setPropertyIsDir(true);
} else {
item.setDataSize((long) items[index].getContent().length);
}
item.setPropertyPath(items[index].getPath());
return item;
}
public ISequentialInStream getStream(int i) throws SevenZipException {
if (items[i].getContent() == null) {
return null;
}
return new ByteArrayStream(items[i].getContent(), true);
}
}
private Item[] items;
public static void main(String[] args) {
if (args.length == 1) {
new CompressNonGeneric7z().compress(args[0]);
return;
}
System.out.println("Usage: java CompressNonGeneric7z <archive>");
}
private void compress(String filename) {
items = CompressArchiveStructure.create();
boolean success = false;
RandomAccessFile raf = null;
IOutCreateArchive7z outArchive = null;
try {
raf = new RandomAccessFile(filename, "rw");
outArchive = SevenZip.openOutArchive7z();
outArchive.setLevel(5);
outArchive.setSolid(true);
outArchive.createArchive(new RandomAccessFileOutStream(raf),
items.length, new MyCreateCallback());
success = true;
} catch (SevenZipException e) {
System.err.println("7z-Error occurs:");
e.printStackTraceExtended();
} catch (Exception e) {
System.err.println("Error occurs: " + e);
} finally {
if (outArchive != null) {
try {
outArchive.close();
} catch (IOException e) {
System.err.println("Error closing archive: " + e);
success = false;
}
}
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
System.err.println("Error closing file: " + e);
success = false;
}
}
}
if (success) {
System.out.println("Compression operation succeeded");
}
}
}
For instructions on how to running the snippet and check the results see
Creating Zip archive using archive format specific API.
Creating tar archives is pretty much the same, as creating 7z archives, since the default values for most properties
are good enough in most cases. Note, that the tar archive format do have attribute property.
But due to the Unix-nature of the tar, it was renamed to PosixAttributes. Also the meaning of the bits is different.
import java.io.IOException;
import java.io.RandomAccessFile;
import net.sf.sevenzipjbinding.IOutCreateArchiveTar;
import net.sf.sevenzipjbinding.IOutCreateCallback;
import net.sf.sevenzipjbinding.IOutItemTar;
import net.sf.sevenzipjbinding.ISequentialInStream;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.impl.OutItemFactory;
import net.sf.sevenzipjbinding.impl.RandomAccessFileOutStream;
import net.sf.sevenzipjbinding.junit.snippets.CompressArchiveStructure.Item;
import net.sf.sevenzipjbinding.util.ByteArrayStream;
public class CompressNonGenericTar {
/**
* The callback provides information about archive items.
*/
private final class MyCreateCallback
implements IOutCreateCallback<IOutItemTar> {
public void setOperationResult(boolean operationResultOk)
throws SevenZipException {
}
public void setTotal(long total) throws SevenZipException {
}
public void setCompleted(long complete) throws SevenZipException {
}
public IOutItemTar getItemInformation(int index,
OutItemFactory<IOutItemTar> outItemFactory) {
IOutItemTar item = outItemFactory.createOutItem();
if (items[index].getContent() == null) {
item.setPropertyIsDir(true);
} else {
item.setDataSize((long) items[index].getContent().length);
}
item.setPropertyPath(items[index].getPath());
return item;
}
public ISequentialInStream getStream(int i) throws SevenZipException {
if (items[i].getContent() == null) {
return null;
}
return new ByteArrayStream(items[i].getContent(), true);
}
}
private Item[] items;
public static void main(String[] args) {
if (args.length == 1) {
new CompressNonGenericTar().compress(args[0]);
return;
}
System.out.println("Usage: java CompressNonGenericTar <archive>");
}
private void compress(String filename) {
items = CompressArchiveStructure.create();
boolean success = false;
RandomAccessFile raf = null;
IOutCreateArchiveTar outArchive = null;
try {
raf = new RandomAccessFile(filename, "rw");
outArchive = SevenZip.openOutArchiveTar();
outArchive.createArchive(new RandomAccessFileOutStream(raf),
items.length, new MyCreateCallback());
success = true;
} catch (SevenZipException e) {
System.err.println("Tar-Error occurs:");
e.printStackTraceExtended();
} catch (Exception e) {
System.err.println("Error occurs: " + e);
} finally {
if (outArchive != null) {
try {
outArchive.close();
} catch (IOException e) {
System.err.println("Error closing archive: " + e);
success = false;
}
}
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
System.err.println("Error closing file: " + e);
success = false;
}
}
}
if (success) {
System.out.println("Compression operation succeeded");
}
}
}
For instructions on how to running the snippet and check the results see Creating Zip archive using archive format specific API.
GZip format is a stream archive format meaning, that it can only compress a single file. This simplifies
the programming quite a bit. In the following snippet a single message passed through the second command line
parameter get compressed. Note, that like non-stream archive formats GZip also supports optional Path and
LastModificationTime properties for the single archive item.
import java.io.IOException;
import java.io.RandomAccessFile;
import net.sf.sevenzipjbinding.IOutCreateArchiveGZip;
import net.sf.sevenzipjbinding.IOutCreateCallback;
import net.sf.sevenzipjbinding.IOutItemGZip;
import net.sf.sevenzipjbinding.ISequentialInStream;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.impl.OutItemFactory;
import net.sf.sevenzipjbinding.impl.RandomAccessFileOutStream;
import net.sf.sevenzipjbinding.util.ByteArrayStream;
public class CompressNonGenericGZip {
/**
* The callback provides information about archive items.
*/
private final class MyCreateCallback
implements IOutCreateCallback<IOutItemGZip> {
public void setOperationResult(boolean operationResultOk)
throws SevenZipException {
}
public void setTotal(long total) throws SevenZipException {
}
public void setCompleted(long complete) throws SevenZipException {
}
public IOutItemGZip getItemInformation(int index,
OutItemFactory<IOutItemGZip> outItemFactory) {
IOutItemGZip item = outItemFactory.createOutItem();
item.setDataSize((long) content.length);
return item;
}
public ISequentialInStream getStream(int i) throws SevenZipException {
return new ByteArrayStream(content, true);
}
}
byte[] content;
public static void main(String[] args) {
if (args.length == 1) {
new CompressNonGenericGZip().compress(args[0]);
return;
}
System.out.println("Usage: java CompressNonGenericGZip <archive>");
}
private void compress(String filename) {
boolean success = false;
RandomAccessFile raf = null;
IOutCreateArchiveGZip outArchive = null;
content = CompressArchiveStructure.create()[0].getContent();
try {
raf = new RandomAccessFile(filename, "rw");
outArchive = SevenZip.openOutArchiveGZip();
outArchive.setLevel(5);
outArchive.createArchive(new RandomAccessFileOutStream(raf),
1, new MyCreateCallback());
success = true;
} catch (SevenZipException e) {
System.err.println("GZip-Error occurs:");
e.printStackTraceExtended();
} catch (Exception e) {
System.err.println("Error occurs: " + e);
} finally {
if (outArchive != null) {
try {
outArchive.close();
} catch (IOException e) {
System.err.println("Error closing archive: " + e);
success = false;
}
}
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
System.err.println("Error closing file: " + e);
success = false;
}
}
}
if (success) {
System.out.println("Compression operation succeeded");
}
}
}
For instructions on how to running the snippet and check the results see Creating Zip archive using archive format specific API.
BZip2 is like GZip a stream archive format. It compresses single archive item supporting no additional
archive item properties at all.
import java.io.IOException;
import java.io.RandomAccessFile;
import net.sf.sevenzipjbinding.IOutCreateArchiveBZip2;
import net.sf.sevenzipjbinding.IOutCreateCallback;
import net.sf.sevenzipjbinding.IOutItemBZip2;
import net.sf.sevenzipjbinding.ISequentialInStream;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.impl.OutItemFactory;
import net.sf.sevenzipjbinding.impl.RandomAccessFileOutStream;
import net.sf.sevenzipjbinding.util.ByteArrayStream;
public class CompressNonGenericBZip2 {
/**
* The callback provides information about archive items.
*/
private final class MyCreateCallback
implements IOutCreateCallback<IOutItemBZip2> {
public void setOperationResult(boolean operationResultOk)
throws SevenZipException {
}
public void setTotal(long total) throws SevenZipException {
}
public void setCompleted(long complete) throws SevenZipException {
}
public IOutItemBZip2 getItemInformation(int index,
OutItemFactory<IOutItemBZip2> outItemFactory) {
IOutItemBZip2 item = outItemFactory.createOutItem();
item.setDataSize((long) content.length);
return item;
}
public ISequentialInStream getStream(int i) throws SevenZipException {
return new ByteArrayStream(content, true);
}
}
byte[] content;
public static void main(String[] args) {
if (args.length == 1) {
new CompressNonGenericBZip2().compress(args[0]);
return;
}
System.out.println("Usage: java CompressNonGenericBZip2 <archive>");
}
private void compress(String filename) {
boolean success = false;
RandomAccessFile raf = null;
IOutCreateArchiveBZip2 outArchive = null;
content = CompressArchiveStructure.create()[0].getContent();
try {
raf = new RandomAccessFile(filename, "rw");
outArchive = SevenZip.openOutArchiveBZip2();
outArchive.setLevel(5);
outArchive.createArchive(new RandomAccessFileOutStream(raf),
1, new MyCreateCallback());
success = true;
} catch (SevenZipException e) {
System.err.println("BZip2-Error occurs:");
e.printStackTraceExtended();
} catch (Exception e) {
System.err.println("Error occurs: " + e);
} finally {
if (outArchive != null) {
try {
outArchive.close();
} catch (IOException e) {
System.err.println("Error closing archive: " + e);
success = false;
}
}
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
System.err.println("Error closing file: " + e);
success = false;
}
}
}
if (success) {
System.out.println("Compression operation succeeded");
}
}
}
For instructions on how to running the snippet and check the results see Creating Zip archive using archive format specific API.
The one of the great features of the 7-Zip (and though of the 7-Zip-JBinding) is the ability to write archive format
independent code supporting most or even all of the archive formats, supported by 7-Zip. The following code snippet
accepts the required archive format as the first parameter compressing the test data in the specified archive format.
The key steps to write a generic compression code are
import java.io.IOException;
import java.io.RandomAccessFile;
import net.sf.sevenzipjbinding.ArchiveFormat;
import net.sf.sevenzipjbinding.IOutCreateArchive;
import net.sf.sevenzipjbinding.IOutCreateCallback;
import net.sf.sevenzipjbinding.IOutFeatureSetLevel;
import net.sf.sevenzipjbinding.IOutFeatureSetMultithreading;
import net.sf.sevenzipjbinding.IOutItemAllFormats;
import net.sf.sevenzipjbinding.ISequentialInStream;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.impl.OutItemFactory;
import net.sf.sevenzipjbinding.impl.RandomAccessFileOutStream;
import net.sf.sevenzipjbinding.junit.snippets.CompressArchiveStructure.Item;
import net.sf.sevenzipjbinding.util.ByteArrayStream;
public class CompressGeneric {
/**
* The callback provides information about archive items.
*/
private final class MyCreateCallback
implements IOutCreateCallback<IOutItemAllFormats> {
public void setOperationResult(boolean operationResultOk)
throws SevenZipException {
}
public void setTotal(long total) throws SevenZipException {
}
public void setCompleted(long complete) throws SevenZipException {
}
public IOutItemAllFormats getItemInformation(int index,
OutItemFactory<IOutItemAllFormats> outItemFactory) {
IOutItemAllFormats item = outItemFactory.createOutItem();
if (items[index].getContent() == null) {
item.setPropertyIsDir(true);
} else {
item.setDataSize((long) items[index].getContent().length);
}
item.setPropertyPath(items[index].getPath());
return item;
}
public ISequentialInStream getStream(int i) throws SevenZipException {
if (items[i].getContent() == null) {
return null;
}
return new ByteArrayStream(items[i].getContent(), true);
}
}
private Item[] items;
public static void main(String[] args) {
if (args.length != 3) {
System.out.println("Usage: java CompressGeneric "
+ "<archive-format> <archive> <count-of-files>");
for (ArchiveFormat af : ArchiveFormat.values()) {
if (af.isOutArchiveSupported()) {
System.out.println("Supported formats: " + af.name());
}
}
return;
}
int itemsCount = Integer.valueOf(args[2]);
new CompressGeneric().compress(args[0], args[1], itemsCount);
}
private void compress(String filename, String fmtName, int count) {
items = CompressArchiveStructure.create();
boolean success = false;
RandomAccessFile raf = null;
IOutCreateArchive<IOutItemAllFormats> outArchive = null;
ArchiveFormat archiveFormat = ArchiveFormat.valueOf(fmtName);
try {
raf = new RandomAccessFile(filename, "rw");
outArchive = SevenZip.openOutArchive(archiveFormat);
if (outArchive instanceof IOutFeatureSetLevel) {
((IOutFeatureSetLevel) outArchive).setLevel(5);
}
if (outArchive instanceof IOutFeatureSetMultithreading) {
((IOutFeatureSetMultithreading) outArchive).setThreadCount(2);
}
outArchive.createArchive(new RandomAccessFileOutStream(raf),
count, new MyCreateCallback());
success = true;
} catch (SevenZipException e) {
System.err.println("7z-Error occurs:");
e.printStackTraceExtended();
} catch (Exception e) {
System.err.println("Error occurs: " + e);
} finally {
if (outArchive != null) {
try {
outArchive.close();
} catch (IOException e) {
System.err.println("Error closing archive: " + e);
success = false;
}
}
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
System.err.println("Error closing file: " + e);
success = false;
}
}
}
if (success) {
System.out.println(archiveFormat.getMethodName()
+ " archive with " + count + " item(s) created");
}
}
}
Now you can run the snippet with different parameters creating archives with different formats. The last parameter
specifies, how many archive items from the CompressArchiveStructure should be
compressed. This number should be between 1 and 5 for 7z, Zip and Tar, and must be 1 for the stream formats GZip and BZip2.
- To create a 7z archive with 5 items:
C:\Test> java -cp ... CompressGeneric SEVEN_ZIP compressed_generic.zip 5
7z archive with 5 item(s) created
- To create a Zip archive with 5 items:
C:\Test> java -cp ... CompressGeneric ZIP compressed_generic.zip 5
Zip archive with 5 item(s) created
- To create a Tar archive with 5 items:
C:\Test> java -cp ... CompressGeneric TAR compressed_generic.tar 5
Tar archive with 5 item(s) created
- To create a GZip archive with 1 item:
C:\Test> java -cp ... CompressGeneric GZIP compressed_generic.zip 1
GZip archive with 1 item(s) created
- To create a BZip2 archive with 1 item:
C:\Test> java -cp ... CompressGeneric BZIP2 compressed_generic.bz2 1
BZip2 archive with 1 item(s) created
Also a bunch of the compressed_generic.* archives should be created with the corresponding contents.
Depending on the archive format it's possible to create and update password protected archives or even encrypt the archive header.
Encrypting archive items secures the content of the item leaving the item meta data (like archive item name, size, ...) unprotected.
The header encryption solves this problem by encrypting the entire archive.
To create a password protected archive the create- or update-callback class should additionally implement the ICryptoGetTextPassword interface.
The cryptoGetTextPassword() method of the interface is called to get the password for the encryption. The header encryption can be
activated using IOutFeatureSetEncryptHeader.setHeaderEncryption() method of the outArchive object, if the outArchive object implements the
IOutFeatureSetEncryptHeader interface an though supports the header encryption.
The following snippet takes the password as a second argument of the main() method. It uses the password to encrypt archive items.
It also activates the header encryption. It's supported by the selected 7z algorithm.
import java.io.IOException;
import java.io.RandomAccessFile;
import net.sf.sevenzipjbinding.ICryptoGetTextPassword;
import net.sf.sevenzipjbinding.IOutCreateArchive7z;
import net.sf.sevenzipjbinding.IOutCreateCallback;
import net.sf.sevenzipjbinding.IOutItem7z;
import net.sf.sevenzipjbinding.ISequentialInStream;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.impl.OutItemFactory;
import net.sf.sevenzipjbinding.impl.RandomAccessFileOutStream;
import net.sf.sevenzipjbinding.junit.snippets.CompressArchiveStructure.Item;
import net.sf.sevenzipjbinding.util.ByteArrayStream;
public class CompressWithPassword {
/**
* The callback provides information about archive items.
*
* It also implements
* <ul>
* <li>{@link ICryptoGetTextPassword}
* </ul>
* to provide a password for encryption.
*/
private final class MyCreateCallback
implements IOutCreateCallback<IOutItem7z>, ICryptoGetTextPassword {
public void setOperationResult(boolean operationResultOk)
throws SevenZipException {
}
public void setTotal(long total) throws SevenZipException {
}
public void setCompleted(long complete) throws SevenZipException {
}
public IOutItem7z getItemInformation(int index,
OutItemFactory<IOutItem7z> outItemFactory) {
IOutItem7z item = outItemFactory.createOutItem();
if (items[index].getContent() == null) {
item.setPropertyIsDir(true);
} else {
item.setDataSize((long) items[index].getContent().length);
}
item.setPropertyPath(items[index].getPath());
return item;
}
public ISequentialInStream getStream(int i) throws SevenZipException {
if (items[i].getContent() == null) {
return null;
}
return new ByteArrayStream(items[i].getContent(), true);
}
public String cryptoGetTextPassword() throws SevenZipException {
return password;
}
}
private Item[] items;
private String password;
public static void main(String[] args) {
if (args.length == 2) {
new CompressWithPassword().compress(args[0], args[1]);
return;
}
System.out.println("Usage: java CompressWithPassword <archive> <pass>");
}
private void compress(String filename, String pass) {
items = CompressArchiveStructure.create();
password = pass;
boolean success = false;
RandomAccessFile raf = null;
IOutCreateArchive7z outArchive = null;
try {
raf = new RandomAccessFile(filename, "rw");
outArchive = SevenZip.openOutArchive7z();
outArchive.setHeaderEncryption(true);
outArchive.createArchive(new RandomAccessFileOutStream(raf),
items.length, new MyCreateCallback());
success = true;
} catch (SevenZipException e) {
System.err.println("7z-Error occurs:");
e.printStackTraceExtended();
} catch (Exception e) {
System.err.println("Error occurs: " + e);
} finally {
if (outArchive != null) {
try {
outArchive.close();
} catch (IOException e) {
System.err.println("Error closing archive: " + e);
success = false;
}
}
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
System.err.println("Error closing file: " + e);
success = false;
}
}
}
if (success) {
System.out.println("Compression operation succeeded");
}
}
}
You can test the snippet like this:
C:\Test> java -cp ... CompressWithPassword compressed_with_pass.zip my-pass
Compression operation succeeded
7-Zip-JBinding provides API for archive modification. Especially by small changes the modification of an archive
is much faster compared to the extraction and the consequent compression.
The archive modification API (like the compression API) offers archive format specific
and archive format independent variants.
The process of the modification of an existing archive contains following steps:
The following snippets show the modification process in details using archive format independent API.
The archive to be modified is one of the Zip, 7z or Tar archives created by the corresponding compression
snippets on this page. The structure of those archives is specified in the CompressArchiveStructure class.
The first snippet modifies one existing item with index 2 (info.txt, 16 bytes):
- It changes the path (name of the item) to the "info2.txt"
- It changes the content to the "More Info!" (10 bytes)
import java.io.Closeable;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import net.sf.sevenzipjbinding.IInArchive;
import net.sf.sevenzipjbinding.IInStream;
import net.sf.sevenzipjbinding.IOutCreateCallback;
import net.sf.sevenzipjbinding.IOutItemAllFormats;
import net.sf.sevenzipjbinding.IOutUpdateArchive;
import net.sf.sevenzipjbinding.ISequentialInStream;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.impl.OutItemFactory;
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
import net.sf.sevenzipjbinding.impl.RandomAccessFileOutStream;
import net.sf.sevenzipjbinding.util.ByteArrayStream;
public class UpdateAlterItems {
/**
* The callback defines the modification to be made.
*/
private final class MyCreateCallback
implements IOutCreateCallback<IOutItemAllFormats> {
public void setOperationResult(boolean operationResultOk)
throws SevenZipException {
}
public void setTotal(long total) throws SevenZipException {
}
public void setCompleted(long complete) throws SevenZipException {
}
public IOutItemAllFormats getItemInformation(int index,
OutItemFactory<IOutItemAllFormats> outItemFactory)
throws SevenZipException {
if (index != 2) {
return outItemFactory.createOutItem(index);
}
IOutItemAllFormats item;
item = outItemFactory.createOutItemAndCloneProperties(index);
item.setUpdateIsNewProperties(true);
item.setPropertyPath("info2.txt");
item.setUpdateIsNewData(true);
item.setDataSize((long) NEW_CONTENT.length);
return item;
}
public ISequentialInStream getStream(int i) throws SevenZipException {
if (i != 2) {
return null;
}
return new ByteArrayStream(NEW_CONTENT, true);
}
}
static final byte[] NEW_CONTENT = "More Info!".getBytes();
public static void main(String[] args) {
if (args.length == 2) {
new UpdateAlterItems().compress(args[0], args[1]);
return;
}
System.out.println("Usage: java UpdateAlterItems <in-arc> <out-arc>");
}
private void compress(String in, String out) {
boolean success = false;
RandomAccessFile inRaf = null;
RandomAccessFile outRaf = null;
IInArchive inArchive;
IOutUpdateArchive<IOutItemAllFormats> outArchive = null;
List<Closeable> closeables = new ArrayList<Closeable>();
try {
inRaf = new RandomAccessFile(in, "r");
closeables.add(inRaf);
IInStream inStream = new RandomAccessFileInStream(inRaf);
inArchive = SevenZip.openInArchive(null, inStream);
closeables.add(inArchive);
outRaf = new RandomAccessFile(out, "rw");
closeables.add(outRaf);
outArchive = inArchive.getConnectedOutArchive();
outArchive.updateItems(new RandomAccessFileOutStream(outRaf),
inArchive.getNumberOfItems(), new MyCreateCallback());
success = true;
} catch (SevenZipException e) {
System.err.println("7z-Error occurs:");
e.printStackTraceExtended();
} catch (Exception e) {
System.err.println("Error occurs: " + e);
} finally {
for (int i = closeables.size() - 1; i >= 0; i--) {
try {
closeables.get(i).close();
} catch (Throwable e) {
System.err.println("Error closing resource: " + e);
success = false;
}
}
}
if (success) {
System.out.println("Update successful");
}
}
}
If you run this program with (on Linux)
$ java -cp ‹path-to-lib›\sevenzipjbinding.jar; \
‹path-to-lib›\sevenzipjbinding-Windows-x86.jar;. \
UpdateAlterItems /testdata/snippets/to-update.7z updated.7z
you will get the output
Now lets look at original and modified archives:
$ 7z l /testdata/snippets/to-update.7z
...
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2015-09-14 07:57:09 ..... 38 159 dir1/file1.txt
2015-09-14 07:57:09 ..... 38 dir2/file2.txt
2015-09-14 07:57:09 ..... 16 info.txt
2015-09-14 07:57:09 ..... 100 random-100-bytes.dump
2015-09-14 07:57:09 D.... 0 0 dir2/
------------------- ----- ------------ ------------ ------------------------
192 159 4 files, 1 folders
$ 7z l updated.7z
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2015-09-14 07:57:09 ..... 38 151 dir1/file1.txt
2015-09-14 07:57:09 ..... 38 dir2/file2.txt
2015-09-14 07:57:09 ..... 100 random-100-bytes.dump
2015-09-14 07:57:09 ..... 10 16 info2.txt
2015-09-14 07:57:09 D.... 0 0 dir2/
------------------- ----- ------------ ------------ ------------------------
186 167 4 files, 1 folders
As you can see, the file "info.txt" (16 bytes) was replaces with the file "info2.txt" (10 bytes).
Now lets see how archive items can be added and removed.
In order to remove an archive item a reindexing is necessary. In the previous snippet for each archive
item the indexes in the old archive and the index in the new archive were the same. But after removing one item all consequent
indexes in the new archive will change and will be less, that corresponding indexes in the old archive. Here is an example of removing
an item C with index 2:
Index: 0 1 2 3 4
Old archive: (A) (B) (C) (D) (E)
New archive: (A) (B) (D) (E)
Here the index of D in the old archive is 3, but in the new archive is 2.
In order to add a new item the count of items in archive passed to the IOutArchive.updateItems() method should be increased.
In the callback the new item with the new index (that doesn't map to any old archive items) should be initialized exactly, like
new items get initialized during a compression operation. The next snippet
- Removes "info.txt" file
- Adds "data.dmp" file with 11 bytes of content
import java.io.Closeable;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import net.sf.sevenzipjbinding.IInArchive;
import net.sf.sevenzipjbinding.IInStream;
import net.sf.sevenzipjbinding.IOutCreateCallback;
import net.sf.sevenzipjbinding.IOutItemAllFormats;
import net.sf.sevenzipjbinding.IOutUpdateArchive;
import net.sf.sevenzipjbinding.ISequentialInStream;
import net.sf.sevenzipjbinding.PropID;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.impl.OutItemFactory;
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
import net.sf.sevenzipjbinding.impl.RandomAccessFileOutStream;
import net.sf.sevenzipjbinding.util.ByteArrayStream;
public class UpdateAddRemoveItems {
/**
* The callback defines the modification to be made.
*/
private final class MyCreateCallback
implements IOutCreateCallback<IOutItemAllFormats> {
public void setOperationResult(boolean operationResultOk)
throws SevenZipException {
}
public void setTotal(long total) throws SevenZipException {
}
public void setCompleted(long complete) throws SevenZipException {
}
public IOutItemAllFormats getItemInformation(int index,
OutItemFactory<IOutItemAllFormats> outItemFactory)
throws SevenZipException {
if (index == itemToAdd) {
IOutItemAllFormats outItem = outItemFactory.createOutItem();
outItem.setPropertyPath(itemToAddPath);
outItem.setDataSize((long) itemToAddContent.length);
return outItem;
}
if (index < itemToRemove) {
return outItemFactory.createOutItem(index);
}
return outItemFactory.createOutItem(index + 1);
}
public ISequentialInStream getStream(int i) throws SevenZipException {
if (i != itemToAdd) {
return null;
}
return new ByteArrayStream(itemToAddContent, true);
}
}
int itemToAdd;
String itemToAddPath;
byte[] itemToAddContent;
int itemToRemove;
private void initUpdate(IInArchive inArchive) throws SevenZipException {
itemToAdd = inArchive.getNumberOfItems() - 1;
itemToAddPath = "data.dmp";
itemToAddContent = "dmp-content".getBytes();
itemToRemove = -1;
for (int i = 0; i < inArchive.getNumberOfItems(); i++) {
if (inArchive.getProperty(i, PropID.PATH).equals("info.txt")) {
itemToRemove = i;
break;
}
}
if (itemToRemove == -1) {
throw new RuntimeException("Item 'info.txt' not found");
}
}
public static void main(String[] args) {
if (args.length == 2) {
new UpdateAddRemoveItems().compress(args[0], args[1]);
return;
}
System.out.println("Usage: java UpdateAddRemoveItems <in> <out>");
}
private void compress(String in, String out) {
boolean success = false;
RandomAccessFile inRaf = null;
RandomAccessFile outRaf = null;
IInArchive inArchive;
IOutUpdateArchive<IOutItemAllFormats> outArchive = null;
List<Closeable> closeables = new ArrayList<Closeable>();
try {
inRaf = new RandomAccessFile(in, "r");
closeables.add(inRaf);
IInStream inStream = new RandomAccessFileInStream(inRaf);
inArchive = SevenZip.openInArchive(null, inStream);
closeables.add(inArchive);
initUpdate(inArchive);
outRaf = new RandomAccessFile(out, "rw");
closeables.add(outRaf);
outArchive = inArchive.getConnectedOutArchive();
outArchive.updateItems(new RandomAccessFileOutStream(outRaf),
inArchive.getNumberOfItems(), new MyCreateCallback());
success = true;
} catch (SevenZipException e) {
System.err.println("7z-Error occurs:");
e.printStackTraceExtended();
} catch (Exception e) {
System.err.println("Error occurs: " + e);
} finally {
for (int i = closeables.size() - 1; i >= 0; i--) {
try {
closeables.get(i).close();
} catch (Throwable e) {
System.err.println("Error closing resource: " + e);
success = false;
}
}
}
if (success) {
System.out.println("Update successful");
}
}
}
If you run this program with (on Linux)
$ java -cp ‹path-to-lib›\sevenzipjbinding.jar; \
‹path-to-lib›\sevenzipjbinding-Windows-x86.jar;. \
UpdateAddRemoveItems ‹git›/testdata/snippets/to-update.7z updated.7z
you will get the output
Now lets look at original and modified archives:
$ 7z l /testdata/snippets/to-update.7z
...
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2015-09-14 07:57:09 ..... 38 159 dir1/file1.txt
2015-09-14 07:57:09 ..... 38 dir2/file2.txt
2015-09-14 07:57:09 ..... 16 info.txt
2015-09-14 07:57:09 ..... 100 random-100-bytes.dump
2015-09-14 07:57:09 D.... 0 0 dir2/
------------------- ----- ------------ ------------ ------------------------
192 159 4 files, 1 folders
$ 7z l updated.7z
...
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2015-09-14 07:57:09 ..... 38 151 dir1/file1.txt
2015-09-14 07:57:09 ..... 38 dir2/file2.txt
2015-09-14 07:57:09 ..... 100 random-100-bytes.dump
2015-09-16 21:43:52 ..... 11 16 data.dmp
2015-09-14 07:57:09 D.... 0 0 dir2/
------------------- ----- ------------ ------------ ------------------------
187 167 4 files, 1 folders
As you can see, the file info.txt is gone and the file data.dmp (11 bytes) appears in the archive.
One of the weak sides of the 7-zip compression engine is a rather simple error reporting. If
some provided data doesn't satisfy the compressor it fails without any descriptive error message.
One way to get an clue is to use 7-Zip-JBinding tracing feature. Here is the code passing invalid
data size for the item 1 and though failing.
import java.io.IOException;
import java.io.RandomAccessFile;
import net.sf.sevenzipjbinding.IOutCreateArchiveTar;
import net.sf.sevenzipjbinding.IOutCreateCallback;
import net.sf.sevenzipjbinding.IOutItemTar;
import net.sf.sevenzipjbinding.ISequentialInStream;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.impl.OutItemFactory;
import net.sf.sevenzipjbinding.impl.RandomAccessFileOutStream;
import net.sf.sevenzipjbinding.junit.snippets.CompressArchiveStructure.Item;
import net.sf.sevenzipjbinding.util.ByteArrayStream;
public class CompressWithError {
/**
* The callback provides information about archive items.
*/
private final class MyCreateCallback
implements IOutCreateCallback<IOutItemTar> {
public void setOperationResult(boolean operationResultOk)
throws SevenZipException {
}
public void setTotal(long total) throws SevenZipException {
}
public void setCompleted(long complete) throws SevenZipException {
}
public IOutItemTar getItemInformation(int index,
OutItemFactory<IOutItemTar> outItemFactory) {
IOutItemTar item = outItemFactory.createOutItem();
if (items[index].getContent() == null) {
item.setPropertyIsDir(true);
} else {
item.setDataSize((long) items[index].getContent().length);
}
item.setPropertyPath(items[index].getPath());
if (index == 1) {
item.setDataSize(null);
}
return item;
}
public ISequentialInStream getStream(int i) throws SevenZipException {
if (items[i].getContent() == null) {
return null;
}
return new ByteArrayStream(items[i].getContent(), true);
}
}
private Item[] items;
public static void main(String[] args) {
if (args.length == 1) {
new CompressWithError().compress(args[0]);
return;
}
System.out.println("Usage: java CompressNonGenericTar <archive>");
}
private void compress(String filename) {
items = CompressArchiveStructure.create();
boolean success = false;
RandomAccessFile raf = null;
IOutCreateArchiveTar outArchive = null;
try {
raf = new RandomAccessFile(filename, "rw");
outArchive = SevenZip.openOutArchiveTar();
outArchive.setTrace(true);
outArchive.createArchive(new RandomAccessFileOutStream(raf),
items.length, new MyCreateCallback());
success = true;
} catch (SevenZipException e) {
System.err.println("Tar-Error occurs:");
e.printStackTraceExtended();
} catch (Exception e) {
System.err.println("Error occurs: " + e);
} finally {
if (outArchive != null) {
try {
outArchive.close();
} catch (IOException e) {
System.err.println("Error closing archive: " + e);
success = false;
}
}
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
System.err.println("Error closing file: " + e);
success = false;
}
}
}
if (success) {
System.out.println("Compression operation succeeded");
}
}
}
If you run this program you will get the following error message printed to the System.err:
0x80070057 (Invalid argument). Error creating 'tar' archive with 5 items
The error message provides no useful information for finding the bug. But since the snippet enables
tracing by calling IOutArchive.setTrace(true), the trace log get printed to the System.out.
Compressing 5 items
Get update info (new data: true) (new props: true) (old index: -1) (index: 0)
Get property 'propertyIsDir' (index: 0)
Get property 'propertyPosixAttributes' (index: 0)
Get property 'propertyLastModificationTime' (index: 0)
Get property 'propertyPath' (index: 0)
Get property 'propertyUser' (index: 0)
Get property 'propertyGroup' (index: 0)
Get property 'dataSize' (index: 0)
Get update info (new data: true) (new props: true) (old index: -1) (index: 1)
Get property 'propertyIsDir' (index: 1)
Get property 'propertyPosixAttributes' (index: 1)
Get property 'propertyLastModificationTime' (index: 1)
Get property 'propertyPath' (index: 1)
Get property 'propertyUser' (index: 1)
Get property 'propertyGroup' (index: 1)
Get property 'dataSize' (index: 1)
As you see, the tracing stops just after 7-zip retrieved the size of the data for the item 1. This
suggests, that the value for the size of the data of the item 1 may cause the failure. In this
small example, like in most other cases, this will help to find the problem.
|