diff --git a/java/openapi/src/com/intellij/openapi/roots/libraries/JarVersionDetectionUtil.java b/java/openapi/src/com/intellij/openapi/roots/libraries/JarVersionDetectionUtil.java index 903ad9168dd5..04c21395d4df 100644 --- a/java/openapi/src/com/intellij/openapi/roots/libraries/JarVersionDetectionUtil.java +++ b/java/openapi/src/com/intellij/openapi/roots/libraries/JarVersionDetectionUtil.java @@ -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; diff --git a/platform/core-api/src/com/intellij/openapi/vfs/JarFile.java b/platform/core-api/src/com/intellij/openapi/vfs/JarFile.java index be4ec5be6e23..c363a815577a 100644 --- a/platform/core-api/src/com/intellij/openapi/vfs/JarFile.java +++ b/platform/core-api/src/com/intellij/openapi/vfs/JarFile.java @@ -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(); diff --git a/platform/core-impl/src/com/intellij/openapi/vfs/impl/jar/JarHandlerBase.java b/platform/core-impl/src/com/intellij/openapi/vfs/impl/jar/JarHandlerBase.java index 06f8391e10b4..d5f8610f3567 100644 --- a/platform/core-impl/src/com/intellij/openapi/vfs/impl/jar/JarHandlerBase.java +++ b/platform/core-impl/src/com/intellij/openapi/vfs/impl/jar/JarHandlerBase.java @@ -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 myJarFile = new TimedReference(null); + private final Object myLock = new Object(); private volatile Reference> myRelPathsToEntries = new SoftReference>(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(); map.put("", new EntryInfo(null, "", true, DEFAULT_LENGTH, DEFAULT_TIMESTAMP)); - Enumeration 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 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>(Collections.unmodifiableMap(map)); - } - else { - map = Collections.emptyMap(); - } + myRelPathsToEntries = new SoftReference>(Collections.unmodifiableMap(map)); } } } @@ -172,7 +159,13 @@ public class JarHandlerBase { } @NotNull - private static EntryInfo getOrCreate(JarFile.JarEntry entry, Map 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 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 map, JarFile zip) { + private static EntryInfo getOrCreate(String entryName, Map 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; diff --git a/platform/platform-api/src/com/intellij/openapi/vfs/JarFileSystem.java b/platform/platform-api/src/com/intellij/openapi/vfs/JarFileSystem.java index a94b4420f252..b4c0e77ec10f 100644 --- a/platform/platform-api/src/com/intellij/openapi/vfs/JarFileSystem.java +++ b/platform/platform-api/src/com/intellij/openapi/vfs/JarFileSystem.java @@ -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; /** diff --git a/platform/platform-impl/src/com/intellij/openapi/project/FileContentQueue.java b/platform/platform-impl/src/com/intellij/openapi/project/FileContentQueue.java index 313630083c1f..bf0027c675ad 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/FileContentQueue.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/FileContentQueue.java @@ -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; } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java index 9ad72c75dc9a..e86ea895cd9b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java @@ -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 events) { - List rootsToRefresh = new ArrayList(); + List 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 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(); } diff --git a/platform/util/src/com/intellij/openapi/util/io/ZipFileCache.java b/platform/util/src/com/intellij/openapi/util/io/ZipFileCache.java new file mode 100644 index 000000000000..392a82a48c70 --- /dev/null +++ b/platform/util/src/com/intellij/openapi/util/io/ZipFileCache.java @@ -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; + +/** + *

Utility class which tries to keep frequently requested .zip files open + * to avoid time loss on closing/reopening ZipFile instances.

+ * + *

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.

+ * + *

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.

+ * + *

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.

+ */ +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 ourPathCache = ContainerUtil.newTroveMap(FileUtil.PATH_HASHING_STRATEGY); + private static final Map ourFileCache = ContainerUtil.newHashMap(); + private static final Map ourQueue = ContainerUtil.newHashMap(); + + static { + ConcurrencyUtil.newSingleScheduledThreadExecutor("ZipFileCache Dispose", Thread.MIN_PRIORITY).scheduleWithFixedDelay(new Runnable() { + @Override + public void run() { + List 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 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 getFilesToClose(int limit, long timeout) { + List toClose = null; + + synchronized (ourLock) { + Iterator 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 paths) { + debug("resetting %s", paths); + + List 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 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); + } +} diff --git a/platform/util/src/com/intellij/util/lang/JarLoader.java b/platform/util/src/com/intellij/util/lang/JarLoader.java index 6dcbef212b4c..a088275a7913 100644 --- a/platform/util/src/com/intellij/util/lang/JarLoader.java +++ b/platform/util/src/com/intellij/util/lang/JarLoader.java @@ -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 myMemoryLoader; private final boolean myCanLockJar; - - private final TimedComputable myZipFileRef = new TimedComputable(null) { - @Override - @NotNull - protected ZipFile calc() { - try { - return doGetZipFile(); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - }; + private SoftReference 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.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(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 entries = zipFile.entries(); + Enumeration 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 + "]"; } diff --git a/platform/util/src/com/intellij/util/lang/JarMemoryLoader.java b/platform/util/src/com/intellij/util/lang/JarMemoryLoader.java index f5761e69ad50..e29a37127159 100644 --- a/platform/util/src/com/intellij/util/lang/JarMemoryLoader.java +++ b/platform/util/src/com/intellij/util/lang/JarMemoryLoader.java @@ -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 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; } } diff --git a/platform/util/src/com/intellij/util/lang/MemoryResource.java b/platform/util/src/com/intellij/util/lang/MemoryResource.java new file mode 100644 index 000000000000..6d658a73819b --- /dev/null +++ b/platform/util/src/com/intellij/util/lang/MemoryResource.java @@ -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); + } +}