1212import java .nio .ByteBuffer ;
1313import java .nio .file .Path ;
1414import java .nio .file .Paths ;
15+ import java .util .LinkedHashMap ;
1516import java .util .LinkedHashSet ;
17+ import java .util .Map ;
1618import java .util .Set ;
1719import java .util .stream .Stream ;
1820
2426 */
2527public class ReadOnlyZipStore extends ZipStore {
2628
29+ private Map <String , Long > fileIndex ;
30+ private Set <String > directoryIndex ;
31+ private boolean isCached = false ;
32+
2733 public ReadOnlyZipStore (@ Nonnull StoreHandle underlyingStore ) {
2834 super (underlyingStore );
2935 }
@@ -36,6 +42,33 @@ public ReadOnlyZipStore(@Nonnull String underlyingStorePath) {
3642 this (Paths .get (underlyingStorePath ));
3743 }
3844
45+ private synchronized void ensureCache () {
46+ if (isCached ) return ;
47+
48+ fileIndex = new LinkedHashMap <>();
49+ directoryIndex = new LinkedHashSet <>();
50+
51+ InputStream inputStream = underlyingStore .getInputStream ();
52+ if (inputStream == null ) {
53+ isCached = true ;
54+ return ;
55+ }
56+
57+ try (ZipArchiveInputStream zis = new ZipArchiveInputStream (inputStream )) {
58+ ZipArchiveEntry entry ;
59+ while ((entry = zis .getNextEntry ()) != null ) {
60+ String name = normalizeEntryName (entry .getName ());
61+ if (entry .isDirectory ()) {
62+ directoryIndex .add (name );
63+ } else {
64+ fileIndex .put (name , entry .getSize ());
65+ }
66+ }
67+ } catch (IOException ignored ) {
68+ }
69+ isCached = true ;
70+ }
71+
3972 String resolveKeys (String [] keys ) {
4073 return String .join ("/" , keys );
4174 }
@@ -46,7 +79,8 @@ String[] resolveEntryKeys(String entryKey) {
4679
4780 @ Override
4881 public boolean exists (String [] keys ) {
49- return get (keys , 0 , 0 ) != null ;
82+ ensureCache ();
83+ return fileIndex .containsKey (resolveKeys (keys ));
5084 }
5185
5286 @ Nullable
@@ -64,6 +98,12 @@ public ByteBuffer get(String[] keys, long start) {
6498 @ Nullable
6599 @ Override
66100 public ByteBuffer get (String [] keys , long start , long end ) {
101+ ensureCache ();
102+ String key = resolveKeys (keys );
103+ if (!fileIndex .containsKey (key )) {
104+ return null ;
105+ }
106+
67107 InputStream inputStream = underlyingStore .getInputStream ();
68108 if (inputStream == null ) {
69109 return null ;
@@ -128,52 +168,40 @@ public String toString() {
128168
129169 @ Override
130170 public Stream <String []> list (String [] prefixKeys ) {
171+ ensureCache ();
131172 Stream .Builder <String []> builder = Stream .builder ();
132- InputStream inputStream = underlyingStore .getInputStream ();
133- if (inputStream == null ) return builder .build ();
134173
135174 String prefix = resolveKeys (prefixKeys );
136175 if (!prefix .isEmpty () && !prefix .endsWith ("/" )) {
137176 prefix += "/" ;
138177 }
139178
140- try (ZipArchiveInputStream zis = new ZipArchiveInputStream (inputStream )) {
141- ZipArchiveEntry entry ;
142- while ((entry = zis .getNextEntry ()) != null ) {
143- String name = normalizeEntryName (entry .getName ());
144- if (name .startsWith (prefix ) && !entry .isDirectory ()) {
145- builder .add (resolveEntryKeys (name .substring (prefix .length ())));
146- }
179+ for (String name : fileIndex .keySet ()) {
180+ if (name .startsWith (prefix )) {
181+ builder .add (resolveEntryKeys (name .substring (prefix .length ())));
147182 }
148- } catch (IOException ignored ) {
149183 }
150184 return builder .build ();
151185 }
152186
153187 @ Override
154188 public Stream <String > listChildren (String [] prefixKeys ) {
189+ ensureCache ();
155190 Set <String > children = new LinkedHashSet <>();
156- InputStream inputStream = underlyingStore .getInputStream ();
157- if (inputStream == null ) return Stream .empty ();
158191
159192 String prefix = resolveKeys (prefixKeys );
160193 if (!prefix .isEmpty () && !prefix .endsWith ("/" )) {
161194 prefix += "/" ;
162195 }
163196
164- try (ZipArchiveInputStream zis = new ZipArchiveInputStream (inputStream )) {
165- ZipArchiveEntry entry ;
166- while ((entry = zis .getNextEntry ()) != null ) {
167- String name = normalizeEntryName (entry .getName ());
168-
169- if (name .startsWith (prefix ) && !name .equals (prefix )) {
170- String relative = name .substring (prefix .length ());
171- String [] parts = relative .split ("/" );
172- children .add (parts [0 ]);
173- }
197+ String finalPrefix = prefix ;
198+ Stream .concat (fileIndex .keySet ().stream (), directoryIndex .stream ()).forEach (name -> {
199+ if (name .startsWith (finalPrefix ) && !name .equals (finalPrefix )) {
200+ String relative = name .substring (finalPrefix .length ());
201+ String [] parts = relative .split ("/" );
202+ children .add (parts [0 ]);
174203 }
175- } catch (IOException ignored ) {
176- }
204+ });
177205
178206 return children .stream ();
179207 }
@@ -187,6 +215,12 @@ private String normalizeEntryName(String name) {
187215
188216 @ Override
189217 public InputStream getInputStream (String [] keys , long start , long end ) {
218+ ensureCache ();
219+ String key = resolveKeys (keys );
220+ if (!fileIndex .containsKey (key )) {
221+ return null ;
222+ }
223+
190224 InputStream baseStream = underlyingStore .getInputStream ();
191225
192226 try {
@@ -221,6 +255,17 @@ public InputStream getInputStream(String[] keys, long start, long end) {
221255
222256 @ Override
223257 public long getSize (String [] keys ) {
258+ ensureCache ();
259+ String key = resolveKeys (keys );
260+ Long cachedSize = fileIndex .get (key );
261+ if (cachedSize == null ) {
262+ return -1 ;
263+ }
264+ if (cachedSize >= 0 ) {
265+ return cachedSize ;
266+ }
267+
268+ // if size is not in header/cache, we fallback to reading
224269 InputStream inputStream = underlyingStore .getInputStream ();
225270 if (inputStream == null ) {
226271 throw new RuntimeException (new IOException ("Underlying store input stream is null" ));
0 commit comments