--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/jkernel/DownloadManager.java Fri Jun 12 14:56:32 2009 -0400
@@ -0,0 +1,1676 @@
+/*
+ * Copyright 2008 - 2009 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.jkernel;
+
+import java.io.*;
+import java.security.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.jar.*;
+import java.util.zip.*;
+import sun.misc.Launcher;
+
+/**
+ * Handles the downloading of additional JRE components. The bootstrap class
+ * loader automatically invokes DownloadManager when it comes across a resource
+ * that can't be located.
+ *
+ *@author Ethan Nicholas
+ */
+public class DownloadManager {
+ public static final String KERNEL_DOWNLOAD_URL_PROPERTY =
+ "kernel.download.url";
+ public static final String KERNEL_DOWNLOAD_ENABLED_PROPERTY =
+ "kernel.download.enabled";
+
+ public static final String KERNEL_DOWNLOAD_DIALOG_PROPERTY =
+ "kernel.download.dialog";
+
+ public static final String KERNEL_DEBUG_PROPERTY = "kernel.debug";
+ // disables JRE completion when set to true, used as part of the build
+ // process
+ public static final String KERNEL_NOMERGE_PROPERTY = "kernel.nomerge";
+
+ public static final String KERNEL_SIMULTANEOUS_DOWNLOADS_PROPERTY =
+ "kernel.simultaneous.downloads";
+
+ // used to bypass some problems with JAR entry modtimes not matching.
+ // originally was set to zero, but apparently the epochs are different
+ // for zip and pack so the pack/unpack cycle was causing the modtimes
+ // to change. With some recent changes to the reconstruction, I'm
+ // not sure if this is actually necessary anymore.
+ public static final int KERNEL_STATIC_MODTIME = 10000000;
+
+ // indicates that bundles should be grabbed using getResource(), rather
+ // than downloaded from a network path -- this is used during the build
+ // process
+ public static final String RESOURCE_URL = "internal-resource/";
+ public static final String REQUESTED_BUNDLES_PATH = "lib" + File.separator +
+ "bundles" + File.separator + "requested.list";
+
+ private static final boolean disableDownloadDialog = "false".equals(
+ System.getProperty(KERNEL_DOWNLOAD_DIALOG_PROPERTY));
+
+ static boolean debug = "true".equals(
+ System.getProperty(KERNEL_DEBUG_PROPERTY));
+ // points to stderr in case we need to println before System.err is
+ // initialized
+ private static OutputStream errorStream;
+ private static OutputStream logStream;
+
+ static String MUTEX_PREFIX;
+
+ static boolean complete;
+
+ // 1 if jbroker started; 0 otherwise
+ private static int _isJBrokerStarted = -1;
+
+ // maps bundle names to URL strings
+ private static Properties bundleURLs;
+
+ public static final String JAVA_HOME = System.getProperty("java.home");
+ public static final String USER_HOME = System.getProperty("user.home");
+ public static final String JAVA_VERSION =
+ System.getProperty("java.version");
+ static final int BUFFER_SIZE = 2048;
+
+ static volatile boolean jkernelLibLoaded = false;
+
+ public static String DEFAULT_DOWNLOAD_URL =
+ "http://javadl.sun.com/webapps/download/GetList/"
+ + System.getProperty("java.runtime.version") + "-kernel/windows-i586/";
+
+ private static final String CUSTOM_PREFIX = "custom";
+ private static final String KERNEL_PATH_SUFFIX = "-kernel";
+
+ public static final String JAR_PATH_PROPERTY = "jarpath";
+ public static final String SIZE_PROPERTY = "size";
+ public static final String DEPENDENCIES_PROPERTY = "dependencies";
+ public static final String INSTALL_PROPERTY = "install";
+
+ private static boolean reportErrors = true;
+
+ static final int ERROR_UNSPECIFIED = 0;
+ static final int ERROR_DISK_FULL = 1;
+ static final int ERROR_MALFORMED_BUNDLE_PROPERTIES = 2;
+ static final int ERROR_DOWNLOADING_BUNDLE_PROPERTIES = 3;
+ static final int ERROR_MALFORMED_URL = 4;
+ static final int ERROR_RETRY_CANCELLED = 5;
+ static final int ERROR_NO_SUCH_BUNDLE = 6;
+
+
+ // tracks whether the current thread is downloading. A count of zero means
+ // not currently downloading, >0 means the current thread is downloading or
+ // installing a bundle.
+ static ThreadLocal<Integer> downloading = new ThreadLocal<Integer>() {
+ protected Integer initialValue() {
+ return 0;
+ }
+ };
+
+ private static File[] additionalBootStrapPaths = { };
+
+ private static String[] bundleNames;
+ private static String[] criticalBundleNames;
+
+ private static String downloadURL;
+
+ private static boolean visitorIdDetermined;
+ private static String visitorId;
+
+ /**
+ * File and path where the Check value properties are gotten from
+ */
+ public static String CHECK_VALUES_FILE = "check_value.properties";
+ static String CHECK_VALUES_DIR = "sun/jkernel/";
+ static String CHECK_VALUES_PATH = CHECK_VALUES_DIR + CHECK_VALUES_FILE;
+
+ /**
+ * The contents of the bundle.properties file, which contains various
+ * information about individual bundles.
+ */
+ private static Map<String, Map<String, String>> bundleProperties;
+
+
+ /**
+ * The contents of the resource_map file, which maps resources
+ * to their respective bundles.
+ */
+ private static Map<String, String> resourceMap;
+
+
+ /**
+ * The contents of the file_map file, which maps files
+ * to their respective bundles.
+ */
+ private static Map<String, String> fileMap;
+
+ private static boolean extDirDetermined;
+ private static boolean extDirIncluded;
+
+ static {
+ AccessController.doPrivileged(new PrivilegedAction() {
+ public Object run() {
+ if (debug)
+ println("DownloadManager startup");
+
+ // this mutex is global and will apply to all different
+ // version of java kernel installed on the local machine
+ MUTEX_PREFIX = "jkernel";
+ boolean downloadEnabled = !"false".equals(
+ System.getProperty(KERNEL_DOWNLOAD_ENABLED_PROPERTY));
+ complete = !getBundlePath().exists() ||
+ !downloadEnabled;
+
+ // only load jkernel.dll if we are not "complete".
+ // DownloadManager will be loaded during build time, before
+ // jkernel.dll is built. We only need to load jkernel.dll
+ // when DownloadManager needs to download something, which is
+ // not necessary during build time
+ if (!complete) {
+ loadJKernelLibrary();
+ log("Log opened");
+
+ if (isWindowsVista()) {
+ getLocalLowTempBundlePath().mkdirs();
+ }
+
+ new Thread() {
+ public void run() {
+ startBackgroundDownloads();
+ }
+ }.start();
+
+ try {
+ String dummyPath;
+ if (isWindowsVista()) {
+ dummyPath = USER_HOME +
+ "\\appdata\\locallow\\dummy.kernel";
+ } else {
+ dummyPath = USER_HOME + "\\dummy.kernel";
+ }
+
+ File f = new File(dummyPath);
+ FileOutputStream out = new FileOutputStream(f, true);
+ out.close();
+ f.deleteOnExit();
+
+ } catch (IOException e) {
+ log(e);
+ }
+ // end of warm up code
+
+ new Thread("BundleDownloader") {
+ public void run() {
+ downloadRequestedBundles();
+ }
+ }.start();
+ }
+ return null;
+ }
+ });
+ }
+
+
+ static synchronized void loadJKernelLibrary() {
+ if (!jkernelLibLoaded) {
+ try {
+ System.loadLibrary("jkernel");
+ jkernelLibLoaded = true;
+ debug = getDebugProperty();
+ } catch (Exception e) {
+ throw new Error(e);
+ }
+ }
+ }
+
+ static String appendTransactionId(String url) {
+ StringBuilder result = new StringBuilder(url);
+ String visitorId = DownloadManager.getVisitorId();
+ if (visitorId != null) {
+ if (url.indexOf("?") == -1)
+ result.append('?');
+ else
+ result.append('&');
+ result.append("transactionId=");
+ result.append(DownloadManager.getVisitorId());
+ }
+ return result.toString();
+ }
+
+
+ /**
+ * Returns the URL for the directory from which bundles should be
+ * downloaded.
+ */
+ static synchronized String getBaseDownloadURL() {
+ if (downloadURL == null) {
+ log("Determining download URL...");
+ loadJKernelLibrary();
+
+ /*
+ * First check if system property has been set - system
+ * property should take over registry key setting.
+ */
+ downloadURL = System.getProperty(
+ DownloadManager.KERNEL_DOWNLOAD_URL_PROPERTY);
+ log("System property kernel.download.url = " + downloadURL);
+
+ /*
+ * Now check if registry key has been set
+ */
+ if (downloadURL == null){
+ downloadURL = getUrlFromRegistry();
+ log("getUrlFromRegistry = " + downloadURL);
+ }
+
+ /*
+ * Use default download url
+ */
+ if (downloadURL == null)
+ downloadURL = DEFAULT_DOWNLOAD_URL;
+ log("Final download URL: " + downloadURL);
+ }
+ return downloadURL;
+ }
+
+
+ /**
+ * Loads a file representing a node tree. The format is described in
+ * SplitJRE.writeTreeMap(). The node paths (such as
+ * core/java/lang/Object.class) are interpreted with the root node as the
+ * value and the remaining nodes as
+ * the key, so the mapping for this entry would be java/lang/Object.class =
+ * core.
+ */
+ static Map<String, String> readTreeMap(InputStream rawIn)
+ throws IOException {
+ // "token level" refers to the 0-31 byte that occurs prior to every
+ // token in the stream, and would be e.g. <0> core <1> java <2> lang
+ // <3> Object.class <3> String.class, which gives us two mappings:
+ // java/lang/Object.class = core, and java/lang/String.class = core.
+ // See the format description in SplitJRE.writeTreeMap for more details.
+ Map<String, String> result = new HashMap<String, String>();
+ InputStream in = new BufferedInputStream(rawIn);
+ // holds the current token sequence,
+ // e.g. {"core", "java", "lang", "Object.class"}
+ List<String> tokens = new ArrayList<String>();
+ StringBuilder currentToken = new StringBuilder();
+ for (;;) {
+ int c = in.read();
+ if (c == -1) // eof
+ break;
+ if (c < 32) { // new token level
+ if (tokens.size() > 0) {
+ // replace the null at the end of the list with the token
+ // we just finished reading
+ tokens.set(tokens.size() - 1, currentToken.toString());
+ }
+
+ currentToken.setLength(0);
+
+ if (c > tokens.size()) {
+ // can't increase by more than one token level at a step
+ throw new InternalError("current token level is " +
+ (tokens.size() - 1) + " but encountered token " +
+ "level " + c);
+ }
+ else if (c == tokens.size()) {
+ // token level increased by 1; this means we are still
+ // adding tokens for the current mapping -- e.g. we have
+ // read "core", "java", "lang" and are just about to read
+ // "Object.class"
+ // add a placeholder for the new token
+ tokens.add(null);
+ }
+ else {
+ // we just stayed at the same level or backed up one or more
+ // token levels; this means that the current sequence is
+ // complete and needs to be added to the result map
+ StringBuilder key = new StringBuilder();
+ // combine all tokens except the first into a single string
+ for (int i = 1; i < tokens.size(); i++) {
+ if (i > 1)
+ key.append('/');
+ key.append(tokens.get(i));
+ }
+ // map the combined string to the first token, e.g.
+ // java/lang/Object.class = core
+ result.put(key.toString(), tokens.get(0));
+ // strip off tokens until we get back to the current token
+ // level
+ while (c < tokens.size())
+ tokens.remove(c);
+ // placeholder for upcoming token
+ tokens.add(null);
+ }
+ }
+ else if (c < 254) // character
+ currentToken.append((char) c);
+ else if (c == 255)
+ currentToken.append(".class");
+ else { // out-of-band value
+ throw new InternalError("internal error processing " +
+ "resource_map (can't-happen error)");
+ }
+ }
+ if (tokens.size() > 0) // add token we just finished reading
+ tokens.set(tokens.size() - 1, currentToken.toString());
+ StringBuilder key = new StringBuilder();
+ // add the last entry to the map
+ for (int i = 1; i < tokens.size(); i++) {
+ if (i > 1)
+ key.append('/');
+ key.append(tokens.get(i));
+ }
+ if (!tokens.isEmpty())
+ result.put(key.toString(), tokens.get(0));
+ in.close();
+ return Collections.unmodifiableMap(result);
+ }
+
+
+ /**
+ * Returns the contents of the resource_map file, which maps
+ * resources names to their respective bundles.
+ */
+ public static Map<String, String> getResourceMap() throws IOException {
+ if (resourceMap == null) {
+ InputStream in = DownloadManager.class.getResourceAsStream("resource_map");
+ if (in != null) {
+ in = new BufferedInputStream(in);
+ try {
+ resourceMap = readTreeMap(in);
+ in.close();
+ }
+ catch (IOException e) {
+ // turns out we can be returned a broken stream instead of
+ // just null
+ resourceMap = new HashMap<String, String>();
+ complete = true;
+ log("Can't find resource_map, forcing complete to true");
+ }
+ in.close();
+ }
+ else {
+ resourceMap = new HashMap<String, String>();
+ complete = true;
+ log("Can't find resource_map, forcing complete to true");
+ }
+
+ for (int i = 1; ; i++) { // run through the numbered custom bundles
+ String name = CUSTOM_PREFIX + i;
+ File customPath = new File(getBundlePath(), name + ".jar");
+ if (customPath.exists()) {
+ JarFile custom = new JarFile(customPath);
+ Enumeration entries = custom.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry entry = (JarEntry) entries.nextElement();
+ if (!entry.isDirectory())
+ resourceMap.put(entry.getName(), name);
+ }
+ }
+ else
+ break;
+ }
+ }
+ return resourceMap;
+ }
+
+
+ /**
+ * Returns the contents of the file_map file, which maps
+ * file names to their respective bundles.
+ */
+ public static Map<String, String> getFileMap() throws IOException {
+ if (fileMap == null) {
+ InputStream in = DownloadManager.class.getResourceAsStream("file_map");
+ if (in != null) {
+ in = new BufferedInputStream(in);
+ try {
+ fileMap = readTreeMap(in);
+ in.close();
+ }
+ catch (IOException e) {
+ // turns out we can be returned a broken stream instead of
+ // just null
+ fileMap = new HashMap<String, String>();
+ complete = true;
+ log("Can't find file_map, forcing complete to true");
+ }
+ in.close();
+ }
+ else {
+ fileMap = new HashMap<String, String>();
+ complete = true;
+ log("Can't find file_map, forcing complete to true");
+ }
+ }
+ return fileMap;
+ }
+
+
+ /**
+ * Returns the contents of the bundle.properties file, which maps
+ * bundle names to a pipe-separated list of their properties. Properties
+ * include:
+ * jarpath - By default, the JAR files (unpacked from classes.pack in the
+ * bundle) are stored under lib/bundles. The jarpath property
+ * overrides this default setting, causing the JAR to be unpacked
+ * at the specified location. This is used to preserve the
+ * identity of JRE JAR files such as lib/deploy.jar.
+ * size - The size of the download in bytes.
+ */
+ private static synchronized Map<String, Map<String, String>> getBundleProperties()
+ throws IOException {
+ if (bundleProperties == null) {
+ InputStream in = DownloadManager.class.getResourceAsStream("bundle.properties");
+ if (in == null) {
+ complete = true;
+ log("Can't find bundle.properties, forcing complete to true");
+ return null;
+ }
+ in = new BufferedInputStream(in);
+ Properties tmp = new Properties();
+ tmp.load(in);
+ bundleProperties = new HashMap<String, Map<String, String>>();
+ for (Map.Entry e : tmp.entrySet()) {
+ String key = (String) e.getKey();
+ String[] properties = ((String) e.getValue()).split("\\|");
+ Map<String, String> map = new HashMap<String, String>();
+ for (String entry : properties) {
+ int equals = entry.indexOf("=");
+ if (equals == -1)
+ throw new InternalError("error parsing bundle.properties: " +
+ entry);
+ map.put(entry.substring(0, equals).trim(),
+ entry.substring(equals + 1).trim());
+ }
+ bundleProperties.put(key, map);
+ }
+ in.close();
+ }
+ return bundleProperties;
+ }
+
+
+ /**
+ * Returns a single bundle property value loaded from the bundle.properties
+ * file.
+ */
+ static String getBundleProperty(String bundleName, String property) {
+ try {
+ Map<String, Map<String, String>> props = getBundleProperties();
+ Map/*<String, String>*/ map = props != null ? props.get(bundleName) : null;
+ return map != null ? (String) map.get(property) : null;
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ /** Returns an array of all supported bundle names. */
+ static String[] getBundleNames() throws IOException {
+ if (bundleNames == null) {
+ Set<String> result = new HashSet<String>();
+ Map<String, String> resourceMap = getResourceMap();
+ if (resourceMap != null)
+ result.addAll(resourceMap.values());
+ Map<String, String> fileMap = getFileMap();
+ if (fileMap != null)
+ result.addAll(fileMap.values());
+ bundleNames = result.toArray(new String[result.size()]);
+ }
+ return bundleNames;
+ }
+
+
+ /**
+ * Returns an array of all "critical" (must be downloaded prior to
+ * completion) bundle names.
+ */
+ private static String[] getCriticalBundleNames() throws IOException {
+ if (criticalBundleNames == null) {
+ Set<String> result = new HashSet<String>();
+ Map<String, String> fileMap = getFileMap();
+ if (fileMap != null)
+ result.addAll(fileMap.values());
+ criticalBundleNames = result.toArray(new String[result.size()]);
+ }
+ return criticalBundleNames;
+ }
+
+
+ public static void send(InputStream in, OutputStream out)
+ throws IOException {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int c;
+ while ((c = in.read(buffer)) > 0)
+ out.write(buffer, 0, c);
+ }
+
+
+ /**
+ * Determine whether all bundles have been downloaded, and if so create
+ * the merged jars that will eventually replace rt.jar and resoures.jar.
+ * IMPORTANT: this method should only be called from the background
+ * download process.
+ */
+ static void performCompletionIfNeeded() {
+ if (debug)
+ log("DownloadManager.performCompletionIfNeeded: checking (" +
+ complete + ", " + System.getProperty(KERNEL_NOMERGE_PROPERTY)
+ + ")");
+ if (complete ||
+ "true".equals(System.getProperty(KERNEL_NOMERGE_PROPERTY)))
+ return;
+ Bundle.loadReceipts();
+ try {
+ if (debug) {
+ List critical = new ArrayList(Arrays.asList(getCriticalBundleNames()));
+ critical.removeAll(Bundle.receipts);
+ log("DownloadManager.performCompletionIfNeeded: still need " +
+ critical.size() + " bundles (" + critical + ")");
+ }
+ if (Bundle.receipts.containsAll(Arrays.asList(getCriticalBundleNames()))) {
+ log("DownloadManager.performCompletionIfNeeded: running");
+ // all done!
+ new Thread("JarMerger") {
+ public void run() {
+ createMergedJars();
+ }
+ }.start();
+ }
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ /**
+ * Returns the bundle corresponding to a given resource path (e.g.
+ * "java/lang/Object.class"). If the resource does not appear in a bundle,
+ * null is returned.
+ */
+ public static Bundle getBundleForResource(String resource)
+ throws IOException {
+ String bundleName = getResourceMap().get(resource);
+ return bundleName != null ? Bundle.getBundle(bundleName) : null;
+ }
+
+
+ /**
+ * Returns the bundle corresponding to a given JRE file path (e.g.
+ * "bin/awt.dll"). If the file does not appear in a bundle, null is
+ * returned.
+ */
+ private static Bundle getBundleForFile(String file) throws IOException {
+ String bundleName = getFileMap().get(file);
+ return bundleName != null ? Bundle.getBundle(bundleName) : null;
+ }
+
+
+ /**
+ * Returns the path to the lib/bundles directory.
+ */
+ static File getBundlePath() {
+ return new File(JAVA_HOME, "lib" + File.separatorChar + "bundles");
+ }
+
+ private static String getAppDataLocalLow() {
+ return USER_HOME + "\\appdata\\locallow\\";
+ }
+
+ public static String getKernelJREDir() {
+ return "kerneljre" + JAVA_VERSION;
+ }
+
+ static File getLocalLowTempBundlePath() {
+ return new File(getLocalLowKernelJava() + "-bundles");
+ }
+
+ static String getLocalLowKernelJava() {
+ return getAppDataLocalLow() + getKernelJREDir();
+ }
+
+ /**
+ * Returns an array of JAR files which have been added to the boot strap
+ * class path since the JVM was first booted.
+ */
+ public static synchronized File[] getAdditionalBootStrapPaths() {
+ return additionalBootStrapPaths != null ? additionalBootStrapPaths :
+ new File[0];
+ }
+
+
+ private static void addEntryToBootClassPath(File path) {
+ // Must acquire these locks in this order
+ synchronized(Launcher.class) {
+ synchronized(DownloadManager.class) {
+ File[] newBootStrapPaths = new File[
+ additionalBootStrapPaths.length + 1];
+ System.arraycopy(additionalBootStrapPaths, 0, newBootStrapPaths,
+ 0, additionalBootStrapPaths.length);
+ newBootStrapPaths[newBootStrapPaths.length - 1] = path;
+ additionalBootStrapPaths = newBootStrapPaths;
+ Launcher.flushBootstrapClassPath();
+ }
+ }
+ }
+
+
+ /**
+ * Scan through java.ext.dirs to see if the lib/ext directory is included.
+ * If not, we shouldn't be "finding" lib/ext jars for download.
+ */
+ private static synchronized boolean extDirIsIncluded() {
+ if (!extDirDetermined) {
+ extDirDetermined = true;
+ String raw = System.getProperty("java.ext.dirs");
+ String ext = JAVA_HOME + File.separator + "lib" + File.separator + "ext";
+ int index = 0;
+ while (index < raw.length()) {
+ int newIndex = raw.indexOf(File.pathSeparator, index);
+ if (newIndex == -1)
+ newIndex = raw.length();
+ String path = raw.substring(index, newIndex);
+ if (path.equals(ext)) {
+ extDirIncluded = true;
+ break;
+ }
+ index = newIndex + 1;
+ }
+ }
+ return extDirIncluded;
+ }
+
+
+ private static String doGetBootClassPathEntryForResource(
+ String resourceName) {
+ boolean retry = false;
+ do {
+ Bundle bundle = null;
+ try {
+ bundle = getBundleForResource(resourceName);
+ if (bundle != null) {
+ File path = bundle.getJarPath();
+ boolean isExt = path.getParentFile().getName().equals("ext");
+ if (isExt && !extDirIsIncluded()) // this is a lib/ext jar, but
+ return null; // lib/ext isn't in the path
+ if (getBundleProperty(bundle.getName(), JAR_PATH_PROPERTY) == null) {
+ // if the bundle doesn't have its own JAR path, that means it's
+ // going to be merged into rt.jar. If we already have the
+ // merged rt.jar, we can simply point to that.
+ Bundle merged = Bundle.getBundle("merged");
+ if (merged != null && merged.isInstalled()) {
+ File jar;
+ if (resourceName.endsWith(".class"))
+ jar = merged.getJarPath();
+ else
+ jar = new File(merged.getJarPath().getPath().replaceAll("merged-rt.jar",
+ "merged-resources.jar"));
+ addEntryToBootClassPath(jar);
+ return jar.getPath();
+ }
+ }
+ if (!bundle.isInstalled()) {
+ bundle.queueDependencies(true);
+ log("On-demand downloading " +
+ bundle.getName() + " for resource " +
+ resourceName + "...");
+ bundle.install();
+ log(bundle + " install finished.");
+ }
+ log("Double-checking " + bundle + " state...");
+ if (!bundle.isInstalled()) {
+ throw new IllegalStateException("Expected state of " +
+ bundle + " to be INSTALLED");
+ }
+ if (isExt) {
+ // don't add lib/ext entries to the boot class path, add
+ // them to the extension classloader instead
+ Launcher.addURLToExtClassLoader(path.toURL());
+ return null;
+ }
+
+ if ("javaws".equals(bundle.getName())) {
+ Launcher.addURLToAppClassLoader(path.toURL());
+ log("Returning null for javaws");
+ return null;
+ }
+
+ if ("core".equals(bundle.getName()))
+ return null;
+
+ // else add to boot class path
+ addEntryToBootClassPath(path);
+
+ return path.getPath();
+ }
+ return null; // not one of the JRE's classes
+ }
+ catch (Throwable e) {
+ retry = handleException(e);
+ log("Error downloading bundle for " +
+ resourceName + ":");
+ log(e);
+ if (e instanceof IOException) {
+ // bundle did not get installed correctly, remove incomplete
+ // bundle files
+ if (bundle != null) {
+ if (bundle.getJarPath() != null) {
+ File packTmp = new File(bundle.getJarPath() + ".pack");
+ packTmp.delete();
+ bundle.getJarPath().delete();
+ }
+ if (bundle.getLocalPath() != null) {
+ bundle.getLocalPath().delete();
+ }
+ bundle.setState(Bundle.NOT_DOWNLOADED);
+ }
+ }
+ }
+ } while (retry);
+ sendErrorPing(ERROR_RETRY_CANCELLED); // bundle failed to install, user cancelled
+
+ return null; // failed, user chose not to retry
+ }
+
+ static synchronized void sendErrorPing(int code) {
+ try {
+ File bundlePath;
+ if (isWindowsVista()) {
+ bundlePath = getLocalLowTempBundlePath();
+ } else {
+ bundlePath = getBundlePath();
+ }
+ File tmp = new File(bundlePath, "tmp");
+ File errors = new File(tmp, "errors");
+ String errorString = String.valueOf(code);
+ if (errors.exists()) {
+ BufferedReader in = new BufferedReader(new FileReader(errors));
+ String line = in.readLine();
+ while (line != null) {
+ if (line.equals(errorString))
+ return; // we have already pinged this error
+ line = in.readLine();
+ }
+ }
+ tmp.mkdirs();
+ Writer out = new FileWriter(errors, true);
+ out.write(errorString + System.getProperty("line.separator"));
+ out.close();
+ postDownloadError(code);
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+
+ /**
+ * Displays an error dialog and prompts the user to retry or cancel.
+ * Returns true if the user chose to retry, false if he chose to cancel.
+ */
+ static boolean handleException(Throwable e) {
+ if (e instanceof IOException) {
+ // I don't know of a better method to determine the root cause of
+ // the exception, unfortunately...
+ int code = ERROR_UNSPECIFIED;
+ if (e.getMessage().indexOf("not enough space") != -1)
+ code = ERROR_DISK_FULL;
+ return askUserToRetryDownloadOrQuit(code);
+ }
+ else
+ return false;
+ }
+
+
+ static synchronized void flushBundleURLs() {
+ bundleURLs = null;
+ }
+
+
+ static synchronized Properties getBundleURLs(boolean showUI)
+ throws IOException {
+ if (bundleURLs == null) {
+ log("Entering DownloadManager.getBundleURLs");
+ String base = getBaseDownloadURL();
+ String url = appendTransactionId(base);
+ // use PID instead of createTempFile or other random filename so as
+ // to avoid dependencies on the random number generator libraries
+ File bundlePath = null;
+ // write temp file to locallow directory on vista
+ if (isWindowsVista()) {
+ bundlePath = getLocalLowTempBundlePath();
+ } else {
+ bundlePath = getBundlePath();
+ }
+ File tmp = new File(bundlePath, "urls." + getCurrentProcessId() +
+ ".properties");
+ try {
+ log("Downloading from " + url + " to " + tmp);
+ downloadFromURL(url, tmp, "", showUI);
+ bundleURLs = new Properties();
+ if (tmp.exists()) {
+ addToTotalDownloadSize((int) tmp.length()); // better late than never
+ InputStream in = new FileInputStream(tmp);
+ in = new BufferedInputStream(in);
+ bundleURLs.load(in);
+ in.close();
+ if (bundleURLs.isEmpty()) {
+ fatalError(ERROR_MALFORMED_BUNDLE_PROPERTIES);
+ }
+ } else {
+ fatalError(ERROR_DOWNLOADING_BUNDLE_PROPERTIES);
+ }
+ } finally {
+ // delete the temp file
+ if (!debug)
+ tmp.delete();
+ }
+ log("Leaving DownloadManager.getBundleURLs");
+ // else an error occurred and user chose not to retry; leave
+ // bundleURLs empty so we don't continually try to re-download it
+ }
+ return bundleURLs;
+ }
+
+ /**
+ * Checks to see if the specified resource is part of a bundle, and if so
+ * downloads it. Returns either a string which should be added to the boot
+ * class path (the newly-downloaded JAR's location), or null to indicate
+ * that it isn't one of the JRE's resources or could not be downloaded.
+ */
+ public static String getBootClassPathEntryForResource(
+ final String resourceName) {
+ if (debug)
+ log("Entering getBootClassPathEntryForResource(" + resourceName + ")");
+ if (isJREComplete() || downloading == null ||
+ resourceName.startsWith("sun/jkernel")) {
+ if (debug)
+ log("Bailing: " + isJREComplete() + ", " + (downloading == null));
+ return null;
+ }
+ incrementDownloadCount();
+ try {
+ String result = (String) AccessController.doPrivileged(
+ new PrivilegedAction() {
+ public Object run() {
+ return (String) doGetBootClassPathEntryForResource(
+ resourceName);
+ }
+ }
+ );
+ log("getBootClassPathEntryForResource(" + resourceName + ") == " + result);
+ return result;
+ }
+ finally {
+ decrementDownloadCount();
+ }
+ }
+
+
+ /**
+ * Called by the boot class loader when it encounters a class it can't find.
+ * This method will check to see if the class is part of a bundle, and if so
+ * download it. Returns either a string which should be added to the boot
+ * class path (the newly-downloaded JAR's location), or null to indicate
+ * that it isn't one of the JRE's classes or could not be downloaded.
+ */
+ public static String getBootClassPathEntryForClass(final String className) {
+ return getBootClassPathEntryForResource(className.replace('.', '/') +
+ ".class");
+ }
+
+
+ private static boolean doDownloadFile(String relativePath)
+ throws IOException {
+ Bundle bundle = getBundleForFile(relativePath);
+ if (bundle != null) {
+ bundle.queueDependencies(true);
+ log("On-demand downloading " + bundle.getName() +
+ " for file " + relativePath + "...");
+ bundle.install();
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Locates the bundle for the specified JRE file (e.g. "bin/awt.dll") and
+ * installs it. Returns true if the file is indeed part of the JRE and has
+ * now been installed, false if the file is not part of the JRE, and throws
+ * an IOException if the file is part of the JRE but could not be
+ * downloaded.
+ */
+ public static boolean downloadFile(final String relativePath)
+ throws IOException {
+ if (isJREComplete() || downloading == null)
+ return false;
+
+ incrementDownloadCount();
+ try {
+ Object result =
+ AccessController.doPrivileged(new PrivilegedAction() {
+ public Object run() {
+ File path = new File(JAVA_HOME,
+ relativePath.replace('/', File.separatorChar));
+ if (path.exists())
+ return true;
+ try {
+ return new Boolean(doDownloadFile(relativePath));
+ }
+ catch (IOException e) {
+ return e;
+ }
+ }
+ });
+ if (result instanceof Boolean)
+ return ((Boolean) result).booleanValue();
+ else
+ throw (IOException) result;
+ }
+ finally {
+ decrementDownloadCount();
+ }
+ }
+
+
+ // increments the counter that tracks whether the current thread is involved
+ // in any download-related activities. A non-zero count indicates that the
+ // thread is currently downloading or installing a bundle.
+ static void incrementDownloadCount() {
+ downloading.set(downloading.get() + 1);
+ }
+
+
+ // increments the counter that tracks whether the current thread is involved
+ // in any download-related activities. A non-zero count indicates that the
+ // thread is currently downloading or installing a bundle.
+ static void decrementDownloadCount() {
+ // will generate an exception if incrementDownloadCount() hasn't been
+ // called first, this is intentional
+ downloading.set(downloading.get() - 1);
+ }
+
+
+ /**
+ * Returns <code>true</code> if the current thread is in the process of
+ * downloading a bundle. This is called by ClassLoader.loadLibrary(), so
+ * that when we run into a library required by the download process itself,
+ * we don't call back into DownloadManager in an attempt to download it
+ * (which would lead to infinite recursion).
+ *
+ * All classes and libraries required to download classes must by
+ * definition already be present. So if this method returns true, we are
+ * currently in the middle of performing a download, and the class or
+ * library load must be happening due to the download itself. We can
+ * immediately abort such requests -- the class or library should already
+ * be present. If it isn't, we're not going to be able to download it,
+ * since we have just established that it is required to perform a
+ * download, and we might as well just let the NoClassDefFoundError /
+ * UnsatisfiedLinkError occur.
+ */
+ public static boolean isCurrentThreadDownloading() {
+ return downloading != null ? downloading.get() > 0 : false;
+ }
+
+
+ /**
+ * Returns true if everything is downloaded and the JRE has been
+ * reconstructed. Also returns true if kernel functionality is disabled
+ * for any other reason.
+ */
+ public static boolean isJREComplete() {
+ return complete;
+ }
+
+
+ // called by BackgroundDownloader
+ static void doBackgroundDownloads(boolean showProgress) {
+ if (!complete) {
+ if (!showProgress && !debug)
+ reportErrors = false;
+ try {
+ // install swing first for ergonomic reasons
+ Bundle swing = Bundle.getBundle("javax_swing_core");
+ if (!swing.isInstalled())
+ swing.install(showProgress, false, false);
+ // install remaining bundles
+ for (String name : getCriticalBundleNames()) {
+ Bundle bundle = Bundle.getBundle(name);
+ if (!bundle.isInstalled()) {
+ bundle.install(showProgress, false, true);
+ }
+ }
+ shutdown();
+ }
+ catch (IOException e) {
+ log(e);
+ }
+ }
+ }
+
+ // copy receipt file to destination path specified
+ static void copyReceiptFile(File from, File to) throws IOException {
+ DataInputStream in = new DataInputStream(
+ new BufferedInputStream(new FileInputStream(from)));
+ OutputStream out = new FileOutputStream(to);
+ String line = in.readLine();
+ while (line != null) {
+ out.write((line + '\n').getBytes("utf-8"));
+ line = in.readLine();
+ }
+ in.close();
+ out.close();
+ }
+
+
+ private static void downloadRequestedBundles() {
+ log("Checking for requested bundles...");
+ try {
+ File list = new File(JAVA_HOME, REQUESTED_BUNDLES_PATH);
+ if (list.exists()) {
+ FileInputStream in = new FileInputStream(list);
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ send(in, buffer);
+ in.close();
+
+ // split string manually to avoid relying on regexes or
+ // StringTokenizer
+ String raw = new String(buffer.toByteArray(), "utf-8");
+ List/*<String>*/ bundles = new ArrayList/*<String>*/();
+ StringBuilder token = new StringBuilder();
+ for (int i = 0; i < raw.length(); i++) {
+ char c = raw.charAt(i);
+ if (c == ',' || Character.isWhitespace(c)) {
+ if (token.length() > 0) {
+ bundles.add(token.toString());
+ token.setLength(0);
+ }
+ }
+ else
+ token.append(c);
+ }
+ if (token.length() > 0)
+ bundles.add(token.toString());
+ log("Requested bundles: " + bundles);
+ for (int i = 0; i < bundles.size(); i++) {
+ Bundle bundle = Bundle.getBundle((String) bundles.get(i));
+ if (bundle != null && !bundle.isInstalled()) {
+ log("Downloading " + bundle + " due to requested.list");
+ bundle.install(true, false, false);
+ }
+ }
+ }
+ }
+ catch (IOException e) {
+ log(e);
+ }
+ }
+
+
+ static void fatalError(int code) {
+ fatalError(code, null);
+ }
+
+
+ /**
+ * Called to cleanly shut down the VM when a fatal download error has
+ * occurred. Calls System.exit() if outside of the Java Plug-In, otherwise
+ * throws an error.
+ */
+ static void fatalError(int code, String arg) {
+ sendErrorPing(code);
+
+ for (int i = 0; i < Bundle.THREADS; i++)
+ bundleInstallComplete();
+ if (reportErrors)
+ displayError(code, arg);
+ // inPlugIn check isn't 100% reliable but should be close enough.
+ // headless is for the browser side of things in the out-of-process
+ // plug-in
+ boolean inPlugIn = (Boolean.getBoolean("java.awt.headless") ||
+ System.getProperty("javaplugin.version") != null);
+ KernelError error = new KernelError("Java Kernel bundle download failed");
+ if (inPlugIn)
+ throw error;
+ else {
+ log(error);
+ System.exit(1);
+ }
+ }
+
+
+ // start the background download process using the jbroker broker process
+ // the method will first launch the broker process, if it is not already
+ // running
+ // it will then send the command necessary to start the background download
+ // process to the broker process
+ private static void startBackgroundDownloadWithBroker() {
+
+ if (!BackgroundDownloader.getBackgroundDownloadProperty()) {
+ // If getBackgroundDownloadProperty() returns false
+ // we're doing the downloads from this VM; we don't want to
+ // spawn another one
+ return;
+ }
+
+ // launch broker process if necessary
+ if (!launchBrokerProcess()) {
+ return;
+ }
+
+
+ String kernelDownloadURLProperty = getBaseDownloadURL();
+
+ String kernelDownloadURL;
+
+ // only set KERNEL_DOWNLOAD_URL_PROPERTY if we override
+ // the default download url
+ if (kernelDownloadURLProperty == null ||
+ kernelDownloadURLProperty.equals(DEFAULT_DOWNLOAD_URL)) {
+ kernelDownloadURL = " ";
+ } else {
+ kernelDownloadURL = kernelDownloadURLProperty;
+ }
+
+ startBackgroundDownloadWithBrokerImpl(kernelDownloadURLProperty);
+ }
+
+ private static void startBackgroundDownloads() {
+ if (!complete) {
+ if (BackgroundDownloader.getBackgroundMutex().acquire(0)) {
+ // we don't actually need to hold the mutex -- it was just a
+ // quick check to see if there is any point in even attempting
+ // to start the background downloader
+ BackgroundDownloader.getBackgroundMutex().release();
+ if (isWindowsVista()) {
+ // use broker process to start background download
+ // at high integrity
+ startBackgroundDownloadWithBroker();
+ } else {
+ BackgroundDownloader.startBackgroundDownloads();
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Increases the total download size displayed in the download progress
+ * dialog.
+ */
+ static native void addToTotalDownloadSize(int size);
+
+
+ /**
+ * Displays a progress dialog while downloading from the specified URL.
+ *
+ *@param url the URL string from which to download
+ *@param file the destination path
+ *@param name the user-visible name of the component we are downloading
+ */
+ static void downloadFromURL(String url, File file, String name,
+ boolean showProgress) {
+ // do not show download dialog if kernel.download.dialog is false
+ downloadFromURLImpl(url, file, name,
+ disableDownloadDialog ? false : showProgress);
+ }
+
+ private static native void downloadFromURLImpl(String url, File file,
+ String name, boolean showProgress);
+
+ // This is for testing purposes only - allows to specify URL
+ // to download kernel bundles from through the registry key.
+ static native String getUrlFromRegistry();
+
+ static native String getVisitorId0();
+
+ static native void postDownloadComplete();
+
+ static native void postDownloadError(int code);
+
+ // Returns the visitor ID set by the installer, will be sent to the server
+ // during bundle downloads for logging purposes.
+ static synchronized String getVisitorId() {
+ if (!visitorIdDetermined) {
+ visitorIdDetermined = true;
+ visitorId = getVisitorId0();
+ }
+ return visitorId;
+ }
+
+ // display an error message using a native dialog
+ public static native void displayError(int code, String arg);
+
+ // prompt user whether to retry download, or quit
+ // returns true if the user chose to retry
+ public static native boolean askUserToRetryDownloadOrQuit(int code);
+
+ // returns true if we are running Windows Vista; false otherwise
+ static native boolean isWindowsVista();
+
+ private static native void startBackgroundDownloadWithBrokerImpl(
+ String command);
+
+ private static int isJBrokerStarted() {
+ if (_isJBrokerStarted == -1) {
+ // initialize state of jbroker
+ _isJBrokerStarted = isJBrokerRunning() ? 1 : 0;
+ }
+ return _isJBrokerStarted;
+ }
+
+ // returns true if broker process (jbroker) is running; false otherwise
+ private static native boolean isJBrokerRunning();
+
+ // returns true if we are running in IE protected mode; false otherwise
+ private static native boolean isIEProtectedMode();
+
+ private static native boolean launchJBroker(String jbrokerPath);
+
+ static native void bundleInstallStart();
+
+ static native void bundleInstallComplete();
+
+ private static native boolean moveFileWithBrokerImpl(String fromPath,
+ String userHome);
+
+ private static native boolean moveDirWithBrokerImpl(String fromPath,
+ String userHome);
+
+ static boolean moveFileWithBroker(String fromPath) {
+ // launch jbroker if necessary
+ if (!launchBrokerProcess()) {
+ return false;
+ }
+
+ return moveFileWithBrokerImpl(fromPath, USER_HOME);
+ }
+
+ static boolean moveDirWithBroker(String fromPath) {
+ // launch jbroker if necessary
+ if (!launchBrokerProcess()) {
+ return false;
+ }
+
+ return moveDirWithBrokerImpl(fromPath, USER_HOME);
+ }
+
+ private static synchronized boolean launchBrokerProcess() {
+ // launch jbroker if necessary
+ if (isJBrokerStarted() == 0) {
+ // launch jbroker if needed
+ boolean ret = launchJBroker(JAVA_HOME);
+ // set state of jbroker
+ _isJBrokerStarted = ret ? 1 : 0;
+ return ret;
+ }
+ return true;
+ }
+
+ private static class StreamMonitor implements Runnable {
+ private InputStream istream;
+ public StreamMonitor(InputStream stream) {
+ istream = new BufferedInputStream(stream);
+ new Thread(this).start();
+ }
+ public void run() {
+ byte[] buffer = new byte[4096];
+ try {
+ int ret = istream.read(buffer);
+ while (ret != -1) {
+ ret = istream.read(buffer);
+ }
+ } catch (IOException e) {
+ try {
+ istream.close();
+ } catch (IOException e2) {
+ } // Should allow clean exit when process shuts down
+ }
+ }
+ }
+
+
+ /** Copy a file tree, excluding certain named files. */
+ private static void copyAll(File src, File dest, Set/*<String>*/ excludes)
+ throws IOException {
+ if (!excludes.contains(src.getName())) {
+ if (src.isDirectory()) {
+ File[] children = src.listFiles();
+ if (children != null) {
+ for (int i = 0; i < children.length; i++)
+ copyAll(children[i],
+ new File(dest, children[i].getName()),
+ excludes);
+ }
+ }
+ else {
+ dest.getParentFile().mkdirs();
+ FileInputStream in = new FileInputStream(src);
+ FileOutputStream out = new FileOutputStream(dest);
+ send(in, out);
+ in.close();
+ out.close();
+ }
+ }
+ }
+
+
+ public static void dumpOutput(final Process p) {
+ Thread outputReader = new Thread("outputReader") {
+ public void run() {
+ try {
+ InputStream in = p.getInputStream();
+ DownloadManager.send(in, System.out);
+ } catch (IOException e) {
+ log(e);
+ }
+ }
+ };
+ outputReader.start();
+ Thread errorReader = new Thread("errorReader") {
+ public void run() {
+ try {
+ InputStream in = p.getErrorStream();
+ DownloadManager.send(in, System.err);
+ } catch (IOException e) {
+ log(e);
+ }
+ }
+ };
+ errorReader.start();
+ }
+
+
+ /**
+ * Creates the merged rt.jar and resources.jar files.
+ */
+ private static void createMergedJars() {
+ log("DownloadManager.createMergedJars");
+ File bundlePath;
+ if (isWindowsVista()) {
+ bundlePath = getLocalLowTempBundlePath();
+ } else {
+ bundlePath = getBundlePath();
+ }
+ File tmp = new File(bundlePath, "tmp");
+ // explicitly check the final location, not the (potentially) local-low
+ // location -- a local-low finished isn't good enough to call it done
+ if (new File(getBundlePath(), "tmp" + File.separator + "finished").exists())
+ return; // already done
+ log("DownloadManager.createMergedJars: running");
+ tmp.mkdirs();
+ boolean retry = false;
+ do {
+ try {
+ Bundle.getBundle("merged").install(false, false, true);
+ postDownloadComplete();
+ // done, write an empty "finished" file to flag completion
+ File finished = new File(tmp, "finished");
+ new FileOutputStream(finished).close();
+ if (isWindowsVista()) {
+ if (!moveFileWithBroker(getKernelJREDir() +
+ "-bundles\\tmp\\finished")) {
+ throw new IOException("unable to create 'finished' file");
+ }
+ }
+ log("DownloadManager.createMergedJars: created " + finished);
+ // next JRE startup will move these files into their final
+ // locations, as long as no other JREs are running
+
+ // clean up the local low bundle directory on vista
+ if (isWindowsVista()) {
+ File tmpDir = getLocalLowTempBundlePath();
+ File[] list = tmpDir.listFiles();
+ if (list != null) {
+ for (int i = 0; i < list.length; i++) {
+ list[i].delete();
+ }
+ }
+ tmpDir.delete();
+ log("Finished cleanup, " + tmpDir + ".exists(): " + tmpDir.exists());
+ }
+ }
+ catch (IOException e) {
+ log(e);
+ }
+ }
+ while (retry);
+ log("DownloadManager.createMergedJars: finished");
+ }
+
+
+ private static void shutdown() {
+ try {
+ ExecutorService e = Bundle.getThreadPool();
+ e.shutdown();
+ e.awaitTermination(60 * 60 * 24, TimeUnit.SECONDS);
+ }
+ catch (InterruptedException e) {
+ }
+ }
+
+
+ // returns the registry key for kernel.debug
+ static native boolean getDebugKey();
+
+
+ // returns the final value for the kernel debug property
+ public static boolean getDebugProperty(){
+ /*
+ * Check registry key value
+ */
+ boolean debugEnabled = getDebugKey();
+
+ /*
+ * Check system property - it should override the registry
+ * key value.
+ */
+ if (System.getProperty(KERNEL_DEBUG_PROPERTY) != null) {
+ debugEnabled = Boolean.valueOf(
+ System.getProperty(KERNEL_DEBUG_PROPERTY));
+ }
+ return debugEnabled;
+
+ }
+
+
+ /**
+ * Outputs to the error stream even when System.err has not yet been
+ * initialized.
+ */
+ static void println(String msg) {
+ if (System.err != null)
+ System.err.println(msg);
+ else {
+ try {
+ if (errorStream == null)
+ errorStream = new FileOutputStream(FileDescriptor.err);
+ errorStream.write((msg +
+ System.getProperty("line.separator")).getBytes("utf-8"));
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+
+ static void log(String msg) {
+ if (debug) {
+ println(msg);
+ try {
+ if (logStream == null) {
+ loadJKernelLibrary();
+ File path = isWindowsVista() ? getLocalLowTempBundlePath() :
+ getBundlePath();
+ path = new File(path, "kernel." + getCurrentProcessId() + ".log");
+ logStream = new FileOutputStream(path);
+ }
+ logStream.write((msg +
+ System.getProperty("line.separator")).getBytes("utf-8"));
+ logStream.flush();
+ }
+ catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+
+ static void log(Throwable e) {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ PrintStream p = new PrintStream(buffer);
+ e.printStackTrace(p);
+ p.close();
+ log(buffer.toString(0));
+ }
+
+
+ /** Dump the contents of a map to System.out. */
+ private static void printMap(Map/*<String, String>*/ map) {
+ int size = 0;
+ Set<Integer> identityHashes = new HashSet<Integer>();
+ Iterator/*<Map.Entry<String, String>>*/ i = map.entrySet().iterator();
+ while (i.hasNext()) {
+ Map.Entry/*<String, String>*/ e = (Map.Entry) i.next();
+ String key = (String) e.getKey();
+ String value = (String) e.getValue();
+ System.out.println(key + ": " + value);
+ Integer keyHash = Integer.valueOf(System.identityHashCode(key));
+ if (!identityHashes.contains(keyHash)) {
+ identityHashes.add(keyHash);
+ size += key.length();
+ }
+ Integer valueHash = Integer.valueOf(System.identityHashCode(value));
+ if (!identityHashes.contains(valueHash)) {
+ identityHashes.add(valueHash);
+ size += value.length();
+ }
+ }
+ System.out.println(size + " bytes");
+ }
+
+
+ /** Process the "-dumpmaps" command-line argument. */
+ private static void dumpMaps() throws IOException {
+ System.out.println("Resources:");
+ System.out.println("----------");
+ printMap(getResourceMap());
+ System.out.println();
+ System.out.println("Files:");
+ System.out.println("----------");
+ printMap(getFileMap());
+ }
+
+
+ /** Process the "-download" command-line argument. */
+ private static void processDownload(String bundleName) throws IOException {
+ if (bundleName.equals("all")) {
+ debug = true;
+ doBackgroundDownloads(true);
+ performCompletionIfNeeded();
+ }
+ else {
+ Bundle bundle = Bundle.getBundle(bundleName);
+ if (bundle == null) {
+ println("Unknown bundle: " + bundleName);
+ System.exit(1);
+ }
+ else
+ bundle.install();
+ }
+ }
+
+
+ static native int getCurrentProcessId();
+
+
+ public static void main(String[] arg) throws Exception {
+ AccessController.checkPermission(new AllPermission());
+
+ boolean valid = false;
+ if (arg.length == 2 && arg[0].equals("-install")) {
+ valid = true;
+ Bundle bundle = new Bundle() {
+ protected void updateState() {
+ // the bundle path was provided on the command line, so we
+ // just claim it has already been "downloaded" to the local
+ // filesystem
+ state = DOWNLOADED;
+ }
+ };
+
+ File jarPath;
+ int index = 0;
+ do {
+ index++;
+ jarPath = new File(getBundlePath(),
+ CUSTOM_PREFIX + index + ".jar");
+ }
+ while (jarPath.exists());
+ bundle.setName(CUSTOM_PREFIX + index);
+ bundle.setLocalPath(new File(arg[1]));
+ bundle.setJarPath(jarPath);
+ bundle.setDeleteOnInstall(false);
+ bundle.install();
+ }
+ else if (arg.length == 2 && arg[0].equals("-download")) {
+ valid = true;
+ processDownload(arg[1]);
+ }
+ else if (arg.length == 1 && arg[0].equals("-dumpmaps")) {
+ valid = true;
+ dumpMaps();
+ }
+ else if (arg.length == 2 && arg[0].equals("-sha1")) {
+ valid = true;
+ System.out.println(BundleCheck.getInstance(new File(arg[1])));
+ }
+ else if (arg.length == 1 && arg[0].equals("-downloadtest")) {
+ valid = true;
+ File file = File.createTempFile("download", ".test");
+ for (;;) {
+ file.delete();
+ downloadFromURL(getBaseDownloadURL(), file, "URLS", true);
+ System.out.println("Downloaded " + file.length() + " bytes");
+ }
+ }
+ if (!valid) {
+ System.out.println("usage: DownloadManager -install <path>.zip |");
+ System.out.println(" DownloadManager -download " +
+ "<bundle_name> |");
+ System.out.println(" DownloadManager -dumpmaps");
+ System.exit(1);
+ }
+ }
+}