blob: c13a21e76b1964580027d783184459a632a98148 [file] [log] [blame]
/*
* Copyright 2000-2013 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.jar;
import com.intellij.openapi.diagnostic.LogUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.io.BufferExposingByteArrayInputStream;
import com.intellij.openapi.util.io.FileAttributes;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.JarFile;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.reference.SoftReference;
import com.intellij.util.ArrayUtil;
import com.intellij.util.TimedReference;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.Reference;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class JarHandlerBase {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.impl.jar.JarHandlerBase");
private static final long DEFAULT_LENGTH = 0L;
private static final long DEFAULT_TIMESTAMP = -1L;
private final TimedReference<JarFile> myJarFile = new TimedReference<JarFile>(null);
private Reference<Map<String, EntryInfo>> myRelPathsToEntries = new SoftReference<Map<String, EntryInfo>>(null);
private final Object lock = new Object();
protected final String myBasePath;
protected static class EntryInfo {
protected final boolean isDirectory;
protected final String shortName;
protected final EntryInfo parent;
public EntryInfo(@NotNull String shortName, final EntryInfo parent, final boolean directory) {
this.shortName = shortName;
this.parent = parent;
isDirectory = directory;
}
}
public JarHandlerBase(@NotNull String path) {
myBasePath = path;
}
protected void clear() {
synchronized (lock) {
myRelPathsToEntries = null;
myJarFile.set(null);
}
}
public File getMirrorFile(@NotNull File originalFile) {
return originalFile;
}
@Nullable
public JarFile getJar() {
JarFile jar = myJarFile.get();
if (jar == null) {
synchronized (lock) {
jar = myJarFile.get();
if (jar == null) {
jar = createJarFile();
if (jar != null) {
myJarFile.set(jar);
}
}
}
}
return jar;
}
@Nullable
protected JarFile createJarFile() {
final File originalFile = getOriginalFile();
try {
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed") final ZipFile zipFile = new ZipFile(getMirrorFile(originalFile));
class MyJarEntry implements JarFile.JarEntry {
private final ZipEntry myEntry;
MyJarEntry(ZipEntry entry) {
myEntry = entry;
}
public ZipEntry getEntry() {
return myEntry;
}
@Override
public String getName() {
return myEntry.getName();
}
@Override
public long getSize() {
return myEntry.getSize();
}
@Override
public long getTime() {
return myEntry.getTime();
}
@Override
public boolean isDirectory() {
return myEntry.isDirectory();
}
}
return new JarFile() {
@Override
public JarFile.JarEntry getEntry(String name) {
try {
ZipEntry entry = zipFile.getEntry(name);
if (entry != null) {
return new MyJarEntry(entry);
}
}
catch (IllegalArgumentException e) {
LOG.warn(e);
}
return null;
}
@Override
public InputStream getInputStream(JarFile.JarEntry entry) throws IOException {
return zipFile.getInputStream(((MyJarEntry)entry).myEntry);
}
@Override
public Enumeration<? extends JarFile.JarEntry> entries() {
return new Enumeration<JarEntry>() {
private final Enumeration<? extends ZipEntry> entries = zipFile.entries();
@Override
public boolean hasMoreElements() {
return entries.hasMoreElements();
}
@Override
public JarEntry nextElement() {
try {
ZipEntry entry = entries.nextElement();
if (entry != null) {
return new MyJarEntry(entry);
}
}
catch (IllegalArgumentException e) {
LOG.warn(e);
}
return null;
}
};
}
@Override
public ZipFile getZipFile() {
return zipFile;
}
};
}
catch (IOException e) {
LOG.warn(e.getMessage() + ": " + originalFile.getPath(), e);
return null;
}
}
@NotNull
protected File getOriginalFile() {
return new File(myBasePath);
}
@NotNull
private static EntryInfo getOrCreate(@NotNull String entryName, boolean isDirectory, @NotNull Map<String, EntryInfo> map) {
EntryInfo info = map.get(entryName);
if (info == null) {
int idx = entryName.lastIndexOf('/');
final String parentEntryName = idx > 0 ? entryName.substring(0, idx) : "";
String shortName = idx > 0 ? entryName.substring(idx + 1) : entryName;
if (".".equals(shortName)) return getOrCreate(parentEntryName, true, map);
info = new EntryInfo(shortName, getOrCreate(parentEntryName, true, map), isDirectory);
map.put(entryName, info);
}
return info;
}
@NotNull
public String[] list(@NotNull final VirtualFile file) {
synchronized (lock) {
EntryInfo parentEntry = getEntryInfo(file);
Set<String> names = new HashSet<String>();
for (EntryInfo info : getEntriesMap().values()) {
if (info.parent == parentEntry) {
names.add(info.shortName);
}
}
return ArrayUtil.toStringArray(names);
}
}
protected EntryInfo getEntryInfo(@NotNull VirtualFile file) {
synchronized (lock) {
String parentPath = getRelativePath(file);
return getEntryInfo(parentPath);
}
}
public EntryInfo getEntryInfo(@NotNull String parentPath) {
return getEntriesMap().get(parentPath);
}
@NotNull
protected Map<String, EntryInfo> getEntriesMap() {
synchronized (lock) {
Map<String, EntryInfo> map = SoftReference.dereference(myRelPathsToEntries);
if (map == null) {
final JarFile zip = getJar();
if (zip != null) {
LogUtil.debug(LOG, "mapping %s", myBasePath);
map = new THashMap<String, EntryInfo>();
map.put("", new EntryInfo("", null, true));
final Enumeration<? extends JarFile.JarEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
final JarFile.JarEntry entry = entries.nextElement();
final String name = entry.getName();
final boolean isDirectory = StringUtil.endsWithChar(name, '/');
getOrCreate(isDirectory ? name.substring(0, name.length() - 1) : name, isDirectory, map);
}
myRelPathsToEntries = new SoftReference<Map<String, EntryInfo>>(map);
}
else {
map = Collections.emptyMap();
}
}
return map;
}
}
@NotNull
private String getRelativePath(@NotNull VirtualFile file) {
final String path = file.getPath().substring(myBasePath.length() + 1);
return StringUtil.startsWithChar(path, '/') ? path.substring(1) : path;
}
@Nullable
private JarFile.JarEntry convertToEntry(@NotNull VirtualFile file) {
String path = getRelativePath(file);
final JarFile jar = getJar();
return jar == null ? null : jar.getEntry(path);
}
public long getLength(@NotNull final VirtualFile file) {
final JarFile.JarEntry entry = convertToEntry(file);
synchronized (lock) {
return entry == null ? DEFAULT_LENGTH : entry.getSize();
}
}
@NotNull
public InputStream getInputStream(@NotNull final VirtualFile file) throws IOException {
return new BufferExposingByteArrayInputStream(contentsToByteArray(file));
}
@NotNull
public byte[] contentsToByteArray(@NotNull final VirtualFile file) throws IOException {
final JarFile.JarEntry entry = convertToEntry(file);
if (entry == null) {
return ArrayUtil.EMPTY_BYTE_ARRAY;
}
synchronized (lock) {
final JarFile jar = getJar();
assert jar != null : file;
final InputStream stream = jar.getInputStream(entry);
assert stream != null : file;
try {
return FileUtil.loadBytes(stream, (int)entry.getSize());
}
finally {
stream.close();
}
}
}
public long getTimeStamp(@NotNull final VirtualFile file) {
if (file.getParent() == null) return getOriginalFile().lastModified(); // Optimization
final JarFile.JarEntry entry = convertToEntry(file);
synchronized (lock) {
return entry == null ? DEFAULT_TIMESTAMP : entry.getTime();
}
}
public boolean isDirectory(@NotNull final VirtualFile file) {
if (file.getParent() == null) return true; // Optimization
synchronized (lock) {
final String path = getRelativePath(file);
final EntryInfo info = getEntryInfo(path);
return info == null || info.isDirectory;
}
}
public boolean exists(@NotNull final VirtualFile fileOrDirectory) {
if (fileOrDirectory.getParent() == null) {
// Optimization. Do not build entries if asked for jar root existence.
return myJarFile.get() != null || getOriginalFile().exists();
}
return getEntryInfo(fileOrDirectory) != null;
}
@Nullable
public FileAttributes getAttributes(@NotNull final VirtualFile file) {
final JarFile.JarEntry entry = convertToEntry(file);
synchronized (lock) {
final EntryInfo entryInfo = getEntryInfo(getRelativePath(file));
if (entryInfo == null) return null;
final long length = entry != null ? entry.getSize() : DEFAULT_LENGTH;
final long timeStamp = entry != null ? entry.getTime() : DEFAULT_TIMESTAMP;
return new FileAttributes(entryInfo.isDirectory, false, false, false, length, timeStamp, false);
}
}
}