blob: 1f87430cd15990e2cb31fd1cdb0e41ccbc5092cc [file] [log] [blame]
/*******************************************************************************
* 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.*;
/**
* 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 &lt;id&gt;: the identifier of the application to run
* </dd>
* <dd>
* -arch &lt;architecture&gt;: sets the processor architecture value
* </dd>
* <dd>
* -boot &lt;location&gt;: 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 &lt;location&gt;: 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 &lt;location&gt;: 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 &lt;id&gt;: 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 &lt;location&gt;: the location of the authorization database on disk. This argument
* has to be used together with the -password argument.
* </dd>
* <dd>
* -nl &lt;locale&gt;: 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 &lt;operating system&gt;: sets the operating system value
* </dd>
* <dd>
* -password &lt;passwd&gt;: the password for the authorization database
* </dd>
* <dd>
* -plugins &lt;location&gt;: 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 &lt;properties file&gt;: 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 &lt;window system&gt;: 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 rootLocation = null;
/**
* The identifier of the application to run.
*/
protected String application;
/**
* The identifier of the feature to run.
*/
protected String feature;
/**
* The identifier of the configuration to use.
*/
protected String configuration;
/**
* The path for finding find plugins.
*/
protected URL pluginPathLocation;
/**
* The boot path location.
*/
protected String location;
/**
* The class path entries.
*/
protected String devClassPath = null;
/**
* Indicates whether this instance is running in development mode.
*/
protected boolean inDevelopmentMode = false;
// splash handling
private String showSplash = null;
private String endSplash = null;
private boolean cmdInitialize = false;
private boolean cmdFirstUse = false;
private Process showProcess = null;
private boolean splashDown = false;
private final Runnable endSplashHandler = new Runnable() {
public void run() {
takeDownSplash();
}
};
// configuration properties
private Properties props = new Properties();
private HashMap featureIndex = new HashMap();
private String baseLocation = null;
// constants
private static final String APPLICATION = "-application"; //$NON-NLS-1$
private static final String BOOT = "-boot"; //$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 DEBUG = "-debug"; //$NON-NLS-1$
private static final String DEV = "-dev"; //$NON-NLS-1$
private static final String DATA = "-data"; //$NON-NLS-1$
private static final String CONFIGURATION = "-configuration"; //$NON-NLS-1$
private static final String FEATURE = "-feature"; //$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 FIRST_USE = "-firstuse"; //$NON-NLS-1$
private static final String SPLASH_IMAGE = "splash.bmp"; //$NON-NLS-1$
private static final String PI_BOOT = "org.eclipse.core.boot"; //$NON-NLS-1$
private static final String BOOTLOADER = "org.eclipse.core.boot.BootLoader"; //$NON-NLS-1$
private static final String BOOTJAR = "boot.jar"; //$NON-NLS-1$
private static final String PLATFORM_URL = "platform:/base/"; //$NON-NLS-1$
// constants: configuration file location
private static final String CONFIG_FILE = "platform.cfg"; //$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 ARG_USER_DIR = "user.dir"; //$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: configuration file elements
private static final String CFG_CORE_BOOT = "bootstrap." + PI_BOOT; //$NON-NLS-1$
private static final String CFG_FEATURE_ENTRY = "feature"; //$NON-NLS-1$
private static final String CFG_FEATURE_ENTRY_DEFAULT = "feature.default.id"; //$NON-NLS-1$
private static final String CFG_FEATURE_ENTRY_ID = "id"; //$NON-NLS-1$
private static final String CFG_FEATURE_ENTRY_ROOT = "root"; //$NON-NLS-1$
private static final String CFG_EOF = "eof"; //$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 static File logFile = null;
protected static BufferedWriter log = null;
protected static String[] arguments;
protected static boolean newSession = true;
static class Identifier {
private static final String DELIM = ". "; //$NON-NLS-1$
private int major, minor, service;
private Identifier(int major, int minor, int service) {
super();
this.major = major;
this.minor = minor;
this.service = service;
}
private 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());
// service
if (tokenizer.hasMoreTokens())
service = Integer.parseInt(tokenizer.nextToken());
}
/**
* 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
*/
private 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;
}
}
/**
* 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 {
// 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
Class clazz = getBootLoader(bootPath);
Method method = clazz.getDeclaredMethod("run", new Class[] { String.class, URL.class, String.class, String[].class, Runnable.class }); //$NON-NLS-1$
try {
return method.invoke(clazz, new Object[] { application, pluginPathLocation, location, args, endSplashHandler });
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof Error)
throw (Error) e.getTargetException();
else
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
*/
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 == '%') {
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[0]);
}
/**
* Creates and returns a platform <code>BootLoader</code> which can be used to start
* up and run the platform.
*
* @return the new boot loader
* @param path search path for the BootLoader
*/
public Class getBootLoader(URL[] path) throws Exception {
URLClassLoader loader = new URLClassLoader(path, null);
return loader.loadClass(BOOTLOADER);
}
/**
* 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
*/
protected URL[] getDevPath(URL base) throws MalformedURLException {
URL url;
String devBase = base.toExternalForm();
if (!inDevelopmentMode) {
url = new URL(devBase + BOOTJAR);
return new URL[] {url};
}
String[] locations = getArrayFromList(devClassPath);
ArrayList result = new ArrayList(locations.length);
for (int i = 0; i < locations.length; i++) {
String spec = devBase + locations[i];
char lastChar = spec.charAt(spec.length() - 1);
if ((spec.endsWith(".jar") || (lastChar == '/' || lastChar == '\\'))) //$NON-NLS-1$
url = new URL (spec);
else
url = new URL(spec + "/"); //$NON-NLS-1$
//make sure URL exists before adding to path
if (new java.io.File(url.getFile()).exists())
result.add(url);
}
url = new URL(devBase + BOOTJAR);
if (new java.io.File(url.getFile()).exists())
result.add(url);
return (URL[])result.toArray(new URL[result.size()]);
}
/**
* 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 MalformedURLException {
URL url = null;
// if the given location is not null, assume it is correct and use it.
if (base != null) {
url = new URL(base);
if (debug)
System.out.println("Boot URL: " + url.toExternalForm()); //$NON-NLS-1$
return new URL[] {url};
}
// search for boot in root location
URL[] result = null;
url = new URL(rootLocation);
String path = url.getFile();
path = searchForBoot(path);
// add on any dev path elements
url = new URL(url.getProtocol(), url.getHost(), url.getPort(), path);
result = getDevPath(url);
if (debug) {
System.out.println("Boot URL:"); //$NON-NLS-1$
for (int i = 0; i < result.length; i++)
System.out.println(" " + result[i].toExternalForm()); //$NON-NLS-1$
}
return result;
}
/**
* Searches for a boot 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 boot directory was found
* @param start the location to begin searching at
*/
protected String searchForBoot(String start) {
FileFilter filter = new FileFilter() {
public boolean accept(File candidate) {
return candidate.isDirectory() &&
(candidate.getName().equals(PI_BOOT)
|| candidate.getName().startsWith(PI_BOOT + "_")); //$NON-NLS-1$
}
};
File[] boots = new File(start, "plugins").listFiles(filter); //$NON-NLS-1$
if (boots == null)
throw new RuntimeException("Could not find bootstrap code. Check location of boot plug-in or specify -boot."); //$NON-NLS-1$
String result = null;
Object maxVersion = null;
for (int i = 0; i < boots.length; i++) {
String name = boots[i].getName();
int index = name.indexOf('_');
String version;
Object currentVersion;
if (index == -1)
version = ""; //$NON-NLS-1$ // Note: directory with version suffix is always > than directory without version suffix
else
version = name.substring(index + 1);
currentVersion = getVersionElements(version);
if (maxVersion == null) {
result = boots[i].getAbsolutePath();
maxVersion = currentVersion;
} else {
if (compareVersion((Object[])maxVersion, (Object[])currentVersion) < 0) {
result = boots[i].getAbsolutePath();
maxVersion = currentVersion;
}
}
}
if (result == null)
throw new RuntimeException("Could not find bootstrap code. Check location of boot plug-in or specify -boot."); //$NON-NLS-1$
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;
}
/**
* 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>.
*
* @see #run
*
* @param args the command line arguments
*/
public static void main(String[] args) {
Object result = null;
arguments = args;
// Check to see if we are running with a compatible VM.
// If not, then return exit code "14" which will be recognized
// by the executable and an appropriate message will be displayed
// to the user.
if (!isCompatible())
System.exit(14);
// Check to see if there is already a platform running in
// this workspace. If there is, then return an exit code of "15" which
// will be recognized by the executable and an appropriate message
// will be displayed to the user.
if (isAlreadyRunning())
System.exit(15);
Main launcher = new Main();
try {
result = launcher.run(args);
} catch (Throwable e) {
// try and take down the splash screen.
launcher.takeDownSplash();
log("Exception launching the Eclipse Platform:"); //$NON-NLS-1$
log(e);
// 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.
System.exit(13);
}
int exitCode = result instanceof Integer ? ((Integer) result).intValue() : 0;
System.exit(exitCode);
}
/**
* 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) throws Exception {
Vector list = new Vector(5);
for (StringTokenizer tokens = new StringTokenizer(argString, " "); tokens.hasMoreElements();) //$NON-NLS-1$
list.addElement((String) tokens.nextElement());
main((String[]) list.toArray(new String[list.size()]));
}
/**
* Processes the command line arguments
*
* @return the arguments to pass through to the launched application
* @param args the command line arguments
*/
protected String[] processCommandLine(String[] args) throws Exception {
int[] configArgs = new int[100];
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;
}
// check if this is initialization pass
if (args[i].equalsIgnoreCase(INITIALIZE)) {
cmdInitialize = 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;
}
// 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 laucher to run
if (args[i - 1].equalsIgnoreCase(BOOT)) {
bootLocation = arg;
found = true;
}
// look explicitly set install root
if (args[i - 1].equalsIgnoreCase(INSTALL)) {
rootLocation = arg;
found = true;
}
// look for the development mode and class path entries.
if (args[i - 1].equalsIgnoreCase(DEV)) {
inDevelopmentMode = true;
devClassPath = arg;
continue;
}
// look for the location of workspace
if (args[i - 1].equalsIgnoreCase(DATA)) {
baseLocation = arg;
continue; // pass the arg on
}
// look for the application to run
if (args[i - 1].equalsIgnoreCase(APPLICATION)) {
application = arg;
found = true;
}
// look for the feature to run
if (args[i - 1].equalsIgnoreCase(FEATURE)) {
feature = arg;
continue; // pass the arg on [20063]
}
// look for the configuration to use
if (args[i - 1].equalsIgnoreCase(CONFIGURATION)) {
configuration = arg;
// we mark -configuration for removal. It will be
// reinserted after we determine the actual URL of
// the configuration file to use.
found = true;
}
// look for token to use to show the splash screen
if (args[i - 1].equalsIgnoreCase(SHOWSPLASH)) {
showSplash = arg;
found = true;
}
// look for token to use to end the splash screen
if (args[i - 1].equalsIgnoreCase(ENDSPLASH)) {
endSplash = 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 args;
String[] passThruArgs = new String[args.length - configArgIndex];
configArgIndex = 0;
int j = 0;
for (int i = 0; i < args.length; i++) {
if (i == configArgs[configArgIndex])
configArgIndex++;
else
passThruArgs[j++] = args[i];
}
return passThruArgs;
}
/**
* Runs the application to be launched.
*
* @return the return value from the launched application
* @param args the arguments to pass to the application
* @exception thrown if a problem occurs during launching
*/
public Object run(String[] args) throws Exception {
String[] passThruArgs = processCommandLine(args);
passThruArgs = processConfiguration(passThruArgs);
return basicRun(passThruArgs);
}
/*
* After the command line arguments have been processed, we try
* to locate and load the platform configuration file. It contains
* information maintained by the install/ update support. In
* particular, the following are needed at this point
* in the startup sequence:
* -> if -boot was not specified, which boot.jar to load
* BootLoader from (original core.boot plugin may have been updated)
* -> if -feature was not specified, what is the default feature
* (product packagers can set the default)
* -> if we were requested to put up the splash (-showsplash
* was specified), which one (based on defaulted or
* specified feature information)
* Note, that if we can't find the platform configuration file,
* or it does not contain the information we are looking for,
* the startup support ends up computing "reasonable" defaults
* as before (based on relative locations within the file system)
*/
private String[] processConfiguration(String[] passThruArgs) throws MalformedURLException {
// get install root location, if not specified
if (rootLocation == null)
rootLocation = getRootURL().toExternalForm();
// attempt to locate configuration file
URL configURL = null;
if (configuration != null && !configuration.trim().equals("")) { //$NON-NLS-1$
configuration = configuration.replace(File.separatorChar, '/');
if (configuration.equalsIgnoreCase(ARG_USER_DIR)) {
// configuration is in current working directory
String tmp = System.getProperty("user.dir"); //$NON-NLS-1$
if (!tmp.endsWith(File.separator))
tmp += File.separator;
configURL = new URL("file:" + tmp.replace(File.separatorChar,'/') + CONFIG_FILE); //$NON-NLS-1$
} else if(configuration.endsWith("/")) { //$NON-NLS-1$
// configuration specified as directory URL
configURL = new URL(configuration + CONFIG_FILE);
} else {
// configuration specified down to a file
configURL = new URL(configuration);
}
} else {
// configuration not specified - defer to BootLoader
}
// load configuration
loadConfiguration(configURL);
// get boot url, if none was specified
if (bootLocation == null) {
String urlString = loadAttribute(props, CFG_CORE_BOOT, null);
if (urlString != null) {
try {
urlString = resolve(urlString);
URL bootDir = new URL(urlString);
URL bootURL = new URL(bootDir, BOOTJAR);
if (bootDir.getProtocol().equals("file")) { //$NON-NLS-1$
File jar = new File(bootURL.getFile());
if (jar.exists())
// verify boot dir exists
bootLocation = bootURL.toExternalForm();
else
// Somebody turned the tables on us. The info in
// platform.cfg is stale, or the workspace is being used
// with a new install. Do default search for boot and force
// "first use" processing (optimistic reconciliation)
cmdFirstUse = true;
} else
bootLocation = bootURL.toExternalForm();
} catch(MalformedURLException e) {
// continue ... will do default search for boot
}
}
}
// reconstruct command line arguments for configuration elements
// (-boot and -application are not passed to BootLoader)
if (configURL == null && rootLocation == null)
return passThruArgs;
ArrayList args = new ArrayList(Arrays.asList(passThruArgs));
if (configURL != null) {
args.add(CONFIGURATION);
args.add(decode(configURL.toExternalForm()));
}
if (cmdFirstUse) {
args.add(FIRST_USE);
}
// pass root location downstream
if (rootLocation != null) {
args.add(INSTALL);
args.add(rootLocation);
}
return (String[])args.toArray(new String[0]);
}
/**
* Returns url of the location this class was loaded from
*/
private URL getRootURL() throws MalformedURLException {
if (rootLocation != null)
return new URL(rootLocation);
URL url = getClass().getProtectionDomain().getCodeSource().getLocation();
String path = decode(url.getFile());
path = new File(path).getAbsolutePath().replace(File.separatorChar,'/');
if (path.endsWith(".jar")) //$NON-NLS-1$
path = path.substring(0, path.lastIndexOf("/")+1); //$NON-NLS-1$
url = new URL(url.getProtocol(), url.getHost(), url.getPort(), path);
rootLocation = url.toExternalForm();
return url;
}
/**
* Returns the url of the default location for configuration.
* 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.
*/
private URL getDefaultStateLocation() throws IOException {
URL installURL = getRootURL();
File installDir = new File(installURL.getFile());
if ("file".equals(installURL.getProtocol()) && installDir.canWrite()) { //$NON-NLS-1$
if (debug)
System.out.println("Using the installation directory."); //$NON-NLS-1$
return installURL;
} else {
if (debug)
System.out.println("Using the user.home location."); //$NON-NLS-1$
String appName = "." + ECLIPSE; //$NON-NLS-1$
File eclipseProduct = new File(installDir, PRODUCT_SITE_MARKER );
if (eclipseProduct.exists()) {
Properties props = new Properties();
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$
}
String userHome = System.getProperty("user.home"); //$NON-NLS-1$
File configDir = new File(userHome, appName);
configDir.mkdirs();
return configDir.toURL();
}
}
/*
* Load the configuration file. If not specified, default to default state location
*/
private void loadConfiguration(URL url) {
if (url == null) {
// configuration URL was not specified ..... search
URL defaultStateURL = null;
try {
defaultStateURL = getDefaultStateLocation();
URL cfigURL = new URL(defaultStateURL, CONFIG_FILE);
// first look for configuration in base location (workspace or as specified)
props = loadProperties(cfigURL);
if (debug)
System.out.println("Startup: using configuration " + cfigURL.toString()); //$NON-NLS-1$
return; // we're done looking
} catch (IOException e1) {
// .continue
}
try {
File cfg = null;
// look for configuration in install root (set up by -initialize)
String install = getRootURL().getFile().replace('/',File.separatorChar);
if (!install.endsWith(File.separator))
install += File.separator;
cfg = new File(install + ".config" + File.separator + CONFIG_FILE); //$NON-NLS-1$
url = new URL("file", null, 0, cfg.getAbsolutePath()); //$NON-NLS-1$
props = loadProperties(url);
if (debug)
System.out.println("Startup: using configuration " + url.toString()); //$NON-NLS-1$
} catch(IOException e) {
// continue ...
if (debug)
System.out.println("Startup: unable to load configuration\n" + e); //$NON-NLS-1$
}
} else {
try {
// configuration url was specified ... use it
props = loadProperties(url);
if (debug)
System.out.println("Startup: using configuration " + url.toString()); //$NON-NLS-1$
} catch(IOException e) {
// continue ...
if (debug)
System.out.println("Startup: unable to load configuration\n" + e); //$NON-NLS-1$
}
}
}
private Properties loadProperties(URL url) throws IOException {
// try to load saved configuration file (watch for failed prior save())
Properties props = null;
IOException originalException = null;
try {
props = load(url, null); // try to load config file
} catch(IOException e1) {
originalException = e1;
try {
props = load(url, CONFIG_FILE_TEMP_SUFFIX); // check for failures on save
} catch(IOException e2) {
try {
props = load(url, CONFIG_FILE_BAK_SUFFIX); // check for failures on save
} catch(IOException e3) {
throw originalException; // we tried, but no config here ...
}
}
}
return props;
}
/*
* 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 (!CFG_EOF.equals(props.getProperty(CFG_EOF))) {
throw new IOException();
}
} finally {
if (is!=null) {
try {
is.close();
} catch(IOException e) {
//ignore failure to close
}
}
}
// load feature index
if (props != null) {
String id = props.getProperty(CFG_FEATURE_ENTRY+".0."+CFG_FEATURE_ENTRY_ID); //$NON-NLS-1$
for (int i=1; id != null; i++) {
featureIndex.put(id, Integer.toString(i-1));
id = props.getProperty(CFG_FEATURE_ENTRY+"."+i+"."+CFG_FEATURE_ENTRY_ID); //$NON-NLS-1$ //$NON-NLS-2$
}
}
return props;
}
/*
* Load a configuration attribute
*/
private String loadAttribute(Properties props, String name, String dflt) {
if (props == null)
return dflt;
String prop = props.getProperty(name);
if (prop == null)
return dflt;
else
return prop.trim();
}
/*
* 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[] bootPath) {
// run without splash if we are initializing
if (cmdInitialize) {
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 path
String path = getSplashPath(bootPath);
if (debug && path != null) {
System.out.println("Startup: splash path = "+path); //$NON-NLS-1$
}
// 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[] cmd = new String[ (path != null ? 4 : 3) ];
int sIndex = 0;
int eIndex = showSplash.indexOf( " -show" ); //$NON-NLS-1$
if (eIndex == -1)
return; // invalid -showsplash command
cmd[0] = showSplash.substring( sIndex, eIndex );
sIndex = eIndex + 1;
eIndex = showSplash.indexOf( " ", sIndex ); //$NON-NLS-1$
if (eIndex == -1)
return; // invalid -showsplash command
cmd[1] = showSplash.substring( sIndex, eIndex );
cmd[2] = showSplash.substring( eIndex+1 );
if (path != null)
cmd[3] = path;
try {
showProcess = Runtime.getRuntime().exec(cmd);
} catch (Exception e) {
// continue without splash ...
log("Exception showing splash screen."); //$NON-NLS-1$
log(e);
}
return;
}
/*
* take down the splash screen. Try both take-down methods just in case
* (only one should ever be set)
*/
private 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
*/
private String getSplashPath(URL[] bootPath) {
// see if we can get a splash for the launched feature
String[] featurePath = getFeatureRoot();
String path = lookupSplash(featurePath);
if (path != null)
return path;
// no feature path, default to current boot plugin
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);
path = lookupSplash(new String[] { temp });
return path;
}
}
// sorry, no splash found
return null;
}
/*
* Do a locale-sensitive lookup of splash image
*/
private String lookupSplash(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 splash = new File(path);
if (splash.exists())
return splash.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;
}
/*
* Return the feature identifier to use (specified or defaulted)
*/
private String getFeatureIdentifier() {
if (feature != null) // -feature was specified on command line
return feature;
// feature was not specified on command line
String dflt = props.getProperty(CFG_FEATURE_ENTRY_DEFAULT);
if (dflt != null)
return dflt; // return customized default if set.
else
return null; // otherwise let BootLoader pick default
}
/*
* Return the feature root if known
*/
private String[] getFeatureRoot() {
String ix = featureIndex(getFeatureIdentifier());
String urlString = props.getProperty(CFG_FEATURE_ENTRY + "." + ix + "." + CFG_FEATURE_ENTRY_ROOT + ".0"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if (urlString == null)
return null;
ArrayList result = new ArrayList();
URL url = null;
for (int i=1; urlString != null; i++) {
try {
urlString = resolve(urlString); // resolve platform relative URLs
url = new URL(urlString);
if (url.getProtocol().equals("file")) { //$NON-NLS-1$
result.add(url.getFile().replace('/', File.separatorChar));
} else
continue; // in the future may cache
} catch(MalformedURLException e) {
// skip bad entries
}
urlString = props.getProperty(CFG_FEATURE_ENTRY + "." + ix + "." + CFG_FEATURE_ENTRY_ROOT + "." + i); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
if (result.size()>0)
return (String[])result.toArray(new String[0]);
else
return null;
}
/*
* resolve platform:/base/ URLs
*/
private String resolve(String urlString) throws MalformedURLException {
if (urlString.startsWith(PLATFORM_URL)) {
String root = getRootURL().toExternalForm();
if (!root.endsWith("/")) //$NON-NLS-1$
root += "/"; //$NON-NLS-1$
String path = urlString.substring(PLATFORM_URL.length());
return root + path;
} else
return urlString;
}
/*
*
*/
private String featureIndex(String id) {
if (id==null)
return null;
return (String)featureIndex.get(id);
}
/*
* Entry point for logging.
*/
private static synchronized void log(Object obj) {
if (obj == null)
return;
try {
openLogFile();
try {
if (newSession) {
log.write(SESSION);
log.write(' ');
for (int i=SESSION.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 static 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 static void computeLogFileLocation() {
if (logFile != null)
return;
// compute the base location and then append the name of the log file
File base = computeMetadataLocation();
logFile = new File(base, ".log"); //$NON-NLS-1$
logFile.getParentFile().mkdirs();
}
private static File computeLockFileLocation() {
// compute the base location and then append the name of the lock file
File base = computeMetadataLocation();
File result = new File(base, ".lock"); //$NON-NLS-1$
result.getParentFile().mkdirs();
return result;
}
private static File computeMetadataLocation() {
File result = null;
// check to see if the user specified a workspace location in the command-line args
for (int i=0; arguments != null && result == null && i<arguments.length; i++) {
if (arguments[i].equalsIgnoreCase(DATA)) {
// found the -data command line argument so the next argument should be the
// workspace location. Ensure that we have another arg to check
if (i+1<arguments.length)
result = new File(arguments[i+1]);
}
}
// otherwise use the default location
if (result == null)
result = new File(System.getProperty("user.dir"), "workspace");//$NON-NLS-1$ //$NON-NLS-2$
// append the .metadata directory to the path
return new File(result, ".metadata"); //$NON-NLS-1$
}
/**
* 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");
}
}
private static 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 static 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 static void closeLogFile() throws IOException {
try {
if (log != null) {
log.flush();
log.close();
}
} finally {
log = null;
}
}
/**
* Return a boolean value indicating whether or not the version of the JVM is
* deemed to be compatible with Eclipse.
*/
private static boolean isCompatible() {
try {
String vmVersionString = System.getProperty("java.version"); //$NON-NLS-1$
Identifier minimum = new Identifier(1, 3, 0);
Identifier version = new Identifier(vmVersionString);
return version.isGreaterEqualTo(minimum);
} 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;
}
}
/**
* Return a boolean value indicating whether or not the platform is already
* running in this workspace.
*/
private static boolean isAlreadyRunning() {
// Calculate the location of the lock file
File lockFile = computeLockFileLocation();
// if the lock file already exists, try to delete,
// assume failure means another eclipse has it open
if (lockFile.exists())
lockFile.delete();
return lockFile.exists();
}
}