/******************************************************************************* * Copyright (c) 2000, 2003 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Common Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/cpl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.core.launcher; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.*; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; /** * The framework to run. This is used if the bootLocation (-boot) is not specified. * The value can be specified on the command line as -framework. * Startup class for Eclipse. Creates a class loader using * supplied URL of platform installation, loads and calls * the Eclipse Boot Loader. The startup arguments are as follows: * <dl> * <dd> * -application <id>: the identifier of the application to run * </dd> * <dd> * -arch <architecture>: sets the processor architecture value * </dd> * <dd> * -boot <location>: the location, expressed as a URL, of the platform's boot.jar. * <i>Deprecated: replaced by -configuration</i> * </dd> * <dd> * -classloaderproperties [properties file]: activates platform class loader enhancements using * the class loader properties file at the given location, if specified. The (optional) file argument * can be either a file path or an absolute URL. * </dd> * <dd> * -configuration <location>: the location, expressed as a URL, for the Eclipse platform * configuration file. The configuration file determines the location of the Eclipse platform, the set * of available plug-ins, and the primary feature. * </dd> * <dd> * -consolelog : enables log to the console. Handy when combined with -debug * </dd> * <dd> * -data <location>: sets the workspace location and the default location for projects * </dd> * <dd> * -debug [options file]: turns on debug mode for the platform and optionally specifies a location * for the .options file. This file indicates what debug points are available for a * plug-in and whether or not they are enabled. If a location is not specified, the platform searches * for the .options file under the install directory. * </dd> * <dd> * -dev [entries]: turns on dev mode and optionally specifies comma-separated class path entries * which are added to the class path of each plug-in * </dd> * <dd> * -feature <id>: the identifier of the primary feature. The primary feature gives the launched * instance of Eclipse its product personality, and determines the product customization * information. * </dd> * <dd> * -keyring <location>: the location of the authorization database on disk. This argument * has to be used together with the -password argument. * </dd> * <dd> * -nl <locale>: sets the name of the locale on which Eclipse platform will run * </dd> * <dd> * -nolazyregistrycacheloading : deactivates platform plug-in registry cache loading optimization. * By default, extensions' configuration elements will be loaded from the registry cache (when * available) only on demand, reducing memory footprint. This option will force the registry cache * to be fully loaded at startup. * </dd> * <dd> * -nopackageprefixes: deactivates classloader package prefixes optimization * </dd> * <dd> * -noregistrycache: bypasses the reading and writing of an internal plug-in registry cache file * </dd> * <dd> * -os <operating system>: sets the operating system value * </dd> * <dd> * -password <passwd>: the password for the authorization database * </dd> * <dd> * -plugins <location>: the arg is a URL pointing to a file which specs the plugin * path for the platform. The file is in property file format where the keys are user-defined * names and the values are comma separated lists of either explicit paths to plugin.xml * files or directories containing plugins (e.g., .../eclipse/plugins). * <i>Deprecated: replaced by -configuration</i> * </dd> * <dd> * -plugincustomization <properties file>: the location of a properties file containing default * settings for plug-in preferences. These default settings override default settings specified in the * primary feature. Relative paths are interpreted relative to the directory that eclipse was started * from. * </dd> * <dd> * -ws <window system>: sets the window system value * </dd> * </dl> */ 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 String installLocation = null; /** * The location of the configuration information for this instance */ protected String 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. */ protected String devClassPath = null; /** * Indicates whether this instance is running in development mode. */ protected boolean inDevelopmentMode = false; private String exitData = null; private String vm = null; private String[] vmargs = null; private String[] commands = null; // splash handling private String showSplash = null; private String endSplash = null; private boolean initialize = false; private Process showProcess = null; private boolean splashDown = false; private final Runnable endSplashHandler = new Runnable() { public void run() { takeDownSplash(); } }; // 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 EXITDATA = "-exitdata"; //$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 ENDSPLASH = "-endsplash"; //$NON-NLS-1$ private static final String SPLASH_IMAGE = "splash.bmp"; //$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$ // 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_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$ private 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_LOGFILE = "osgi.logfile"; //$NON-NLS-1$ private static final String PROP_EOF = "eof"; //$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$ // 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$ // 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.core.launcher"; //$NON-NLS-1$ protected File logFile = null; protected BufferedWriter log = null; protected boolean newSession = true; /** * Executes the launch. * * @return the result of performing the launch * @param args command-line arguments * @exception Exception thrown if a problem occurs during the launch */ protected Object basicRun(String[] args) throws Exception { System.getProperties().setProperty("eclipse.debug.startupTime", Long.toString(System.currentTimeMillis())); //$NON-NLS-1$ commands = args; String[] passThruArgs = processCommandLine(args); setupVMProperties(); processConfiguration(); // 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(); // locate boot plugin (may return -dev mode variations) URL[] bootPath = getBootPath(bootLocation); // 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); // load the BootLoader and startup the platform URLClassLoader loader = new URLClassLoader(bootPath, null); Class clazz = loader.loadClass(STARTER); Method method = clazz.getDeclaredMethod("run", new Class[] {String[].class, Runnable.class}); //$NON-NLS-1$ try { return method.invoke(clazz, new Object[] {passThruArgs, endSplashHandler}); } 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; } } /** * 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 */ private 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 == '%') { 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 */ private 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 { String devBase = base.toExternalForm(); ArrayList result = new ArrayList(5); if (inDevelopmentMode) addDevEntries(devBase, result); //$NON-NLS-1$ //The jars from the base always need to be added, even when running in dev mode (bug 46772) addBaseJars(devBase, result); return (URL[]) result.toArray(new URL[result.size()]); } private void addBaseJars(String devBase, ArrayList result) throws IOException { String baseJarList = System.getProperty(PROP_CLASSPATH); if (baseJarList == null) { Properties defaults = loadProperties(devBase + "eclipse.properties"); baseJarList = defaults.getProperty(PROP_CLASSPATH); if (baseJarList == null) throw new IOException("Unable to initialize " + PROP_CLASSPATH); System.getProperties().put(PROP_CLASSPATH, baseJarList); } String[] baseJars = getArrayFromList(baseJarList); for (int i = 0; i < baseJars.length; i++) { String string = baseJars[i]; try { URL url = new URL(string); addEntry(url, result); } catch (MalformedURLException e) { addEntry(new URL(devBase + string), result); } } } private void addEntry(URL url, List result) { if (new File(url.getFile()).exists()) result.add(url); } private void addDevEntries(String devBase, List result) throws MalformedURLException { String[] locations = getArrayFromList(devClassPath); for (int i = 0; i < locations.length; i++) { String spec = devBase + locations[i]; char lastChar = spec.charAt(spec.length() - 1); URL url; if ((spec.endsWith(".jar") || (lastChar == '/' || lastChar == '\\'))) //$NON-NLS-1$ url = new URL(spec); else url = new URL(spec + "/"); //$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 */ private URL[] getBootPath(String base) throws IOException { URL url = null; if (base != null) { url = new URL(base); } else { // search in the root location url = new URL(getInstallLocation()); String path = url.getFile() + "plugins"; //$NON-NLS-1$ path = searchFor(framework, path); if (path == null) throw new RuntimeException("Could not find framework"); //$NON-NLS-1$ // add on any dev path elements 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()); 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 */ private String searchFor(final String target, String start) { FileFilter filter = new FileFilter() { public boolean accept(File candidate) { return candidate.isDirectory() && (candidate.getName().equals(target) || candidate.getName().startsWith(target + "_")); //$NON-NLS-1$ } }; File[] candidates = new File(start).listFiles(filter); //$NON-NLS-1$ if (candidates == null) return null; String result = null; Object maxVersion = null; for (int i = 0; i < candidates.length; i++) { String name = candidates[i].getName(); 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 = candidates[i].getAbsolutePath(); maxVersion = currentVersion; } else { if (compareVersion((Object[]) maxVersion, (Object[]) currentVersion) < 0) { result = candidates[i].getAbsolutePath(); maxVersion = currentVersion; } } } if (result == null) return null; return result.replace(File.separatorChar, '/') + "/"; //$NON-NLS-1$ } /** * 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) { 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 URL buildURL(String spec, boolean trailingSlash) { if (spec == null) return null; // if the spec is a file: url then see if it is absolute. If not, break it up // and make it absolute. if (spec.startsWith("file:")) { File file = new File(spec.substring(5)); if (!file.isAbsolute()) spec = "file:" + file.getAbsolutePath(); } try { spec = adjustTrailingSlash(spec, true); return new URL(spec); } catch (MalformedURLException e) { if (spec.startsWith("file:")) return null; return buildURL("file:" + spec, trailingSlash); } } 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.equalsIgnoreCase(USER_HOME)) location = computeDefaultUserAreaLocation(userDefaultAppendage); if (location.equalsIgnoreCase(USER_DIR)) location = new File(System.getProperty(PROP_USER_DIR), userDefaultAppendage).getAbsolutePath(); result = buildURL(location, true); } } finally { if (result != null) System.getProperties().put(property, result.toExternalForm()); } return result; } /** * 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. String install = getInstallLocation(); // TODO a little dangerous here. Basically we have to assume that it is a file URL. if (install.startsWith("file:")) { File installDir = new File(install.substring(5)); if (installDir.canWrite()) 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); } /** * 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 = buildURL(getInstallLocation(), true); if (installURL == null) return null; File installDir = new File(installURL.getPath()); 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 * @exception Exception thrown if a problem occurs during launching */ 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 */ public static void main(String[] args) { int result = new Main().run(args); 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) { // try and take down the splash screen. takeDownSplash(); // 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))) { 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; } // 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 (exitData == null || data == null) return; runCommand(exitData, data, " " + EXITDATA); } /** * 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) { // TODO temporarily handle the fact that PDE appends the -showsplash <timeout> onto // the *end* of the command line. This interferes with the -vmargs arg. Process // -showsplash now and remove it from the end. This code should be removed soon. int end = args.length; if (args.length > 1 && args[end - 2].equalsIgnoreCase(SHOWSPLASH)) { showSplash = args[end - 1]; end -= 2; } String[] arguments = new String[end]; System.arraycopy(args, 0, arguments, 0, end); int[] configArgs = new int[arguments.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 < arguments.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 (arguments[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 (arguments[i].equalsIgnoreCase(NOSPLASH)) { splashDown = true; found = true; } // check if this is initialization pass if (arguments[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 (arguments[i].equalsIgnoreCase(DEV) && ((i + 1 == arguments.length) || ((i + 1 < arguments.length) && (arguments[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 (arguments[i].equalsIgnoreCase(VMARGS)) { // consume the -vmargs arg itself arguments[i] = null; i++; vmargs = new String[arguments.length - i]; for (int j = 0; i < arguments.length; i++) { vmargs[j++] = arguments[i]; arguments[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 == arguments.length - 1 || arguments[i + 1].startsWith("-")) //$NON-NLS-1$ continue; String arg = arguments[++i]; // look for the development mode and class path entries. if (arguments[i - 1].equalsIgnoreCase(DEV)) { inDevelopmentMode = true; devClassPath = processDevArg(arg); continue; } // look for the framework to run if (arguments[i - 1].equalsIgnoreCase(FRAMEWORK)) { framework = arg; found = true; } // 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 (arguments[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 (arguments[i - 1].equalsIgnoreCase(CONFIGURATION)) { System.getProperties().put(PROP_CONFIG_AREA, arg); found = true; } // look for the command to use to set exit data in the launcher if (arguments[i - 1].equalsIgnoreCase(EXITDATA)) { exitData = arg; found = true; } // look for the command to use to show the splash screen if (arguments[i - 1].equalsIgnoreCase(SHOWSPLASH)) { showSplash = arg; found = true; } // look for the command to use to end the splash screen if (arguments[i - 1].equalsIgnoreCase(ENDSPLASH)) { endSplash = arg; found = true; } // look for the VM location arg if (arguments[i - 1].equalsIgnoreCase(VM)) { vm = 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 if (configArgIndex == 0) return arguments; String[] passThruArgs = new String[arguments.length - configArgIndex - (vmargs == null ? 0 : vmargs.length + 1)]; configArgIndex = 0; int j = 0; for (int i = 0; i < arguments.length; i++) { if (i == configArgs[configArgIndex]) configArgIndex++; else if (arguments[i] != null) passThruArgs[j++] = arguments[i]; } return passThruArgs; } private String processDevArg(String arg) { if (arg == null) return null; try { URL location = new URL(arg); Properties props = load(location, null); String result = props.getProperty("org.eclipse.osgi"); return result == null ? props.getProperty("*") : result; } catch (MalformedURLException e) { // the arg was not a URL so use it as is. return arg; } catch (IOException e) { // TODO consider logging here return null; } } private String getConfigurationLocation() { if (configurationLocation != null) return configurationLocation; URL result = buildLocation(PROP_CONFIG_AREA, null, CONFIG_DIR); if (result == null) result = buildURL(computeDefaultConfigurationLocation(), true); if (result == null) return null; configurationLocation = adjustTrailingSlash(result.toExternalForm(), true); System.getProperties().put(PROP_CONFIG_AREA, configurationLocation); if (debug) System.out.println("Configuration location:\n " + configurationLocation); 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) // 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 = buildURL(getInstallLocation() + CONFIG_DIR, true); baseConfiguration = loadConfiguration(baseConfigurationLocation.toExternalForm()); 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.toExternalForm())) configuration = loadConfiguration(getConfigurationLocation()); mergeProperties(System.getProperties(), configuration); if ("false".equalsIgnoreCase(System.getProperty(PROP_CONFIG_CASCADED))) // if we are not cascaded then remvoe the parent property even if it was set. System.getProperties().remove(PROP_SHARED_CONFIG_AREA); else { URL sharedConfigURL = buildLocation(PROP_SHARED_CONFIG_AREA, null, CONFIG_DIR); if (sharedConfigURL == null) // here we access the install location but this is very early. This case will only happen if // the config is cascaded and the parent config area is not set (or is bogus). // In this case we compute based on the install location. Note that we should not // precompute this value and use it as the default in the call to buildLocation as it will // unnecessarily bind the install location. sharedConfigURL = buildURL(getInstallLocation() + CONFIG_DIR, true); // if the parent location is different from the config location, read it too. if (sharedConfigURL != null) { String location = sharedConfigURL.toExternalForm(); if (location.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(location); mergeProperties(System.getProperties(), configuration); System.getProperties().put(PROP_SHARED_CONFIG_AREA, location); if (debug) System.out.println("Shared configuration location:\n " + location); } } } // setup the path to the framework String urlString = System.getProperty(PROP_FRAMEWORK, null); if (urlString != null) { urlString = adjustTrailingSlash(urlString, true); System.getProperties().put(PROP_FRAMEWORK, urlString); bootLocation = resolve(urlString); } } /** * Returns url of the location this class was loaded from */ private String getInstallLocation() { if (installLocation != null) return installLocation; // value is not set so compute the default and set the value installLocation = System.getProperty(PROP_INSTALL_AREA); if (installLocation != null) { URL location = buildURL(installLocation, true); if (location == null) throw new IllegalStateException("Install location is invalid: " + installLocation); installLocation = location.toExternalForm(); System.getProperties().put(PROP_INSTALL_AREA, installLocation); if (debug) System.out.println("Install location:\n " + installLocation); return installLocation; } URL result = Main.class.getProtectionDomain().getCodeSource().getLocation(); String path = decode(result.getFile()); path = new File(path).getAbsolutePath().replace(File.separatorChar, '/'); // TODO need a better test for windows // If on Windows then canonicalize the drive letter to be lowercase. 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.endsWith(".jar")) //$NON-NLS-1$ path = path.substring(0, path.lastIndexOf("/") + 1); //$NON-NLS-1$ try { installLocation = new URL(result.getProtocol(), result.getHost(), result.getPort(), path).toExternalForm(); System.getProperties().put(PROP_INSTALL_AREA, installLocation); } catch (MalformedURLException e) { // TODO Very unlikely case. log here. } if (debug) System.out.println("Install location:\n " + installLocation); return installLocation; } /* * Load the given configuration file */ private Properties loadConfiguration(String url) { Properties result = null; url += CONFIG_FILE; 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(String location) throws IOException { // try to load saved configuration file (watch for failed prior save()) URL url = buildURL(location, false); 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); // check to see if we have complete config file if (!PROP_EOF.equals(props.getProperty(PROP_EOF))) throw new IOException("Incomplete configuration file: " + url.toExternalForm()); //$NON-NLS-1$ } 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 scenarios this * results in the process created as a result of the -showsplash command * being destroyed. * * @param bootPath 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 = null; endSplash = null; return; } // if -endsplash is specified, use it and ignore any -showsplash command if (endSplash != null) { showSplash = null; return; } // check if we are running without a splash screen if (showSplash == null) 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; showProcess = runCommand(showSplash, location, " " + SHOWSPLASH); //$NON-NLS-1$ } private Process runCommand(String command, String data, String separator) { // Parse the showsplash command into its separate arguments. // The command format is: // <executable> -show <magicArg> [<splashPath>] // If either the <executable> or the <splashPath> arguments contain a // space, Runtime.getRuntime().exec( String ) will not work, even // if both arguments are enclosed in double-quotes. The solution is to // use the Runtime.getRuntime().exec( String[] ) method. String[] args = new String[(data != null ? 4 : 3)]; // get the executable part int sIndex = 0; int eIndex = command.indexOf(separator); if (eIndex == -1) return null; // invalid command args[0] = command.substring(sIndex, eIndex); // get the command part sIndex = eIndex + 1; eIndex = command.indexOf(" ", sIndex); //$NON-NLS-1$ if (eIndex == -1) return null; // invalid command args[1] = command.substring(sIndex, eIndex); // get the magic arg part args[2] = command.substring(eIndex + 1); // add on our data if (data != null) args[3] = data; Process result = null; try { result = Runtime.getRuntime().exec(args); } catch (Exception e) { log("Exception running command: " + command); //$NON-NLS-1$ log(e); } return result; } /* * take down the splash screen. Try both take-down methods just in case * (only one should ever be set) */ protected void takeDownSplash() { if (splashDown) // splash is already down return; // check if -endsplash was specified if (endSplash != null) { try { Runtime.getRuntime().exec(endSplash); } catch (Exception e) { //ignore failure to end splash } } // check if -showsplash was specified and executed if (showProcess != null) { showProcess.destroy(); showProcess = null; } splashDown = true; } /* * 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:")) { 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]); } // 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 current locale information String localePath = Locale.getDefault().toString().replace('_', File.separatorChar); // search the specified path while (localePath != null) { String suffix; if (localePath.equals("")) { //$NON-NLS-1$ // look for nl'ed splash image suffix = SPLASH_IMAGE; } else { // look for default splash image suffix = "nl" + File.separator + localePath + File.separator + SPLASH_IMAGE; //$NON-NLS-1$ } // check for file in searchPath for (int i = 0; i < searchPath.length; i++) { String path = searchPath[i]; if (!path.endsWith(File.separator)) path += File.separator; path += suffix; File result = new File(path); if (result.exists()) return result.getAbsolutePath(); // return the first match found [20063] } // try the next variant if (localePath.equals("")) //$NON-NLS-1$ localePath = null; else { int ix = localePath.lastIndexOf(File.separator); if (ix == -1) localePath = ""; //$NON-NLS-1$ else localePath = localePath.substring(0, ix); } } // sorry, could not find splash image return null; } /* * resolve platform:/base/ URLs */ private String resolve(String urlString) { // handle the case where people mistakenly spec a refererence: url. if (urlString.startsWith("reference:")) { 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. */ private 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(' '); try { DateFormat formatter = new SimpleDateFormat("MMM dd, yyyy kk:mm:ss.SS"); //$NON-NLS-1$ log.write(formatter.format(new Date())); } catch (Exception e) { // continue if we can't write out the date log.write(Long.toString(System.currentTimeMillis())); } log.newLine(); log.write(MESSAGE); log.write(' '); log.write(String.valueOf(obj)); } log.newLine(); } private void computeLogFileLocation() { String logFileProp = System.getProperty(PROP_LOGFILE); if (logFileProp != null) { if (logFile == null || !logFileProp.equals(logFile.getAbsolutePath())) { logFile = new File(logFileProp); logFile.getParentFile().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$ logFile.getParentFile().mkdirs(); System.setProperty(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_EOF)) { String value = source.getProperty(key); if (destination.getProperty(key) == null) destination.put(key, value); } } } public 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++) { result.append(value[i]); result.append('\n'); } System.getProperties().put(property, result.toString()); } } private String adjustTrailingSlash(String value, boolean slash) { boolean hasSlash = value.endsWith("/") || value.endsWith(File.separator); if (hasSlash == slash) return value; if (hasSlash) return value.substring(0, value.length() - 1); return value + "/"; } }