From a27e3419bd3a041c653c2a947910aa6ef9fc4c15 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Tue, 18 Jun 2019 08:18:43 -0500 Subject: Bug 548394 - Extract useful functions of ZipBundleFile to an abstract class Change-Id: Iba32ba31f3f80f6db2ab33d959c54deeb8156dee Signed-off-by: Thomas Watson --- .../storage/bundlefile/CloseableBundleFile.java | 548 +++++++++++++++++++++ .../osgi/storage/bundlefile/ZipBundleFile.java | 507 ++----------------- 2 files changed, 590 insertions(+), 465 deletions(-) create mode 100644 bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/CloseableBundleFile.java diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/CloseableBundleFile.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/CloseableBundleFile.java new file mode 100644 index 000000000..a06223c46 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/CloseableBundleFile.java @@ -0,0 +1,548 @@ +/******************************************************************************* + * Copyright (c) 2005, 2018 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + * Rob Harrop - SpringSource Inc. (bug 253942) + *******************************************************************************/ + +package org.eclipse.osgi.storage.bundlefile; + +import java.io.File; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +import org.eclipse.osgi.container.ModuleContainerAdaptor.ContainerEvent; +import org.eclipse.osgi.container.ModuleRevision; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.internal.debug.Debug; +import org.eclipse.osgi.internal.framework.EquinoxContainer; +import org.eclipse.osgi.internal.messages.Msg; +import org.eclipse.osgi.storage.BundleInfo; +import org.eclipse.osgi.storage.Storage.StorageException; +import org.eclipse.osgi.util.NLS; + +/** + * A BundleFile that manages the number of open bundle files by using the MRUBundleFileList + * @param a type specified by extending classes to call {@link #getInputStream(Object)} + */ +public abstract class CloseableBundleFile extends BundleFile { + + // A reentrant lock is used here (instead of intrinsic synchronization) + // to allow the lock conditional held + // see lockOpen() and open(boolean) + private final ReentrantLock openLock = new ReentrantLock(); + private final Condition refCondition = openLock.newCondition(); + + private final MRUBundleFileList mruList; + + protected final BundleInfo.Generation generation; + + protected final Debug debug; + + /** + * The closed flag + */ + private volatile boolean closed = true; + + private int referenceCount = 0; + + public CloseableBundleFile(File basefile, BundleInfo.Generation generation, MRUBundleFileList mruList, Debug debug) { + super(basefile); + this.debug = debug; + this.generation = generation; + this.closed = true; + this.mruList = mruList; + } + + /** + * Checks if the bundle file is open + * @return true if the bundle file is open and locked + */ + private boolean lockOpen() { + try { + open(true); + return true; + } catch (IOException e) { + if (generation != null) { + ModuleRevision r = generation.getRevision(); + if (r != null) { + ContainerEvent eventType = ContainerEvent.ERROR; + // If the revision has been removed from the list of revisions then it has been deleted + // because the bundle has been uninstalled or updated + if (!r.getRevisions().getModuleRevisions().contains(r)) { + // instead of filling the log with errors about missing files from + // uninstalled/updated bundles just give it an info level + eventType = ContainerEvent.INFO; + } + generation.getBundleInfo().getStorage().getAdaptor().publishContainerEvent(eventType, r.getRevisions().getModule(), e); + } + } + // TODO not sure if throwing a runtime exception is better + // throw new RuntimeException("Failed to open bundle file.", e); + return false; + } + } + + /** + * Opens this bundle file. + * @param keepLock true if the open lock should be retained + * @throws IOException + */ + private void open(boolean keepLock) throws IOException { + openLock.lock(); + try { + if (closed) { + boolean needBackPressure = mruListAdd(); + if (needBackPressure) { + // release lock before applying back pressure + openLock.unlock(); + try { + mruListApplyBackPressure(); + } finally { + // get lock back after back pressure + openLock.lock(); + } + } + // check close again after getting open lock again + if (closed) { + // always add again if back pressure was applied in case + // the bundle file got removed while releasing the open lock + if (needBackPressure) { + mruListAdd(); + } + // This can throw an IO exception resulting in closed remaining true on exit + doOpen(); + closed = false; + } + } else { + mruListUse(); + } + } finally { + if (!keepLock || closed) { + openLock.unlock(); + } + } + } + + /** + * Opens the bundle file + * @throws IOException if an error occurs + */ + protected abstract void doOpen() throws IOException; + + /** + * Extracts a directory and all sub content to disk + * @param dirName the directory name to extract + * @return the File used to extract the content to. A value + * of null is returned if the directory to extract does + * not exist or if content extraction is not supported. + */ + File extractDirectory(String dirName) { + if (!lockOpen()) { + return null; + } + try { + for (String path : getPaths()) { + if (path.startsWith(dirName) && !path.endsWith("/")) //$NON-NLS-1$ + getFile(path, false); + } + return getExtractFile(dirName); + } finally { + openLock.unlock(); + } + } + + protected abstract Iterable getPaths(); + + private File getExtractFile(String entryName) { + if (generation == null) + return null; + return generation.getExtractFile(".cp", entryName); //$NON-NLS-1$ + } + + @Override + public File getFile(String entry, boolean nativeCode) { + if (!lockOpen()) { + return null; + } + try { + BundleEntry bEntry = getEntry(entry); + if (bEntry == null) + return null; + + try { + File nested = getExtractFile(bEntry.getName()); + if (nested != null) { + if (nested.exists()) { + /* the entry is already cached */ + if (debug.DEBUG_BUNDLE_FILE) + Debug.println("File already present: " + nested.getPath()); //$NON-NLS-1$ + if (nested.isDirectory()) + // must ensure the complete directory is extracted (bug 182585) + extractDirectory(bEntry.getName()); + } else { + if (bEntry.getName().endsWith("/")) { //$NON-NLS-1$ + nested.mkdirs(); + if (!nested.isDirectory()) { + if (debug.DEBUG_BUNDLE_FILE) + Debug.println("Unable to create directory: " + nested.getPath()); //$NON-NLS-1$ + throw new IOException(NLS.bind(Msg.ADAPTOR_DIRECTORY_CREATE_EXCEPTION, nested.getAbsolutePath())); + } + extractDirectory(bEntry.getName()); + } else { + InputStream in = bEntry.getInputStream(); + if (in == null) + return null; + generation.storeContent(nested, in, nativeCode); + } + } + + return nested; + } + } catch (IOException | StorageException e) { + if (debug.DEBUG_BUNDLE_FILE) + Debug.printStackTrace(e); + generation.getBundleInfo().getStorage().getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, "Unable to extract content: " + generation.getRevision() + ": " + entry, e); //$NON-NLS-1$ //$NON-NLS-2$ + } + } finally { + openLock.unlock(); + } + return null; + } + + @Override + public boolean containsDir(String dir) { + if (!lockOpen()) { + return false; + } + try { + if (dir == null) + return false; + + if (dir.length() == 0) + return true; + + if (dir.charAt(0) == '/') { + if (dir.length() == 1) + return true; + dir = dir.substring(1); + } + + if (dir.length() > 0 && dir.charAt(dir.length() - 1) != '/') + dir = dir + '/'; + + for (String entry : getPaths()) { + if (entry.startsWith(dir)) { + return true; + } + } + } finally { + openLock.unlock(); + } + return false; + } + + @Override + public BundleEntry getEntry(String path) { + if (!lockOpen()) { + return null; + } + try { + return findEntry(path); + } finally { + openLock.unlock(); + } + } + + /** + * Finds the bundle entry for the specified path + * @param path the path of the entry to find + * @return the entry or {@code null} if no entry exists + */ + protected abstract BundleEntry findEntry(String path); + + @Override + public Enumeration getEntryPaths(String path, boolean recurse) { + if (!lockOpen()) { + return null; + } + try { + if (path == null) + throw new NullPointerException(); + + // Strip any leading '/' off of path. + if (path.length() > 0 && path.charAt(0) == '/') + path = path.substring(1); + // Append a '/', if not already there, to path if not an empty string. + if (path.length() > 0 && path.charAt(path.length() - 1) != '/') + path = new StringBuilder(path).append("/").toString(); //$NON-NLS-1$ + + LinkedHashSet result = new LinkedHashSet<>(); + // Get all entries and add the ones of interest. + for (String entryPath : getPaths()) { + // Is the entry of possible interest? Note that + // string.startsWith("") == true. + if (entryPath.startsWith(path)) { + // If we get here, we know that the entry is either (1) equal to + // path, (2) a file under path, or (3) a subdirectory of path. + if (path.length() < entryPath.length()) { + // If we get here, we know that entry is not equal to path. + getEntryPaths(path, entryPath.substring(path.length()), recurse, result); + } + } + } + return result.size() == 0 ? null : Collections.enumeration(result); + } finally { + openLock.unlock(); + } + } + + private void getEntryPaths(String path, String entry, boolean recurse, LinkedHashSet entries) { + if (entry.length() == 0) + return; + int slash = entry.indexOf('/'); + if (slash == -1) + entries.add(path + entry); + else { + path = path + entry.substring(0, slash + 1); + entries.add(path); + if (recurse) + getEntryPaths(path, entry.substring(slash + 1), true, entries); + } + } + + @Override + public void close() throws IOException { + openLock.lock(); + try { + if (!closed) { + if (referenceCount > 0 && isMruListClosing()) { + // there are some opened streams to this BundleFile still; + // wait for them all to close because this is being closed by the MRUBundleFileList + try { + refCondition.await(1000, TimeUnit.MICROSECONDS); // timeout after 1 second + } catch (InterruptedException e) { + // do nothing for now ... + } + if (referenceCount != 0 || closed) + // either another thread closed the bundle file or we timed waiting for all the reference inputstreams to close + // If the referenceCount did not reach zero then this bundle file will remain open until the + // bundle file is closed explicitly (i.e. bundle is updated/uninstalled or framework is shutdown) + return; + + } + closed = true; + doClose(); + mruListRemove(); + postClose(); + } + } finally { + openLock.unlock(); + } + } + + /** + * Closes the bundle file + * @throws IOException if an error occurs closing + */ + protected abstract void doClose() throws IOException; + + /** + * Called after closing the bundle file. + */ + protected abstract void postClose(); + + private boolean isMruListClosing() { + return this.mruList != null && this.mruList.isClosing(this); + } + + private boolean isMruEnabled() { + return this.mruList != null && this.mruList.isEnabled(); + } + + private void mruListRemove() { + if (this.mruList != null) { + this.mruList.remove(this); + } + } + + private void mruListUse() { + if (this.mruList != null) { + mruList.use(this); + } + } + + private void mruListApplyBackPressure() { + if (this.mruList != null) { + this.mruList.applyBackpressure(); + } + } + + private boolean mruListAdd() { + if (this.mruList != null) { + return mruList.add(this); + } + return false; + } + + @Override + public void open() throws IOException { + open(false); + } + + void incrementReference() { + openLock.lock(); + try { + referenceCount += 1; + } finally { + openLock.unlock(); + } + } + + void decrementReference() { + openLock.lock(); + try { + referenceCount = Math.max(0, referenceCount - 1); + // only notify if the referenceCount is zero. + if (referenceCount == 0) + refCondition.signal(); + } finally { + openLock.unlock(); + } + } + + /** + * Gets the input stream for the specified entry. + * This method will ensure the bundle file is open, + * call {@link #doGetInputStream(Object)} to get the + * actual input stream, then if the bundle file limit + * is enabled it will wrapper the input stream in a + * special input stream that keeps track of active + * input streams to prevent the bundle file from being + * closed until the stream is closed (or a timeout happens). + * @param entry the entry to get the input stream for + * @return the input stream for the entry + * @throws IOException + */ + public InputStream getInputStream(E entry) throws IOException { + if (!lockOpen()) { + throw new IOException("Failed to lock bundle file."); //$NON-NLS-1$ + } + try { + InputStream in = doGetInputStream(entry); + if (isMruEnabled()) { + in = new BundleEntryInputStream(in); + } + return in; + } finally { + openLock.unlock(); + } + } + + /** + * Gets the input stream for the specified entry. + * @param entry the entry to get the input stream for. The type is specified by the + * extending class. + * @return the input steam for the entry + * @throws IOException if an error occurs + */ + protected abstract InputStream doGetInputStream(E entry) throws IOException; + + private class BundleEntryInputStream extends FilterInputStream { + + private boolean streamClosed = false; + + public BundleEntryInputStream(InputStream stream) { + super(stream); + incrementReference(); + } + + @Override + public int available() throws IOException { + try { + return super.available(); + } catch (IOException e) { + throw enrichExceptionWithBaseFile(e); + } + } + + @Override + public void close() throws IOException { + try { + super.close(); + } catch (IOException e) { + throw enrichExceptionWithBaseFile(e); + } finally { + synchronized (this) { + if (streamClosed) + return; + streamClosed = true; + } + decrementReference(); + } + } + + @Override + public int read() throws IOException { + try { + return super.read(); + } catch (IOException e) { + throw enrichExceptionWithBaseFile(e); + } + } + + @Override + public int read(byte[] var0, int var1, int var2) throws IOException { + try { + return super.read(var0, var1, var2); + } catch (IOException e) { + throw enrichExceptionWithBaseFile(e); + } + } + + @Override + public int read(byte[] var0) throws IOException { + try { + return super.read(var0); + } catch (IOException e) { + throw enrichExceptionWithBaseFile(e); + } + } + + @Override + public void reset() throws IOException { + try { + super.reset(); + } catch (IOException e) { + throw enrichExceptionWithBaseFile(e); + } + } + + @Override + public long skip(long var0) throws IOException { + try { + return super.skip(var0); + } catch (IOException e) { + throw enrichExceptionWithBaseFile(e); + } + } + + private IOException enrichExceptionWithBaseFile(IOException e) { + return new IOException(getBaseFile().toString(), e); + } + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/ZipBundleFile.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/ZipBundleFile.java index c1cb4d84d..d20689927 100644 --- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/ZipBundleFile.java +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/ZipBundleFile.java @@ -16,135 +16,36 @@ package org.eclipse.osgi.storage.bundlefile; import java.io.File; -import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; -import java.util.Collections; import java.util.Enumeration; -import java.util.LinkedHashSet; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.ReentrantLock; +import java.util.Iterator; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import org.eclipse.osgi.container.ModuleContainerAdaptor.ContainerEvent; -import org.eclipse.osgi.container.ModuleRevision; -import org.eclipse.osgi.framework.log.FrameworkLogEntry; import org.eclipse.osgi.internal.debug.Debug; -import org.eclipse.osgi.internal.framework.EquinoxContainer; import org.eclipse.osgi.internal.messages.Msg; import org.eclipse.osgi.storage.BundleInfo; -import org.eclipse.osgi.storage.Storage.StorageException; import org.eclipse.osgi.util.NLS; /** * A BundleFile that uses a ZipFile as it base file. */ -public class ZipBundleFile extends BundleFile { +public class ZipBundleFile extends CloseableBundleFile { - // A reentrant lock is used here (instead of intrinsic synchronization) - // to allow the lock conditional held - // see lockOpen() and getZipFile() - private final ReentrantLock openLock = new ReentrantLock(); - private final Condition refCondition = openLock.newCondition(); - - private final MRUBundleFileList mruList; - - private final BundleInfo.Generation generation; - - private final Debug debug; /** * The zip file */ - private volatile ZipFile zipFile; - /** - * The closed flag - */ - private volatile boolean closed = true; - - private int referenceCount = 0; + volatile ZipFile zipFile; public ZipBundleFile(File basefile, BundleInfo.Generation generation, MRUBundleFileList mruList, Debug debug) throws IOException { - super(basefile); + super(basefile, generation, mruList, debug); if (!BundleFile.secureAction.exists(basefile)) throw new IOException(NLS.bind(Msg.ADAPTER_FILEEXIST_EXCEPTION, basefile)); - this.debug = debug; - this.generation = generation; - this.closed = true; - this.mruList = mruList; - } - - /** - * Checks if the zip file is open - * @return true if the zip file is open - */ - private boolean lockOpen() { - try { - return getZipFile(true) != null; - } catch (IOException e) { - if (generation != null) { - ModuleRevision r = generation.getRevision(); - if (r != null) { - ContainerEvent eventType = ContainerEvent.ERROR; - // If the revision has been removed from the list of revisions then it has been deleted - // because the bundle has been uninstalled or updated - if (!r.getRevisions().getModuleRevisions().contains(r)) { - // instead of filling the log with errors about missing files from - // uninstalled/updated bundles just give it an info level - eventType = ContainerEvent.INFO; - } - generation.getBundleInfo().getStorage().getAdaptor().publishContainerEvent(eventType, r.getRevisions().getModule(), e); - } - } - // TODO not sure if throwing a runtime exception is better - // throw new RuntimeException("Failed to open bundle file.", e); - return false; - } } - /** - * Returns an open ZipFile for this bundle file. If an open - * ZipFile does not exist then a new one is created and - * returned. - * @param keepLock true if the open zip lock should be retained - * @return an open ZipFile for this bundle - * @throws IOException - */ - private ZipFile getZipFile(boolean keepLock) throws IOException { - openLock.lock(); - try { - if (closed) { - boolean needBackPressure = mruListAdd(); - if (needBackPressure) { - // release lock before applying back pressure - openLock.unlock(); - try { - mruListApplyBackPressure(); - } finally { - // get lock back after back pressure - openLock.lock(); - } - } - // check close again after getting open lock again - if (closed) { - // always add again if back pressure was applied in case - // the bundle file got removed while releasing the open lock - if (needBackPressure) { - mruListAdd(); - } - // This can throw an IO exception resulting in closed remaining true on exit - zipFile = BundleFile.secureAction.getZipFile(this.basefile); - closed = false; - } - } else { - mruListUse(); - } - return zipFile; - } finally { - if (!keepLock || closed) { - openLock.unlock(); - } - } + @Override + protected void doOpen() throws IOException { + zipFile = BundleFile.secureAction.getZipFile(this.basefile); } /** @@ -167,383 +68,59 @@ public class ZipBundleFile extends BundleFile { return entry; } - /** - * Extracts a directory and all sub content to disk - * @param dirName the directory name to extract - * @return the File used to extract the content to. A value - * of null is returned if the directory to extract does - * not exist or if content extraction is not supported. - */ - File extractDirectory(String dirName) { - if (!lockOpen()) { - return null; - } - try { - Enumeration entries = zipFile.entries(); - while (entries.hasMoreElements()) { - String entryPath = entries.nextElement().getName(); - if (entryPath.startsWith(dirName) && !entryPath.endsWith("/")) //$NON-NLS-1$ - getFile(entryPath, false); - } - return getExtractFile(dirName); - } finally { - openLock.unlock(); - } - } - - private File getExtractFile(String entryName) { - if (generation == null) - return null; - return generation.getExtractFile(".cp", entryName); //$NON-NLS-1$ - } - @Override - public File getFile(String entry, boolean nativeCode) { - if (!lockOpen()) { - return null; - } - try { - ZipEntry zipEntry = getZipEntry(entry); - if (zipEntry == null) - return null; - - try { - File nested = getExtractFile(zipEntry.getName()); - if (nested != null) { - if (nested.exists()) { - /* the entry is already cached */ - if (debug.DEBUG_BUNDLE_FILE) - Debug.println("File already present: " + nested.getPath()); //$NON-NLS-1$ - if (nested.isDirectory()) - // must ensure the complete directory is extracted (bug 182585) - extractDirectory(zipEntry.getName()); - } else { - if (zipEntry.getName().endsWith("/")) { //$NON-NLS-1$ - nested.mkdirs(); - if (!nested.isDirectory()) { - if (debug.DEBUG_BUNDLE_FILE) - Debug.println("Unable to create directory: " + nested.getPath()); //$NON-NLS-1$ - throw new IOException(NLS.bind(Msg.ADAPTOR_DIRECTORY_CREATE_EXCEPTION, nested.getAbsolutePath())); - } - extractDirectory(zipEntry.getName()); - } else { - InputStream in = zipFile.getInputStream(zipEntry); - if (in == null) - return null; - generation.storeContent(nested, in, nativeCode); - } - } - - return nested; - } - } catch (IOException | StorageException e) { - if (debug.DEBUG_BUNDLE_FILE) - Debug.printStackTrace(e); - generation.getBundleInfo().getStorage().getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, "Unable to extract content: " + generation.getRevision() + ": " + entry, e); //$NON-NLS-1$ //$NON-NLS-2$ + protected BundleEntry findEntry(String path) { + ZipEntry zipEntry = getZipEntry(path); + if (zipEntry == null) { + if (path.length() == 0 || path.charAt(path.length() - 1) == '/') { + // this is a directory request lets see if any entries exist in this directory + if (containsDir(path)) + return new DirZipBundleEntry(this, path); } - } finally { - openLock.unlock(); + return null; } - return null; + return new ZipBundleEntry(zipEntry, this); } @Override - public boolean containsDir(String dir) { - if (!lockOpen()) { - return false; - } - try { - if (dir == null) - return false; - - if (dir.length() == 0) - return true; - - if (dir.charAt(0) == '/') { - if (dir.length() == 1) - return true; - dir = dir.substring(1); - } - - if (dir.length() > 0 && dir.charAt(dir.length() - 1) != '/') - dir = dir + '/'; - - Enumeration entries = zipFile.entries(); - ZipEntry zipEntry; - String entryPath; - while (entries.hasMoreElements()) { - zipEntry = entries.nextElement(); - entryPath = zipEntry.getName(); - if (entryPath.startsWith(dir)) { - return true; - } - } - } finally { - openLock.unlock(); - } - return false; + protected void doClose() throws IOException { + zipFile.close(); } @Override - public BundleEntry getEntry(String path) { - if (!lockOpen()) { - return null; - } - try { - ZipEntry zipEntry = getZipEntry(path); - if (zipEntry == null) { - if (path.length() == 0 || path.charAt(path.length() - 1) == '/') { - // this is a directory request lets see if any entries exist in this directory - if (containsDir(path)) - return new DirZipBundleEntry(this, path); - } - return null; - } - - return new ZipBundleEntry(zipEntry, this); - } finally { - openLock.unlock(); - } + protected void postClose() { + zipFile = null; } @Override - public Enumeration getEntryPaths(String path, boolean recurse) { - if (!lockOpen()) { - return null; - } - try { - if (path == null) - throw new NullPointerException(); - - // Strip any leading '/' off of path. - if (path.length() > 0 && path.charAt(0) == '/') - path = path.substring(1); - // Append a '/', if not already there, to path if not an empty string. - if (path.length() > 0 && path.charAt(path.length() - 1) != '/') - path = new StringBuilder(path).append("/").toString(); //$NON-NLS-1$ - - LinkedHashSet result = new LinkedHashSet<>(); - // Get all zip file entries and add the ones of interest. - Enumeration entries = zipFile.entries(); - while (entries.hasMoreElements()) { - ZipEntry zipEntry = entries.nextElement(); - String entryPath = zipEntry.getName(); - // Is the entry of possible interest? Note that - // string.startsWith("") == true. - if (entryPath.startsWith(path)) { - // If we get here, we know that the entry is either (1) equal to - // path, (2) a file under path, or (3) a subdirectory of path. - if (path.length() < entryPath.length()) { - // If we get here, we know that entry is not equal to path. - getEntryPaths(path, entryPath.substring(path.length()), recurse, result); - } - } - } - return result.size() == 0 ? null : Collections.enumeration(result); - } finally { - openLock.unlock(); - } - } - - private void getEntryPaths(String path, String entry, boolean recurse, LinkedHashSet entries) { - if (entry.length() == 0) - return; - int slash = entry.indexOf('/'); - if (slash == -1) - entries.add(path + entry); - else { - path = path + entry.substring(0, slash + 1); - entries.add(path); - if (recurse) - getEntryPaths(path, entry.substring(slash + 1), true, entries); - } + protected InputStream doGetInputStream(ZipEntry entry) throws IOException { + return zipFile.getInputStream(entry); } @Override - public void close() throws IOException { - openLock.lock(); - try { - if (!closed) { - if (referenceCount > 0 && isMruListClosing()) { - // there are some opened streams to this BundleFile still; - // wait for them all to close because this is being closed by the MRUBundleFileList - try { - refCondition.await(1000, TimeUnit.MICROSECONDS); // timeout after 1 second - } catch (InterruptedException e) { - // do nothing for now ... + protected Iterable getPaths() { + return new Iterable() { + @Override + public Iterator iterator() { + final Enumeration entries = zipFile.entries(); + return new Iterator() { + @Override + public boolean hasNext() { + return entries.hasMoreElements(); } - if (referenceCount != 0 || closed) - // either another thread closed the bundle file or we timed waiting for all the reference inputstreams to close - // If the referenceCount did not reach zero then this bundle file will remain open until the - // bundle file is closed explicitly (i.e. bundle is updated/uninstalled or framework is shutdown) - return; - } - closed = true; - zipFile.close(); - mruListRemove(); - zipFile = null; - } - } finally { - openLock.unlock(); - } - } - - private boolean isMruListClosing() { - return this.mruList != null && this.mruList.isClosing(this); - } - - private boolean isMruEnabled() { - return this.mruList != null && this.mruList.isEnabled(); - } - - private void mruListRemove() { - if (this.mruList != null) { - this.mruList.remove(this); - } - } - - private void mruListUse() { - if (this.mruList != null) { - mruList.use(this); - } - } - - private void mruListApplyBackPressure() { - if (this.mruList != null) { - this.mruList.applyBackpressure(); - } - } - - private boolean mruListAdd() { - if (this.mruList != null) { - return mruList.add(this); - } - return false; - } - - @Override - public void open() throws IOException { - getZipFile(false); - } - - void incrementReference() { - openLock.lock(); - try { - referenceCount += 1; - } finally { - openLock.unlock(); - } - } - - void decrementReference() { - openLock.lock(); - try { - referenceCount = Math.max(0, referenceCount - 1); - // only notify if the referenceCount is zero. - if (referenceCount == 0) - refCondition.signal(); - } finally { - openLock.unlock(); - } - } - - InputStream getInputStream(ZipEntry entry) throws IOException { - if (!lockOpen()) { - throw new IOException("Failed to open zip file."); //$NON-NLS-1$ - } - try { - InputStream zipStream = zipFile.getInputStream(entry); - if (isMruEnabled()) { - zipStream = new ZipBundleEntryInputStream(zipStream); - } - return zipStream; - } finally { - openLock.unlock(); - } - } - - private class ZipBundleEntryInputStream extends FilterInputStream { - - private boolean streamClosed = false; - - public ZipBundleEntryInputStream(InputStream stream) { - super(stream); - incrementReference(); - } - - @Override - public int available() throws IOException { - try { - return super.available(); - } catch (IOException e) { - throw enrichExceptionWithBaseFile(e); - } - } - - @Override - public void close() throws IOException { - try { - super.close(); - } catch (IOException e) { - throw enrichExceptionWithBaseFile(e); - } finally { - synchronized (this) { - if (streamClosed) - return; - streamClosed = true; - } - decrementReference(); - } - } - - @Override - public int read() throws IOException { - try { - return super.read(); - } catch (IOException e) { - throw enrichExceptionWithBaseFile(e); - } - } - - @Override - public int read(byte[] var0, int var1, int var2) throws IOException { - try { - return super.read(var0, var1, var2); - } catch (IOException e) { - throw enrichExceptionWithBaseFile(e); - } - } - - @Override - public int read(byte[] var0) throws IOException { - try { - return super.read(var0); - } catch (IOException e) { - throw enrichExceptionWithBaseFile(e); - } - } - - @Override - public void reset() throws IOException { - try { - super.reset(); - } catch (IOException e) { - throw enrichExceptionWithBaseFile(e); - } - } + @Override + public String next() { + ZipEntry entry = entries.nextElement(); + return entry.getName(); + } - @Override - public long skip(long var0) throws IOException { - try { - return super.skip(var0); - } catch (IOException e) { - throw enrichExceptionWithBaseFile(e); + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; } - } - - private IOException enrichExceptionWithBaseFile(IOException e) { - return new IOException(getBaseFile().toString(), e); - } + }; } } -- cgit v1.2.3