aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build/android/ziputils/CentralDirectory.java
blob: 25e50c873d70991a0ae5d5a315cd512787ae0a0e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// 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;
  }
}