IDEA-122914 (unclosed zip files)

This commit is contained in:
Roman Shevchenko
2014-05-05 16:20:08 +04:00
parent cf649f8633
commit 0b888e543c
10 changed files with 463 additions and 286 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2014 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.
@@ -59,7 +59,7 @@ public class JarVersionDetectionUtil {
}
/** @deprecated use {@link JarUtil#getJarAttribute(File, Attributes.Name)} (to remove in IDEA 15) */
@SuppressWarnings("UnusedDeclaration")
@SuppressWarnings({"UnusedDeclaration", "deprecation"})
public static String detectJarVersion(@Nullable com.intellij.openapi.vfs.JarFile zipFile) {
if (zipFile == null) {
return null;
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
* Copyright 2000-2014 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.
@@ -22,9 +22,7 @@ import java.io.InputStream;
import java.util.Enumeration;
import java.util.zip.ZipFile;
/**
* Encapsulates operations with .jar file
*/
/** @deprecated causes ZipFile leaks, do not use (to be removed in IDEA 15) */
public interface JarFile {
interface JarEntry {
String getName();
@@ -17,16 +17,12 @@ 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.FileSystemUtil;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.*;
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;
@@ -45,10 +41,9 @@ public class JarHandlerBase {
protected static final long DEFAULT_LENGTH = 0L;
protected static final long DEFAULT_TIMESTAMP = -1L;
private final TimedReference<JarFile> myJarFile = new TimedReference<JarFile>(null);
private final Object myLock = new Object();
private volatile Reference<Map<String, EntryInfo>> myRelPathsToEntries = new SoftReference<Map<String, EntryInfo>>(null);
private boolean myCorruptedJar = false;
private final Object myLock = new Object();
protected final String myBasePath;
@@ -76,34 +71,17 @@ public class JarHandlerBase {
return originalFile;
}
@Nullable
/** @deprecated to be removed in IDEA 15 */
@SuppressWarnings("deprecation")
public JarFile getJar() {
JarFile jar = myJarFile.get();
if (jar == null) {
synchronized (myLock) {
if (myCorruptedJar) {
return null;
}
jar = myJarFile.get();
if (jar == null) {
try {
jar = createJarFile();
myJarFile.set(jar);
}
catch (IOException e) {
myCorruptedJar = true;
LOG.warn(e.getMessage() + ": " + myBasePath, e);
return null;
}
}
}
try {
File mirror = getMirrorFile(getOriginalFile());
return new MyJarFile(new ZipFile(mirror));
}
catch (IOException e) {
LOG.warn(e.getMessage() + ": " + myBasePath, e);
return null;
}
return jar;
}
private JarFile createJarFile() throws IOException {
File mirrorFile = getMirrorFile(getOriginalFile());
return new MyJarFile(new ZipFile(mirrorFile));
}
@NotNull
@@ -146,25 +124,34 @@ public class JarHandlerBase {
map = SoftReference.dereference(myRelPathsToEntries);
if (map == null) {
JarFile zip = getJar();
if (zip != null) {
if (myCorruptedJar) {
map = Collections.emptyMap();
}
else {
LogUtil.debug(LOG, "mapping %s", myBasePath);
map = new THashMap<String, EntryInfo>();
map.put("", new EntryInfo(null, "", true, DEFAULT_LENGTH, DEFAULT_TIMESTAMP));
Enumeration<? extends JarFile.JarEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
JarFile.JarEntry entry = entries.nextElement();
if (entry == null) break; // corrupted .jar
getOrCreate(entry, map, zip);
try {
ZipFile zip = getZipFile();
try {
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
getOrCreate(entries.nextElement(), map, zip);
}
}
finally {
ZipFileCache.release(zip);
}
}
catch (IOException e) {
myCorruptedJar = true;
LOG.warn(e.getMessage() + ": " + myBasePath, e);
map = Collections.emptyMap();
}
}
myRelPathsToEntries = new SoftReference<Map<String, EntryInfo>>(Collections.unmodifiableMap(map));
}
else {
map = Collections.emptyMap();
}
myRelPathsToEntries = new SoftReference<Map<String, EntryInfo>>(Collections.unmodifiableMap(map));
}
}
}
@@ -172,7 +159,13 @@ public class JarHandlerBase {
}
@NotNull
private static EntryInfo getOrCreate(JarFile.JarEntry entry, Map<String, EntryInfo> map, JarFile zip) {
private ZipFile getZipFile() throws IOException {
File mirror = getMirrorFile(getOriginalFile());
return ZipFileCache.acquire(mirror.getPath());
}
@NotNull
private static EntryInfo getOrCreate(ZipEntry entry, Map<String, EntryInfo> map, ZipFile zip) {
boolean isDirectory = entry.isDirectory();
String entryName = entry.getName();
if (StringUtil.endsWithChar(entryName, '/')) {
@@ -199,11 +192,11 @@ public class JarHandlerBase {
}
@NotNull
private static EntryInfo getOrCreate(String entryName, Map<String, EntryInfo> map, JarFile zip) {
private static EntryInfo getOrCreate(String entryName, Map<String, EntryInfo> map, ZipFile zip) {
EntryInfo info = map.get(entryName);
if (info == null) {
JarFile.JarEntry entry = zip.getEntry(entryName + "/");
ZipEntry entry = zip.getEntry(entryName + "/");
if (entry != null) {
return getOrCreate(entry, map, zip);
}
@@ -218,8 +211,7 @@ public class JarHandlerBase {
}
if (!info.isDirectory) {
//noinspection ConstantConditions
LOG.info(zip.getZipFile().getName() + ": " + entryName + " should be a directory");
LOG.info(zip.getName() + ": " + entryName + " should be a directory");
info = new EntryInfo(info.parent, info.shortName, true, info.length, info.timestamp);
map.put(entryName, info);
}
@@ -246,11 +238,9 @@ public class JarHandlerBase {
}
public boolean exists(@NotNull VirtualFile file) {
if (file.getParent() == null) {
return myJarFile.get() != null || getOriginalFile().exists();
}
return getEntryInfo(file) != null;
if (file.getParent() == null) return getOriginalFile().exists();
EntryInfo info = getEntryInfo(file);
return info != null;
}
@Nullable
@@ -271,11 +261,11 @@ public class JarHandlerBase {
@NotNull
public byte[] contentsToByteArray(@NotNull VirtualFile file) throws IOException {
JarFile jar = getJar();
if (jar != null) {
JarFile.JarEntry entry = jar.getEntry(getRelativePath(file));
ZipFile zip = getZipFile();
try {
ZipEntry entry = zip.getEntry(getRelativePath(file));
if (entry != null) {
InputStream stream = jar.getInputStream(entry);
InputStream stream = zip.getInputStream(entry);
if (stream != null) {
try {
return FileUtil.loadBytes(stream, (int)entry.getSize());
@@ -286,9 +276,13 @@ public class JarHandlerBase {
}
}
}
finally {
ZipFileCache.release(zip);
}
return ArrayUtil.EMPTY_BYTE_ARRAY;
}
@SuppressWarnings("deprecation")
private static class MyJarFile implements JarFile {
private static class MyJarEntry implements JarFile.JarEntry {
private final ZipEntry myEntry;
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
* Copyright 2000-2014 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.
@@ -39,7 +39,8 @@ public abstract class JarFileSystem extends NewVirtualFileSystem implements JarC
@Nullable
public abstract VirtualFile getVirtualFileForJar(@Nullable VirtualFile entryVFile);
/** @deprecated do not use (leaks file handles), to remove in IDEA 15 */
/** @deprecated to be removed in IDEA 15 */
@SuppressWarnings({"UnusedDeclaration", "deprecation"})
public abstract JarFile getJarFile(@NotNull VirtualFile entryVFile) throws IOException;
/**
@@ -215,20 +215,19 @@ public class FileContentQueue {
if (isValidFile(virtualFileToLoad)) {
try {
content.getBytes();
return content;
}
catch (IOException e) {
LOG.info(virtualFileToLoad + ": " + e);
}
catch (InvalidVirtualFileAccessException e) {
LOG.info(virtualFileToLoad + ": " + e);
}
catch (Throwable t) {
if (t instanceof IOException || t instanceof InvalidVirtualFileAccessException) {
LOG.info(t);
}
else {
LOG.error(t);
}
content.setEmptyContent();
LOG.error(virtualFileToLoad + ": " + t);
}
}
else {
content.setEmptyContent();
}
content.setEmptyContent();
return content;
}
@@ -22,6 +22,7 @@ import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileAttributes;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.ZipFileCache;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.newvfs.*;
@@ -39,7 +40,6 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -64,7 +64,7 @@ public class JarFileSystemImpl extends JarFileSystem implements ApplicationCompo
bus.connect().subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener.Adapter() {
@Override
public void after(@NotNull List<? extends VFileEvent> events) {
List<VirtualFile> rootsToRefresh = new ArrayList<VirtualFile>();
List<VirtualFile> rootsToRefresh = ContainerUtil.newSmartList();
for (VFileEvent event : events) {
if (event.getFileSystem() instanceof LocalFileSystem) {
@@ -91,6 +91,14 @@ public class JarFileSystemImpl extends JarFileSystem implements ApplicationCompo
}
if (!rootsToRefresh.isEmpty()) {
List<String> pathsToReset = ContainerUtil.newArrayListWithCapacity(rootsToRefresh.size());
for (VirtualFile root : rootsToRefresh) {
String rootPath = root.getPath();
String jarPath = rootPath.substring(0, rootPath.length() - 2);
pathsToReset.add(FileUtil.toSystemDependentName(jarPath));
}
ZipFileCache.reset(pathsToReset);
boolean async = !ApplicationManager.getApplication().isUnitTestMode();
RefreshQueue.getInstance().refresh(async, true, null, rootsToRefresh);
}
@@ -144,7 +152,9 @@ public class JarFileSystemImpl extends JarFileSystem implements ApplicationCompo
return StandardFileSystems.getVirtualFileForJar(entryVFile);
}
@SuppressWarnings("deprecation")
@Override
/** @deprecated to be removed in IDEA 15 */
public JarFile getJarFile(@NotNull VirtualFile entryVFile) throws IOException {
return getHandler(entryVFile).getJar();
}
@@ -0,0 +1,238 @@
/*
* Copyright 2000-2014 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.util.io;
import com.intellij.openapi.diagnostic.LogUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipFile;
/**
* <p>Utility class which tries to keep frequently requested .zip files open
* to avoid time loss on closing/reopening ZipFile instances.</p>
*
* <p>Clients obtain a file by calling {@link #acquire(String)}
* and indicate the loss of interest to it via {@link #release(ZipFile)}.
* Released files are closed after some period of time (about 30 seconds),
* unless requested again within the period.</p>
*
* <p>Since ZipFiles are read-only objects allowing concurrent access,
* a same instance may be returned to a different threads requesting a same path.
* A file may be closed only after being released by all applicants.</p>
*
* <p>The class does not expect .zip files on a disk to be changed,
* so it may return an outdated instance of ZipFile (reading from it
* may return inaccurate data or even cause an exceptions to happen).
* It's a clients' responsibility to keep a track of .zip files
* and call the {@link #reset(Collection)} method for a paths
* which are possibly changed. Reset paths are removed from the cache
* and are closed immediately after being released.</p>
*/
public class ZipFileCache {
private static final int PERIOD = 10000; // disposer schedule, ms
private static final int TIMEOUT = 30000; // released file close delay, ms
private static class CacheRecord {
private final String path;
private final ZipFile file;
private int count = 1;
private long released = 0;
private CacheRecord(@NotNull String path, @NotNull ZipFile file) throws IOException {
this.path = path;
this.file = file;
}
}
private static final Object ourLock = new Object();
private static final Map<String, CacheRecord> ourPathCache = ContainerUtil.newTroveMap(FileUtil.PATH_HASHING_STRATEGY);
private static final Map<ZipFile, CacheRecord> ourFileCache = ContainerUtil.newHashMap();
private static final Map<ZipFile, Integer> ourQueue = ContainerUtil.newHashMap();
static {
ConcurrencyUtil.newSingleScheduledThreadExecutor("ZipFileCache Dispose", Thread.MIN_PRIORITY).scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
List<ZipFile> toClose = getFilesToClose(0, System.currentTimeMillis() - TIMEOUT);
if (toClose != null) {
close(toClose);
}
}
}, PERIOD, PERIOD, TimeUnit.MILLISECONDS);
}
@NotNull
public static ZipFile acquire(@NotNull String path) throws IOException {
path = FileUtil.toCanonicalPath(path);
synchronized (ourLock) {
CacheRecord record = ourPathCache.get(path);
if (record != null) {
record.count++;
return record.file;
}
}
CacheRecord record;
ZipFile file = tryOpen(path);
synchronized (ourLock) {
record = ourPathCache.get(path);
if (record == null) {
record = new CacheRecord(path, file);
ourPathCache.put(path, record);
ourFileCache.put(file, record);
return file;
}
else {
record.count++;
}
}
close(file);
return record.file;
}
private static ZipFile tryOpen(String path) throws IOException {
path = FileUtil.toSystemDependentName(path);
debug("opening %s", path);
try {
return new ZipFile(path);
}
catch (IOException e) {
String reason = e.getMessage();
if ("too many open files".equalsIgnoreCase(reason) && tryCloseFiles() > 0) {
return new ZipFile(path);
}
else {
throw e;
}
}
}
private static int tryCloseFiles() {
List<ZipFile> toClose = getFilesToClose(5, 0);
if (toClose == null) return 0;
close(toClose);
logger().warn("too many open files, closed: " + toClose.size());
return toClose.size();
}
@Nullable
private static List<ZipFile> getFilesToClose(int limit, long timeout) {
List<ZipFile> toClose = null;
synchronized (ourLock) {
Iterator<CacheRecord> i = ourPathCache.values().iterator();
while (i.hasNext() && (limit == 0 || toClose == null || toClose.size() < limit)) {
CacheRecord record = i.next();
if (record.count <= 0 && (timeout == 0 || record.released <= timeout)) {
i.remove();
ourFileCache.remove(record.file);
if (toClose == null) toClose = ContainerUtil.newArrayList();
toClose.add(record.file);
}
}
}
return toClose;
}
public static void release(@NotNull ZipFile file) {
synchronized (ourLock) {
CacheRecord record = ourFileCache.get(file);
if (record != null) {
record.count--;
record.released = System.currentTimeMillis();
logger().assertTrue(record.count >= 0, record.path);
return;
}
Integer count = ourQueue.get(file);
if (count != null) {
count--;
if (count == 0) {
ourQueue.remove(file);
close(file);
}
else {
ourQueue.put(file, count);
}
return;
}
}
logger().warn(new IllegalArgumentException("stray file: " + file.getName()));
close(file);
}
public static void reset(@NotNull Collection<String> paths) {
debug("resetting %s", paths);
List<ZipFile> toClose = ContainerUtil.newSmartList();
synchronized (ourLock) {
for (String path : paths) {
CacheRecord record = ourPathCache.remove(path);
if (record != null) {
ourFileCache.remove(record.file);
if (record.count > 0) {
ourQueue.put(record.file, record.count);
}
else {
toClose.add(record.file);
}
}
}
}
close(toClose);
}
private static void close(@NotNull List<ZipFile> files) {
for (ZipFile file : files) {
close(file);
}
}
private static void close(@NotNull ZipFile file) {
debug("closing %s", file.getName());
try {
file.close();
}
catch (IOException e) {
logger().info(file.getName(), e);
}
}
private static Logger logger() {
return Logger.getInstance(ZipFileCache.class);
}
private static void debug(@NotNull String format, Object... args) {
LogUtil.debug(logger(), format, args);
}
}
@@ -17,14 +17,12 @@ package com.intellij.util.lang;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.TimedComputable;
import com.intellij.openapi.util.io.ZipFileCache;
import com.intellij.util.io.URLUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import sun.misc.Resource;
import java.io.*;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.Enumeration;
@@ -35,21 +33,8 @@ class JarLoader extends Loader {
private static final Logger LOG = Logger.getInstance(JarLoader.class);
private final URL myURL;
private SoftReference<JarMemoryLoader> myMemoryLoader;
private final boolean myCanLockJar;
private final TimedComputable<ZipFile> myZipFileRef = new TimedComputable<ZipFile>(null) {
@Override
@NotNull
protected ZipFile calc() {
try {
return doGetZipFile();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
};
private SoftReference<JarMemoryLoader> myMemoryLoader;
JarLoader(URL url, boolean canLockJar, int index) throws IOException {
super(new URL(URLUtil.JAR_PROTOCOL, "", -1, url + "!/"), index);
@@ -57,61 +42,44 @@ class JarLoader extends Loader {
myCanLockJar = canLockJar;
}
void preloadClasses() {
ZipFile zipFile = null;
try {
zipFile = acquireZipFile();
if (zipFile == null) return;
try {
File file = new File(zipFile.getName());
myMemoryLoader = new SoftReference<JarMemoryLoader>(JarMemoryLoader.load(file, getBaseURL()));
}
catch (Exception e) {
LOG.error(e);
}
}
catch (Exception e) {
// it happens :) eg tools.jar under MacOS
}
finally {
try {
releaseZipFile(zipFile);
}
catch (IOException ignore) {
}
}
}
@Nullable
private ZipFile acquireZipFile() throws IOException {
if (myCanLockJar) {
return myZipFileRef.acquire();
}
return doGetZipFile();
String path = FileUtil.unquote(myURL.getFile());
//noinspection IOResourceOpenedButNotSafelyClosed
return myCanLockJar ? ZipFileCache.acquire(path) : new ZipFile(path);
}
private void releaseZipFile(final ZipFile zipFile) throws IOException {
private void releaseZipFile(ZipFile zipFile) throws IOException {
if (myCanLockJar) {
myZipFileRef.release();
ZipFileCache.release(zipFile);
}
else if (zipFile != null) {
zipFile.close();
}
}
private ZipFile doGetZipFile() throws IOException {
return new ZipFile(FileUtil.unquote(myURL.getFile()));
void preloadClasses() {
try {
ZipFile zipFile = acquireZipFile();
try {
JarMemoryLoader loader = JarMemoryLoader.load(zipFile, getBaseURL());
if (loader != null) {
myMemoryLoader = new SoftReference<JarMemoryLoader>(loader);
}
}
finally {
releaseZipFile(zipFile);
}
}
catch (Exception e) {
LOG.error(e);
}
}
@Override
void buildCache(final ClasspathCache cache) throws IOException {
ZipFile zipFile = null;
void buildCache(ClasspathCache cache) throws IOException {
ZipFile zipFile = acquireZipFile();
try {
zipFile = acquireZipFile();
if (zipFile == null) return;
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
String name = zipEntry.getName();
@@ -133,101 +101,26 @@ class JarLoader extends Loader {
if (resource != null) return resource;
}
ZipFile file = null;
try {
file = acquireZipFile();
if (file == null) return null;
ZipEntry entry = file.getEntry(name);
if (entry != null) {
return new MyResource(entry, new URL(getBaseURL(), name));
ZipFile file = acquireZipFile();
try {
ZipEntry entry = file.getEntry(name);
if (entry != null) {
return MemoryResource.load(getBaseURL(), file, entry);
}
}
finally {
releaseZipFile(file);
}
}
catch (Exception e) {
return null;
}
finally {
try {
releaseZipFile(file);
}
catch (IOException ignored) { }
LOG.error(e);
}
return null;
}
private class MyResource extends Resource {
private final ZipEntry myEntry;
private final URL myUrl;
public MyResource(ZipEntry name, URL url) {
myEntry = name;
myUrl = url;
}
@Override
public String getName() {
return myEntry.getName();
}
@Override
public URL getURL() {
return myUrl;
}
@Override
public URL getCodeSourceURL() {
return myURL;
}
@Override
@Nullable
public InputStream getInputStream() throws IOException {
final boolean[] wasReleased = {false};
ZipFile file = null;
try {
file = acquireZipFile();
if (file == null) {
releaseZipFile(file);
return null;
}
final InputStream inputStream = file.getInputStream(myEntry);
if (inputStream == null) {
releaseZipFile(file);
return null; // if entry was not found
}
final ZipFile finalFile = file;
return new FilterInputStream(inputStream) {
private boolean myClosed = false;
@Override
public void close() throws IOException {
super.close();
if (!myClosed) {
releaseZipFile(finalFile);
}
myClosed = true;
wasReleased[0] = true;
}
};
}
catch (IOException e) {
e.printStackTrace();
releaseZipFile(file);
assert !wasReleased[0];
return null;
}
}
@Override
public int getContentLength() {
return (int)myEntry.getSize();
}
}
@NonNls
@Override
public String toString() {
return "JarLoader [" + myURL + "]";
}
@@ -16,21 +16,22 @@
package com.intellij.util.lang;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.io.UnsyncByteArrayInputStream;
import com.intellij.util.io.zip.ZipShort;
import gnu.trove.THashMap;
import org.jetbrains.annotations.Nullable;
import sun.misc.Resource;
import java.io.*;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipFile;
/**
* @author Dmitry Avdeev
* Date: 7/12/11
* @since 12/07/2011
*/
public class JarMemoryLoader {
public static final String SIZE_ENTRY = "META-INF/jb/$$size$$";
@@ -45,73 +46,32 @@ public class JarMemoryLoader {
@Nullable
public static JarMemoryLoader load(File file, URL baseUrl) throws IOException {
FileInputStream inputStream = new FileInputStream(file);
ZipFile zipFile = new ZipFile(file);
try {
return load(inputStream, baseUrl);
return load(zipFile, baseUrl);
}
finally {
inputStream.close();
zipFile.close();
}
}
@Nullable
public static JarMemoryLoader load(InputStream inputStream, URL baseUrl) throws IOException {
ZipInputStream zipStream = new ZipInputStream(inputStream);
try {
ZipEntry sizeEntry = zipStream.getNextEntry();
if (sizeEntry == null || !sizeEntry.getName().equals(SIZE_ENTRY)) return null;
byte[] bytes = FileUtil.loadBytes(zipStream, 2);
int size = ZipShort.getValue(bytes);
public static JarMemoryLoader load(ZipFile zipFile, URL baseUrl) throws IOException {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
if (!entries.hasMoreElements()) return null;
JarMemoryLoader loader = new JarMemoryLoader();
for (int i = 0; i < size; i++) {
ZipEntry entry = zipStream.getNextEntry();
if (entry == null) return loader;
byte[] content = FileUtil.loadBytes(zipStream, (int)entry.getSize());
MyResource resource = new MyResource(entry.getName(), new URL(baseUrl, entry.getName()), content);
loader.myResources.put(entry.getName(), resource);
}
return loader;
}
finally {
zipStream.close();
}
}
ZipEntry sizeEntry = entries.nextElement();
if (sizeEntry == null || !sizeEntry.getName().equals(SIZE_ENTRY)) return null;
private static class MyResource extends Resource {
private String myName;
private URL myUrl;
private final byte[] myContent;
byte[] bytes = FileUtil.loadBytes(zipFile.getInputStream(sizeEntry), 2);
int size = ZipShort.getValue(bytes);
public MyResource(String name, URL url, byte[] content) {
myName = name;
myUrl = url;
myContent = content;
}
@Override
public String getName() {
return myName;
}
@Override
public URL getURL() {
return myUrl;
}
@Override
public URL getCodeSourceURL() {
return myUrl;
}
@Override
public InputStream getInputStream() throws IOException {
return new UnsyncByteArrayInputStream(myContent);
}
@Override
public int getContentLength() throws IOException {
return myContent.length;
JarMemoryLoader loader = new JarMemoryLoader();
for (int i = 0; i < size; i++) {
ZipEntry entry = entries.nextElement();
MemoryResource resource = MemoryResource.load(baseUrl, zipFile, entry);
loader.myResources.put(entry.getName(), resource);
}
return loader;
}
}
@@ -0,0 +1,84 @@
/*
* Copyright 2000-2014 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.util.lang;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.io.UnsyncByteArrayInputStream;
import org.jetbrains.annotations.NotNull;
import sun.misc.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
class MemoryResource extends Resource {
private String myName;
private URL myUrl;
private final byte[] myContent;
public MemoryResource(String name, URL url, byte[] content) {
myName = name;
myUrl = url;
myContent = content;
}
@Override
public String getName() {
return myName;
}
@Override
public URL getURL() {
return myUrl;
}
@Override
public URL getCodeSourceURL() {
return myUrl;
}
@Override
public InputStream getInputStream() throws IOException {
return new UnsyncByteArrayInputStream(myContent);
}
@Override
public int getContentLength() throws IOException {
return myContent.length;
}
@NotNull
public static MemoryResource load(URL baseUrl, @NotNull ZipFile zipFile, @NotNull ZipEntry entry) throws IOException {
String name = entry.getName();
URL url = new URL(baseUrl, name);
byte[] content = ArrayUtil.EMPTY_BYTE_ARRAY;
InputStream stream = zipFile.getInputStream(entry);
if (stream != null) {
try {
content = FileUtil.loadBytes(stream, (int)entry.getSize());
}
finally {
stream.close();
}
}
return new MemoryResource(name, url, content);
}
}