| // Copyright 2015 The Bazel Authors. 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.devtools.build.android.ziputils; |
| |
| import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENOFF; |
| |
| import com.google.common.base.Preconditions; |
| import java.nio.ByteBuffer; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.NavigableMap; |
| import java.util.TreeMap; |
| |
| /** |
| * Provides a view of a zip file's central directory. For reading, a single memory mapped view is |
| * used. For writing, the central directory is stored as one or more views, each backed by a direct |
| * byte buffer. |
| */ |
| public class CentralDirectory extends View<CentralDirectory> { |
| |
| // Cached map from entry name to directory entry. |
| private NavigableMap<String, DirectoryEntry> mapByNameSorted; |
| // Cached map from entry file offset to directory entry. |
| private NavigableMap<Integer, DirectoryEntry> mapByOffsetSorted; |
| // Number of directory entries in this view. |
| private int count; |
| // Parsed or added entries |
| private List<DirectoryEntry> entries; |
| |
| /** |
| * Gets the number of directory entries in this view. |
| */ |
| public int getCount() { |
| return count; |
| } |
| |
| /** |
| * Returns a list of directory entries, in the order they occur in the central directory. |
| * This will typically also be the order of entries in the zip file, although that's not |
| * guaranteed. |
| */ |
| public List<DirectoryEntry> list() { |
| return entries; |
| } |
| |
| /** |
| * Returns a navigable map of directory entries, by zip entry file offset. |
| */ |
| public NavigableMap<Integer, DirectoryEntry> mapByOffset() { |
| if (entries == null) { |
| return null; |
| } |
| return mapEntriesByOffset(); |
| } |
| |
| /** |
| * Returns a navigable map of directory entries, by entry filename. |
| */ |
| public NavigableMap<String, DirectoryEntry> mapByFilename() { |
| if (entries == null) { |
| return null; |
| } |
| return mapEntriesByName(); |
| } |
| |
| /** |
| * Returns a {@code CentralDirectory} of the given buffer. This may be a full or a partial |
| * central directory. This method assumes ownership of the underlying buffer. Unlike most |
| * "view-of" methods, this method doesn't slice the argument buffer, and rather than advancing |
| * the buffer position, it sets it to 0. |
| * |
| * @param buffer containing data of a central directory. |
| * @return a {@code CentralDirectory} of the data at the current position of the given byte |
| * buffer. |
| */ |
| public static CentralDirectory viewOf(ByteBuffer buffer) { |
| buffer.position(0); |
| return new CentralDirectory(buffer); |
| } |
| |
| private CentralDirectory(ByteBuffer buffer) { |
| super(buffer); |
| count = -1; |
| } |
| |
| /** |
| * Parses this central directory, and maps the contained entries with {@link DirectoryEntry}s. |
| * |
| * @return this central directory view |
| * @throws IllegalStateException if the file offset is not set prior to parsing |
| */ |
| public CentralDirectory parse() throws IllegalStateException { |
| Preconditions.checkState(fileOffset != -1, "File offset not set prior to parsing"); |
| count = 0; |
| clearMaps(); |
| int relPos = 0; |
| buffer.position(0); |
| while (buffer.hasRemaining() && buffer.getInt(buffer.position()) == DirectoryEntry.SIGNATURE) { |
| count++; |
| DirectoryEntry entry = DirectoryEntry.viewOf(buffer).at(fileOffset + relPos); |
| entries.add(entry); |
| relPos += entry.getSize(); |
| buffer.position(relPos); |
| } |
| return this; |
| } |
| |
| /** |
| * Creates a new directory entry for output. The given entry is copied into the buffer of this |
| * central directory, and a view of the copied data is returned. |
| * |
| * @param entry prototype directory entry, typically an entry read from another zip file, for |
| * an entry being copied. |
| * @return a directory entry view of the copied entry. |
| */ |
| public DirectoryEntry nextEntry(DirectoryEntry entry) { |
| DirectoryEntry clone = entry.copy(buffer); |
| if (count == -1) { |
| clearMaps(); |
| count = 1; |
| } else { |
| count++; |
| } |
| entries.add(clone); |
| return clone; |
| } |
| |
| private NavigableMap<String, DirectoryEntry> mapEntriesByName() { |
| if (mapByNameSorted == null) { |
| mapByNameSorted = new TreeMap<>(); |
| for (DirectoryEntry entry : entries) { |
| mapByNameSorted.put(entry.getFilename(), entry); |
| } |
| } |
| return mapByNameSorted; |
| } |
| |
| private NavigableMap<Integer, DirectoryEntry> mapEntriesByOffset() { |
| if (mapByOffsetSorted == null) { |
| mapByOffsetSorted = new TreeMap<>(); |
| for (DirectoryEntry entry : entries) { |
| mapByOffsetSorted.put(entry.get(CENOFF), entry); |
| } |
| } |
| return mapByOffsetSorted; |
| } |
| |
| private void clearMaps() { |
| entries = new ArrayList<>(); |
| mapByOffsetSorted = null; |
| mapByNameSorted = null; |
| } |
| } |