aboutsummaryrefslogtreecommitdiff
path: root/generator/src/main/java/com/google/archivepatcher/generator/MinimalZipEntry.java
diff options
context:
space:
mode:
Diffstat (limited to 'generator/src/main/java/com/google/archivepatcher/generator/MinimalZipEntry.java')
-rw-r--r--generator/src/main/java/com/google/archivepatcher/generator/MinimalZipEntry.java273
1 files changed, 273 insertions, 0 deletions
diff --git a/generator/src/main/java/com/google/archivepatcher/generator/MinimalZipEntry.java b/generator/src/main/java/com/google/archivepatcher/generator/MinimalZipEntry.java
new file mode 100644
index 0000000..d6342d0
--- /dev/null
+++ b/generator/src/main/java/com/google/archivepatcher/generator/MinimalZipEntry.java
@@ -0,0 +1,273 @@
+// Copyright 2016 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.archivepatcher.generator;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+
+/**
+ * A class that contains <em>just enough data</em> to generate a patch.
+ */
+public class MinimalZipEntry {
+ /**
+ * The compression method that was used, typically 8 (for deflate) or 0 (for stored).
+ */
+ private final int compressionMethod;
+
+ /**
+ * The CRC32 of the <em>uncompressed</em> data.
+ */
+ private final long crc32OfUncompressedData;
+
+ /**
+ * The size of the data as it exists in the archive. For compressed entries, this is the size of
+ * the compressed data; for uncompressed entries, this is the same as {@link #uncompressedSize}.
+ */
+ private final long compressedSize;
+
+ /**
+ * The size of the <em>uncompressed</em data.
+ */
+ private final long uncompressedSize;
+
+ /**
+ * The file name for the entry. By convention, names ending with '/' denote directories. The
+ * encoding is controlled by the general purpose flags, bit 11. See {@link #getFileName()} for
+ * more information.
+ */
+ private final byte[] fileNameBytes;
+
+ /**
+ * The value of the 11th bit of the general purpose flag, which controls the encoding of file
+ * names and comments. See {@link #getFileName()} for more information.
+ */
+ private final boolean generalPurposeFlagBit11;
+
+ /**
+ * The file offset at which the first byte of the local entry header begins.
+ */
+ private final long fileOffsetOfLocalEntry;
+
+ /**
+ * The file offset at which the first byte of the data for the entry begins. For compressed data,
+ * this is the first byte of the deflated data; for uncompressed data, this is the first byte of
+ * the uncompressed data.
+ */
+ private long fileOffsetOfCompressedData = -1;
+
+ /**
+ * Create a new Central Directory entry with the corresponding data.
+ * @param compressionMethod the method used to compress the data
+ * @param crc32OfUncompressedData the CRC32 of the uncompressed data
+ * @param compressedSize the size of the data in its compressed form
+ * @param uncompressedSize the size of the data in its uncompressed form
+ * @param fileNameBytes the name of the file, as a byte array; see {@link #getFileName()} for
+ * information on encoding
+ * @param generalPurposeFlagBit11 the value of the 11th bit of the general purpose flag, which
+ * nominally controls the default character encoding for file names and comments; see
+ * {@link #getFileName()} for more information on encoding
+ * @param fileOffsetOfLocalEntry the file offset at which the local entry begins
+ */
+ public MinimalZipEntry(
+ int compressionMethod,
+ long crc32OfUncompressedData,
+ long compressedSize,
+ long uncompressedSize,
+ byte[] fileNameBytes,
+ boolean generalPurposeFlagBit11,
+ long fileOffsetOfLocalEntry) {
+ this.compressionMethod = compressionMethod;
+ this.crc32OfUncompressedData = crc32OfUncompressedData;
+ this.compressedSize = compressedSize;
+ this.uncompressedSize = uncompressedSize;
+ this.fileNameBytes = fileNameBytes == null ? null : fileNameBytes.clone();
+ this.generalPurposeFlagBit11 = generalPurposeFlagBit11;
+ this.fileOffsetOfLocalEntry = fileOffsetOfLocalEntry;
+ }
+
+ /**
+ * Sets the file offset at which the data for this entry begins.
+ * @param offset the offset
+ */
+ public void setFileOffsetOfCompressedData(long offset) {
+ fileOffsetOfCompressedData = offset;
+ }
+
+ /**
+ * Returns the compression method that was used, typically 8 (for deflate) or 0 (for stored).
+ * @return as described
+ */
+ public int getCompressionMethod() {
+ return compressionMethod;
+ }
+
+ /**
+ * Returns the CRC32 of the uncompressed data.
+ * @return as described
+ */
+ public long getCrc32OfUncompressedData() {
+ return crc32OfUncompressedData;
+ }
+
+ /**
+ * Returns the size of the data as it exists in the archive. For compressed entries, this is the
+ * size of the compressed data; for uncompressed entries, this is the same as
+ * {@link #getUncompressedSize()}.
+ * @return as described
+ */
+ public long getCompressedSize() {
+ return compressedSize;
+ }
+
+ /**
+ * Returns the size of the uncompressed data.
+ * @return as described
+ */
+ public long getUncompressedSize() {
+ return uncompressedSize;
+ }
+
+ /**
+ * Returns a copy of the bytes of the file name, exactly the same as they were in the archive
+ * file. See {@link #getFileName()} for an explanation of why this is useful.
+ * @return as described
+ */
+ public byte[] getFileNameBytes() {
+ return fileNameBytes == null ? null : fileNameBytes.clone();
+ }
+
+ /**
+ * Returns a best-effort conversion of the file name into a string, based on strict adherence to
+ * the PKWARE APPNOTE that defines this behavior. If the value of the 11th bit of the general
+ * purpose flag was set to 1, these bytes should be encoded with the UTF8 character set; otherwise
+ * the character set should be Cp437. Adherence to this standard varies significantly, and some
+ * systems use the default character set for the environment instead of Cp437 when writing these
+ * bytes. For such instances, callers can obtain the raw bytes by using
+ * {@link #getFileNameBytes()} instead and checking the value of the 11th bit of the general
+ * purpose bit flag for a hint using {@link #getGeneralPurposeFlagBit11()}. There is also
+ * something called EFS ("0x0008 extra field storage") that specifies additional behavior for
+ * character encoding, but this tool doesn't support it as the use is not standardized.
+ * @return as described
+ */
+ // TODO(andrewhayden): Support EFS
+ public String getFileName() {
+ String charsetName = generalPurposeFlagBit11 ? "UTF8" : "Cp437";
+ try {
+ return new String(fileNameBytes, charsetName);
+ } catch (UnsupportedEncodingException e) {
+ // Cp437 has been supported at least since JDK 1.6.0, so this should rarely occur in practice.
+ // Older versions of the JDK also support Cp437, but as part of charsets.jar, which didn't
+ // ship in every distribution; it is conceivable that those systems might have problems here.
+ throw new RuntimeException("System doesn't support " + charsetName, e);
+ }
+ }
+
+ /**
+ * Returns the value of the 11th bit of the general purpose flag; true for 1, false for 0. See
+ * {@link #getFileName()} for more information on the usefulness of this flag.
+ * @return as described
+ */
+ public boolean getGeneralPurposeFlagBit11() {
+ return generalPurposeFlagBit11;
+ }
+
+ /**
+ * Returns the file offset at which the first byte of the local entry header begins.
+ * @return as described
+ */
+ public long getFileOffsetOfLocalEntry() {
+ return fileOffsetOfLocalEntry;
+ }
+
+ /**
+ * Returns the file offset at which the first byte of the data for the entry begins. For
+ * compressed data, this is the first byte of the deflated data; for uncompressed data, this is
+ * the first byte of the uncompressed data.
+ * @return as described
+ */
+ public long getFileOffsetOfCompressedData() {
+ return fileOffsetOfCompressedData;
+ }
+
+ /**
+ * Convenience methods that returns true if and only if the entry is compressed with deflate.
+ * @return as described
+ */
+ public boolean isDeflateCompressed() {
+ // 8 is deflate according to the zip spec.
+ if (getCompressionMethod() != 8) {
+ return false;
+ }
+ // Some tools may list compression method deflate but set level to zero (store), so they will
+ // have a compressed size equal to the uncompresesd size. Don't consider such things to be
+ // compressed, even if they are "deflated".
+ return getCompressedSize() != getUncompressedSize();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (int) (compressedSize ^ (compressedSize >>> 32));
+ result = prime * result + compressionMethod;
+ result = prime * result + (int) (crc32OfUncompressedData ^ (crc32OfUncompressedData >>> 32));
+ result = prime * result + Arrays.hashCode(fileNameBytes);
+ result =
+ prime * result + (int) (fileOffsetOfCompressedData ^ (fileOffsetOfCompressedData >>> 32));
+ result = prime * result + (int) (fileOffsetOfLocalEntry ^ (fileOffsetOfLocalEntry >>> 32));
+ result = prime * result + (generalPurposeFlagBit11 ? 1231 : 1237);
+ result = prime * result + (int) (uncompressedSize ^ (uncompressedSize >>> 32));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ MinimalZipEntry other = (MinimalZipEntry) obj;
+ if (compressedSize != other.compressedSize) {
+ return false;
+ }
+ if (compressionMethod != other.compressionMethod) {
+ return false;
+ }
+ if (crc32OfUncompressedData != other.crc32OfUncompressedData) {
+ return false;
+ }
+ if (!Arrays.equals(fileNameBytes, other.fileNameBytes)) {
+ return false;
+ }
+ if (fileOffsetOfCompressedData != other.fileOffsetOfCompressedData) {
+ return false;
+ }
+ if (fileOffsetOfLocalEntry != other.fileOffsetOfLocalEntry) {
+ return false;
+ }
+ if (generalPurposeFlagBit11 != other.generalPurposeFlagBit11) {
+ return false;
+ }
+ if (uncompressedSize != other.uncompressedSize) {
+ return false;
+ }
+ return true;
+ }
+}