diff -r 225896f7b33c -r 72dc6088d57a jdk/src/share/classes/sun/jkernel/Bundle.java --- a/jdk/src/share/classes/sun/jkernel/Bundle.java Wed Feb 23 14:56:44 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,922 +0,0 @@ -/* - * Copyright (c) 2008, 2009, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package sun.jkernel; - -import java.io.*; -import java.net.HttpRetryException; -import java.util.*; -import java.util.concurrent.*; -import java.util.jar.*; -import java.util.zip.GZIPInputStream; - -/** - * Represents a bundle which may or may not currently be installed. - * - *@author Ethan Nicholas - */ -public class Bundle { - static { - if (!DownloadManager.jkernelLibLoaded) { - // This code can be invoked directly by the deploy build. - System.loadLibrary("jkernel"); - } - } - /** - * Compress file sourcePath with "extra" algorithm (e.g. 7-Zip LZMA) - * if available, put the uncompressed data into file destPath and - * return true. If not available return false and do nothing with destPath. - * - * @param srcPath path to existing uncompressed file - * @param destPath path for the compressed file to be created - * @returns true if extra algorithm used, false if not - * @throws IOException if the extra compression code should be available - * but cannot be located or linked to, the destination file already - * exists or cannot be opened for writing, or the compression fails - */ - public static native boolean extraCompress(String srcPath, - String destPath) throws IOException; - - /** - * Decompress file sourcePath with "extra" algorithm (e.g. 7-Zip LZMA) - * if available, put the uncompressed data into file destPath and - * return true. If not available return false and do nothing with - * destPath. - * @param srcPath path to existing compressed file - * @param destPath path to uncompressed file to be created - * @returns true if extra algorithm used, false if not - * @throws IOException if the extra uncompression code should be available - * but cannot be located or linked to, the destination file already - * exists or cannot be opened for writing, or the uncompression fails - */ - public static native boolean extraUncompress(String srcPath, - String destPath) throws IOException; - - private static final String BUNDLE_JAR_ENTRY_NAME = "classes.jar"; - - /** The bundle is not present. */ - protected static final int NOT_DOWNLOADED = 0; - - /** - * The bundle is in the download queue but has not finished downloading. - */ - protected static final int QUEUED = 1; - - /** The bundle has finished downloading but is not installed. */ - protected static final int DOWNLOADED = 2; - - /** The bundle is fully installed and functional. */ - protected static final int INSTALLED = 3; - - /** Thread pool used to manage dependency downloads. */ - private static ExecutorService threadPool; - - /** Size of thread pool. */ - static final int THREADS; - - static { - String downloads = System.getProperty( - DownloadManager.KERNEL_SIMULTANEOUS_DOWNLOADS_PROPERTY); - if (downloads != null) - THREADS = Integer.parseInt(downloads.trim()); - else - THREADS = 1; - } - - /** Mutex used to safely access receipts file. */ - private static Mutex receiptsMutex; - - /** Maps bundle names to known bundle instances. */ - private static Map bundles = - new HashMap(); - - /** Contains the names of currently-installed bundles. */ - static Set receipts = new HashSet(); - - private static int bytesDownloaded; - - /** Path where bundle receipts are written. */ - private static File receiptPath = new File(DownloadManager.getBundlePath(), - "receipts"); - - /** The size of the receipts file the last time we saw it. */ - private static int receiptsSize; - - /** The bundle name, e.g. "java_awt". */ - private String name; - - /** The path to which we are saving the downloaded bundle file. */ - private File localPath; - - /** - * The path of the extracted JAR file containing the bundle's classes. - */ - private File jarPath; - - // for vista IE7 protected mode - private File lowJarPath; - private File lowJavaPath = null; - - /** The current state (DOWNLOADED, INSTALLED, etc.). */ - protected int state; - - /** - * True if we should delete the downloaded bundle after installing it. - */ - protected boolean deleteOnInstall = true; - - private static Mutex getReceiptsMutex() { - if (receiptsMutex == null) - receiptsMutex = Mutex.create(DownloadManager.MUTEX_PREFIX + - "receipts"); - return receiptsMutex; - } - - - /** - * Reads the receipts file in order to seed the list of currently - * installed bundles. - */ - static synchronized void loadReceipts() { - getReceiptsMutex().acquire(); - try { - if (receiptPath.exists()) { - int size = (int) receiptPath.length(); - if (size != receiptsSize) { // ensure that it has actually - // been modified - DataInputStream in = null; - try { - receipts.clear(); - for (String bundleName : DownloadManager.getBundleNames()) { - if ("true".equals(DownloadManager.getBundleProperty(bundleName, - DownloadManager.INSTALL_PROPERTY))) - receipts.add(bundleName); - } - if (receiptPath.exists()) { - in = new DataInputStream(new BufferedInputStream( - new FileInputStream(receiptPath))); - String line; - while ((line = in.readLine()) != null) { - receipts.add(line.trim()); - } - } - receiptsSize = size; - } - catch (IOException e) { - DownloadManager.log(e); - // safe to continue, as the worst that happens is - // we re-download existing bundles - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException ioe) { - DownloadManager.log(ioe); - } - } - } - } - } - } - finally { - getReceiptsMutex().release(); - } - } - - - /** Returns the bundle corresponding to the specified name. */ - public static synchronized Bundle getBundle(String bundleId) - throws IOException { - Bundle result =(Bundle) bundles.get(bundleId); - if (result == null && (bundleId.equals("merged") || - Arrays.asList(DownloadManager.getBundleNames()).contains(bundleId))) { - result = new Bundle(); - result.name = bundleId; - - if (DownloadManager.isWindowsVista()) { - result.localPath = - new File(DownloadManager.getLocalLowTempBundlePath(), - bundleId + ".zip"); - result.lowJavaPath = new File( - DownloadManager.getLocalLowKernelJava() + bundleId); - } else { - result.localPath = new File(DownloadManager.getBundlePath(), - bundleId + ".zip"); - } - - String jarPath = DownloadManager.getBundleProperty(bundleId, - DownloadManager.JAR_PATH_PROPERTY); - if (jarPath != null) { - if (DownloadManager.isWindowsVista()) { - result.lowJarPath = new File( - DownloadManager.getLocalLowKernelJava() + bundleId, - jarPath); - } - result.jarPath = new File(DownloadManager.JAVA_HOME, - jarPath); - - } else { - - if (DownloadManager.isWindowsVista()) { - result.lowJarPath = new File( - DownloadManager.getLocalLowKernelJava() + bundleId + - "\\lib\\bundles", - bundleId + ".jar"); - } - - result.jarPath = new File(DownloadManager.getBundlePath(), - bundleId + ".jar"); - - } - - bundles.put(bundleId, result); - } - return result; - } - - - /** - * Returns the name of this bundle. The name is typically defined by - * the bundles.xml file. - */ - public String getName() { - return name; - } - - - /** - * Sets the name of this bundle. - */ - public void setName(String name) { - this.name = name; - } - - - /** - * Returns the path to the bundle file on the local filesystem. The file - * will only exist if the bundle has already been downloaded; otherwise - * it will be created when download() is called. - */ - public File getLocalPath() { - return localPath; - } - - - /** - * Sets the location of the bundle file on the local filesystem. If the - * file already exists, the bundle will be considered downloaded; - * otherwise the file will be created when download() is called. - */ - public void setLocalPath(File localPath) { - this.localPath = localPath; - } - - - /** - * Returns the path to the extracted JAR file containing this bundle's - * classes. This file should only exist after the bundle has been - * installed. - */ - public File getJarPath() { - return jarPath; - } - - - /** - * Sets the path to the extracted JAR file containing this bundle's - * classes. This file will be created as part of installing the bundle. - */ - public void setJarPath(File jarPath) { - this.jarPath = jarPath; - } - - - /** - * Returns the size of the bundle download in bytes. - */ - public int getSize() { - return Integer.valueOf(DownloadManager.getBundleProperty(getName(), - DownloadManager.SIZE_PROPERTY)); - } - - - /** - * Returns true if the bundle file (getLocalPath()) should be deleted - * when the bundle is successfully installed. Defaults to true. - */ - public boolean getDeleteOnInstall() { - return deleteOnInstall; - } - - - /** - * Sets whether the bundle file (getLocalPath()) should be deleted - * when the bundle is successfully installed. Defaults to true. - */ - public void setDeleteOnInstall(boolean deleteOnInstall) { - this.deleteOnInstall = deleteOnInstall; - } - - - /** Sets the current state of this bundle to match reality. */ - protected void updateState() { - synchronized(Bundle.class) { - loadReceipts(); - if (receipts.contains(name) || - "true".equals(DownloadManager.getBundleProperty(name, - DownloadManager.INSTALL_PROPERTY))) - state = Bundle.INSTALLED; - else if (localPath.exists()) - state = Bundle.DOWNLOADED; - } - } - - - private String getURL(boolean showUI) throws IOException { - Properties urls = DownloadManager.getBundleURLs(showUI); - String result = urls.getProperty(name + ".zip"); - if (result == null) { - result = urls.getProperty(name); - if (result == null) { - DownloadManager.log("Unable to determine bundle URL for " + this); - DownloadManager.log("Bundle URLs: " + urls); - DownloadManager.sendErrorPing(DownloadManager.ERROR_NO_SUCH_BUNDLE); - - throw new NullPointerException("Unable to determine URL " + - "for bundle: " + this); - } - } - return result; - } - - - /** - * Downloads the bundle. This method blocks until the download is - * complete. - * - *@param showProgress true to display a progress dialog - */ - private void download(boolean showProgress) { - if (DownloadManager.isJREComplete()) - return; - Mutex mutex = Mutex.create(DownloadManager.MUTEX_PREFIX + name + - ".download"); - mutex.acquire(); - try { - long start = System.currentTimeMillis(); - - boolean retry; - - do { - retry = false; - updateState(); - if (state == DOWNLOADED || state == INSTALLED) { - return; - } - File tmp = null; - try { - tmp = new File(localPath + ".tmp"); - - // tmp.deleteOnExit(); - - if (DownloadManager.getBaseDownloadURL().equals( - DownloadManager.RESOURCE_URL)) { - // RESOURCE_URL is used during build process, to - // avoid actual network traffic. This is called in - // the SplitJRE DownloadTest to determine which - // classes are needed to support downloads, but we - // bypass the actual HTTP download to simplify the - // build process (it's all native code, so from - // DownloadTest's standpoint it doesn't matter if we - // really call it or not). - String path = "/" + name + ".zip"; - InputStream in = - getClass().getResourceAsStream(path); - if (in == null) - throw new IOException("could not locate " + - "resource: " + path); - FileOutputStream out = new FileOutputStream(tmp); - DownloadManager.send(in, out); - in.close(); - out.close(); - } - else { - try { - String bundleURL = getURL(showProgress); - DownloadManager.log("Downloading from: " + - bundleURL); - DownloadManager.downloadFromURL(bundleURL, tmp, - name.replace('_', '.'), showProgress); - } - catch (HttpRetryException e) { - // Akamai returned a 403, get new URL - DownloadManager.flushBundleURLs(); - String bundleURL = getURL(showProgress); - DownloadManager.log("Retrying at new " + - "URL: " + bundleURL); - DownloadManager.downloadFromURL(bundleURL, tmp, - name.replace('_', '.'), - showProgress); - // we intentionally don't do a 403 retry - // again, to avoid infinite retries - } - } - if (!tmp.exists() || tmp.length() == 0) { - if (showProgress) { - // since showProgress = true, native code should - // have offered to retry. Since we ended up here, - // we conclude that download failed & user opted to - // cancel. Set complete to true to stop bugging - // him in the future (if one bundle fails, the - // rest are virtually certain to). - DownloadManager.complete = true; - } - DownloadManager.fatalError(DownloadManager.ERROR_UNSPECIFIED); - } - - /** - * Bundle security - * - * Check for corruption/spoofing - */ - - - /* Create a bundle check from the tmp file */ - BundleCheck gottenCheck = BundleCheck.getInstance(tmp); - - /* Get the check expected for the Bundle */ - BundleCheck expectedCheck = BundleCheck.getInstance(name); - - // Do they match? - - if (expectedCheck.equals(gottenCheck)) { - - // Security check OK, uncompress the bundle file - // into the local path - - long uncompressedLength = tmp.length(); - localPath.delete(); - - File uncompressedPath = new File(tmp.getPath() + - ".jar0"); - if (! extraUncompress(tmp.getPath(), - uncompressedPath.getPath())) { - // Extra uncompression not available, fall - // back to alternative if it is enabled. - if (DownloadManager.debug) { - DownloadManager.log("Uncompressing with GZIP"); - } - GZIPInputStream in = new GZIPInputStream( new - BufferedInputStream(new FileInputStream(tmp), - DownloadManager.BUFFER_SIZE)); - BufferedOutputStream out = new BufferedOutputStream( - new FileOutputStream(uncompressedPath), - DownloadManager.BUFFER_SIZE); - DownloadManager.send(in,out); - in.close(); - out.close(); - if (! uncompressedPath.renameTo(localPath)) { - throw new IOException("unable to rename " + - uncompressedPath + " to " + localPath); - } - } else { - if (DownloadManager.debug) { - DownloadManager.log("Uncompressing with LZMA"); - } - if (! uncompressedPath.renameTo(localPath)) { - throw new IOException("unable to rename " + - uncompressedPath + " to " + localPath); - } - } - state = DOWNLOADED; - bytesDownloaded += uncompressedLength; - long time = (System.currentTimeMillis() - - start); - DownloadManager.log("Downloaded " + name + - " in " + time + "ms. Downloaded " + - bytesDownloaded + " bytes this session."); - - // Normal completion - } else { - - // Security check not OK: remove the temp file - // and consult the user - - tmp.delete(); - - DownloadManager.log( - "DownloadManager: Security check failed for " + - "bundle " + name); - - // only show dialog if we are not in silent mode - if (showProgress) { - retry = DownloadManager.askUserToRetryDownloadOrQuit( - DownloadManager.ERROR_UNSPECIFIED); - } - - if (!retry) { - // User wants to give up - throw new RuntimeException( - "Failed bundle security check and user " + - "canceled"); - } - } - } - catch (IOException e) { - // Look for "out of space" using File.getUsableSpace() - // here when downloadFromURL starts throwing IOException - // (or preferably a distinct exception for this case). - DownloadManager.log(e); - } - } while (retry); - } finally { - mutex.release(); - } - } - - - /** - * Calls {@link #queueDownload()} on all of this bundle's dependencies. - */ - void queueDependencies(boolean showProgress) { - try { - String dependencies = - DownloadManager.getBundleProperty(name, - DownloadManager.DEPENDENCIES_PROPERTY); - if (dependencies != null) { - StringTokenizer st = new StringTokenizer(dependencies, - " ,"); - while (st.hasMoreTokens()) { - Bundle b = getBundle(st.nextToken()); - if (b != null && !b.isInstalled()) { - if (DownloadManager.debug) { - DownloadManager.log("Queueing " + b.name + - " as a dependency of " + name + "..."); - } - b.install(showProgress, true, false); - } - } - } - } catch (IOException e) { - // shouldn't happen - DownloadManager.log(e); - } - } - - - static synchronized ExecutorService getThreadPool() { - if (threadPool == null) { - threadPool = Executors.newFixedThreadPool(THREADS, - new ThreadFactory () { - public Thread newThread(Runnable r) { - Thread result = new Thread(r); - result.setDaemon(true); - return result; - } - } - ); - } - return threadPool; - } - - - private void unpackBundle() throws IOException { - File useJarPath = null; - if (DownloadManager.isWindowsVista()) { - useJarPath = lowJarPath; - File jarDir = useJarPath.getParentFile(); - if (jarDir != null) { - jarDir.mkdirs(); - } - } else { - useJarPath = jarPath; - } - - DownloadManager.log("Unpacking " + this + " to " + useJarPath); - - InputStream rawStream = new FileInputStream(localPath); - JarInputStream in = new JarInputStream(rawStream) { - public void close() throws IOException { - // prevent any sub-processes here from actually closing the - // input stream; we'll use rawsStream.close() when we're - // done with it - } - }; - - try { - File jarTmp = null; - JarEntry entry; - while ((entry = in.getNextJarEntry()) != null) { - String entryName = entry.getName(); - if (entryName.equals("classes.pack")) { - File packTmp = new File(useJarPath + ".pack"); - packTmp.getParentFile().mkdirs(); - DownloadManager.log("Writing temporary .pack file " + packTmp); - OutputStream tmpOut = new FileOutputStream(packTmp); - try { - DownloadManager.send(in, tmpOut); - } finally { - tmpOut.close(); - } - // we unpack to a temporary file and then, towards the end - // of this method, use a (hopefully atomic) rename to put it - // into its final location; this should avoid the problem of - // partially-completed downloads. Doing the rename last - // allows us to check for the presence of the JAR file to - // see whether the bundle has in fact been downloaded. - jarTmp = new File(useJarPath + ".tmp"); - DownloadManager.log("Writing temporary .jar file " + jarTmp); - unpack(packTmp, jarTmp); - packTmp.delete(); - } else if (!entryName.startsWith("META-INF")) { - File dest; - if (DownloadManager.isWindowsVista()) { - dest = new File(lowJavaPath, - entryName.replace('/', File.separatorChar)); - } else { - dest = new File(DownloadManager.JAVA_HOME, - entryName.replace('/', File.separatorChar)); - } - if (entryName.equals(BUNDLE_JAR_ENTRY_NAME)) - dest = useJarPath; - File destTmp = new File(dest + ".tmp"); - boolean exists = dest.exists(); - if (!exists) { - DownloadManager.log(dest + ".mkdirs()"); - dest.getParentFile().mkdirs(); - } - try { - DownloadManager.log("Using temporary file " + destTmp); - FileOutputStream out = - new FileOutputStream(destTmp); - try { - byte[] buffer = new byte[2048]; - int c; - while ((c = in.read(buffer)) > 0) - out.write(buffer, 0, c); - } finally { - out.close(); - } - if (exists) - dest.delete(); - DownloadManager.log("Renaming from " + destTmp + " to " + dest); - if (!destTmp.renameTo(dest)) { - throw new IOException("unable to rename " + - destTmp + " to " + dest); - } - - } catch (IOException e) { - if (!exists) - throw e; - // otherwise the file already existed and the fact - // that we failed to re-write it probably just - // means that it was in use - } - } - } - - // rename the temporary jar into its final location - if (jarTmp != null) { - if (useJarPath.exists()) - jarTmp.delete(); - else if (!jarTmp.renameTo(useJarPath)) { - throw new IOException("unable to rename " + jarTmp + - " to " + useJarPath); - } - } - if (DownloadManager.isWindowsVista()) { - // move bundle to real location - DownloadManager.log("Using broker to move " + name); - if (!DownloadManager.moveDirWithBroker( - DownloadManager.getKernelJREDir() + name)) { - throw new IOException("unable to create " + name); - } - DownloadManager.log("Broker finished " + name); - } - DownloadManager.log("Finished unpacking " + this); - } finally { - rawStream.close(); - } - if (deleteOnInstall) { - localPath.delete(); - } - - } - - - public static void unpack(File pack, File jar) throws IOException { - Process p = Runtime.getRuntime().exec(DownloadManager.JAVA_HOME + File.separator + - "bin" + File.separator + "unpack200 -Hoff \"" + pack + "\" \"" + jar + "\""); - try { - p.waitFor(); - } - catch (InterruptedException e) { - } - } - - - /** - * Unpacks and installs the bundle. The bundle's classes are not - * immediately added to the boot class path; this happens when the VM - * attempts to load a class and calls getBootClassPathEntryForClass(). - */ - public void install() throws IOException { - install(true, false, true); - } - - - /** - * Unpacks and installs the bundle, optionally hiding the progress - * indicator. The bundle's classes are not immediately added to the - * boot class path; this happens when the VM attempts to load a class - * and calls getBootClassPathEntryForClass(). - * - *@param showProgress true to display a progress dialog - *@param downloadOnly true to download but not install - *@param block true to wait until the operation is complete before returning - */ - public synchronized void install(final boolean showProgress, - final boolean downloadOnly, boolean block) throws IOException { - if (DownloadManager.isJREComplete()) - return; - if (state == NOT_DOWNLOADED || state == QUEUED) { - // we allow an already-queued bundle to be placed into the queue - // again, to handle the case where the bundle is queued with - // downloadOnly true and then we try to queue it again with - // downloadOnly false -- the second queue entry will actually - // install it. - if (state != QUEUED) { - DownloadManager.addToTotalDownloadSize(getSize()); - state = QUEUED; - } - if (getThreadPool().isShutdown()) { - if (state == NOT_DOWNLOADED || state == QUEUED) - doInstall(showProgress, downloadOnly); - } - else { - Future task = getThreadPool().submit(new Runnable() { - public void run() { - try { - if (state == NOT_DOWNLOADED || state == QUEUED || - (!downloadOnly && state == DOWNLOADED)) { - doInstall(showProgress, downloadOnly); - } - } - catch (IOException e) { - // ignore - } - } - }); - queueDependencies(showProgress); - if (block) { - try { - task.get(); - } - catch (Exception e) { - throw new Error(e); - } - } - } - } - else if (state == DOWNLOADED && !downloadOnly) - doInstall(showProgress, false); - } - - - private void doInstall(boolean showProgress, boolean downloadOnly) - throws IOException { - Mutex mutex = Mutex.create(DownloadManager.MUTEX_PREFIX + name + - ".install"); - DownloadManager.bundleInstallStart(); - try { - mutex.acquire(); - updateState(); - if (state == NOT_DOWNLOADED || state == QUEUED) { - download(showProgress); - } - - if (state == DOWNLOADED && downloadOnly) { - return; - } - - if (state == INSTALLED) { - return; - } - if (state != DOWNLOADED) { - DownloadManager.fatalError(DownloadManager.ERROR_UNSPECIFIED); - } - - DownloadManager.log("Calling unpackBundle for " + this); - unpackBundle(); - DownloadManager.log("Writing receipt for " + this); - writeReceipt(); - updateState(); - DownloadManager.log("Finished installing " + this + ", state=" + state); - } finally { - if (lowJavaPath != null) { - lowJavaPath.delete(); - } - mutex.release(); - DownloadManager.bundleInstallComplete(); - } - } - - - synchronized void setState(int state) { - this.state = state; - } - - - /** Returns true if this bundle has been installed. */ - public boolean isInstalled() { - synchronized (Bundle.class) { - updateState(); - return state == INSTALLED; - } - } - - - /** - * Adds an entry to the receipts file indicating that this bundle has - * been successfully downloaded. - */ - private void writeReceipt() { - getReceiptsMutex().acquire(); - File useReceiptPath = null; - try { - - try { - - receipts.add(name); - - if (DownloadManager.isWindowsVista()) { - // write out receipts to locallow - useReceiptPath = new File( - DownloadManager.getLocalLowTempBundlePath(), - "receipts"); - - if (receiptPath.exists()) { - // copy original file to locallow location - DownloadManager.copyReceiptFile(receiptPath, - useReceiptPath); - } - - // update receipt in locallow path - // only append if original receipt path exists - FileOutputStream out = new FileOutputStream(useReceiptPath, - receiptPath.exists()); - out.write((name + System.getProperty("line.separator")).getBytes("utf-8")); - out.close(); - - // use broker to move back to real path - if (!DownloadManager.moveFileWithBroker( - DownloadManager.getKernelJREDir() - + "-bundles" + File.separator + "receipts")) { - throw new IOException("failed to write receipts"); - } - } else { - useReceiptPath = receiptPath; - FileOutputStream out = new FileOutputStream(useReceiptPath, - true); - out.write((name + System.getProperty("line.separator")).getBytes("utf-8")); - out.close(); - } - - - } catch (IOException e) { - DownloadManager.log(e); - // safe to continue, as the worst that happens is we - // re-download existing bundles - } - } - finally { - getReceiptsMutex().release(); - } - } - - - public String toString() { - return "Bundle[" + name + "]"; - } -}