Support This Project Flattr this SourceForge.net Logo
LogoLogo Big

Archive compression and modification code snippets

Index

Creating new archives

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.

Archive test structure

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() {

        //     <root>
        //     |
        //     +- info.txt
        //     +- random-100-bytes.dump
        //     +- dir1
        //     |  +- file-in-a-directory1.txt
        //     +- dir2
        //        +- file-in-a-directory2.txt

        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);

        // dir1 doesn't have separate archive item
        items[2] = new Item("dir1" + File.separator + "file1.txt", 
                "This file located in a directory 'dir'");

        // dir2 does have separate archive item
        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;
        }
    }
}

Creating archives with the archive format specific API

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

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 {
            // Track each operation result here
        }

        public void setTotal(long total) throws SevenZipException {
            // Track operation progress here
        }

        public void setCompleted(long complete) throws SevenZipException {
            // Track operation progress here
        }

        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) {
                // Directory
                item.setPropertyIsDir(true);
                attr |= PropID.AttributesBitMask.FILE_ATTRIBUTE_DIRECTORY;
                attr |= 0x81ED << 16; // permissions: drwxr-xr-x
            } else {
                // File
                item.setDataSize((long) items[index].getContent().length);
                attr |= 0x81a4 << 16; // permissions: -rw-r--r--
            }
            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");

            // Open out-archive object
            outArchive = SevenZip.openOutArchiveZip();

            // Configure archive
            outArchive.setLevel(5);

            // Create archive
            outArchive.createArchive(new RandomAccessFileOutStream(raf),
                    items.length, new MyCreateCallback());

            success = true;
        } catch (SevenZipException e) {
            System.err.println("7z-Error occurs:");
            // Get more information using extended method
            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 7-Zip archive using archive format specific API

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 {
            // Track each operation result here
        }

        public void setTotal(long total) throws SevenZipException {
            // Track operation progress here
        }

        public void setCompleted(long complete) throws SevenZipException {
            // Track operation progress here
        }

        public IOutItem7z getItemInformation(int index,
                OutItemFactory<IOutItem7z> outItemFactory) {
            IOutItem7z item = outItemFactory.createOutItem();

            if (items[index].getContent() == null) {
                // Directory
                item.setPropertyIsDir(true);
            } else {
                // File
                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");

            // Open out-archive object
            outArchive = SevenZip.openOutArchive7z();

            // Configure archive
            outArchive.setLevel(5);
            outArchive.setSolid(true);

            // Create archive
            outArchive.createArchive(new RandomAccessFileOutStream(raf),
                    items.length, new MyCreateCallback());

            success = true;
        } catch (SevenZipException e) {
            System.err.println("7z-Error occurs:");
            // Get more information using extended method
            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 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 {
            // Track each operation result here
        }

        public void setTotal(long total) throws SevenZipException {
            // Track operation progress here
        }

        public void setCompleted(long complete) throws SevenZipException {
            // Track operation progress here
        }

        public IOutItemTar getItemInformation(int index,
                OutItemFactory<IOutItemTar> outItemFactory) {
            IOutItemTar item = outItemFactory.createOutItem();

            if (items[index].getContent() == null) {
                // Directory
                item.setPropertyIsDir(true);
            } else {
                // File
                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");

            // Open out-archive object
            outArchive = SevenZip.openOutArchiveTar();

            // No configuration methods for Tar

            // Create archive
            outArchive.createArchive(new RandomAccessFileOutStream(raf),
                    items.length, new MyCreateCallback());

            success = true;
        } catch (SevenZipException e) {
            System.err.println("Tar-Error occurs:");
            // Get more information using extended method
            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 GZip 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 {
            // Track each operation result here
        }

        public void setTotal(long total) throws SevenZipException {
            // Track operation progress here
        }

        public void setCompleted(long complete) throws SevenZipException {
            // Track operation progress here
        }

        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");

            // Open out-archive object
            outArchive = SevenZip.openOutArchiveGZip();

            // Configure archive
            outArchive.setLevel(5);

            // Create archive
            outArchive.createArchive(new RandomAccessFileOutStream(raf),
                    1, new MyCreateCallback());

            success = true;
        } catch (SevenZipException e) {
            System.err.println("GZip-Error occurs:");
            // Get more information using extended method
            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 BZip2 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 {
            // Track each operation result here
        }

        public void setTotal(long total) throws SevenZipException {
            // Track operation progress here
        }

        public void setCompleted(long complete) throws SevenZipException {
            // Track operation progress here
        }

        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");

            // Open out-archive object
            outArchive = SevenZip.openOutArchiveBZip2();

            // Configure archive
            outArchive.setLevel(5);

            // Create archive
            outArchive.createArchive(new RandomAccessFileOutStream(raf),
                    1, new MyCreateCallback());

            success = true;
        } catch (SevenZipException e) {
            System.err.println("BZip2-Error occurs:");
            // Get more information using extended method
            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 archives with the generic 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 {
            // Track each operation result here
        }

        public void setTotal(long total) throws SevenZipException {
            // Track operation progress here
        }

        public void setCompleted(long complete) throws SevenZipException {
            // Track operation progress here
        }

        public IOutItemAllFormats getItemInformation(int index,
                OutItemFactory<IOutItemAllFormats> outItemFactory) {
            IOutItemAllFormats item = outItemFactory.createOutItem();

            if (items[index].getContent() == null) {
                // Directory
                item.setPropertyIsDir(true);
            } else {
                // File
                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");

            // Open out-archive object
            outArchive = SevenZip.openOutArchive(archiveFormat);

            // Configure archive
            if (outArchive instanceof IOutFeatureSetLevel) {
                ((IOutFeatureSetLevel) outArchive).setLevel(5);
            }
            if (outArchive instanceof IOutFeatureSetMultithreading) {
                ((IOutFeatureSetMultithreading) outArchive).setThreadCount(2);
            }

            // Create archive
            outArchive.createArchive(new RandomAccessFileOutStream(raf),
                    count, new MyCreateCallback());

            success = true;
        } catch (SevenZipException e) {
            System.err.println("7z-Error occurs:");
            // Get more information using extended method
            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.


Modifying existing archives

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.

Altering existing archive items

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 {
            // Track each operation result here
        }

        public void setTotal(long total) throws SevenZipException {
            // Track operation progress here
        }

        public void setCompleted(long complete) throws SevenZipException {
            // Track operation progress here
        }

        public IOutItemAllFormats getItemInformation(int index,
                OutItemFactory<IOutItemAllFormats> outItemFactory) 
                throws SevenZipException {
            if (index != 2) {
                // Keep properties and content
                return outItemFactory.createOutItem(index);
            }

            IOutItemAllFormats item;
            item = outItemFactory.createOutItemAndCloneProperties(index);

            // Change property PATH
            item.setUpdateIsNewProperties(true);
            item.setPropertyPath("info2.txt");

            // Change content
            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 {
            // Open input file
            inRaf = new RandomAccessFile(in, "r");
            closeables.add(inRaf);
            IInStream inStream = new RandomAccessFileInStream(inRaf);

            // Open in-archive
            inArchive = SevenZip.openInArchive(null, inStream);
            closeables.add(inArchive);

            outRaf = new RandomAccessFile(out, "rw");
            closeables.add(outRaf);

            // Open out-archive object
            outArchive = inArchive.getConnectedOutArchive();

            // Modify archive
            outArchive.updateItems(new RandomAccessFileOutStream(outRaf),
                    inArchive.getNumberOfItems(), new MyCreateCallback());

            success = true;
        } catch (SevenZipException e) {
            System.err.println("7z-Error occurs:");
            // Get more information using extended method
            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

Update successful

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).

Adding and removing archive items

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 {
            // Track each operation result here
        }

        public void setTotal(long total) throws SevenZipException {
            // Track operation progress here
        }

        public void setCompleted(long complete) throws SevenZipException {
            // Track operation progress here
        }

        public IOutItemAllFormats getItemInformation(int index,
                OutItemFactory<IOutItemAllFormats> outItemFactory) 
                throws SevenZipException {
            if (index == itemToAdd) {
                // Adding new item
                IOutItemAllFormats outItem = outItemFactory.createOutItem();
                outItem.setPropertyPath(itemToAddPath);
                outItem.setDataSize((long) itemToAddContent.length);

                return outItem;
            }

            // Remove item by changing the mapping "new index"->"old index"
            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; // New index of the item to add
    String itemToAddPath;
    byte[] itemToAddContent;

    int itemToRemove; // Old index of the item to be removed

    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 {
            // Open input file
            inRaf = new RandomAccessFile(in, "r");
            closeables.add(inRaf);
            IInStream inStream = new RandomAccessFileInStream(inRaf);

            // Open in-archive
            inArchive = SevenZip.openInArchive(null, inStream);
            closeables.add(inArchive);

            initUpdate(inArchive);

            outRaf = new RandomAccessFile(out, "rw");
            closeables.add(outRaf);

            // Open out-archive object
            outArchive = inArchive.getConnectedOutArchive();

            // Modify archive
            outArchive.updateItems(new RandomAccessFileOutStream(outRaf),
                    inArchive.getNumberOfItems(), new MyCreateCallback());

            success = true;
        } catch (SevenZipException e) {
            System.err.println("7z-Error occurs:");
            // Get more information using extended method
            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

Update successful

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.

Troubleshoot problems using tracing

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 {
            // Track each operation result here
        }

        public void setTotal(long total) throws SevenZipException {
            // Track operation progress here
        }

        public void setCompleted(long complete) throws SevenZipException {
            // Track operation progress here
        }

        public IOutItemTar getItemInformation(int index,
                OutItemFactory<IOutItemTar> outItemFactory) {
            IOutItemTar item = outItemFactory.createOutItem();

            if (items[index].getContent() == null) {
                // Directory
                item.setPropertyIsDir(true);
            } else {
                // File
                item.setDataSize((long) items[index].getContent().length);
            }

            item.setPropertyPath(items[index].getPath());

            if (index == 1) {
                item.setDataSize(null); // Provide invalid data for item 1
            }

            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");

            // Open out-archive object
            outArchive = SevenZip.openOutArchiveTar();

            // Activate tracing
            outArchive.setTrace(true);

            // Create archive
            outArchive.createArchive(new RandomAccessFileOutStream(raf),
                    items.length, new MyCreateCallback());

            success = true;
        } catch (SevenZipException e) {
            System.err.println("Tar-Error occurs:");
            // Get more information using extended method
            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.