/* * Copyright 2000-2015 JetBrains s.r.o. * * 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.intellij.openapi.vfs.impl; import com.intellij.openapi.util.Condition; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; /** * Map of relativePath => ArchiveHandler.EntryInfo optimised for memory: * - it does not store keys (may be recovered from the ArchiveHandler.EntryInfo) * - does not support removal */ class ZipEntryMap extends AbstractMap<String, ArchiveHandler.EntryInfo> { private ArchiveHandler.EntryInfo[] entries; private int size; ZipEntryMap(int expectedSize) { size = 0; // expectedSize is the number of entries in the zip file, and the actual number of entries in the // hash map will be larger because we also need to store intermediate directories which aren't // represented by ZipEntry instances. Therefore, choose a smaller load factor than the one used in rehash(). entries = new ArchiveHandler.EntryInfo[Math.max(10, expectedSize * 2)]; // load factor 0.5 } @Override public ArchiveHandler.EntryInfo get(@NotNull Object key) { String relativePath = (String)key; int index = index(relativePath, entries); ArchiveHandler.EntryInfo entry; int i = index; while (true) { entry = entries[i]; if (entry == null || isTheOne(entry, relativePath)) break; if (++i == entries.length) { i = 0; } if (i == index) { entry = null; break; } } return entry; } private static int index(@NotNull String relativePath, @NotNull ArchiveHandler.EntryInfo[] entries) { return (relativePath.hashCode() & 0x7fffffff) % entries.length; } @Override public ArchiveHandler.EntryInfo put(String relativePath, ArchiveHandler.EntryInfo value) { if (size >= 5 * entries.length / 8) { // 0.625 rehash(); } ArchiveHandler.EntryInfo old = put(relativePath, value, entries); if (old == null){ size++; } return old; } @Nullable private static ArchiveHandler.EntryInfo put(@NotNull String relativePath, @NotNull ArchiveHandler.EntryInfo value, @NotNull ArchiveHandler.EntryInfo[] entries) { int index = index(relativePath, entries); ArchiveHandler.EntryInfo entry; int i = index; while (true) { entry = entries[i]; if (entry == null || isTheOne(entry, relativePath)) { entries[i] = value; break; } if (++i == entries.length) { i = 0; } } return entry; } private static boolean isTheOne(@NotNull ArchiveHandler.EntryInfo entry, @NotNull CharSequence relativePath) { int endIndex = relativePath.length(); for (ArchiveHandler.EntryInfo e = entry; e != null; e = e.parent) { CharSequence shortName = e.shortName; if (!CharArrayUtil.regionMatches(relativePath, endIndex - shortName.length(), relativePath.length(), shortName)) { return false; } endIndex -= shortName.length(); if (e.parent != null && e.parent.shortName.length() != 0 && endIndex != 0) { // match "/" if (relativePath.charAt(endIndex-1) == '/') { endIndex -= 1; } else { return false; } } } return endIndex==0; } @NotNull private ArchiveHandler.EntryInfo[] rehash() { ArchiveHandler.EntryInfo[] newEntries = new ArchiveHandler.EntryInfo[entries.length < 1000 ? entries.length * 2 : entries.length * 3/2]; for (ArchiveHandler.EntryInfo entry : entries) { if (entry != null) { put(getRelativePath(entry), entry, newEntries); } } entries = newEntries; return newEntries; } @NotNull private static String getRelativePath(@NotNull ArchiveHandler.EntryInfo entry) { StringBuilder result = new StringBuilder(entry.shortName.length() + 10); for (ArchiveHandler.EntryInfo e = entry; e != null; e = e.parent) { if (result.length() != 0 && e.shortName.length() != 0) { result.append('/'); } appendReversed(result, e.shortName); } return result.reverse().toString(); } private static void appendReversed(@NotNull StringBuilder builder, @NotNull CharSequence sequence) { for (int i=sequence.length()-1; i>=0 ;i--) { builder.append(sequence.charAt(i)); } } @Override public ArchiveHandler.EntryInfo remove(@NotNull Object key) { throw new UnsupportedOperationException(); } @Override public int size() { return size; } @Override public void clear() { size = 0; entries = new ArchiveHandler.EntryInfo[10]; } private EntrySet entrySet; @NotNull @Override public EntrySet entrySet() { EntrySet es; return (es = entrySet) == null ? (entrySet = new EntrySet()) : es; } private final class EntrySet extends AbstractSet<Entry<String, ArchiveHandler.EntryInfo>> { @Override public final int size() { return ZipEntryMap.this.size(); } @Override public final void clear() { ZipEntryMap.this.clear(); } @Override public final Iterator<Entry<String, ArchiveHandler.EntryInfo>> iterator() { return ContainerUtil.mapIterator(ContainerUtil.iterate(entries, Condition.NOT_NULL).iterator(), entry -> new SimpleEntry<>(getRelativePath(entry), entry)); } @Override public final boolean contains(Object o) { if (!(o instanceof Map.Entry)) { return false; } Map.Entry<?, ?> e = (Map.Entry<?, ?>)o; String key = (String)e.getKey(); ArchiveHandler.EntryInfo value = (ArchiveHandler.EntryInfo)e.getValue(); return value.equals(get(key)); } @Override public final boolean remove(Object o) { throw new UnsupportedOperationException(); } } @NotNull @Override public Collection<ArchiveHandler.EntryInfo> values() { return ContainerUtil.filter(entries, Condition.NOT_NULL); } }