summaryrefslogtreecommitdiff
path: root/src/main/java/com/android/tools/build/apkzlib/zip/CentralDirectoryHeader.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/android/tools/build/apkzlib/zip/CentralDirectoryHeader.java')
-rw-r--r--src/main/java/com/android/tools/build/apkzlib/zip/CentralDirectoryHeader.java434
1 files changed, 434 insertions, 0 deletions
diff --git a/src/main/java/com/android/tools/build/apkzlib/zip/CentralDirectoryHeader.java b/src/main/java/com/android/tools/build/apkzlib/zip/CentralDirectoryHeader.java
new file mode 100644
index 0000000..353ed3d
--- /dev/null
+++ b/src/main/java/com/android/tools/build/apkzlib/zip/CentralDirectoryHeader.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.android.tools.build.apkzlib.zip;
+
+import com.android.tools.build.apkzlib.zip.utils.MsDosDateTimeUtils;
+import com.google.common.base.Verify;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import javax.annotation.Nonnull;
+
+/**
+ * The Central Directory Header contains information about files stored in the zip. Instances of
+ * this class contain information for files that already are in the zip and, for which the data was
+ * read from the Central Directory. But some instances of this class are used for new files.
+ * Because instances of this class can refer to files not yet on the zip, some of the fields may
+ * not be filled in, or may be filled in with default values.
+ * <p>
+ * Because compression decision is done lazily, some data is stored with futures.
+ */
+public class CentralDirectoryHeader implements Cloneable {
+
+ /**
+ * Default "version made by" field: upper byte needs to be 0 to set to MS-DOS compatibility.
+ * Lower byte can be anything, really. We use 18 because aapt uses 17 :)
+ */
+ private static final int DEFAULT_VERSION_MADE_BY = 0x0018;
+
+ /**
+ * Name of the file.
+ */
+ @Nonnull
+ private String name;
+
+ /**
+ * CRC32 of the data. 0 if not yet computed.
+ */
+ private long crc32;
+
+ /**
+ * Size of the file uncompressed. 0 if the file has no data.
+ */
+ private long uncompressedSize;
+
+ /**
+ * Code of the program that made the zip. We actually don't care about this.
+ */
+ private long madeBy;
+
+ /**
+ * General-purpose bit flag.
+ */
+ @Nonnull
+ private GPFlags gpBit;
+
+ /**
+ * Last modification time in MS-DOS format (see {@link MsDosDateTimeUtils#packTime(long)}).
+ */
+ private long lastModTime;
+
+ /**
+ * Last modification time in MS-DOS format (see {@link MsDosDateTimeUtils#packDate(long)}).
+ */
+ private long lastModDate;
+
+ /**
+ * Extra data field contents. This field follows a specific structure according to the
+ * specification.
+ */
+ @Nonnull
+ private ExtraField extraField;
+
+ /**
+ * File comment.
+ */
+ @Nonnull
+ private byte[] comment;
+
+ /**
+ * File internal attributes.
+ */
+ private long internalAttributes;
+
+ /**
+ * File external attributes.
+ */
+ private long externalAttributes;
+
+ /**
+ * Offset in the file where the data is located. This will be -1 if the header corresponds to
+ * a new file that is not yet written in the zip and, therefore, has no written data.
+ */
+ private long offset;
+
+ /**
+ * Encoded file name.
+ */
+ private byte[] encodedFileName;
+
+ /**
+ * Compress information that may not have been computed yet due to lazy compression.
+ */
+ @Nonnull
+ private Future<CentralDirectoryHeaderCompressInfo> compressInfo;
+
+ /**
+ * The file this header belongs to.
+ */
+ @Nonnull
+ private final ZFile file;
+
+ /**
+ * Creates data for a file.
+ *
+ * @param name the file name
+ * @param encodedFileName the encoded file name, this array will be owned by the header
+ * @param uncompressedSize the uncompressed file size
+ * @param compressInfo computation that defines the compression information
+ * @param flags flags used in the entry
+ * @param zFile the file this header belongs to
+ */
+ CentralDirectoryHeader(
+ @Nonnull String name,
+ @Nonnull byte[] encodedFileName,
+ long uncompressedSize,
+ @Nonnull Future<CentralDirectoryHeaderCompressInfo> compressInfo,
+ @Nonnull GPFlags flags,
+ @Nonnull ZFile zFile) {
+ this.name = name;
+ this.uncompressedSize = uncompressedSize;
+ crc32 = 0;
+
+ /*
+ * Set sensible defaults for the rest.
+ */
+ madeBy = DEFAULT_VERSION_MADE_BY;
+
+ gpBit = flags;
+ lastModTime = MsDosDateTimeUtils.packCurrentTime();
+ lastModDate = MsDosDateTimeUtils.packCurrentDate();
+ extraField = new ExtraField();
+ comment = new byte[0];
+ internalAttributes = 0;
+ externalAttributes = 0;
+ offset = -1;
+ this.encodedFileName = encodedFileName;
+ this.compressInfo = compressInfo;
+ file = zFile;
+ }
+
+ /**
+ * Obtains the name of the file.
+ *
+ * @return the name
+ */
+ @Nonnull
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Obtains the size of the uncompressed file.
+ *
+ * @return the size of the file
+ */
+ public long getUncompressedSize() {
+ return uncompressedSize;
+ }
+
+ /**
+ * Obtains the CRC32 of the data.
+ *
+ * @return the CRC32, 0 if not yet computed
+ */
+ public long getCrc32() {
+ return crc32;
+ }
+
+ /**
+ * Sets the CRC32 of the data.
+ *
+ * @param crc32 the CRC 32
+ */
+ void setCrc32(long crc32) {
+ this.crc32 = crc32;
+ }
+
+ /**
+ * Obtains the code of the program that made the zip.
+ *
+ * @return the code
+ */
+ public long getMadeBy() {
+ return madeBy;
+ }
+
+ /**
+ * Sets the code of the progtram that made the zip.
+ *
+ * @param madeBy the code
+ */
+ void setMadeBy(long madeBy) {
+ this.madeBy = madeBy;
+ }
+
+ /**
+ * Obtains the general-purpose bit flag.
+ *
+ * @return the bit flag
+ */
+ @Nonnull
+ public GPFlags getGpBit() {
+ return gpBit;
+ }
+
+ /**
+ * Obtains the last modification time of the entry.
+ *
+ * @return the last modification time in MS-DOS format (see
+ * {@link MsDosDateTimeUtils#packTime(long)})
+ */
+ public long getLastModTime() {
+ return lastModTime;
+ }
+
+ /**
+ * Sets the last modification time of the entry.
+ *
+ * @param lastModTime the last modification time in MS-DOS format (see
+ * {@link MsDosDateTimeUtils#packTime(long)})
+ */
+ void setLastModTime(long lastModTime) {
+ this.lastModTime = lastModTime;
+ }
+
+ /**
+ * Obtains the last modification date of the entry.
+ *
+ * @return the last modification date in MS-DOS format (see
+ * {@link MsDosDateTimeUtils#packDate(long)})
+ */
+ public long getLastModDate() {
+ return lastModDate;
+ }
+
+ /**
+ * Sets the last modification date of the entry.
+ *
+ * @param lastModDate the last modification date in MS-DOS format (see
+ * {@link MsDosDateTimeUtils#packDate(long)})
+ */
+ void setLastModDate(long lastModDate) {
+ this.lastModDate = lastModDate;
+ }
+
+ /**
+ * Obtains the data in the extra field.
+ *
+ * @return the data (returns an empty array if there is none)
+ */
+ @Nonnull
+ public ExtraField getExtraField() {
+ return extraField;
+ }
+
+ /**
+ * Sets the data in the extra field.
+ *
+ * @param extraField the data to set
+ */
+ public void setExtraField(@Nonnull ExtraField extraField) {
+ setExtraFieldNoNotify(extraField);
+ file.centralDirectoryChanged();
+ }
+
+ /**
+ * Sets the data in the extra field, but does not notify {@link ZFile}. This method is invoked
+ * when the {@link ZFile} knows the extra field is being set.
+ *
+ * @param extraField the data to set
+ */
+ void setExtraFieldNoNotify(@Nonnull ExtraField extraField) {
+ this.extraField = extraField;
+ }
+
+ /**
+ * Obtains the entry's comment.
+ *
+ * @return the comment (returns an empty array if there is no comment)
+ */
+ @Nonnull
+ public byte[] getComment() {
+ return comment;
+ }
+
+ /**
+ * Sets the entry's comment.
+ *
+ * @param comment the comment
+ */
+ void setComment(@Nonnull byte[] comment) {
+ this.comment = comment;
+ }
+
+ /**
+ * Obtains the entry's internal attributes.
+ *
+ * @return the entry's internal attributes
+ */
+ public long getInternalAttributes() {
+ return internalAttributes;
+ }
+
+ /**
+ * Sets the entry's internal attributes.
+ *
+ * @param internalAttributes the entry's internal attributes
+ */
+ void setInternalAttributes(long internalAttributes) {
+ this.internalAttributes = internalAttributes;
+ }
+
+ /**
+ * Obtains the entry's external attributes.
+ *
+ * @return the entry's external attributes
+ */
+ public long getExternalAttributes() {
+ return externalAttributes;
+ }
+
+ /**
+ * Sets the entry's external attributes.
+ *
+ * @param externalAttributes the entry's external attributes
+ */
+ void setExternalAttributes(long externalAttributes) {
+ this.externalAttributes = externalAttributes;
+ }
+
+ /**
+ * Obtains the offset in the zip file where this entry's data is.
+ *
+ * @return the offset or {@code -1} if the file has no data in the zip and, therefore, data
+ * is stored in memory
+ */
+ public long getOffset() {
+ return offset;
+ }
+
+ /**
+ * Sets the offset in the zip file where this entry's data is.
+ *
+ * @param offset the offset or {@code -1} if the file is new and has no data in the zip yet
+ */
+ void setOffset(long offset) {
+ this.offset = offset;
+ }
+
+ /**
+ * Obtains the encoded file name.
+ *
+ * @return the encoded file name
+ */
+ public byte[] getEncodedFileName() {
+ return encodedFileName;
+ }
+
+ /**
+ * Resets the deferred CRC flag in the GP flags.
+ */
+ void resetDeferredCrc() {
+ /*
+ * We actually create a new set of flags. Since the only information we care about is the
+ * UTF-8 encoding, we'll just create a brand new object.
+ */
+ gpBit = GPFlags.make(gpBit.isUtf8FileName());
+ }
+
+ @Override
+ protected CentralDirectoryHeader clone() throws CloneNotSupportedException {
+ CentralDirectoryHeader cdr = (CentralDirectoryHeader) super.clone();
+ cdr.extraField = extraField;
+ cdr.comment = Arrays.copyOf(comment, comment.length);
+ cdr.encodedFileName = Arrays.copyOf(encodedFileName, encodedFileName.length);
+ return cdr;
+ }
+
+ /**
+ * Obtains the future with the compression information.
+ *
+ * @return the information
+ */
+ @Nonnull
+ public Future<CentralDirectoryHeaderCompressInfo> getCompressionInfo() {
+ return compressInfo;
+ }
+
+ /**
+ * Equivalent to {@code getCompressionInfo().get()} but masking the possible exceptions and
+ * guaranteeing non-{@code null} return.
+ *
+ * @return the result of the future
+ * @throws IOException failed to get the information
+ */
+ @Nonnull
+ public CentralDirectoryHeaderCompressInfo getCompressionInfoWithWait()
+ throws IOException {
+ try {
+ CentralDirectoryHeaderCompressInfo info = getCompressionInfo().get();
+ Verify.verifyNotNull(info, "info == null");
+ return info;
+ } catch (InterruptedException e) {
+ throw new IOException("Interrupted while waiting for compression information.", e);
+ } catch (ExecutionException e) {
+ throw new IOException("Execution of compression failed.", e);
+ }
+ }
+}