diff options
author | Andrew Niefer | 2006-12-05 22:10:36 +0000 |
---|---|---|
committer | Andrew Niefer | 2006-12-05 22:10:36 +0000 |
commit | f39c77a020b5dc7484945ecd8bc718b0d61ced21 (patch) | |
tree | 2234e836dec32975e18394c26647f3e79be52cd4 /bundles | |
parent | bbfad4091158b2b32677ff1e8b66130e5f96fa8a (diff) | |
download | rt.equinox.framework-f39c77a020b5dc7484945ecd8bc718b0d61ced21.tar.gz rt.equinox.framework-f39c77a020b5dc7484945ecd8bc718b0d61ced21.tar.xz rt.equinox.framework-f39c77a020b5dc7484945ecd8bc718b0d61ced21.zip |
startup.jar code moving from equinox-incubator/org.eclipse.equinox.startup
Diffstat (limited to 'bundles')
10 files changed, 2683 insertions, 3 deletions
diff --git a/bundles/org.eclipse.equinox.launcher/.classpath b/bundles/org.eclipse.equinox.launcher/.classpath new file mode 100644 index 000000000..751c8f2e5 --- /dev/null +++ b/bundles/org.eclipse.equinox.launcher/.classpath @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/bundles/org.eclipse.equinox.launcher/.settings/org.eclipse.cdt.core.prefs b/bundles/org.eclipse.equinox.launcher/.settings/org.eclipse.cdt.core.prefs deleted file mode 100644 index a44ad7e23..000000000 --- a/bundles/org.eclipse.equinox.launcher/.settings/org.eclipse.cdt.core.prefs +++ /dev/null @@ -1,3 +0,0 @@ -#Mon Nov 13 14:48:49 EST 2006 -eclipse.preferences.version=1 -indexerId=org.eclipse.cdt.core.fastIndexer diff --git a/bundles/org.eclipse.equinox.launcher/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.equinox.launcher/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..9db76a606 --- /dev/null +++ b/bundles/org.eclipse.equinox.launcher/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +#Fri Nov 24 13:39:42 EST 2006 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=disabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.4 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=warning +org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning +org.eclipse.jdt.core.compiler.source=1.3 diff --git a/bundles/org.eclipse.equinox.launcher/.settings/org.eclipse.pde.core.prefs b/bundles/org.eclipse.equinox.launcher/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 000000000..fe921f9c4 --- /dev/null +++ b/bundles/org.eclipse.equinox.launcher/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,4 @@ +#Mon Nov 13 11:00:38 EST 2006 +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/bundles/org.eclipse.equinox.launcher/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.launcher/META-INF/MANIFEST.MF new file mode 100644 index 000000000..e0cec4b99 --- /dev/null +++ b/bundles/org.eclipse.equinox.launcher/META-INF/MANIFEST.MF @@ -0,0 +1,9 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Startup Plug-in +Bundle-SymbolicName: org.eclipse.equinox.launcher +Bundle-Version: 1.0.0.qualifier +Main-Class: org.eclipse.core.launcher.Main +Bundle-RequiredExecutionEnvironment: J2SE-1.4, + CDC-1.0/Foundation-1.0, + J2SE-1.3 diff --git a/bundles/org.eclipse.equinox.launcher/build.properties b/bundles/org.eclipse.equinox.launcher/build.properties new file mode 100644 index 000000000..34d2e4d2d --- /dev/null +++ b/bundles/org.eclipse.equinox.launcher/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/bundles/org.eclipse.equinox.launcher/src/org/eclipse/equinox/launcher/JNIBridge.java b/bundles/org.eclipse.equinox.launcher/src/org/eclipse/equinox/launcher/JNIBridge.java new file mode 100644 index 000000000..9d923c924 --- /dev/null +++ b/bundles/org.eclipse.equinox.launcher/src/org/eclipse/equinox/launcher/JNIBridge.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.launcher; + + +/** + * @author aniefer + * + */ +public class JNIBridge { + private native void _set_exit_data(String data); + private native void _update_splash(); + private native long _get_splash_handle(); + private native void _show_splash(String bitmap); + private native void _takedown_splash(); + + private String library; + private boolean libraryLoaded = false; + public JNIBridge(String library) { + this.library = library; + } + + private void loadLibrary() { + if(library != null) { + try { + System.load(library); + } catch (UnsatisfiedLinkError e ) { + //failed + } + } + libraryLoaded = true; + } + + public boolean setExitData(String data) { + try { + _set_exit_data(data); + return true; + } catch (UnsatisfiedLinkError e) { + if(!libraryLoaded){ + loadLibrary(); + return setExitData(data); + } + return false; + } + } + + public boolean showSplash(String bitmap) { + try { + _show_splash(bitmap); + return true; + } catch (UnsatisfiedLinkError e) { + if(!libraryLoaded){ + loadLibrary(); + return showSplash(bitmap); + } + return false; + } + } + + public boolean updateSplash() { + try { + _update_splash(); + return true; + } catch (UnsatisfiedLinkError e) { + if(!libraryLoaded){ + loadLibrary(); + return updateSplash(); + } + return false; + } + } + + public long getSplashHandle() { + try { + return _get_splash_handle(); + } catch (UnsatisfiedLinkError e) { + if(!libraryLoaded){ + loadLibrary(); + return getSplashHandle(); + } + return -1; + } + } + + public boolean takeDownSplash() { + try { + _takedown_splash(); + return true; + } catch (UnsatisfiedLinkError e) { + if(!libraryLoaded){ + loadLibrary(); + return takeDownSplash(); + } + return false; + } + } +} diff --git a/bundles/org.eclipse.equinox.launcher/src/org/eclipse/equinox/launcher/Main.java b/bundles/org.eclipse.equinox.launcher/src/org/eclipse/equinox/launcher/Main.java new file mode 100644 index 000000000..90b759aca --- /dev/null +++ b/bundles/org.eclipse.equinox.launcher/src/org/eclipse/equinox/launcher/Main.java @@ -0,0 +1,2229 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.launcher; + +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.*; +import java.security.*; +import java.util.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * The launcher for Eclipse. + */ +public class Main { + /** + * Indicates whether this instance is running in debug mode. + */ + protected boolean debug = false; + + /** + * The location of the launcher to run. + */ + protected String bootLocation = null; + + /** + * The location of the install root + */ + protected URL installLocation = null; + + /** + * The location of the configuration information for this instance + */ + protected URL configurationLocation = null; + + /** + * The location of the configuration information in the install root + */ + protected String parentConfigurationLocation = null; + + /** + * The id of the bundle that will contain the framework to run. Defaults to org.eclipse.osgi. + */ + protected String framework = OSGI; + + /** + * The extra development time class path entries for the framework. + */ + protected String devClassPath = null; + + /* + * The extra development time class path entries for all bundles. + */ + private Properties devClassPathProps = null; + + /** + * Indicates whether this instance is running in development mode. + */ + protected boolean inDevelopmentMode = false; + + /** + * Indicates which OS was passed in with -os + */ + protected String os = null; + + private String name = null; // The name to brand the launcher + private String launcher = null; // The full path to the launcher + + private String vm = null; + private String[] vmargs = null; + private String[] commands = null; + private String[] extensionPaths = null; + + private JNIBridge bridge = null; + + // splash handling + private boolean showSplash = false; + private String endSplash = null; + private boolean initialize = false; + private boolean splashDown = false; + public final class SplashHandler extends Thread { + public void run() { + takeDownSplash(); + } + public void updateSplash() { + if(bridge != null) { + bridge.updateSplash(); + } + } + } + private final Thread splashHandler = new SplashHandler(); + + // command line args + private static final String FRAMEWORK = "-framework"; //$NON-NLS-1$ + private static final String INSTALL = "-install"; //$NON-NLS-1$ + private static final String INITIALIZE = "-initialize"; //$NON-NLS-1$ + private static final String VM = "-vm"; //$NON-NLS-1$ + private static final String VMARGS = "-vmargs"; //$NON-NLS-1$ + private static final String DEBUG = "-debug"; //$NON-NLS-1$ + private static final String DEV = "-dev"; //$NON-NLS-1$ + private static final String CONFIGURATION = "-configuration"; //$NON-NLS-1$ + private static final String NOSPLASH = "-nosplash"; //$NON-NLS-1$ + private static final String SHOWSPLASH = "-showsplash"; //$NON-NLS-1$ + private static final String NAME = "-name"; //$NON-NLS-1$ + private static final String LAUNCHER = "-launcher"; //$NON-NLS-1$ + private static final String NL = "-nl"; //$NON-NLS-1$ + private static final String ENDSPLASH = "-endsplash"; //$NON-NLS-1$ + private static final String SPLASH_IMAGE = "splash.bmp"; //$NON-NLS-1$ + private static final String CLEAN = "-clean"; //$NON-NLS-1$ + private static final String NOEXIT = "-noExit"; //$NON-NLS-1$ + private static final String OS = "-os"; //$NON-NLS-1$ + + private static final String OSGI = "org.eclipse.osgi"; //$NON-NLS-1$ + private static final String STARTER = "org.eclipse.core.runtime.adaptor.EclipseStarter"; //$NON-NLS-1$ + private static final String PLATFORM_URL = "platform:/base/"; //$NON-NLS-1$ + private static final String ECLIPSE_PROPERTIES = "eclipse.properties"; //$NON-NLS-1$ + private static final String FILE_SCHEME = "file:"; //$NON-NLS-1$ + protected static final String REFERENCE_SCHEME = "reference:"; //$NON-NLS-1$ + protected static final String JAR_SCHEME = "jar:"; //$NON-NLS-1$ + + private static final String DEFAULT_JRE_REQUIRED = "1.4.1"; //$NON-NLS-1$ + + // constants: configuration file location + private static final String CONFIG_DIR = "configuration/"; //$NON-NLS-1$ + private static final String CONFIG_FILE = "config.ini"; //$NON-NLS-1$ + private static final String CONFIG_FILE_TEMP_SUFFIX = ".tmp"; //$NON-NLS-1$ + private static final String CONFIG_FILE_BAK_SUFFIX = ".bak"; //$NON-NLS-1$ + private static final String ECLIPSE = "eclipse"; //$NON-NLS-1$ + private static final String PRODUCT_SITE_MARKER = ".eclipseproduct"; //$NON-NLS-1$ + private static final String PRODUCT_SITE_ID = "id"; //$NON-NLS-1$ + private static final String PRODUCT_SITE_VERSION = "version"; //$NON-NLS-1$ + + // constants: System property keys and/or configuration file elements + private static final String PROP_USER_HOME = "user.home"; //$NON-NLS-1$ + private static final String PROP_USER_DIR = "user.dir"; //$NON-NLS-1$ + private static final String PROP_INSTALL_AREA = "osgi.install.area"; //$NON-NLS-1$ + private static final String PROP_CONFIG_AREA = "osgi.configuration.area"; //$NON-NLS-1$ + private static final String PROP_CONFIG_AREA_DEFAULT = "osgi.configuration.area.default"; //$NON-NLS-1$ + private static final String PROP_BASE_CONFIG_AREA = "osgi.baseConfiguration.area"; //$NON-NLS-1$ + private static final String PROP_SHARED_CONFIG_AREA = "osgi.sharedConfiguration.area"; //$NON-NLS-1$ + private static final String PROP_CONFIG_CASCADED = "osgi.configuration.cascaded"; //$NON-NLS-1$ + protected static final String PROP_FRAMEWORK = "osgi.framework"; //$NON-NLS-1$ + private static final String PROP_SPLASHPATH = "osgi.splashPath"; //$NON-NLS-1$ + private static final String PROP_SPLASHLOCATION = "osgi.splashLocation"; //$NON-NLS-1$ + private static final String PROP_CLASSPATH = "osgi.frameworkClassPath"; //$NON-NLS-1$ + private static final String PROP_EXTENSIONS = "osgi.framework.extensions"; //$NON-NLS-1$ + private static final String PROP_FRAMEWORK_SYSPATH = "osgi.syspath"; //$NON-NLS-1$ + private static final String PROP_FRAMEWORK_SHAPE = "osgi.framework.shape"; //$NON-NLS-1$ + private static final String PROP_LOGFILE = "osgi.logfile"; //$NON-NLS-1$ + private static final String PROP_REQUIRED_JAVA_VERSION = "osgi.requiredJavaVersion"; //$NON-NLS-1$ + private static final String PROP_PARENT_CLASSLOADER = "osgi.parentClassloader"; //$NON-NLS-1$ + private static final String PROP_NL = "osgi.nl"; //$NON-NLS-1$ + private static final String PROP_NOSHUTDOWN = "osgi.noShutdown"; //$NON-NLS-1$ + private static final String PROP_DEBUG = "osgi.debug"; //$NON-NLS-1$ + + private static final String PROP_EXITCODE = "eclipse.exitcode"; //$NON-NLS-1$ + private static final String PROP_EXITDATA = "eclipse.exitdata"; //$NON-NLS-1$ + + private static final String PROP_VM = "eclipse.vm"; //$NON-NLS-1$ + private static final String PROP_VMARGS = "eclipse.vmargs"; //$NON-NLS-1$ + private static final String PROP_COMMANDS = "eclipse.commands"; //$NON-NLS-1$ + private static final String PROP_ECLIPSESECURITY = "eclipse.security"; //$NON-NLS-1$ + + // Data mode constants for user, configuration and data locations. + private static final String NONE = "@none"; //$NON-NLS-1$ + private static final String NO_DEFAULT = "@noDefault"; //$NON-NLS-1$ + private static final String USER_HOME = "@user.home"; //$NON-NLS-1$ + private static final String USER_DIR = "@user.dir"; //$NON-NLS-1$ + + // types of parent classloaders the framework can have + private static final String PARENT_CLASSLOADER_APP = "app"; //$NON-NLS-1$ + private static final String PARENT_CLASSLOADER_EXT = "ext"; //$NON-NLS-1$ + private static final String PARENT_CLASSLOADER_BOOT = "boot"; //$NON-NLS-1$ + + // log file handling + protected static final String SESSION = "!SESSION"; //$NON-NLS-1$ + protected static final String ENTRY = "!ENTRY"; //$NON-NLS-1$ + protected static final String MESSAGE = "!MESSAGE"; //$NON-NLS-1$ + protected static final String STACK = "!STACK"; //$NON-NLS-1$ + protected static final int ERROR = 4; + protected static final String PLUGIN_ID = "org.eclipse.equinox.launcher"; //$NON-NLS-1$ + protected File logFile = null; + protected BufferedWriter log = null; + protected boolean newSession = true; + + /** + * A structured form for a version identifier. + * + * @see http://java.sun.com/j2se/versioning_naming.html for information on valid version strings + */ + static class Identifier { + private static final String DELIM = ". _-"; //$NON-NLS-1$ + private int major, minor, service; + Identifier(int major, int minor, int service) { + super(); + this.major = major; + this.minor = minor; + this.service = service; + } + /** + * @throws NumberFormatException if cannot parse the major and minor version components + */ + Identifier(String versionString) { + super(); + StringTokenizer tokenizer = new StringTokenizer(versionString, DELIM); + + // major + if (tokenizer.hasMoreTokens()) + major = Integer.parseInt(tokenizer.nextToken()); + + // minor + if (tokenizer.hasMoreTokens()) + minor = Integer.parseInt(tokenizer.nextToken()); + + try { + // service + if (tokenizer.hasMoreTokens()) + service = Integer.parseInt(tokenizer.nextToken()); + } catch (NumberFormatException nfe) { + // ignore the service qualifier in that case and default to 0 + // this will allow us to tolerate other non-conventional version numbers + } + } + /** + * Returns true if this id is considered to be greater than or equal to the given baseline. + * e.g. + * 1.2.9 >= 1.3.1 -> false + * 1.3.0 >= 1.3.1 -> false + * 1.3.1 >= 1.3.1 -> true + * 1.3.2 >= 1.3.1 -> true + * 2.0.0 >= 1.3.1 -> true + */ + boolean isGreaterEqualTo(Identifier minimum) { + if (major < minimum.major) + return false; + if (major > minimum.major) + return true; + // major numbers are equivalent so check minor + if (minor < minimum.minor) + return false; + if (minor > minimum.minor) + return true; + // minor numbers are equivalent so check service + return service >= minimum.service; + } + } + + /** + * Sets up the JNI bridge to native calls + */ + protected void setupJNI() { + URL install = getInstallLocation(); + String location = install.getFile(); + + //Guess a library suffix + String suffix = null; + String osName = os; + if(osName == null) { + osName = System.getProperty("os.name"); //$NON-NLS-1$ + } + //windows is dll, others are so + if(osName.regionMatches(true, 0, "win", 0, 3)) //$NON-NLS-1$ + suffix = ".dll"; //$NON-NLS-1$ + else if(osName.regionMatches(true, 0, "mac", 0, 3)) { + location += name + ".app/Contents/MacOS"; + suffix = ".so"; + + } else + suffix = ".so"; //$NON-NLS-1$ + + String library = searchFor("eclipse", suffix, location); //$NON-NLS-1$ + bridge = new JNIBridge(library); + } + + /** + * Executes the launch. + * + * @param args command-line arguments + * @exception Exception thrown if a problem occurs during the launch + */ + protected void basicRun(String[] args) throws Exception { + System.getProperties().put("eclipse.startTime", Long.toString(System.currentTimeMillis())); //$NON-NLS-1$ + commands = args; + String[] passThruArgs = processCommandLine(args); + + if (!debug) + // debug can be specified as system property as well + debug = System.getProperty(PROP_DEBUG) != null; + setupVMProperties(); + processConfiguration(); + + //ensure minimum Java version + if (!checkVersion(System.getProperty("java.version"), System.getProperty(PROP_REQUIRED_JAVA_VERSION, DEFAULT_JRE_REQUIRED))) //$NON-NLS-1$ + return; + + // need to ensure that getInstallLocation is called at least once to initialize the value. + // Do this AFTER processing the configuration to allow the configuration to set + // the install location. + getInstallLocation(); + + //Set up the JNI bridge. We need to know the install location to find the shared library + setupJNI(); + + // locate boot plugin (may return -dev mode variations) + URL[] bootPath = getBootPath(bootLocation); + setSecurityPolicy(bootPath); + // splash handling is done here, because the default case needs to know + // the location of the boot plugin we are going to use + handleSplash(bootPath); + + invokeFramework(passThruArgs, bootPath); + } + + protected void setSecurityPolicy(URL[] bootPath) { + String eclipseSecurity = System.getProperty(PROP_ECLIPSESECURITY); + if (eclipseSecurity != null) { + SecurityManager sm = System.getSecurityManager(); + boolean setSM = false; + if (sm == null) { + if (eclipseSecurity.length() < 1) { + eclipseSecurity = "java.lang.SecurityManager"; //$NON-NLS-1$ + } + try { + Class clazz = Class.forName(eclipseSecurity); + sm = (SecurityManager) clazz.newInstance(); + setSM = true; + } + catch (Throwable t) { + System.getProperties().put("java.security.manager", eclipseSecurity); // let the framework try to load it later. //$NON-NLS-1$ + } + } + + ProtectionDomain domain = Main.class.getProtectionDomain(); + CodeSource source = null; + if (domain != null) + source = Main.class.getProtectionDomain().getCodeSource(); + if (domain == null || source == null) { + log("Can not automatically set the security manager. Please use a policy file."); //$NON-NLS-1$ + return; + } + // get the list of codesource URLs to grant AllPermission to + URL[] rootURLs = new URL[bootPath.length + 1]; + rootURLs[0] = source.getLocation(); + System.arraycopy(bootPath, 0, rootURLs, 1, bootPath.length); + // replace the security policy + Policy eclipsePolicy = new EclipsePolicy(Policy.getPolicy(), rootURLs); + Policy.setPolicy(eclipsePolicy); + if (setSM) + System.setSecurityManager(sm); + } + } + + private void invokeFramework(String[] passThruArgs, URL[] bootPath) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, Error, Exception, InvocationTargetException { + String type = System.getProperty(PROP_PARENT_CLASSLOADER, PARENT_CLASSLOADER_BOOT); + ClassLoader parent = null; + if (PARENT_CLASSLOADER_APP.equalsIgnoreCase(type)) + parent = ClassLoader.getSystemClassLoader(); + else if (PARENT_CLASSLOADER_EXT.equalsIgnoreCase(type)) { + ClassLoader appCL = ClassLoader.getSystemClassLoader(); + if (appCL != null) + parent = appCL.getParent(); + } + URLClassLoader loader = new StartupClassLoader(bootPath, parent); + Class clazz = loader.loadClass(STARTER); + Method method = clazz.getDeclaredMethod("run", new Class[] {String[].class, Runnable.class}); //$NON-NLS-1$ + try { + method.invoke(clazz, new Object[] {passThruArgs, splashHandler}); + } catch (InvocationTargetException e) { + if (e.getTargetException() instanceof Error) + throw (Error) e.getTargetException(); + else if (e.getTargetException() instanceof Exception) + throw (Exception) e.getTargetException(); + else + //could be a subclass of Throwable! + throw e; + } + } + + /** + * Checks whether the given available version is greater or equal to the + * given required version. + * <p>Will set PROP_EXITCODE/PROP_EXITDATA accordingly if check fails.</p> + * + * @return a boolean indicating whether the checking passed + */ + private boolean checkVersion(String availableVersion, String requiredVersion) { + if (requiredVersion == null || availableVersion == null) + return true; + try { + Identifier required = new Identifier(requiredVersion); + Identifier available = new Identifier(availableVersion); + boolean compatible = available.isGreaterEqualTo(required); + if (!compatible) { + // any non-zero value should do it - 14 used to be used for version incompatibility in Eclipse 2.1 + System.getProperties().put(PROP_EXITCODE, "14"); //$NON-NLS-1$ + System.getProperties().put(PROP_EXITDATA, "<title>Incompatible JVM</title>Version " + availableVersion + " of the JVM is not suitable for this product. Version: "+ requiredVersion + " or greater is required."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return compatible; + } catch (SecurityException e) { + // If the security manager won't allow us to get the system property, continue for + // now and let things fail later on their own if necessary. + return true; + } catch (NumberFormatException e) { + // If the version string was in a format that we don't understand, continue and + // let things fail later on their own if necessary. + return true; + } + } + + /** + * Returns a string representation of the given URL String. This converts + * escaped sequences (%..) in the URL into the appropriate characters. + * NOTE: due to class visibility there is a copy of this method + * in InternalBootLoader + */ + protected String decode(String urlString) { + //try to use Java 1.4 method if available + try { + Class clazz = URLDecoder.class; + Method method = clazz.getDeclaredMethod("decode", new Class[] {String.class, String.class}); //$NON-NLS-1$ + //first encode '+' characters, because URLDecoder incorrectly converts + //them to spaces on certain class library implementations. + if (urlString.indexOf('+') >= 0) { + int len = urlString.length(); + StringBuffer buf = new StringBuffer(len); + for (int i = 0; i < len; i++) { + char c = urlString.charAt(i); + if (c == '+') + buf.append("%2B"); //$NON-NLS-1$ + else + buf.append(c); + } + urlString = buf.toString(); + } + Object result = method.invoke(null, new Object[] {urlString, "UTF-8"}); //$NON-NLS-1$ + if (result != null) + return (String) result; + } catch (Exception e) { + //JDK 1.4 method not found -- fall through and decode by hand + } + //decode URL by hand + boolean replaced = false; + byte[] encodedBytes = urlString.getBytes(); + int encodedLength = encodedBytes.length; + byte[] decodedBytes = new byte[encodedLength]; + int decodedLength = 0; + for (int i = 0; i < encodedLength; i++) { + byte b = encodedBytes[i]; + if (b == '%') { + if(i+2 >= encodedLength) + throw new IllegalArgumentException("Malformed URL (\""+urlString+"\"): % must be followed by 2 digits."); //$NON-NLS-1$//$NON-NLS-2$ + byte enc1 = encodedBytes[++i]; + byte enc2 = encodedBytes[++i]; + b = (byte) ((hexToByte(enc1) << 4) + hexToByte(enc2)); + replaced = true; + } + decodedBytes[decodedLength++] = b; + } + if (!replaced) + return urlString; + try { + return new String(decodedBytes, 0, decodedLength, "UTF-8"); //$NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + //use default encoding + return new String(decodedBytes, 0, decodedLength); + } + } + + /** + * Returns the result of converting a list of comma-separated tokens into an array + * + * @return the array of string tokens + * @param prop the initial comma-separated string + */ + protected String[] getArrayFromList(String prop) { + if (prop == null || prop.trim().equals("")) //$NON-NLS-1$ + return new String[0]; + Vector list = new Vector(); + StringTokenizer tokens = new StringTokenizer(prop, ","); //$NON-NLS-1$ + while (tokens.hasMoreTokens()) { + String token = tokens.nextToken().trim(); + if (!token.equals("")) //$NON-NLS-1$ + list.addElement(token); + } + return list.isEmpty() ? new String[0] : (String[]) list.toArray(new String[list.size()]); + } + + /** + * Returns the <code>URL</code>-based class path describing where the boot classes + * are located when running in development mode. + * + * @return the url-based class path + * @param base the base location + * @exception MalformedURLException if a problem occurs computing the class path + */ + private URL[] getDevPath(URL base) throws IOException { + ArrayList result = new ArrayList(5); + if (inDevelopmentMode) + addDevEntries(base, result, OSGI); + //The jars from the base always need to be added, even when running in dev mode (bug 46772) + addBaseJars(base, result); + return (URL[]) result.toArray(new URL[result.size()]); + } + + URL constructURL(URL url, String name) { + //Recognize the following URLs + //url: file:foo/dir/ + //url: file:foo/file.jar + + String externalForm = url.toExternalForm(); + if (externalForm.endsWith(".jar")) { //$NON-NLS-1$ + try { + return new URL(JAR_SCHEME + url + "!/" + name); //$NON-NLS-1$ + } catch (MalformedURLException e) { + //Ignore + } + } + + try { + return new URL(url, name); + } catch (MalformedURLException e) { + //Ignore + return null; + } + } + private void readFrameworkExtensions(URL base, ArrayList result) throws IOException { + String[] extensions = getArrayFromList(System.getProperties().getProperty(PROP_EXTENSIONS)); + String parent = new File(base.getFile()).getParent().toString(); + ArrayList extensionResults = new ArrayList(extensions.length); + for (int i = 0; i < extensions.length; i++) { + //Search the extension relatively to the osgi plugin + String path = searchFor(extensions[i], parent); + if (path == null) { + log("Could not find extension: " + extensions[i]); //$NON-NLS-1$ + continue; + } + if (debug) + System.out.println("Loading extension: " + extensions[i]); //$NON-NLS-1$ + + URL extensionURL = null; + if (installLocation.getProtocol().equals("file")) { //$NON-NLS-1$ + extensionResults.add(path); + extensionURL = new File(path).toURL(); + } else + extensionURL = new URL(installLocation.getProtocol(), installLocation.getHost(), installLocation.getPort(), path); + + //Load a property file of the extension, merge its content, and in case of dev mode add the bin entries + Properties extensionProperties = null; + try { + extensionProperties = loadProperties(constructURL(extensionURL, ECLIPSE_PROPERTIES)); + } catch (IOException e) { + if (debug) + System.out.println("\t" + ECLIPSE_PROPERTIES + " not found"); //$NON-NLS-1$ //$NON-NLS-2$ + } + String extensionClassPath = null; + if (extensionProperties != null) + extensionClassPath = extensionProperties.getProperty(PROP_CLASSPATH); + else // this is a "normal" RFC 101 framework extension bundle just put the base path on the classpath + extensionProperties = new Properties(); + String[] entries = extensionClassPath == null || extensionClassPath.length() == 0 ? new String[] {""} : getArrayFromList(extensionClassPath); //$NON-NLS-1$ + String qualifiedPath; + if (System.getProperty(PROP_CLASSPATH)==null) + qualifiedPath = "."; //$NON-NLS-1$ + else + qualifiedPath = ""; //$NON-NLS-1$ + for (int j = 0; j < entries.length; j++) + qualifiedPath += ", " + FILE_SCHEME + path + entries[j]; //$NON-NLS-1$ + extensionProperties.put(PROP_CLASSPATH, qualifiedPath); + mergeProperties(System.getProperties(), extensionProperties); + if (inDevelopmentMode) + addDevEntries(extensionURL, result, extensions[i]); + } + extensionPaths = (String[]) extensionResults.toArray(new String[extensionResults.size()]); + } + + private void addBaseJars(URL base, ArrayList result) throws IOException { + String baseJarList = System.getProperty(PROP_CLASSPATH); + if (baseJarList == null) { + readFrameworkExtensions(base, result); + baseJarList = System.getProperties().getProperty(PROP_CLASSPATH); + } + + File fwkFile = new File(base.getFile()); + boolean fwkIsDirectory = fwkFile.isDirectory(); + //We found where the fwk is, remember it and its shape + if (fwkIsDirectory) { + System.getProperties().put(PROP_FRAMEWORK_SHAPE, "folder");//$NON-NLS-1$ + } else { + System.getProperties().put(PROP_FRAMEWORK_SHAPE, "jar");//$NON-NLS-1$ + } + String fwkPath = new File(new File(base.getFile()).getParent()).getAbsolutePath(); + if (Character.isUpperCase(fwkPath.charAt(0))) { + char[] chars = fwkPath.toCharArray(); + chars[0] = Character.toLowerCase(chars[0]); + fwkPath = new String(chars); + } + System.getProperties().put(PROP_FRAMEWORK_SYSPATH, fwkPath); + + String[] baseJars = getArrayFromList(baseJarList); + if (baseJars.length == 0) { + if (!inDevelopmentMode && new File(base.getFile()).isDirectory()) + throw new IOException("Unable to initialize " + PROP_CLASSPATH); //$NON-NLS-1$ + addEntry(base, result); + return; + } + for (int i = 0; i < baseJars.length; i++) { + String string = baseJars[i]; + try { + // if the string is a file: URL then *carefully* construct the + // URL. Otherwisejust try to build a URL. In either case, if we fail, use + // string as something to tack on the end of the base. + + if (string.equals(".")) { //$NON-NLS-1$ + addEntry(base, result); + } + URL url = null; + if (string.startsWith(FILE_SCHEME)) + url = new File(string.substring(5)).toURL(); + else + url = new URL(string); + addEntry(url, result); + } catch (MalformedURLException e) { + addEntry(new URL(base, string), result); + } + } + } + + protected void addEntry(URL url, List result) { + if (new File(url.getFile()).exists()) + result.add(url); + } + + private void addDevEntries(URL base, List result, String symbolicName) throws MalformedURLException { + if (devClassPathProps == null) + return; // do nothing + String devPathList = devClassPathProps.getProperty(symbolicName); + if (devPathList == null) + devPathList = devClassPathProps.getProperty("*"); //$NON-NLS-1$ + String[] locations = getArrayFromList(devPathList); + for (int i = 0; i < locations.length; i++) { + String location = locations[i]; + File path = new File(location); + URL url; + if (path.isAbsolute()) + url = path.toURL(); + else { + // dev path is relative, combine with base location + char lastChar = location.charAt(location.length() - 1); + if ((location.endsWith(".jar") || (lastChar == '/' || lastChar == '\\'))) //$NON-NLS-1$ + url = new URL(base, location); + else + url = new URL(base, location + "/"); //$NON-NLS-1$ + } + addEntry(url, result); + } + } + + /** + * Returns the <code>URL</code>-based class path describing where the boot classes are located. + * + * @return the url-based class path + * @param base the base location + * @exception MalformedURLException if a problem occurs computing the class path + */ + protected URL[] getBootPath(String base) throws IOException { + URL url = null; + if (base != null) { + url = buildURL(base, true); + } else { + // search in the root location + url = getInstallLocation(); + String path = new File(url.getFile(), "plugins").toString(); //$NON-NLS-1$ + path = searchFor(framework, path); + if (path == null) + throw new RuntimeException("Could not find framework"); //$NON-NLS-1$ + if (url.getProtocol().equals("file")) //$NON-NLS-1$ + url = new File(path).toURL(); + else + url = new URL(url.getProtocol(), url.getHost(), url.getPort(), path); + } + if (System.getProperty(PROP_FRAMEWORK) == null) + System.getProperties().put(PROP_FRAMEWORK, url.toExternalForm()); + if (debug) + System.out.println("Framework located:\n " + url.toExternalForm()); //$NON-NLS-1$ + // add on any dev path elements + URL[] result = getDevPath(url); + if (debug) { + System.out.println("Framework classpath:"); //$NON-NLS-1$ + for (int i = 0; i < result.length; i++) + System.out.println(" " + result[i].toExternalForm()); //$NON-NLS-1$ + } + return result; + } + + /** + * Searches for the given target directory starting in the "plugins" subdirectory + * of the given location. If one is found then this location is returned; + * otherwise an exception is thrown. + * + * @return the location where target directory was found + * @param start the location to begin searching + */ + protected String searchFor(final String target, String start) { + return searchFor(target, null, start); + } + protected String searchFor(final String target, final String targetSuffix, String start) { + // Note that File.list only gives you file names not the complete path from start + String[] candidates = new File(start).list(); + if (candidates == null) + return null; + + ArrayList matches = new ArrayList(2); + for (int i = 0; i < candidates.length; i++) + if (candidates[i].equals(target) || candidates[i].startsWith(target + "_")) //$NON-NLS-1$ + matches.add(candidates[i]); + String[] names = (String[]) matches.toArray(new String[matches.size()]); + int result = findMax(names); + if (result == -1) + return null; + File candidate = new File(start, names[result]); + return candidate.getAbsolutePath().replace(File.separatorChar, '/') + (candidate.isDirectory() ? "/" : ""); //$NON-NLS-1$//$NON-NLS-2$ + } + + protected int findMax(String[] candidates) { + int result = -1; + Object maxVersion = null; + for (int i = 0; i < candidates.length; i++) { + String name = candidates[i]; + String version = ""; //$NON-NLS-1$ // Note: directory with version suffix is always > than directory without version suffix + int index = name.indexOf('_'); + if (index != -1) + version = name.substring(index + 1); + Object currentVersion = getVersionElements(version); + if (maxVersion == null) { + result = i; + maxVersion = currentVersion; + } else { + if (compareVersion((Object[]) maxVersion, (Object[]) currentVersion) < 0) { + result = i; + maxVersion = currentVersion; + } + } + } + return result; + } + /** + * Compares version strings. + * @return result of comparison, as integer; + * <code><0</code> if left < right; + * <code>0</code> if left == right; + * <code>>0</code> if left > right; + */ + private int compareVersion(Object[] left, Object[] right) { + + int result = ((Integer) left[0]).compareTo((Integer) right[0]); // compare major + if (result != 0) + return result; + + result = ((Integer) left[1]).compareTo((Integer) right[1]); // compare minor + if (result != 0) + return result; + + result = ((Integer) left[2]).compareTo((Integer) right[2]); // compare service + if (result != 0) + return result; + + return ((String) left[3]).compareTo((String) right[3]); // compare qualifier + } + + /** + * Do a quick parse of version identifier so its elements can be correctly compared. + * If we are unable to parse the full version, remaining elements are initialized + * with suitable defaults. + * @return an array of size 4; first three elements are of type Integer (representing + * major, minor and service) and the fourth element is of type String (representing + * qualifier). Note, that returning anything else will cause exceptions in the caller. + */ + private Object[] getVersionElements(String version) { + if (version.endsWith(".jar")) //$NON-NLS-1$ + version = version.substring(0, version.length() - 4); + Object[] result = {new Integer(0), new Integer(0), new Integer(0), ""}; //$NON-NLS-1$ + StringTokenizer t = new StringTokenizer(version, "."); //$NON-NLS-1$ + String token; + int i = 0; + while (t.hasMoreTokens() && i < 4) { + token = t.nextToken(); + if (i < 3) { + // major, minor or service ... numeric values + try { + result[i++] = new Integer(token); + } catch (Exception e) { + // invalid number format - use default numbers (0) for the rest + break; + } + } else { + // qualifier ... string value + result[i++] = token; + } + } + return result; + } + + private static URL buildURL(String spec, boolean trailingSlash) { + if (spec == null) + return null; + boolean isFile = spec.startsWith(FILE_SCHEME); + try { + if (isFile) { + File toAdjust = new File(spec.substring(5)); + if (toAdjust.isDirectory()) + return adjustTrailingSlash(toAdjust.toURL(), trailingSlash); + else + return toAdjust.toURL(); + } else + return new URL(spec); + } catch (MalformedURLException e) { + // if we failed and it is a file spec, there is nothing more we can do + // otherwise, try to make the spec into a file URL. + if (isFile) + return null; + try { + File toAdjust = new File(spec); + if (toAdjust.isDirectory()) + return adjustTrailingSlash(toAdjust.toURL(), trailingSlash); + else + return toAdjust.toURL(); + } catch (MalformedURLException e1) { + return null; + } + } + } + + private static URL adjustTrailingSlash(URL url, boolean trailingSlash) throws MalformedURLException { + String file = url.getFile(); + if (trailingSlash == (file.endsWith("/"))) //$NON-NLS-1$ + return url; + file = trailingSlash ? file + "/" : file.substring(0, file.length() - 1); //$NON-NLS-1$ + return new URL(url.getProtocol(), url.getHost(), file); + } + + private URL buildLocation(String property, URL defaultLocation, String userDefaultAppendage) { + URL result = null; + String location = System.getProperty(property); + System.getProperties().remove(property); + // if the instance location is not set, predict where the workspace will be and + // put the instance area inside the workspace meta area. + try { + if (location == null) + result = defaultLocation; + else if (location.equalsIgnoreCase(NONE)) + return null; + else if (location.equalsIgnoreCase(NO_DEFAULT)) + result = buildURL(location, true); + else { + if (location.startsWith(USER_HOME)) { + String base = substituteVar(location, USER_HOME, PROP_USER_HOME); + location = new File(base, userDefaultAppendage).getAbsolutePath(); + } else if (location.startsWith(USER_DIR)) { + String base = substituteVar(location, USER_DIR, PROP_USER_DIR); + location = new File(base, userDefaultAppendage).getAbsolutePath(); + } + result = buildURL(location, true); + } + } finally { + if (result != null) + System.getProperties().put(property, result.toExternalForm()); + } + return result; + } + + private String substituteVar(String source, String var, String prop) { + String value = System.getProperty(prop, ""); //$NON-NLS-1$ + return value + source.substring(var.length()); + } + + /** + * Retuns the default file system path for the configuration location. + * By default the configuration information is in the installation directory + * if this is writeable. Otherwise it is located somewhere in the user.home + * area relative to the current product. + * @return the default file system path for the configuration information + */ + private String computeDefaultConfigurationLocation() { + // 1) We store the config state relative to the 'eclipse' directory if possible + // 2) If this directory is read-only + // we store the state in <user.home>/.eclipse/<application-id>_<version> where <user.home> + // is unique for each local user, and <application-id> is the one + // defined in .eclipseproduct marker file. If .eclipseproduct does not + // exist, use "eclipse" as the application-id. + + URL install = getInstallLocation(); + // TODO a little dangerous here. Basically we have to assume that it is a file URL. + if (install.getProtocol().equals("file")) { //$NON-NLS-1$ + File installDir = new File(install.getFile()); + if (canWrite(installDir)) + return installDir.getAbsolutePath() + File.separator + CONFIG_DIR; + } + // We can't write in the eclipse install dir so try for some place in the user's home dir + return computeDefaultUserAreaLocation(CONFIG_DIR); + } + + private static boolean canWrite(File installDir) { + if (installDir.canWrite() == false) + return false; + + if (!installDir.isDirectory()) + return false; + + File fileTest = null; + try { + fileTest = File.createTempFile("writtableArea", null, installDir); //$NON-NLS-1$ + } catch (IOException e) { + //If an exception occured while trying to create the file, it means that it is not writtable + return false; + } finally { + if (fileTest != null) + fileTest.delete(); + } + return true; + } + + /** + * Returns a files system path for an area in the user.home region related to the + * current product. The given appendage is added to this base location + * @param pathAppendage the path segments to add to computed base + * @return a file system location in the user.home area related the the current + * product and the given appendage + */ + private String computeDefaultUserAreaLocation(String pathAppendage) { + // we store the state in <user.home>/.eclipse/<application-id>_<version> where <user.home> + // is unique for each local user, and <application-id> is the one + // defined in .eclipseproduct marker file. If .eclipseproduct does not + // exist, use "eclipse" as the application-id. + URL installURL = getInstallLocation(); + if (installURL == null) + return null; + File installDir = new File(installURL.getFile()); + String appName = "." + ECLIPSE; //$NON-NLS-1$ + File eclipseProduct = new File(installDir, PRODUCT_SITE_MARKER); + if (eclipseProduct.exists()) { + Properties props = new Properties(); + try { + props.load(new FileInputStream(eclipseProduct)); + String appId = props.getProperty(PRODUCT_SITE_ID); + if (appId == null || appId.trim().length() == 0) + appId = ECLIPSE; + String appVersion = props.getProperty(PRODUCT_SITE_VERSION); + if (appVersion == null || appVersion.trim().length() == 0) + appVersion = ""; //$NON-NLS-1$ + appName += File.separator + appId + "_" + appVersion; //$NON-NLS-1$ + } catch (IOException e) { + // Do nothing if we get an exception. We will default to a standard location + // in the user's home dir. + } + } + String userHome = System.getProperty(PROP_USER_HOME); + return new File(userHome, appName + "/" + pathAppendage).getAbsolutePath(); //$NON-NLS-1$ + } + + /** + * Runs this launcher with the arguments specified in the given string. + * + * @param argString the arguments string + */ + public static void main(String argString) { + Vector list = new Vector(5); + for (StringTokenizer tokens = new StringTokenizer(argString, " "); tokens.hasMoreElements();) //$NON-NLS-1$ + list.addElement(tokens.nextElement()); + main((String[]) list.toArray(new String[list.size()])); + } + + /** + * Runs the platform with the given arguments. The arguments must identify + * an application to run (e.g., <code>-application com.example.application</code>). + * After running the application <code>System.exit(N)</code> is executed. + * The value of N is derived from the value returned from running the application. + * If the application's return value is an <code>Integer</code>, N is this value. + * In all other cases, N = 0. + * <p> + * Clients wishing to run the platform without a following <code>System.exit</code> + * call should use <code>run()</code>. + * </p> + * + * @param args the command line arguments + * @see #run(String[]) + */ + public static void main(String[] args) { + int result = 0; + try { + result = new Main().run(args); + } catch (Throwable t) { + // This is *really* unlikely to happen - run() takes care of exceptional situations. + // In case something weird happens, just dump stack - logging is not available at this point + t.printStackTrace(); + } finally { + if (!Boolean.getBoolean(PROP_NOSHUTDOWN)) + // make sure we always terminate the VM + System.exit(result); + } + } + + + /** + * Runs the platform with the given arguments. The arguments must identify + * an application to run (e.g., <code>-application com.example.application</code>). + * Returns the value returned from running the application. + * If the application's return value is an <code>Integer</code>, N is this value. + * In all other cases, N = 0. + * + * @param args the command line arguments + */ + public int run(String[] args) { + int result = 0; + try { + basicRun(args); + String exitCode = System.getProperty(PROP_EXITCODE); + try { + result = exitCode == null ? 0 : Integer.parseInt(exitCode); + } catch (NumberFormatException e) { + result = 17; + } + } catch (Throwable e) { + // only log the exceptions if they have not been caught by the + // EclipseStarter (i.e., if the exitCode is not 13) + if (!"13".equals(System.getProperty(PROP_EXITCODE))) { //$NON-NLS-1$ + log("Exception launching the Eclipse Platform:"); //$NON-NLS-1$ + log(e); + String message = "An error has occurred"; //$NON-NLS-1$ + if (logFile == null) + message += " and could not be logged: \n" + e.getMessage(); //$NON-NLS-1$ + else + message += ". See the log file\n" + logFile.getAbsolutePath(); //$NON-NLS-1$ + System.getProperties().put(PROP_EXITDATA, message); + } + // Return "unlucky" 13 as the exit code. The executable will recognize + // this constant and display a message to the user telling them that + // there is information in their log file. + result = 13; + } finally { + // always try putting down the splash screen just in case the application failed to do so + takeDownSplash(); + } + // Return an int exit code and ensure the system property is set. + System.getProperties().put(PROP_EXITCODE, Integer.toString(result)); + setExitData(); + return result; + } + + private void setExitData() { + String data = System.getProperty(PROP_EXITDATA); + if (data == null) + return; + bridge.setExitData(data); + } + + /** + * Processes the command line arguments. The general principle is to NOT + * consume the arguments and leave them to be processed by Eclipse proper. + * There are a few args which are directed towards main() and a few others + * which we need to know about. Very few should actually be consumed here. + * + * @return the arguments to pass through to the launched application + * @param args the command line arguments + */ + protected String[] processCommandLine(String[] args) { + if (args.length == 0) + return args; + int[] configArgs = new int[args.length]; + configArgs[0] = -1; // need to initialize the first element to something that could not be an index. + int configArgIndex = 0; + for (int i = 0; i < args.length; i++) { + boolean found = false; + // check for args without parameters (i.e., a flag arg) + // check if debug should be enabled for the entire platform + if (args[i].equalsIgnoreCase(DEBUG)) { + debug = true; + // passed thru this arg (i.e., do not set found = true) + continue; + } + + // look for and consume the nosplash directive. This supercedes any + // -showsplash command that might be present. + if (args[i].equalsIgnoreCase(NOSPLASH)) { + splashDown = true; + found = true; + } + + if (args[i].equalsIgnoreCase(NOEXIT)) { + System.getProperties().put(PROP_NOSHUTDOWN, "true"); //$NON-NLS-1$ + found = true; + } + + // look for the command to use to show the splash screen + if (args[i].equalsIgnoreCase(SHOWSPLASH)) { + showSplash = true; + found = true; + } + + // check if this is initialization pass + if (args[i].equalsIgnoreCase(INITIALIZE)) { + initialize = true; + // passed thru this arg (i.e., do not set found = true) + continue; + } + + // check if development mode should be enabled for the entire platform + // If this is the last arg or there is a following arg (i.e., arg+1 has a leading -), + // simply enable development mode. Otherwise, assume that that the following arg is + // actually some additional development time class path entries. This will be processed below. + if (args[i].equalsIgnoreCase(DEV) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$ + inDevelopmentMode = true; + // do not mark the arg as found so it will be passed through + continue; + } + + // done checking for args. Remember where an arg was found + if (found) { + configArgs[configArgIndex++] = i; + continue; + } + + // look for the VM args arg. We have to do that before looking to see + // if the next element is a -arg as the thing following -vmargs may in + // fact be another -arg. + if (args[i].equalsIgnoreCase(VMARGS)) { + // consume the -vmargs arg itself + args[i] = null; + i++; + vmargs = new String[args.length - i]; + for (int j = 0; i < args.length; i++) { + vmargs[j++] = args[i]; + args[i] = null; + } + continue; + } + + // check for args with parameters. If we are at the last argument or if the next one + // has a '-' as the first character, then we can't have an arg with a parm so continue. + if (i == args.length - 1 || args[i + 1].startsWith("-")) //$NON-NLS-1$ + continue; + String arg = args[++i]; + + // look for the development mode and class path entries. + if (args[i - 1].equalsIgnoreCase(DEV)) { + inDevelopmentMode = true; + devClassPathProps = processDevArg(arg); + if (devClassPathProps != null) { + devClassPath = devClassPathProps.getProperty(OSGI); + if (devClassPath == null) + devClassPath = devClassPathProps.getProperty("*"); //$NON-NLS-1$ + } + continue; + } + + // look for the framework to run + if (args[i - 1].equalsIgnoreCase(FRAMEWORK)) { + framework = arg; + found = true; + } + + if (args[i - 1].equalsIgnoreCase(OS)) { + os = arg; + // passed thru this arg + continue; + } + + // look for explicitly set install root + // Consume the arg here to ensure that the launcher and Eclipse get the + // same value as each other. + if (args[i - 1].equalsIgnoreCase(INSTALL)) { + System.getProperties().put(PROP_INSTALL_AREA, arg); + found = true; + } + + // look for the configuration to use. + // Consume the arg here to ensure that the launcher and Eclipse get the + // same value as each other. + if (args[i - 1].equalsIgnoreCase(CONFIGURATION)) { + System.getProperties().put(PROP_CONFIG_AREA, arg); + found = true; + } + + // look for the name to use by the launcher + if (args[i - 1].equalsIgnoreCase(NAME)) { + name = arg; + found = true; + } + + // look for the launcher location + if (args[i - 1].equalsIgnoreCase(LAUNCHER)) { + launcher = arg; + found = true; + } + + // look for the command to use to end the splash screen + if (args[i - 1].equalsIgnoreCase(ENDSPLASH)) { + endSplash = arg; + found = true; + } + + // look for the VM location arg + if (args[i - 1].equalsIgnoreCase(VM)) { + vm = arg; + found = true; + } + + //look for the nl setting + if (args[i - 1].equalsIgnoreCase(NL)) { + System.getProperties().put(PROP_NL, arg); + found = true; + } + + // done checking for args. Remember where an arg was found + if (found) { + configArgs[configArgIndex++] = i - 1; + configArgs[configArgIndex++] = i; + } + } + // remove all the arguments consumed by this argument parsing + String[] passThruArgs = new String[args.length - configArgIndex - (vmargs == null ? 0 : vmargs.length + 1)]; + configArgIndex = 0; + int j = 0; + for (int i = 0; i < args.length; i++) { + if (i == configArgs[configArgIndex]) + configArgIndex++; + else if (args[i] != null) + passThruArgs[j++] = args[i]; + } + return passThruArgs; + } + + private Properties processDevArg(String arg) { + if (arg == null) + return null; + try { + URL location = new URL(arg); + return load(location, null); + } catch (MalformedURLException e) { + // the arg was not a URL so use it as is. + Properties result = new Properties(); + result.put("*", arg); //$NON-NLS-1$ + return result; + } catch (IOException e) { + // TODO consider logging here + return null; + } + } + + private URL getConfigurationLocation() { + if (configurationLocation != null) + return configurationLocation; + configurationLocation = buildLocation(PROP_CONFIG_AREA, null, ""); //$NON-NLS-1$ + if (configurationLocation == null) { + configurationLocation = buildLocation(PROP_CONFIG_AREA_DEFAULT, null, ""); //$NON-NLS-1$ + if (configurationLocation == null) + configurationLocation = buildURL(computeDefaultConfigurationLocation(), true); + } + if (configurationLocation != null) + System.getProperties().put(PROP_CONFIG_AREA, configurationLocation.toExternalForm()); + if (debug) + System.out.println("Configuration location:\n " + configurationLocation); //$NON-NLS-1$ + return configurationLocation; + } + + private void processConfiguration() { + // if the configuration area is not already defined, discover the config area by + // trying to find a base config area. This is either defined in a system property or + // is computed relative to the install location. + // Note that the config info read here is only used to determine a value + // for the user configuration area + URL baseConfigurationLocation = null; + Properties baseConfiguration = null; + if (System.getProperty(PROP_CONFIG_AREA) == null) { + String baseLocation = System.getProperty(PROP_BASE_CONFIG_AREA); + if (baseLocation != null) + // here the base config cannot have any symbolic (e..g, @xxx) entries. It must just + // point to the config file. + baseConfigurationLocation = buildURL(baseLocation, true); + if (baseConfigurationLocation == null) + try { + // here we access the install location but this is very early. This case will only happen if + // the config area is not set and the base config area is not set (or is bogus). + // In this case we compute based on the install location. + baseConfigurationLocation = new URL(getInstallLocation(), CONFIG_DIR); + } catch (MalformedURLException e) { + // leave baseConfigurationLocation null + } + baseConfiguration = loadConfiguration(baseConfigurationLocation); + if (baseConfiguration != null) { + // if the base sets the install area then use that value if the property. We know the + // property is not already set. + String location = baseConfiguration.getProperty(PROP_CONFIG_AREA); + if (location != null) + System.getProperties().put(PROP_CONFIG_AREA, location); + // if the base sets the install area then use that value if the property is not already set. + // This helps in selfhosting cases where you cannot easily compute the install location + // from the code base. + location = baseConfiguration.getProperty(PROP_INSTALL_AREA); + if (location != null && System.getProperty(PROP_INSTALL_AREA) == null) + System.getProperties().put(PROP_INSTALL_AREA, location); + } + } + + // Now we know where the base configuration is supposed to be. Go ahead and load + // it and merge into the System properties. Then, if cascaded, read the parent configuration + // Note that the parent may or may not be the same parent as we read above since the + // base can define its parent. The first parent we read was either defined by the user + // on the command line or was the one in the install dir. + // if the config or parent we are about to read is the same as the base config we read above, + // just reuse the base + Properties configuration = baseConfiguration; + if (configuration == null || !getConfigurationLocation().equals(baseConfigurationLocation)) + configuration = loadConfiguration(getConfigurationLocation()); + mergeProperties(System.getProperties(), configuration); + if ("false".equalsIgnoreCase(System.getProperty(PROP_CONFIG_CASCADED))) //$NON-NLS-1$ + // if we are not cascaded then remove the parent property even if it was set. + System.getProperties().remove(PROP_SHARED_CONFIG_AREA); + else { + ensureAbsolute(PROP_SHARED_CONFIG_AREA); + URL sharedConfigURL = buildLocation(PROP_SHARED_CONFIG_AREA, null, ""); //$NON-NLS-1$ + if (sharedConfigURL == null) + try { + // there is no shared config value so compute one + sharedConfigURL = new URL(getInstallLocation(), CONFIG_DIR); + } catch (MalformedURLException e) { + // leave sharedConfigurationLocation null + } + // if the parent location is different from the config location, read it too. + if (sharedConfigURL != null) { + if (sharedConfigURL.equals(getConfigurationLocation())) + // remove the property to show that we do not have a parent. + System.getProperties().remove(PROP_SHARED_CONFIG_AREA); + else { + // if the parent we are about to read is the same as the base config we read above, + // just reuse the base + configuration = baseConfiguration; + if (!sharedConfigURL.equals(baseConfigurationLocation)) + configuration = loadConfiguration(sharedConfigURL); + mergeProperties(System.getProperties(), configuration); + System.getProperties().put(PROP_SHARED_CONFIG_AREA, sharedConfigURL.toExternalForm()); + if (debug) + System.out.println("Shared configuration location:\n " + sharedConfigURL.toExternalForm()); //$NON-NLS-1$ + } + } + } + // setup the path to the framework + String urlString = System.getProperty(PROP_FRAMEWORK, null); + if (urlString != null) { + URL url = buildURL(urlString, true); + System.getProperties().put(PROP_FRAMEWORK, url.toExternalForm()); + bootLocation = resolve(urlString); + } + } + + /** + * Ensures the value for a system property is an absolute URL. Relative URLs are translated to + * absolute URLs by taking the install URL as reference. + * + * @param locationProperty the key for a system property containing a URL + */ + private void ensureAbsolute(String locationProperty) { + String propertyValue = System.getProperty(locationProperty); + if (propertyValue == null) + // property not defined + return; + URL locationURL = null; + try { + locationURL = new URL(propertyValue); + } catch (MalformedURLException e) { + // property is not a valid URL + return; + } + String locationPath = locationURL.getPath(); + if (locationPath.startsWith("/")) //$NON-NLS-1$ + // property value is absolute + return; + URL installURL = getInstallLocation(); + if (!locationURL.getProtocol().equals(installURL.getProtocol())) + // not same protocol + return; + try { + URL absoluteURL = new URL(installURL, locationPath); + System.getProperties().put(locationProperty, absoluteURL.toExternalForm()); + } catch (MalformedURLException e) { + // should not happen - the relative URL is known to be valid + } + } + + /** + * Returns url of the location this class was loaded from + */ + private URL getInstallLocation() { + if (installLocation != null) + return installLocation; + + // value is not set so compute the default and set the value + String installArea = System.getProperty(PROP_INSTALL_AREA); + if (installArea != null) { + installLocation = buildURL(installArea, true); + if (installLocation == null) + throw new IllegalStateException("Install location is invalid: " + installArea); //$NON-NLS-1$ + System.getProperties().put(PROP_INSTALL_AREA, installLocation.toExternalForm()); + if (debug) + System.out.println("Install location:\n " + installLocation); //$NON-NLS-1$ + return installLocation; + } + + ProtectionDomain domain = Main.class.getProtectionDomain(); + CodeSource source = null; + URL result = null; + if (domain != null) + source = domain.getCodeSource(); + if (source == null || domain == null) { + if (debug) + System.out.println("CodeSource location is null. Defaulting the install location to file:startup.jar"); //$NON-NLS-1$ + try { + result = new URL("file:startup.jar"); //$NON-NLS-1$ + } catch (MalformedURLException e2) { + //Ignore + } + } + if (source != null) + result = source.getLocation(); + + String path = decode(result.getFile()); + // normalize to not have leading / so we can check the form + File file = new File(path); + path = file.toString().replace('\\', '/'); + // TODO need a better test for windows + // If on Windows then canonicalize the drive letter to be lowercase. + // remember that there may be UNC paths + if (File.separatorChar == '\\') + if (Character.isUpperCase(path.charAt(0))) { + char[] chars = path.toCharArray(); + chars[0] = Character.toLowerCase(chars[0]); + path = new String(chars); + } + if (path.toLowerCase().endsWith(".jar")) //$NON-NLS-1$ + path = path.substring(0, path.lastIndexOf("/") + 1); //$NON-NLS-1$ + if (path.toLowerCase().endsWith("/plugins/")) //$NON-NLS-1$ + path = path.substring(0, path.length() - "/plugins/".length()); + try { + try { + // create a file URL (via File) to normalize the form (e.g., put + // the leading / on if necessary) + path = new File(path).toURL().getFile(); + } catch (MalformedURLException e1) { + // will never happen. The path is straight from a URL. + } + installLocation = new URL(result.getProtocol(), result.getHost(), result.getPort(), path); + System.getProperties().put(PROP_INSTALL_AREA, installLocation.toExternalForm()); + } catch (MalformedURLException e) { + // TODO Very unlikely case. log here. + } + if (debug) + System.out.println("Install location:\n " + installLocation); //$NON-NLS-1$ + return installLocation; + } + + /* + * Load the given configuration file + */ + private Properties loadConfiguration(URL url) { + Properties result = null; + try { + url = new URL(url, CONFIG_FILE); + } catch (MalformedURLException e) { + return null; + } + try { + if (debug) + System.out.print("Configuration file:\n " + url.toString()); //$NON-NLS-1$ + result = loadProperties(url); + if (debug) + System.out.println(" loaded"); //$NON-NLS-1$ + } catch (IOException e) { + if (debug) + System.out.println(" not found or not read"); //$NON-NLS-1$ + } + return result; + } + + private Properties loadProperties(URL url) throws IOException { + // try to load saved configuration file (watch for failed prior save()) + if (url == null) + return null; + Properties result = null; + IOException originalException = null; + try { + result = load(url, null); // try to load config file + } catch (IOException e1) { + originalException = e1; + try { + result = load(url, CONFIG_FILE_TEMP_SUFFIX); // check for failures on save + } catch (IOException e2) { + try { + result = load(url, CONFIG_FILE_BAK_SUFFIX); // check for failures on save + } catch (IOException e3) { + throw originalException; // we tried, but no config here ... + } + } + } + return result; + } + + /* + * Load the configuration + */ + private Properties load(URL url, String suffix) throws IOException { + // figure out what we will be loading + if (suffix != null && !suffix.equals("")) //$NON-NLS-1$ + url = new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile() + suffix); + + // try to load saved configuration file + Properties props = new Properties(); + InputStream is = null; + try { + is = url.openStream(); + props.load(is); + } finally { + if (is != null) + try { + is.close(); + } catch (IOException e) { + //ignore failure to close + } + } + return props; + } + + /* + * Handle splash screen. + * We support 2 startup scenarios: + * + * (1) the executable launcher put up the splash screen. In that + * scenario we are invoked with -endsplash command which is + * fully formed to take down the splash screen + * + * (2) the executable launcher did not put up the splash screen, + * but invokes Eclipse with partially formed -showsplash command. + * In this scenario we determine which splash to display (based on + * feature information) and then call -showsplash command. + * + * In both scenarios we pass a handler (Runnable) to the platform. + * The handler is called as a result of the launched application calling + * Platform.endSplash(). In the first scenario this results in the + * -endsplash command being executed. In the second scenario this + * results in the process created as a result of the -showsplash command + * being destroyed. + * + * @param defaultPath search path for the boot plugin + */ + private void handleSplash(URL[] defaultPath) { + // run without splash if we are initializing or nosplash + // was specified (splashdown = true) + if (initialize || splashDown) { + showSplash = false; + endSplash = null; + return; + } + + if (showSplash || endSplash != null) { + // Register the endSplashHandler to be run at VM shutdown. This hook will be + // removed once the splash screen has been taken down. + try { + Runtime.getRuntime().addShutdownHook(splashHandler); + } catch(Throwable ex) { + // Best effort to register the handler + } + } + + // if -endsplash is specified, use it and ignore any -showsplash command + if (endSplash != null) { + showSplash = false; + return; + } + + // check if we are running without a splash screen + if (!showSplash) + return; + + // determine the splash location + String location = getSplashLocation(defaultPath); + if (debug) + System.out.println("Splash location:\n " + location); //$NON-NLS-1$ + if (location == null) + return; + + bridge.showSplash(location); + long handle = bridge.getSplashHandle(); + if(handle != 0) { + System.setProperty("org.eclipse.splash.handle", String.valueOf(handle)); + System.setProperty("org.eclipse.splash.bundle", location); + bridge.updateSplash(); + } + } + + /* + * Take down the splash screen. + */ + protected void takeDownSplash() { + if (splashDown) // splash is already down + return; + + splashDown = bridge.takeDownSplash();; + + try { + Runtime.getRuntime().removeShutdownHook(splashHandler); + } catch (Throwable e) { + // OK to ignore this, happens when the VM is already shutting down + } + } + + /* + * Return path of the splash image to use. First search the defined splash path. + * If that does not work, look for a default splash. Currently the splash must be in the file system + * so the return value here is the file system path. + */ + private String getSplashLocation(URL[] bootPath) { + String result = System.getProperty(PROP_SPLASHLOCATION); + if (result != null) + return result; + String splashPath = System.getProperty(PROP_SPLASHPATH); + if (splashPath != null) { + String[] entries = getArrayFromList(splashPath); + ArrayList path = new ArrayList(entries.length); + for (int i = 0; i < entries.length; i++) { + String entry = resolve(entries[i]); + if (entry == null || entry.startsWith(FILE_SCHEME)) { + File entryFile = new File(entry.substring(5).replace('/', File.separatorChar)); + entry = searchFor(entryFile.getName(), entryFile.getParent()); + if (entry != null) + path.add(entry); + } else + log("Invalid splash path entry: " + entries[i]); //$NON-NLS-1$ + } + // see if we can get a splash given the splash path + result = searchForSplash((String[]) path.toArray(new String[path.size()])); + if (result != null) { + System.getProperties().put(PROP_SPLASHLOCATION, result); + return result; + } + } + + // can't find it on the splashPath so look for a default splash + String temp = bootPath[0].getFile(); // take the first path element + temp = temp.replace('/', File.separatorChar); + int ix = temp.lastIndexOf("plugins" + File.separator); //$NON-NLS-1$ + if (ix != -1) { + int pix = temp.indexOf(File.separator, ix + 8); + if (pix != -1) { + temp = temp.substring(0, pix); + result = searchForSplash(new String[] {temp}); + if (result != null) + System.getProperties().put(PROP_SPLASHLOCATION, result); + } + } + return result; + } + + /* + * Do a locale-sensitive lookup of splash image + */ + private String searchForSplash(String[] searchPath) { + if (searchPath == null) + return null; + + // Get the splash screen for the specified locale + String locale = (String) System.getProperties().get(PROP_NL); + if (locale == null) + locale = Locale.getDefault().toString(); + String[] nlVariants = buildNLVariants(locale); + + for (int i=0; i<nlVariants.length; i++) { + for (int j=0; j<searchPath.length; j++) { + // do we have a JAR? + if (isJAR(searchPath[j])) { + String result = extractSplashFromJAR(searchPath[j], nlVariants[i]); + if (result != null) + return result; + } else { + // we have a file or a directory + String path = searchPath[j]; + if (!path.endsWith(File.separator)) + path += File.separator; + path += nlVariants[i]; + File result = new File(path); + if (result.exists()) + return result.getAbsolutePath(); // return the first match found [20063] + } + } + } + + // sorry, could not find splash image + return null; + } + + /** + * Transfers all available bytes from the given input stream to the given output stream. + * Regardless of failure, this method closes both streams. + */ + private static void transferStreams(InputStream source, OutputStream destination) { + byte[] buffer = new byte[8096]; + try { + while (true) { + int bytesRead = -1; + try { + bytesRead = source.read(buffer); + } catch (IOException e) { + return; + } + if (bytesRead == -1) + break; + try { + destination.write(buffer, 0, bytesRead); + } catch (IOException e) { + return; + } + } + } finally { + try { + source.close(); + } catch (IOException e) { + // ignore + } finally { + //close destination in finally in case source.close fails + try { + destination.close(); + } catch (IOException e) { + // ignore + } + } + } + } + + /* + * Look for the specified spash file in the given JAR and extract it to the config + * area for caching purposes. + */ + private String extractSplashFromJAR(String jarPath, String splashPath) { + String configLocation = System.getProperty(PROP_CONFIG_AREA); + if (configLocation == null) { + log("Configuration area not set yet. Unable to extract splash from JAR'd plug-in: " + jarPath); //$NON-NLS-1$ + return null; + } + URL configURL = buildURL(configLocation, false); + if (configURL == null) + return null; + // cache the splash in the OSGi sub-dir in the config area + File splash = new File(configURL.getPath(), OSGI); + splash = new File(splash, splashPath); + // if we have already extracted this file before, then return + if (splash.exists()) { + // if we are running with -clean then delete the cached splash file + boolean clean = false; + for (int i=0; i<commands.length; i++) { + if (CLEAN.equalsIgnoreCase(commands[i])) { + clean = true; + splash.delete(); + break; + } + } + if (!clean) + return splash.getAbsolutePath(); + } + ZipFile file; + try { + file = new ZipFile(jarPath); + } catch (IOException e) { + log("Exception looking for splash in JAR file: " + jarPath); //$NON-NLS-1$ + log(e); + return null; + } + ZipEntry entry = file.getEntry(splashPath.replace(File.separatorChar, '/')); + if (entry == null) + return null; + InputStream input = null; + try { + input = file.getInputStream(entry); + } catch (IOException e) { + log("Exception opening splash: " + entry.getName() + " in JAR file: " + jarPath); //$NON-NLS-1$ //$NON-NLS-2$ + log(e); + return null; + } + new File(splash.getParent()).mkdirs(); + OutputStream output; + try { + output = new BufferedOutputStream(new FileOutputStream(splash)); + } catch (FileNotFoundException e) { + try { + input.close(); + } catch (IOException e1) { + // ignore + } + return null; + } + transferStreams(input, output); + return splash.exists() ? splash.getAbsolutePath() : null; + } + + /* + * Return a boolean value indicating whether or not the given + * path represents a JAR file. + */ + private boolean isJAR(String path) { + if (path.endsWith(File.separator)) + return false; + int index = path.lastIndexOf('.'); + if (index == -1) + return false; + index++; + // handle the case where we have a '.' at the end + if (index >= path.length()) + return false; + return "JAR".equalsIgnoreCase(path.substring(index)); //$NON-NLS-1$ + } + + /* + * Build an array of path suffixes based on the given NL which is suitable + * for splash path searching. The returned array contains paths in order + * from most specific to most generic. So, in the FR_fr locale, it will return + * "nl/fr/FR/splash.bmp", then "nl/fr/splash.bmp", and finally "splash.bmp". + * (we always search the root) + */ + private static String[] buildNLVariants(String locale) { + //build list of suffixes for loading resource bundles + String nl = locale; + ArrayList result = new ArrayList(4); + int lastSeparator; + while (true) { + result.add("nl" + File.separatorChar + nl.replace('_', File.separatorChar) + File.separatorChar + SPLASH_IMAGE); //$NON-NLS-1$ + lastSeparator = nl.lastIndexOf('_'); + if (lastSeparator == -1) + break; + nl = nl.substring(0, lastSeparator); + } + //add the empty suffix last (most general) + result.add(SPLASH_IMAGE); + return (String[]) result.toArray(new String[result.size()]); + } + + /* + * resolve platform:/base/ URLs + */ + private String resolve(String urlString) { + // handle the case where people mistakenly spec a refererence: url. + if (urlString.startsWith(REFERENCE_SCHEME)) { + urlString = urlString.substring(10); + System.getProperties().put(PROP_FRAMEWORK, urlString); + } + if (urlString.startsWith(PLATFORM_URL)) { + String path = urlString.substring(PLATFORM_URL.length()); + return getInstallLocation() + path; + } else + return urlString; + } + + /* + * Entry point for logging. + */ + protected synchronized void log(Object obj) { + if (obj == null) + return; + try { + openLogFile(); + try { + if (newSession) { + log.write(SESSION); + log.write(' '); + String timestamp = new Date().toString(); + log.write(timestamp); + log.write(' '); + for (int i = SESSION.length() + timestamp.length(); i < 78; i++) + log.write('-'); + log.newLine(); + newSession = false; + } + write(obj); + } finally { + if (logFile == null) { + if (log != null) + log.flush(); + } else + closeLogFile(); + } + } catch (Exception e) { + System.err.println("An exception occurred while writing to the platform log:"); //$NON-NLS-1$ + e.printStackTrace(System.err); + System.err.println("Logging to the console instead."); //$NON-NLS-1$ + //we failed to write, so dump log entry to console instead + try { + log = logForStream(System.err); + write(obj); + log.flush(); + } catch (Exception e2) { + System.err.println("An exception occurred while logging to the console:"); //$NON-NLS-1$ + e2.printStackTrace(System.err); + } + } finally { + log = null; + } + } + + /* + * This should only be called from #log() + */ + private void write(Object obj) throws IOException { + if (obj == null) + return; + if (obj instanceof Throwable) { + log.write(STACK); + log.newLine(); + ((Throwable) obj).printStackTrace(new PrintWriter(log)); + } else { + log.write(ENTRY); + log.write(' '); + log.write(PLUGIN_ID); + log.write(' '); + log.write(String.valueOf(ERROR)); + log.write(' '); + log.write(String.valueOf(0)); + log.write(' '); + log.write(getDate(new Date())); + log.newLine(); + log.write(MESSAGE); + log.write(' '); + log.write(String.valueOf(obj)); + } + log.newLine(); + } + + protected String getDate(Date date) { + Calendar c = Calendar.getInstance(); + c.setTime(date); + StringBuffer sb = new StringBuffer(); + appendPaddedInt(c.get(Calendar.YEAR), 4, sb).append('-'); + appendPaddedInt(c.get(Calendar.MONTH) + 1, 2, sb).append('-'); + appendPaddedInt(c.get(Calendar.DAY_OF_MONTH), 2, sb).append(' '); + appendPaddedInt(c.get(Calendar.HOUR_OF_DAY), 2, sb).append(':'); + appendPaddedInt(c.get(Calendar.MINUTE), 2, sb).append(':'); + appendPaddedInt(c.get(Calendar.SECOND), 2, sb).append('.'); + appendPaddedInt(c.get(Calendar.MILLISECOND), 3, sb); + return sb.toString(); + } + + private StringBuffer appendPaddedInt(int value, int pad, StringBuffer buffer) { + pad = pad - 1; + if (pad == 0) + return buffer.append(Integer.toString(value)); + int padding = (int) Math.pow(10, pad); + if (value >= padding) + return buffer.append(Integer.toString(value)); + while (padding > value && padding > 1) { + buffer.append('0'); + padding = padding / 10; + } + buffer.append(value); + return buffer; + } + + private void computeLogFileLocation() { + String logFileProp = System.getProperty(PROP_LOGFILE); + if (logFileProp != null) { + if (logFile == null || !logFileProp.equals(logFile.getAbsolutePath())) { + logFile = new File(logFileProp); + new File(logFile.getParent()).mkdirs(); + } + return; + } + + // compute the base location and then append the name of the log file + URL base = buildURL(System.getProperty(PROP_CONFIG_AREA), false); + if (base == null) + return; + logFile = new File(base.getPath(), Long.toString(System.currentTimeMillis()) + ".log"); //$NON-NLS-1$ + new File(logFile.getParent()).mkdirs(); + System.getProperties().put(PROP_LOGFILE, logFile.getAbsolutePath()); + } + + /** + * Converts an ASCII character representing a hexadecimal + * value into its integer equivalent. + */ + private int hexToByte(byte b) { + switch (b) { + case '0' : + return 0; + case '1' : + return 1; + case '2' : + return 2; + case '3' : + return 3; + case '4' : + return 4; + case '5' : + return 5; + case '6' : + return 6; + case '7' : + return 7; + case '8' : + return 8; + case '9' : + return 9; + case 'A' : + case 'a' : + return 10; + case 'B' : + case 'b' : + return 11; + case 'C' : + case 'c' : + return 12; + case 'D' : + case 'd' : + return 13; + case 'E' : + case 'e' : + return 14; + case 'F' : + case 'f' : + return 15; + default : + throw new IllegalArgumentException("Switch error decoding URL"); //$NON-NLS-1$ + } + } + + private void openLogFile() throws IOException { + computeLogFileLocation(); + try { + log = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(logFile.getAbsolutePath(), true), "UTF-8")); //$NON-NLS-1$ + } catch (IOException e) { + logFile = null; + throw e; + } + } + + private BufferedWriter logForStream(OutputStream output) { + try { + return new BufferedWriter(new OutputStreamWriter(output, "UTF-8")); //$NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + return new BufferedWriter(new OutputStreamWriter(output)); + } + } + + private void closeLogFile() throws IOException { + try { + if (log != null) { + log.flush(); + log.close(); + } + } finally { + log = null; + } + } + + private void mergeProperties(Properties destination, Properties source) { + if (destination == null || source == null) + return; + for (Enumeration e = source.keys(); e.hasMoreElements();) { + String key = (String) e.nextElement(); + if (key.equals(PROP_CLASSPATH)) { + String destinationClasspath = destination.getProperty(PROP_CLASSPATH); + String sourceClasspath = source.getProperty(PROP_CLASSPATH); + if (destinationClasspath == null) + destinationClasspath = sourceClasspath; + else + destinationClasspath = destinationClasspath + sourceClasspath; + destination.put(PROP_CLASSPATH, destinationClasspath); + continue; + } + String value = source.getProperty(key); + if (destination.getProperty(key) == null) + destination.put(key, value); + } + } + + private void setupVMProperties() { + if (vm != null) + System.getProperties().put(PROP_VM, vm); + setMultiValueProperty(PROP_VMARGS, vmargs); + setMultiValueProperty(PROP_COMMANDS, commands); + } + + private void setMultiValueProperty(String property, String[] value) { + if (value != null) { + StringBuffer result = new StringBuffer(300); + for (int i = 0; i < value.length; i++) { + if (value[i] != null) { + result.append(value[i]); + result.append('\n'); + } + } + System.getProperties().put(property, result.toString()); + } + } + + /* + * NOTE: It is ok here for EclipsePolicy to use 1.4 methods because the methods + * that it calls them from don't exist in Foundation so they will never be called. A more + * detailed explanation from Tom: + * + * They will never get called because in a pre 1.4 VM the methods + * getPermissions(CodeSource) and implies(ProtectionDomain, Permission) are + * undefined on the Policy class which is what EclipsePolicy extends. EclipsePolicy + * implements these two methods so it can proxy them to the parent Policy. + * But since these methods are not actually defined on Policy in a pre-1.4 VM + * nobody will actually call them (unless they casted the policy to EclipsePolicy and + * called our methods) + */ + private class EclipsePolicy extends Policy { + // The policy that this EclipsePolicy is replacing + private Policy policy; + + // The set of URLs to give AllPermissions to; this is the set of bootURLs + private URL[] urls; + + // The AllPermissions collection + private PermissionCollection allPermissions; + + // The AllPermission permission + private Permission allPermission = new AllPermission(); + + EclipsePolicy(Policy policy, URL[] urls) { + this.policy = policy; + this.urls = urls; + allPermissions = new PermissionCollection() { + private static final long serialVersionUID = 3258131349494708277L; + + // A simple PermissionCollection that only has AllPermission + public void add(Permission permission) { + } + + public boolean implies(Permission permission) { + return true; + } + + public Enumeration elements() { + return new Enumeration() { + int cur = 0; + + public boolean hasMoreElements() { + return cur < 1; + } + + public Object nextElement() { + if (cur == 0) { + cur = 1; + return allPermission; + } else + throw new NoSuchElementException(); + } + }; + } + }; + } + + public PermissionCollection getPermissions(CodeSource codesource) { + if (contains(codesource.getLocation())) + return allPermissions; + return policy == null ? allPermissions : policy.getPermissions(codesource); + } + + public PermissionCollection getPermissions(ProtectionDomain domain) { + if (contains(domain.getCodeSource().getLocation())) + return allPermissions; + return policy == null ? allPermissions : policy.getPermissions(domain); + } + + public boolean implies(ProtectionDomain domain, Permission permission) { + if (contains(domain.getCodeSource().getLocation())) + return true; + return policy == null ? true : policy.implies(domain, permission); + } + + public void refresh() { + if (policy != null) + policy.refresh(); + } + + private boolean contains(URL url) { + // Check to see if this URL is in our set of URLs to give AllPermissions to. + for (int i = 0; i < urls.length; i++) { + // We do simple equals test here because we assume the URLs will be the same objects. + if (urls[i] == url) + return true; + } + return false; + } + } + + private class StartupClassLoader extends URLClassLoader { + + public StartupClassLoader(URL[] urls) { + super(urls); + } + + public StartupClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + public StartupClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) { + super(urls, parent, factory); + } + + protected String findLibrary(String name) { + if (extensionPaths == null) + return super.findLibrary(name); + String libName = System.mapLibraryName(name); + for (int i = 0; i < extensionPaths.length; i++) { + File libFile = new File(extensionPaths[i], libName); + if (libFile.isFile()) + return libFile.getAbsolutePath(); + } + return super.findLibrary(name); + } + } +} diff --git a/bundles/org.eclipse.equinox.launcher/src/org/eclipse/equinox/launcher/WebStartMain.java b/bundles/org.eclipse.equinox.launcher/src/org/eclipse/equinox/launcher/WebStartMain.java new file mode 100644 index 000000000..dfa71c793 --- /dev/null +++ b/bundles/org.eclipse.equinox.launcher/src/org/eclipse/equinox/launcher/WebStartMain.java @@ -0,0 +1,295 @@ +/******************************************************************************* + * Copyright (c) 2005, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.launcher; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.*; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +/** + * The launcher ot start eclipse using webstart. + * To use this launcher, the client must accept to give all security permissions. + */ +//The bundles are discovered by finding all the jars on the classpath. Then they are added with their full path to the osgi.bundles list. +public class WebStartMain extends Main { + private static final String PROP_WEBSTART_AUTOMATIC_INSTALLATION = "eclipse.webstart.automaticInstallation"; //$NON-NLS-1$ + private static final String DEFAULT_OSGI_BUNDLES = "org.eclipse.equinox.common@2:start, org.eclipse.core.runtime@start"; //$NON-NLS-1$ + private static final String PROP_OSGI_BUNDLES = "osgi.bundles"; //$NON-NLS-1$ + private static final String PROP_WEBSTART_PRECISE_BUNDLEID = "eclipse.webstart.preciseBundleId"; //$NON-NLS-1$ + + + private String[] allJars = null; //List all the jars that are on the classpath + private Map bundleList = null; //Map an entry (the part before the @) from the osgi.bundles list to a list of URLs. Ie: org.eclipse.core.runtime --> file:c:/foo/org.eclipse.core.runtime_3.1.0/..., file:c:/bar/org.eclipse.core.runtime/... + private Map bundleStartInfo = null; //Keep track of the start level info for each bundle from the osgi.bundle list. + + private boolean preciseIdExtraction = false; //Flag indicating if the extraction of the id must be done by looking at bundle ids. + + public static void main(String[] args) { + System.setSecurityManager(null); //TODO Hack so that when the classloader loading the fwk is created we don't have funny permissions. This should be revisited. + int result = new WebStartMain().run(args); + System.exit(result); + } + + private void setDefaultBundles() { + if (System.getProperty(PROP_OSGI_BUNDLES) != null) + return; + System.getProperties().put(PROP_OSGI_BUNDLES, DEFAULT_OSGI_BUNDLES); + } + + protected void basicRun(String[] args) throws Exception { + preciseIdExtraction = Boolean.getBoolean(PROP_WEBSTART_PRECISE_BUNDLEID); + setDefaultBundles(); + addOSGiBundle(); + initializeBundleListStructure(); + mapURLsToBundleList(); + //Set the fwk location since the regular lookup would not find it + String fwkURL = searchFor(framework, null); + System.getProperties().put(PROP_FRAMEWORK, fwkURL); + super.basicRun(args); + } + + private void addOSGiBundle() { + //Add osgi to the bundle list, so we can beneficiate of the infrastructure to find its location. It will be removed from the list later on + System.getProperties().put(PROP_OSGI_BUNDLES, System.getProperty(PROP_OSGI_BUNDLES) + ',' + framework); + } + + protected URL[] getBootPath(String base) throws IOException { + URL[] result = super.getBootPath(base); + buildOSGiBundleList(); + cleanup(); + return result; + } + + /* + * Null out all the fields containing data + */ + private void cleanup() { + allJars = null; + bundleList = null; + bundleStartInfo = null; + } + + /* + * Find the target bundle among all the jars that are on the classpath. + * The start parameter is not used in this context + */ + protected String searchFor(final String target, String start) { + ArrayList matches = (ArrayList) bundleList.get(target); + int numberOfURLs = matches.size(); + if (numberOfURLs == 1) { + return extractInnerURL((String) matches.get(0)); + } + if (numberOfURLs == 0) + return null; + String urls[] = new String[numberOfURLs]; + return extractInnerURL(urls[findMax((String[]) matches.toArray(urls))]); + } + + /* + * Get all the jars available on the webstart classpath + */ + private String[] getAllJars() { + if (allJars != null) + return allJars; + + ArrayList collector = new ArrayList(); + try { + Enumeration resources = WebStartMain.class.getClassLoader().getResources(JarFile.MANIFEST_NAME); + while (resources.hasMoreElements()) { + collector.add(((URL) resources.nextElement()).toExternalForm()); + } + } catch (IOException e) { + e.printStackTrace(); + } + allJars = new String[collector.size()]; + collector.toArray(allJars); + if (debug) + printArray("Jars found on the webstart path:\n", allJars); //$NON-//$NON-NLS-1$ + + return allJars; + } + + /* + * Extract the inner URL from a string representing a JAR url string. + */ + private String extractInnerURL(String url) { + if (url.startsWith(JAR_SCHEME)) { + url = url.substring(url.indexOf(JAR_SCHEME) + 4); + } + int lastBang = url.lastIndexOf('!'); + if (lastBang != -1) { + url = url.substring(0, lastBang); + } + return decode(url); + } + + private void printArray(String header, String[] values) { + System.err.println(header); + for (int i = 0; i < values.length; i++) { + System.err.println("\t" + values[i]); //$NON-NLS-1$ + } + } + + + /* + * Initialize the data structure corresponding to the osgi.bundles list + */ + private void initializeBundleListStructure() { + final char STARTLEVEL_SEPARATOR = '@'; + + //In webstart the bundles list can only contain bundle names with or without a version. + String prop = System.getProperty(PROP_OSGI_BUNDLES); + if (prop == null || prop.trim().equals("")) { //$NON-NLS-1$ + bundleList = new HashMap(0); + return; + } + + bundleList = new HashMap(10); + bundleStartInfo = new HashMap(10); + StringTokenizer tokens = new StringTokenizer(prop, ","); //$NON-NLS-1$ + while (tokens.hasMoreTokens()) { + String token = tokens.nextToken().trim(); + String bundleId = token; + if (token.equals("")) //$NON-NLS-1$ + continue; + int startLevelSeparator; + if ((startLevelSeparator = token.lastIndexOf(STARTLEVEL_SEPARATOR)) != -1) { + bundleId = token.substring(0, startLevelSeparator); + bundleStartInfo.put(bundleId, token.substring(startLevelSeparator)); + } + bundleList.put(bundleId, new ArrayList(1)); // put a list with one element as it is likely that the element will be present + } + + } + + /* + * Associate urls from the list of jars with a bundle from the bundle list + */ + private void mapURLsToBundleList() { + String[] allJars = getAllJars(); + for (int i = 0; i < allJars.length; i++) { + String bundleId = extractBundleId(allJars[i]); + if (bundleId == null) + continue; + ArrayList bundleURLs = (ArrayList) bundleList.get(bundleId); + if (bundleURLs == null) { + int versionIdPosition = bundleId.lastIndexOf('_'); + if (versionIdPosition == -1) + continue; + bundleURLs = (ArrayList) bundleList.get(bundleId.substring(0, versionIdPosition)); + if (bundleURLs == null) + continue; + } + bundleURLs.add(allJars[i]); + allJars[i] = null; //Remove the entry from the list + } + } + + /* + * return a string of the form <bundle>_<version> + */ + private String extractBundleId(String url) { + if (preciseIdExtraction) + return extractBundleIdFromManifest(url); + else + return extractBundleIdFromBundleURL(url); + } + + private String extractBundleIdFromManifest(String url) { + final String BUNDLE_SYMBOLICNAME = "Bundle-SymbolicName"; //$NON-NLS-1$ + final String BUNDLE_VERSION = "Bundle-Version"; //$NON-NLS-1$ + + Manifest mf; + try { + mf = new Manifest(new URL(url).openStream()); + String symbolicNameString = mf.getMainAttributes().getValue(BUNDLE_SYMBOLICNAME); + if (symbolicNameString==null) + return null; + + String bundleVersion = mf.getMainAttributes().getValue(BUNDLE_VERSION); + if (bundleVersion == null) + bundleVersion = ""; //$NON-NLS-1$ + else + bundleVersion = '_' + bundleVersion; + + int pos = symbolicNameString.lastIndexOf(';'); + if (pos != -1) + return symbolicNameString.substring(0, pos) + bundleVersion; + return symbolicNameString + bundleVersion; + } catch (MalformedURLException e) { + //Ignore + } catch (IOException e) { + //Ignore + } + return null; + + } + + private String extractBundleIdFromBundleURL(String url) { + int lastBang = url.lastIndexOf('!'); + if (lastBang == -1) + return null; + boolean jarSuffix = url.regionMatches(true, lastBang - 4, ".jar", 0, 4); //$NON-NLS-1$ + int bundleIdStart = url.lastIndexOf('/', lastBang); + return url.substring(bundleIdStart + 3, lastBang - (jarSuffix ? 4 : 0)); // + 3 because URLs from webstart have a funny prefix + } + + private void buildOSGiBundleList() { + //Remove the framework from the bundle list because it does not need to be installed. See addOSGiBundle + bundleList.remove(framework); + + String[] jarsOnClasspath = getAllJars(); + StringBuffer finalBundleList = new StringBuffer(jarsOnClasspath.length * 25); + + //Add the bundles from the bundle list. + Collection allSelectedBundles = bundleList.entrySet(); + for (Iterator iter = allSelectedBundles.iterator(); iter.hasNext();) { + Map.Entry entry = (Map.Entry) iter.next(); + ArrayList matches = (ArrayList) entry.getValue(); + int numberOfURLs = matches.size(); + + //Get the start info + String startInfo = (String) bundleStartInfo.get(entry.getKey()); + if (startInfo == null) + startInfo = ""; //$NON-NLS-1$ + + if (numberOfURLs == 1) { + finalBundleList.append(REFERENCE_SCHEME).append(extractInnerURL((String) matches.get(0))).append(startInfo).append(','); + continue; + } + if (numberOfURLs == 0) + continue; + String urls[] = new String[numberOfURLs]; + int found = findMax((String[]) matches.toArray(urls)); + for (int i = 0; i < urls.length; i++) { + if (i != found) + continue; + finalBundleList.append(REFERENCE_SCHEME).append(extractInnerURL((String) urls[found])).append(startInfo).append(','); + } + } + + //Add all the other bundles if required - the common case is to add those + if (! Boolean.FALSE.toString().equalsIgnoreCase(System.getProperties().getProperty(PROP_WEBSTART_AUTOMATIC_INSTALLATION))) { + for (int i = 0; i < jarsOnClasspath.length; i++) { + if (jarsOnClasspath[i] != null) + finalBundleList.append(REFERENCE_SCHEME).append(extractInnerURL(jarsOnClasspath[i])).append(','); + } + } + + System.getProperties().put(PROP_OSGI_BUNDLES, finalBundleList.toString()); + if (debug) + log(finalBundleList.toString()); + } + +} diff --git a/bundles/org.eclipse.equinox.launcher/src/org/eclipse/equinox/launcher/package.html b/bundles/org.eclipse.equinox.launcher/src/org/eclipse/equinox/launcher/package.html new file mode 100644 index 000000000..ca2e7ce0c --- /dev/null +++ b/bundles/org.eclipse.equinox.launcher/src/org/eclipse/equinox/launcher/package.html @@ -0,0 +1,18 @@ +<!doctype html public "-//w3c//dtd html 4.0 transitional//en"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <meta name="Author" content="IBM"> + <meta name="GENERATOR" content="Mozilla/4.75 [en] (Windows NT 5.0; U) [Netscape]"> + <title>Package-level Javadoc</title> +</head> +<body> +Launches the Eclipse Platform. +<h2> +Package Specification</h2> + +This package is the main entry point to the Eclipse Platform. To start the platform invoke the Main +class in this package. +<br> +</body> +</html> |