public class ZFile
extends java.lang.Object
implements java.io.Closeable
ZFile provides the main interface for interacting with zip files. A ZFile can
be created on a new file or in an existing file. Once created, files can be added or removed from
the zip file.
Changes in the zip file are always deferred. Any change requested is made in memory and
written to disk only when update() or close() is invoked.
Zip files are open initially in read-only mode and will switch to read-write when needed. This
is done automatically. Because modifications to the file are done in-memory, the zip file can be
manipulated when closed. When invoking update() or close() the zip file will be
reopen and changes will be written. However, the zip file cannot be modified outside the control
of ZFile. So, if a ZFile is closed, modified outside and then a file is added or
removed from the zip file, when reopening the zip file, ZFile will detect the outside
modification and will fail.
In memory manipulation means that files added to the zip file are kept in memory until written
to disk. This provides much faster operation and allows better zip file allocation (see below).
It may, however, increase the memory footprint of the application. When adding large files, if
memory consumption is a concern, a call to update() will actually write the file to disk
and discard the memory buffer. Information about allocation can be obtained from a ByteTracker that can be given to the file on creation.
ZFile keeps track of allocation inside of the zip file. If a file is deleted, its
space is marked as freed and will be reused for an added file if it fits in the space. Allocation
of files to empty areas is done using a best fit algorithm. When adding a file, if it
doesn't fit in any free area, the zip file will be extended.
ZFile provides a fast way to merge data from another zip file (see mergeFrom(ZFile, Predicate)) avoiding recompression and copying of equal files. When merging,
patterns of files may be provided that are ignored. This allows handling special files in the
merging process, such as files in META-INF.
When adding files to the zip file, unless files are explicitly required to be stored, files
will be deflated. However, deflating will not occur if the deflated file is larger then the
stored file, e.g. if compression would yield a bigger file. See Compressor for
details on how compression works.
Because ZFile was designed to be used in a build system and not as general-purpose zip
utility, it is very strict (and unforgiving) about the zip format and unsupported features.
ZFile supports alignment. Alignment means that file data (not entries -- the
local header must be discounted) must start at offsets that are multiple of a number -- the
alignment. Alignment is defined by an alignment rules (AlignmentRule in the ZFileOptions object used to create the ZFile.
When a file is added to the zip, the alignment rules will be checked and alignment will be honored when positioning the file in the zip. This means that unused spaces in the zip may be generated as a result. However, alignment of existing entries will not be changed.
Entries can be realigned individually (see StoredEntry.realign() or the full zip file
may be realigned (see realign()). When realigning the full zip entries that are already
aligned will not be affected.
Because realignment may cause files to move in the zip, realignment is done in-memory meaning
that files that need to change location will moved to memory and will only be flushed when either
update() or close() are called.
Alignment only applies to filed that are forced to be uncompressed. This is because alignment is used to allow mapping files in the archive directly into memory and compressing defeats the purpose of alignment.
Manipulating zip files with ZFile may yield zip files with empty spaces between files.
This happens in two situations: (1) if alignment is required, files may be shifted to conform to
the request alignment leaving an empty space before the previous file, and (2) if a file is
removed or replaced with a file that does not fit the space it was in. By default, ZFile
does not do any special processing in these situations. Files are indexed by their offsets from
the central directory and empty spaces can exist in the zip file.
However, it is possible to tell ZFile to use the extra field in the local header to do
cover the empty spaces. This is done by setting ZFileOptions.setCoverEmptySpaceUsingExtraField(boolean) to true. This has the advantage
of leaving no gaps between entries in the zip, as required by some tools like Oracle's {code jar}
tool. However, setting this option will destroy the contents of the file's extra field.
Activating ZFileOptions.setCoverEmptySpaceUsingExtraField(boolean) may lead to
virtual files being added to the zip file. Since extra field is limited to 64k, it is not
possible to cover any space bigger than that using the extra field. In those cases, virtual
files are added to the file. A virtual file is a file that exists in the actual zip data, but
is not referenced from the central directory. A zip-compliant utility should ignore these files.
However, zip utilities that expect the zip to be a stream, such as Oracle's jar, will find these
files instead of considering the zip to be corrupt.
ZFile support sorting zip files. Sorting (done through the sortZipContents()
method) is a process by which all files are re-read into memory, if not already in memory,
removed from the zip and re-added in alphabetical order, respecting alignment rules. So, in
general, file b will come after file a unless file a is subject to
alignment that forces an empty space before that can be occupied by b. Sorting can be
used to minimize the changes between two zips.
Sorting in ZFile can be done manually or automatically. Manual sorting is done by
invoking sortZipContents(). Automatic sorting is done by setting the ZFileOptions.getAutoSortFiles() option when creating the ZFile. Automatic sorting
invokes sortZipContents() immediately when doing an update() after all
extensions have processed the ZFileExtension.beforeUpdate(). This has the guarantee that
files added by extensions will be sorted, something that does not happen if the invocation is
sequential, i.e., sortZipContents() called before update(). The drawback
of automatic sorting is that sorting will happen every time update() is called and the
file is dirty having a possible penalty in performance.
To allow whole-apk signing, the ZFile allows the central directory location to be
offset by a fixed amount. This amount can be set using the setExtraDirectoryOffset(long)
method. Setting a non-zero value will add extra (unused) space in the zip file before the central
directory. This value can be changed at any time and it will force the central directory
rewritten when the file is updated or closed.
ZFile provides an extension mechanism to allow objects to register with the file and
be notified when changes to the file happen. This should be used to add extra features to the zip
file while providing strong decoupling. See ZFileExtension, addZFileExtension(ZFileExtension) and removeZFileExtension(ZFileExtension).
This class is not thread-safe. Neither are any of the classes associated with it in this package, except when otherwise noticed.
| Modifier and Type | Field and Description |
|---|---|
static char |
SEPARATOR
The file separator in paths in the zip file.
|
| Constructor and Description |
|---|
ZFile(java.io.File file)
Deprecated.
|
ZFile(java.io.File file,
ZFileOptions options)
Deprecated.
|
ZFile(java.io.File file,
ZFileOptions options,
boolean readOnly)
Deprecated.
|
| Modifier and Type | Method and Description |
|---|---|
void |
add(java.lang.String name,
java.io.InputStream stream)
|
void |
add(java.lang.String name,
java.io.InputStream stream,
boolean mayCompress)
Adds a file to the archive.
|
void |
addAllRecursively(java.io.File file)
Adds all files and directories recursively.
|
void |
addAllRecursively(java.io.File file,
com.google.common.base.Predicate<? super java.io.File> mayCompress)
Adds all files and directories recursively.
|
void |
addZFileExtension(ZFileExtension extension)
Adds an extension to this zip file.
|
boolean |
areTimestampsIgnored()
Obtains whether this
ZFile is ignoring timestamps. |
com.android.apksig.util.DataSource |
asDataSource() |
com.android.apksig.util.DataSource |
asDataSource(long offset,
long size) |
void |
close()
Updates the file and closes it.
|
void |
directFullyRead(long offset,
byte[] data)
Reads exactly
data.length bytes of data, failing if it was not possible to read all the
requested data. |
void |
directFullyRead(long offset,
java.nio.ByteBuffer dest)
Reads exactly
dest.remaining() bytes of data, failing if it was not possible to read
all the requested data. |
java.io.InputStream |
directOpen(long start,
long end)
Opens a portion of the zip for reading.
|
int |
directRead(long offset,
byte[] data)
Same as
directRead(offset, data, 0, data.length). |
int |
directRead(long offset,
byte[] data,
int start,
int count)
Directly reads data from the zip file.
|
int |
directRead(long offset,
java.nio.ByteBuffer dest)
Directly reads data from the zip file.
|
long |
directSize()
Returns the current size (in bytes) of the underlying file.
|
void |
directWrite(long offset,
byte[] data)
Same as
directWrite(offset, data, 0, data.length). |
void |
directWrite(long offset,
byte[] data,
int start,
int count)
Directly writes data in the zip file.
|
java.util.Set<StoredEntry> |
entries()
Obtains all entries in the file.
|
void |
finishAllBackgroundTasks()
Wait for any background tasks to finish and report any errors.
|
StoredEntry |
get(java.lang.String path)
Obtains an entry at a given path in the zip.
|
byte[] |
getCentralDirectoryBytes()
Obtains the byte array representation of the central directory.
|
long |
getCentralDirectoryOffset()
Obtains the offset at which the central directory exists, or at which it will be written if the
zip file were to be flushed immediately.
|
long |
getCentralDirectorySize()
Obtains the size of the central directory, if the central directory is written in the zip file.
|
byte[] |
getEocdBytes()
Obtains the byte array representation of the EOCD.
|
byte[] |
getEocdComment()
Obtains the comment in the EOCD.
|
long |
getEocdOffset()
Obtains the offset of the EOCD record, if the EOCD has been written to the file.
|
long |
getEocdSize()
Obtains the size of the EOCD record, if the EOCD has been written to the file.
|
long |
getExtraDirectoryOffset()
Obtains the extra offset for the central directory.
|
java.io.File |
getFile()
Obtains the filesystem path to the zip file.
|
boolean |
getSkipValidation() |
ByteStorage |
getStorage()
Obtains the storage used by the zip to store data.
|
boolean |
hasPendingChangesWithWait()
Are there in-memory changes that have not been written to the zip file?
|
void |
mergeFrom(ZFile src,
com.google.common.base.Predicate<java.lang.String> ignoreFilter)
Adds all files from another zip file, maintaining their compression.
|
void |
openReadOnly()
Deprecated.
use
openReadOnlyIfClosed() if necessary to ensure a ZFile is open
and readable |
static ZFile |
openReadOnly(java.io.File file)
Opens a new
ZFile from the given file in read-only mode. |
static ZFile |
openReadOnly(java.io.File file,
ZFileOptions options)
Opens a new
ZFile from the given file in read-only mode. |
void |
openReadOnlyIfClosed()
If the zip file is closed, opens it in read-only mode.
|
static ZFile |
openReadWrite(java.io.File file)
Opens a new
ZFile from the given file in read-write mode. |
static ZFile |
openReadWrite(java.io.File file,
ZFileOptions options)
Opens a new
ZFile from the given file in read-write mode. |
boolean |
realign()
Realigns all entries in the zip.
|
void |
removeZFileExtension(ZFileExtension extension)
Removes an extension from this zip file.
|
void |
setEocdComment(byte[] comment)
Sets the comment in the EOCD.
|
void |
setExtraDirectoryOffset(long offset)
Sets an extra offset for the central directory.
|
void |
sortZipContents()
Sorts all files in the zip.
|
void |
touch()
|
void |
update()
Updates the file writing new entries and removing deleted entries.
|
public static final char SEPARATOR
@Deprecated
public ZFile(java.io.File file)
throws java.io.IOException
openReadOnly(File) or openReadWrite(File)ZFile will contain an empty structure. However, an (empty) zip file will be created
if either update() or close() are used. If a zip file exists, it will be
parsed and read.file - the zip filejava.io.IOException - some file exists but could not be read@Deprecated
public ZFile(java.io.File file,
ZFileOptions options)
throws java.io.IOException
openReadOnly(File, ZFileOptions) or openReadWrite(File, ZFileOptions)ZFile will contain an empty structure. However, an (empty) zip file will be created
if either update() or close() are used. If a zip file exists, it will be
parsed and read.file - the zip fileoptions - configuration optionsjava.io.IOException - some file exists but could not be read@Deprecated
public ZFile(java.io.File file,
ZFileOptions options,
boolean readOnly)
throws java.io.IOException
openReadOnly(File, ZFileOptions) or openReadWrite(File, ZFileOptions)ZFile will contain an empty structure. However, an (empty) zip file will be created
if either update() or close() are used. If a zip file exists, it will be
parsed and read.file - the zip fileoptions - configuration optionsreadOnly - should the file be open in read-only mode? If true then the file must
exist and no methods can be invoked that could potentially change the filejava.io.IOException - some file exists but could not be read@Deprecated
public void openReadOnly()
throws java.io.IOException
openReadOnlyIfClosed() if necessary to ensure a ZFile is open
and readableopenReadOnlyIfClosed(), method kept for backwards compatibility only.java.io.IOExceptionpublic static ZFile openReadOnly(java.io.File file) throws java.io.IOException
ZFile from the given file in read-only mode.file - the file to openjava.io.IOException - failed to read the filepublic static ZFile openReadOnly(java.io.File file, ZFileOptions options) throws java.io.IOException
ZFile from the given file in read-only mode.file - the file to openoptions - the options to use to open the file; because the file is open read-only, many of
these options won't have any effectjava.io.IOException - failed to read the filepublic static ZFile openReadWrite(java.io.File file) throws java.io.IOException
ZFile from the given file in read-write mode. Opening a file in read-write
mode may force the file to be written even if no changes are made. For example, differences in
signature will force the file to be written. Use openReadOnly(File, ZFileOptions) to
open a file and ensure it won't be written.
The file will be created if it doesn't exist. If the file exists, it must be a valid zip archive.
file - the file to openjava.io.IOException - failed to read the filepublic static ZFile openReadWrite(java.io.File file, ZFileOptions options) throws java.io.IOException
ZFile from the given file in read-write mode. Opening a file in read-write
mode may force the file to be written even if no changes are made. For example, differences in
signature will force the file to be written. Use openReadOnly(File, ZFileOptions) to
open a file and ensure it won't be written.
The file will be created if it doesn't exist. If the file exists, it must be a valid zip archive.
file - the file to openoptions - the options to use to open the filejava.io.IOException - failed to read the filepublic boolean getSkipValidation()
public java.util.Set<StoredEntry> entries()
@Nullable public StoredEntry get(java.lang.String path)
path - the pathnull if none existspublic java.io.InputStream directOpen(long start,
long end)
throws java.io.IOException
start - the index within the zip file to start readingend - the index within the zip file to end reading (the actual byte pointed by
end will not be read)java.io.IOException - failed to open the zip filepublic void update()
throws java.io.IOException
java.io.IOException - failed to update the file; this exception may have been thrown by the
compressor but only reported herepublic void close()
throws java.io.IOException
close in interface java.io.Closeableclose in interface java.lang.AutoCloseablejava.io.IOExceptionpublic byte[] getCentralDirectoryBytes()
throws java.io.IOException
java.io.IOException - failed to compute the central directory byte representationpublic byte[] getEocdBytes()
throws java.io.IOException
java.io.IOException - failed to obtain the byte representation of the EOCDpublic void openReadOnlyIfClosed()
throws java.io.IOException
directRead(long, byte[]), then this method needs to be
called.java.io.IOException - failed to open the filepublic void add(java.lang.String name,
java.io.InputStream stream)
throws java.io.IOException
name - the file name (i.e., path); paths should be defined using slashes and the
name should not end in slashstream - the source for the file's datajava.io.IOException - failed to read the source datajava.lang.IllegalStateException - if the file is in read-only modepublic void add(java.lang.String name,
java.io.InputStream stream,
boolean mayCompress)
throws java.io.IOException
Adding the file will not update the archive immediately. Updating will only happen when the
update() method is invoked.
Adding a file with the same name as an existing file will replace that file in the archive.
name - the file name (i.e., path); paths should be defined using slashes and the
name should not end in slashstream - the source for the file's datamayCompress - can the file be compressed? This flag will be ignored if the alignment rules
force the file to be aligned, in which case the file will not be compressed.java.io.IOException - failed to read the source datajava.lang.IllegalStateException - if the file is in read-only modepublic void mergeFrom(ZFile src, com.google.common.base.Predicate<java.lang.String> ignoreFilter) throws java.io.IOException
This method will not perform any changes in itself, it will only update in-memory data
structures. To actually write the zip file, invoke either update() or close().
src - the source archiveignoreFilter - predicate that, if true, identifies files in src that
should be ignored by merging; merging will behave as if these files were not therejava.io.IOException - failed to read from src or write on the outputjava.lang.IllegalStateException - if the file is in read-only modepublic void touch()
update() or
close() are invoked.java.lang.IllegalStateException - if the file is in read-only modepublic void finishAllBackgroundTasks()
throws java.io.IOException
add(String, InputStream, boolean), update() and close(). However, if
required for some purposes, e.g., ensuring all notifications have been done to
extensions, then this method may be called. It will wait for all background tasks to complete.java.io.IOException - some background work failedpublic boolean realign()
throws java.io.IOException
StoredEntry.realign() for
all entries in the zip file.ZFile may refer to StoredEntrys that are no longer validjava.io.IOException - failed to realign the zip; some entries in the zip may have been lost due
to the I/O errorjava.lang.IllegalStateException - if the file is in read-only modepublic void addZFileExtension(ZFileExtension extension)
extension - the listener to addjava.lang.IllegalStateException - if the file is in read-only modepublic void removeZFileExtension(ZFileExtension extension)
extension - the listener to removejava.lang.IllegalStateException - if the file is in read-only modepublic void directWrite(long offset,
byte[] data,
int start,
int count)
throws java.io.IOException
offset - the offset at which data should be writtendata - the data to write, may be an empty arraystart - start offset in data where data to write is locatedcount - number of bytes of data to writejava.io.IOException - failed to write the datajava.lang.IllegalStateException - if the file is in read-only modepublic void directWrite(long offset,
byte[] data)
throws java.io.IOException
directWrite(offset, data, 0, data.length).offset - the offset at which data should be writtendata - the data to write, may be an empty arrayjava.io.IOException - failed to write the datajava.lang.IllegalStateException - if the file is in read-only modepublic long directSize()
throws java.io.IOException
java.io.IOException - if an I/O error occurspublic int directRead(long offset,
byte[] data,
int start,
int count)
throws java.io.IOException
offset - the offset at which data should be writtendata - the array where read data should be storedstart - start position in the array where to write data tocount - how many bytes of data can be written-1 if there are no more bytes to be
readjava.io.IOException - failed to write the datapublic int directRead(long offset,
java.nio.ByteBuffer dest)
throws java.io.IOException
offset - the offset from which data should be readdest - the output buffer to fill with data from the offset.-1 if there are no more bytes to be
readjava.io.IOException - failed to write the datapublic int directRead(long offset,
byte[] data)
throws java.io.IOException
directRead(offset, data, 0, data.length).offset - the offset at which data should be readdata - receives the read data, may be an empty arrayjava.io.IOException - failed to read the datapublic void directFullyRead(long offset,
byte[] data)
throws java.io.IOException
data.length bytes of data, failing if it was not possible to read all the
requested data.offset - the offset at which to start readingdata - the array that receives the data readjava.io.IOException - failed to read some data or there is not enough data to readpublic void directFullyRead(long offset,
java.nio.ByteBuffer dest)
throws java.io.IOException
dest.remaining() bytes of data, failing if it was not possible to read
all the requested data.offset - the offset at which to start readingdest - the output buffer to fill with datajava.io.IOException - failed to read some data or there is not enough data to readpublic void addAllRecursively(java.io.File file)
throws java.io.IOException
Equivalent to calling addAllRecursively(File, Predicate) using a predicate that
always returns true
file - a file or directory; if it is a directory, all files and directories will be added
recursivelyjava.io.IOException - failed to some (or all ) of the filesjava.lang.IllegalStateException - if the file is in read-only modepublic void addAllRecursively(java.io.File file,
com.google.common.base.Predicate<? super java.io.File> mayCompress)
throws java.io.IOException
file - a file or directory; if it is a directory, all files and directories will be added
recursivelymayCompress - a function that decides whether files may be compressedjava.io.IOException - failed to some (or all ) of the filesjava.lang.IllegalStateException - if the file is in read-only modepublic long getCentralDirectoryOffset()
public long getCentralDirectorySize()
-1 if the central directory has not been
computedpublic long getEocdOffset()
-1 if none exists yetpublic long getEocdSize()
-1 it none exists yetpublic byte[] getEocdComment()
public void setEocdComment(byte[] comment)
comment - the new comment; no conversion is done, these exact bytes will be placed in the
EOCD commentjava.lang.IllegalStateException - if file is in read-only modepublic void setExtraDirectoryOffset(long offset)
offset - the offset or 0 to write the central directory at its current locationjava.lang.IllegalStateException - if file is in read-only modepublic long getExtraDirectoryOffset()
0 if no offset is setpublic boolean areTimestampsIgnored()
ZFile is ignoring timestamps.public void sortZipContents()
throws java.io.IOException
update() to force the entries to be written to disk.java.io.IOException - failed to load or move a file in the zipjava.lang.IllegalStateException - if file is in read-only modepublic java.io.File getFile()
public com.android.apksig.util.DataSource asDataSource()
throws java.io.IOException
java.io.IOExceptionpublic com.android.apksig.util.DataSource asDataSource(long offset,
long size)
throws java.io.IOException
java.io.IOExceptionpublic boolean hasPendingChangesWithWait()
throws java.io.IOException
Waits for all pending processing which may make changes.
java.io.IOExceptionpublic ByteStorage getStorage()