1 /*******************************************************************************
2  * Copyright (c) 2003, 2016 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Alex Blewitt (bug 172969)
14  *******************************************************************************/
15 package org.eclipse.core.runtime.adaptor;
16 
17 import java.io.*;
18 import java.lang.reflect.Method;
19 import java.net.*;
20 import java.security.CodeSource;
21 import java.security.ProtectionDomain;
22 import java.util.*;
23 import java.util.concurrent.Semaphore;
24 import java.util.concurrent.TimeUnit;
25 import org.eclipse.core.runtime.internal.adaptor.*;
26 import org.eclipse.osgi.container.Module;
27 import org.eclipse.osgi.container.ModuleRevision;
28 import org.eclipse.osgi.container.namespaces.EquinoxModuleDataNamespace;
29 import org.eclipse.osgi.framework.log.FrameworkLog;
30 import org.eclipse.osgi.framework.log.FrameworkLogEntry;
31 import org.eclipse.osgi.framework.util.FilePath;
32 import org.eclipse.osgi.internal.debug.Debug;
33 import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
34 import org.eclipse.osgi.internal.framework.EquinoxContainer;
35 import org.eclipse.osgi.internal.location.EquinoxLocations;
36 import org.eclipse.osgi.internal.location.LocationHelper;
37 import org.eclipse.osgi.internal.messages.Msg;
38 import org.eclipse.osgi.launch.Equinox;
39 import org.eclipse.osgi.report.resolution.ResolutionReport;
40 import org.eclipse.osgi.service.datalocation.Location;
41 import org.eclipse.osgi.service.environment.EnvironmentInfo;
42 import org.eclipse.osgi.service.runnable.ApplicationLauncher;
43 import org.eclipse.osgi.service.runnable.StartupMonitor;
44 import org.eclipse.osgi.storage.url.reference.Handler;
45 import org.eclipse.osgi.util.ManifestElement;
46 import org.eclipse.osgi.util.NLS;
47 import org.osgi.framework.*;
48 import org.osgi.framework.launch.Framework;
49 import org.osgi.framework.startlevel.BundleStartLevel;
50 import org.osgi.framework.startlevel.FrameworkStartLevel;
51 import org.osgi.framework.wiring.FrameworkWiring;
52 import org.osgi.resource.Resource;
53 import org.osgi.util.tracker.ServiceTracker;
54 
55 /**
56  * Special startup class for the Eclipse Platform. This class cannot be
57  * instantiated; all functionality is provided by static methods.
58  * <p>
59  * The Eclipse Platform makes heavy use of Java class loaders for loading
60  * plug-ins. Even the Eclipse Runtime itself and the OSGi framework need
61  * to be loaded by special class loaders. The upshot is that a
62  * client program (such as a Java main program, a servlet) cannot
63  * reference any part of Eclipse directly. Instead, a client must use this
64  * loader class to start the platform, invoking functionality defined
65  * in plug-ins, and shutting down the platform when done.
66  * </p>
67  * <p>Note that the fields on this class are not API. </p>
68  * @since 3.0
69  * @noextend This class is not intended to be subclassed by clients.
70  */
71 public class EclipseStarter {
72 	private static BundleContext context;
73 	private static boolean initialize = false;
74 	public static boolean debug = false;
75 	private static boolean running = false;
76 	private static ServiceRegistration<?> defaultMonitorRegistration = null;
77 	private static ServiceRegistration<?> appLauncherRegistration = null;
78 	private static ServiceRegistration<?> splashStreamRegistration = null;
79 
80 	// command line arguments
81 	private static final String CLEAN = "-clean"; //$NON-NLS-1$
82 	private static final String CONSOLE = "-console"; //$NON-NLS-1$
83 	private static final String CONSOLE_LOG = "-consoleLog"; //$NON-NLS-1$
84 	private static final String DEBUG = "-debug"; //$NON-NLS-1$
85 	private static final String INITIALIZE = "-initialize"; //$NON-NLS-1$
86 	private static final String DEV = "-dev"; //$NON-NLS-1$
87 	private static final String WS = "-ws"; //$NON-NLS-1$
88 	private static final String OS = "-os"; //$NON-NLS-1$
89 	private static final String ARCH = "-arch"; //$NON-NLS-1$
90 	private static final String NL = "-nl"; //$NON-NLS-1$
91 	private static final String NL_EXTENSIONS = "-nlExtensions"; //$NON-NLS-1$
92 	private static final String CONFIGURATION = "-configuration"; //$NON-NLS-1$
93 	private static final String USER = "-user"; //$NON-NLS-1$
94 	private static final String NOEXIT = "-noExit"; //$NON-NLS-1$
95 	private static final String LAUNCHER = "-launcher"; //$NON-NLS-1$
96 
97 	// this is more of an Eclipse argument but this OSGi implementation stores its
98 	// metadata alongside Eclipse's.
99 	private static final String DATA = "-data"; //$NON-NLS-1$
100 
101 	// System properties
102 	public static final String PROP_BUNDLES = "osgi.bundles"; //$NON-NLS-1$
103 	public static final String PROP_BUNDLES_STARTLEVEL = "osgi.bundles.defaultStartLevel"; //$NON-NLS-1$ //The start level used to install the bundles
104 	public static final String PROP_EXTENSIONS = "osgi.framework.extensions"; //$NON-NLS-1$
105 	public static final String PROP_INITIAL_STARTLEVEL = "osgi.startLevel"; //$NON-NLS-1$ //The start level when the fwl start
106 	public static final String PROP_DEBUG = "osgi.debug"; //$NON-NLS-1$
107 	public static final String PROP_DEV = "osgi.dev"; //$NON-NLS-1$
108 	public static final String PROP_CLEAN = "osgi.clean"; //$NON-NLS-1$
109 	public static final String PROP_CONSOLE = "osgi.console"; //$NON-NLS-1$
110 	public static final String PROP_CONSOLE_CLASS = "osgi.consoleClass"; //$NON-NLS-1$
111 	public static final String PROP_CHECK_CONFIG = "osgi.checkConfiguration"; //$NON-NLS-1$
112 	public static final String PROP_OS = "osgi.os"; //$NON-NLS-1$
113 	public static final String PROP_WS = "osgi.ws"; //$NON-NLS-1$
114 	public static final String PROP_NL = "osgi.nl"; //$NON-NLS-1$
115 	private static final String PROP_NL_EXTENSIONS = "osgi.nl.extensions"; //$NON-NLS-1$
116 	public static final String PROP_ARCH = "osgi.arch"; //$NON-NLS-1$
117 	public static final String PROP_ADAPTOR = "osgi.adaptor"; //$NON-NLS-1$
118 	public static final String PROP_SYSPATH = "osgi.syspath"; //$NON-NLS-1$
119 	public static final String PROP_LOGFILE = "osgi.logfile"; //$NON-NLS-1$
120 	public static final String PROP_FRAMEWORK = "osgi.framework"; //$NON-NLS-1$
121 	public static final String PROP_INSTALL_AREA = "osgi.install.area"; //$NON-NLS-1$
122 	public static final String PROP_FRAMEWORK_SHAPE = "osgi.framework.shape"; //$NON-NLS-1$ //the shape of the fwk (jar, or folder)
123 	public static final String PROP_NOSHUTDOWN = "osgi.noShutdown"; //$NON-NLS-1$
124 
125 	public static final String PROP_EXITCODE = "eclipse.exitcode"; //$NON-NLS-1$
126 	public static final String PROP_EXITDATA = "eclipse.exitdata"; //$NON-NLS-1$
127 	public static final String PROP_CONSOLE_LOG = "eclipse.consoleLog"; //$NON-NLS-1$
128 	public static final String PROP_IGNOREAPP = "eclipse.ignoreApp"; //$NON-NLS-1$
129 	public static final String PROP_REFRESH_BUNDLES = "eclipse.refreshBundles"; //$NON-NLS-1$
130 	private static final String PROP_ALLOW_APPRELAUNCH = "eclipse.allowAppRelaunch"; //$NON-NLS-1$
131 	private static final String PROP_APPLICATION_LAUNCHDEFAULT = "eclipse.application.launchDefault"; //$NON-NLS-1$
132 
133 	private static final String FILE_SCHEME = "file:"; //$NON-NLS-1$
134 	private static final String REFERENCE_SCHEME = "reference:"; //$NON-NLS-1$
135 	private static final String REFERENCE_PROTOCOL = "reference"; //$NON-NLS-1$
136 	private static final String INITIAL_LOCATION = "initial@"; //$NON-NLS-1$
137 
138 	private static final int DEFAULT_INITIAL_STARTLEVEL = 6; // default value for legacy purposes
139 	private static final String DEFAULT_BUNDLES_STARTLEVEL = "4"; //$NON-NLS-1$
140 
141 	private static FrameworkLog log;
142 	// directory of serch candidates keyed by directory abs path -> directory listing (bug 122024)
143 	private static Map<String, String[]> searchCandidates = new HashMap<>(4);
144 	private static EclipseAppLauncher appLauncher;
145 	private static List<Runnable> shutdownHandlers;
146 
147 	private static ConsoleManager consoleMgr = null;
148 
149 	private static Map<String, String> configuration = null;
150 	private static Framework framework = null;
151 	private static EquinoxConfiguration equinoxConfig;
152 	private static String[] allArgs = null;
153 	private static String[] frameworkArgs = null;
154 	private static String[] appArgs = null;
155 
getProperty(String key)156 	private synchronized static String getProperty(String key) {
157 		if (equinoxConfig != null) {
158 			return equinoxConfig.getConfiguration(key);
159 		}
160 		return getConfiguration().get(key);
161 	}
162 
getProperty(String key, String dft)163 	private synchronized static String getProperty(String key, String dft) {
164 		if (equinoxConfig != null) {
165 			return equinoxConfig.getConfiguration(key, dft);
166 		}
167 		String result = getConfiguration().get(key);
168 		return result == null ? dft : result;
169 	}
170 
setProperty(String key, String value)171 	private synchronized static Object setProperty(String key, String value) {
172 		if (equinoxConfig != null) {
173 			return equinoxConfig.setProperty(key, value);
174 		}
175 		if ("true".equals(getConfiguration().get(EquinoxConfiguration.PROP_USE_SYSTEM_PROPERTIES))) { //$NON-NLS-1$
176 			System.setProperty(key, value);
177 		}
178 		return getConfiguration().put(key, value);
179 	}
180 
clearProperty(String key)181 	private synchronized static Object clearProperty(String key) {
182 		if (equinoxConfig != null) {
183 			return equinoxConfig.clearConfiguration(key);
184 		}
185 		return getConfiguration().remove(key);
186 	}
187 
getConfiguration()188 	private synchronized static Map<String, String> getConfiguration() {
189 		if (configuration == null) {
190 			configuration = new HashMap<>();
191 			// TODO hack to set these to defaults for EclipseStarter
192 			// Note that this hack does not allow this property to be specified in config.ini
193 			configuration.put(EquinoxConfiguration.PROP_USE_SYSTEM_PROPERTIES, System.getProperty(EquinoxConfiguration.PROP_USE_SYSTEM_PROPERTIES, "true")); //$NON-NLS-1$
194 			// we handle this compatibility setting special for EclipseStarter
195 			String systemCompatibilityBoot = System.getProperty(EquinoxConfiguration.PROP_COMPATIBILITY_BOOTDELEGATION);
196 			if (systemCompatibilityBoot != null) {
197 				// The system properties have a specific setting; use it
198 				configuration.put(EquinoxConfiguration.PROP_COMPATIBILITY_BOOTDELEGATION, systemCompatibilityBoot);
199 			} else {
200 				// set a default value; but this value can be overriden by the config.ini
201 				configuration.put(EquinoxConfiguration.PROP_COMPATIBILITY_BOOTDELEGATION + EquinoxConfiguration.PROP_DEFAULT_SUFFIX, "true"); //$NON-NLS-1$
202 			}
203 
204 			String dsDelayedKeepInstances = System.getProperty(EquinoxConfiguration.PROP_DS_DELAYED_KEEPINSTANCES);
205 			if (dsDelayedKeepInstances != null) {
206 				// The system properties have a specific setting; use it
207 				configuration.put(EquinoxConfiguration.PROP_DS_DELAYED_KEEPINSTANCES, dsDelayedKeepInstances);
208 			} else {
209 				// set a default value; but this value can be overriden by the config.ini
210 				configuration.put(EquinoxConfiguration.PROP_DS_DELAYED_KEEPINSTANCES + EquinoxConfiguration.PROP_DEFAULT_SUFFIX, "true"); //$NON-NLS-1$
211 			}
212 		}
213 		return configuration;
214 	}
215 
216 	/**
217 	 * This is the main to start osgi.
218 	 * It only works when the framework is being jared as a single jar
219 	 */
main(String[] args)220 	public static void main(String[] args) throws Exception {
221 		if (getProperty("eclipse.startTime") == null) //$NON-NLS-1$
222 			setProperty("eclipse.startTime", Long.toString(System.currentTimeMillis())); //$NON-NLS-1$
223 		if (getProperty(PROP_NOSHUTDOWN) == null)
224 			setProperty(PROP_NOSHUTDOWN, "true"); //$NON-NLS-1$
225 		// set the compatibility boot delegation flag to false to get "standard" OSGi behavior WRT boot delegation (bug 178477)
226 		if (getProperty(EquinoxConfiguration.PROP_COMPATIBILITY_BOOTDELEGATION) == null)
227 			setProperty(EquinoxConfiguration.PROP_COMPATIBILITY_BOOTDELEGATION, "false"); //$NON-NLS-1$
228 		Object result = run(args, null);
229 		if (result instanceof Integer && !Boolean.valueOf(getProperty(PROP_NOSHUTDOWN)).booleanValue())
230 			System.exit(((Integer) result).intValue());
231 	}
232 
233 	/**
234 	 * Launches the platform and runs a single application. The application is either identified
235 	 * in the given arguments (e.g., -application &lt;app id&gt;) or in the <code>eclipse.application</code>
236 	 * System property.  This convenience method starts
237 	 * up the platform, runs the indicated application, and then shuts down the
238 	 * platform. The platform must not be running already.
239 	 *
240 	 * @param args the command line-style arguments used to configure the platform
241 	 * @param endSplashHandler the block of code to run to tear down the splash
242 	 * 	screen or <code>null</code> if no tear down is required
243 	 * @return the result of running the application
244 	 * @throws Exception if anything goes wrong
245 	 */
run(String[] args, Runnable endSplashHandler)246 	public static Object run(String[] args, Runnable endSplashHandler) throws Exception {
247 		if (running)
248 			throw new IllegalStateException(Msg.ECLIPSE_STARTUP_ALREADY_RUNNING);
249 		boolean startupFailed = true;
250 		try {
251 			startup(args, endSplashHandler);
252 			startupFailed = false;
253 			if (Boolean.valueOf(getProperty(PROP_IGNOREAPP)).booleanValue() || isForcedRestart())
254 				return null;
255 			return run(null);
256 		} catch (Throwable e) {
257 			// ensure the splash screen is down
258 			if (endSplashHandler != null)
259 				endSplashHandler.run();
260 			// may use startupFailed to understand where the error happened
261 			FrameworkLogEntry logEntry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, startupFailed ? Msg.ECLIPSE_STARTUP_STARTUP_ERROR : Msg.ECLIPSE_STARTUP_APP_ERROR, 1, e, null);
262 			if (log != null)
263 				log.log(logEntry);
264 			else
265 				// TODO desperate measure - ideally, we should write this to disk (a la Main.log)
266 				e.printStackTrace();
267 		} finally {
268 			try {
269 				// The application typically sets the exit code however the framework can request that
270 				// it be re-started. We need to check for this and potentially override the exit code.
271 				if (isForcedRestart())
272 					setProperty(PROP_EXITCODE, "23"); //$NON-NLS-1$
273 				if (!Boolean.valueOf(getProperty(PROP_NOSHUTDOWN)).booleanValue())
274 					shutdown();
275 			} catch (Throwable e) {
276 				FrameworkLogEntry logEntry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, Msg.ECLIPSE_STARTUP_SHUTDOWN_ERROR, 1, e, null);
277 				if (log != null)
278 					log.log(logEntry);
279 				else
280 					// TODO desperate measure - ideally, we should write this to disk (a la Main.log)
281 					e.printStackTrace();
282 			}
283 		}
284 		// we only get here if an error happened
285 		if (getProperty(PROP_EXITCODE) == null) {
286 			setProperty(PROP_EXITCODE, "13"); //$NON-NLS-1$
287 			setProperty(PROP_EXITDATA, NLS.bind(Msg.ECLIPSE_STARTUP_ERROR_CHECK_LOG, log == null ? null : log.getFile().getPath()));
288 		}
289 		return null;
290 	}
291 
292 	/**
293 	 * Returns true if the platform is already running, false otherwise.
294 	 * @return whether or not the platform is already running
295 	 */
isRunning()296 	public static boolean isRunning() {
297 		return running;
298 	}
299 
300 	/**
301 	 * Starts the platform and sets it up to run a single application. The application is either identified
302 	 * in the given arguments (e.g., -application &lt;app id&gt;) or in the <code>eclipse.application</code>
303 	 * System property.  The platform must not be running already.
304 	 * <p>
305 	 * The given runnable (if not <code>null</code>) is used to tear down the splash screen if required.
306 	 * </p>
307 	 * @param args the arguments passed to the application
308 	 * @return BundleContext the context of the system bundle
309 	 * @throws Exception if anything goes wrong
310 	 */
startup(String[] args, Runnable endSplashHandler)311 	public static BundleContext startup(String[] args, Runnable endSplashHandler) throws Exception {
312 		if (running)
313 			throw new IllegalStateException(Msg.ECLIPSE_STARTUP_ALREADY_RUNNING);
314 		processCommandLine(args);
315 		framework = new Equinox(getConfiguration());
316 		framework.init();
317 		context = framework.getBundleContext();
318 		ServiceReference<FrameworkLog> logRef = context.getServiceReference(FrameworkLog.class);
319 		log = context.getService(logRef);
320 		ServiceReference<EnvironmentInfo> configRef = context.getServiceReference(EnvironmentInfo.class);
321 		equinoxConfig = (EquinoxConfiguration) context.getService(configRef);
322 
323 		equinoxConfig.setAllArgs(allArgs);
324 		equinoxConfig.setFrameworkArgs(frameworkArgs);
325 		equinoxConfig.setAppArgs(appArgs);
326 
327 		registerFrameworkShutdownHandlers();
328 		publishSplashScreen(endSplashHandler);
329 		consoleMgr = ConsoleManager.startConsole(context, equinoxConfig);
330 
331 		Bundle[] startBundles = loadBasicBundles();
332 
333 		if (startBundles == null || ("true".equals(getProperty(PROP_REFRESH_BUNDLES)) && refreshPackages(getCurrentBundles(false)))) { //$NON-NLS-1$
334 			waitForShutdown();
335 			return context; // cannot continue; loadBasicBundles caused refreshPackages to shutdown the framework
336 		}
337 
338 		framework.start();
339 
340 		if (isForcedRestart()) {
341 			return context;
342 		}
343 		// set the framework start level to the ultimate value.  This will actually start things
344 		// running if they are persistently active.
345 		setStartLevel(getStartLevel());
346 		// they should all be active by this time
347 		ensureBundlesActive(startBundles);
348 
349 		// in the case where the built-in console is disabled we should try to start the console bundle
350 		try {
351 			consoleMgr.checkForConsoleBundle();
352 		} catch (BundleException e) {
353 			FrameworkLogEntry entry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, e.getMessage(), 0, e, null);
354 			log.log(entry);
355 		}
356 		// TODO should log unresolved bundles if in debug or dev mode
357 		running = true;
358 		return context;
359 	}
360 
getStartLevel()361 	private static int getStartLevel() {
362 		String level = getProperty(PROP_INITIAL_STARTLEVEL);
363 		if (level != null)
364 			try {
365 				return Integer.parseInt(level);
366 			} catch (NumberFormatException e) {
367 				if (debug)
368 					Debug.println("Start level = " + level + "  parsed. Using hardcoded default: 6"); //$NON-NLS-1$ //$NON-NLS-2$
369 			}
370 		return DEFAULT_INITIAL_STARTLEVEL;
371 	}
372 
373 	/**
374 	 * Runs the application for which the platform was started. The platform
375 	 * must be running.
376 	 * <p>
377 	 * The given argument is passed to the application being run.  If it is <code>null</code>
378 	 * then the command line arguments used in starting the platform, and not consumed
379 	 * by the platform code, are passed to the application as a <code>String[]</code>.
380 	 * </p>
381 	 * @param argument the argument passed to the application. May be <code>null</code>
382 	 * @return the result of running the application
383 	 * @throws Exception if anything goes wrong
384 	 */
run(Object argument)385 	public static Object run(Object argument) throws Exception {
386 		if (!running)
387 			throw new IllegalStateException(Msg.ECLIPSE_STARTUP_NOT_RUNNING);
388 		// if we are just initializing, do not run the application just return.
389 		if (initialize)
390 			return Integer.valueOf(0);
391 		try {
392 			if (appLauncher == null) {
393 
394 				boolean launchDefault = Boolean.parseBoolean(getProperty(PROP_APPLICATION_LAUNCHDEFAULT, "true")); //$NON-NLS-1$
395 				// create the ApplicationLauncher and register it as a service
396 				appLauncher = new EclipseAppLauncher(context, Boolean.parseBoolean(getProperty(PROP_ALLOW_APPRELAUNCH)), launchDefault, log, equinoxConfig);
397 				appLauncherRegistration = context.registerService(ApplicationLauncher.class.getName(), appLauncher, null);
398 				// must start the launcher AFTER service restration because this method
399 				// blocks and runs the application on the current thread.  This method
400 				// will return only after the application has stopped.
401 				return appLauncher.start(argument);
402 			}
403 			return appLauncher.reStart(argument);
404 		} catch (Exception e) {
405 			if (log != null && context != null) { // context can be null if OSGi failed to launch (bug 151413)
406 				ResolutionReport report = context.getBundle().adapt(Module.class).getContainer().resolve(null, false);
407 				for (Resource unresolved : report.getEntries().keySet()) {
408 					String bsn = ((ModuleRevision) unresolved).getSymbolicName();
409 					FrameworkLogEntry logEntry = new FrameworkLogEntry(bsn != null ? bsn : EquinoxContainer.NAME, FrameworkLogEntry.WARNING, 0, Msg.Module_ResolveError + report.getResolutionReportMessage(unresolved), 1, null, null);
410 					log.log(logEntry);
411 				}
412 			}
413 			throw e;
414 		}
415 	}
416 
417 	/**
418 	 * Shuts down the Platform. The state of the Platform is not automatically
419 	 * saved before shutting down.
420 	 * <p>
421 	 * On return, the Platform will no longer be running (but could be re-launched
422 	 * with another call to startup). If relaunching, care must be taken to reinitialize
423 	 * any System properties which the platform uses (e.g., osgi.instance.area) as
424 	 * some policies in the platform do not allow resetting of such properties on
425 	 * subsequent runs.
426 	 * </p><p>
427 	 * Any objects handed out by running Platform,
428 	 * including Platform runnables obtained via getRunnable, will be
429 	 * permanently invalid. The effects of attempting to invoke methods
430 	 * on invalid objects is undefined.
431 	 * </p>
432 	 * @throws Exception if anything goes wrong
433 	 */
shutdown()434 	public static void shutdown() throws Exception {
435 		if (!running || framework == null)
436 			return;
437 		if (framework.getState() == Bundle.ACTIVE) {
438 			if (appLauncherRegistration != null)
439 				appLauncherRegistration.unregister();
440 			if (splashStreamRegistration != null)
441 				splashStreamRegistration.unregister();
442 			if (defaultMonitorRegistration != null)
443 				defaultMonitorRegistration.unregister();
444 		}
445 		if (appLauncher != null)
446 			appLauncher.shutdown();
447 		appLauncherRegistration = null;
448 		appLauncher = null;
449 		splashStreamRegistration = null;
450 		defaultMonitorRegistration = null;
451 		if (consoleMgr != null) {
452 			consoleMgr.stopConsole();
453 			consoleMgr = null;
454 		}
455 		if (framework.getState() == Bundle.ACTIVE) {
456 			framework.stop();
457 			framework.waitForStop(0);
458 			framework = null;
459 		}
460 		configuration = null;
461 		equinoxConfig = null;
462 		context = null;
463 		running = false;
464 	}
465 
ensureBundlesActive(Bundle[] bundles)466 	private static void ensureBundlesActive(Bundle[] bundles) {
467 		for (Bundle bundle : bundles) {
468 			if (bundle.getState() != Bundle.ACTIVE) {
469 				if (bundle.getState() == Bundle.INSTALLED) {
470 					// Log that the bundle is not resolved
471 					log.log(new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED, bundle.getLocation()), 0, null, null));
472 					continue;
473 				}
474 				// check that the startlevel allows the bundle to be active (111550)
475 				FrameworkStartLevel fwStartLevel = context.getBundle().adapt(FrameworkStartLevel.class);
476 				BundleStartLevel bundleStartLevel = bundle.adapt(BundleStartLevel.class);
477 				if (fwStartLevel != null && (bundleStartLevel.getStartLevel() <= fwStartLevel.getStartLevel())) {
478 					log.log(new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_ACTIVE, bundle), 0, null, null));
479 				}
480 			}
481 		}
482 	}
483 
publishSplashScreen(final Runnable endSplashHandler)484 	private static void publishSplashScreen(final Runnable endSplashHandler) {
485 		if (endSplashHandler == null)
486 			return;
487 		// register the output stream to the launcher if it exists
488 		try {
489 			Method method = endSplashHandler.getClass().getMethod("getOutputStream", new Class[0]); //$NON-NLS-1$
490 			Object outputStream = method.invoke(endSplashHandler, new Object[0]);
491 			if (outputStream instanceof OutputStream) {
492 				Dictionary<String, Object> osProperties = new Hashtable<>();
493 				osProperties.put("name", "splashstream"); //$NON-NLS-1$//$NON-NLS-2$
494 				splashStreamRegistration = context.registerService(OutputStream.class.getName(), outputStream, osProperties);
495 			}
496 		} catch (Exception ex) {
497 			// ignore
498 		}
499 		// keep this splash handler as the default startup monitor
500 		try {
501 			Dictionary<String, Object> monitorProps = new Hashtable<>();
502 			monitorProps.put(Constants.SERVICE_RANKING, Integer.valueOf(Integer.MIN_VALUE));
503 			defaultMonitorRegistration = context.registerService(StartupMonitor.class.getName(), new DefaultStartupMonitor(endSplashHandler, equinoxConfig), monitorProps);
504 		} catch (IllegalStateException e) {
505 			//splash handler did not provide the necessary methods, ignore it
506 		}
507 	}
508 
509 	@SuppressWarnings("deprecation")
searchForBundle(String name, String parent)510 	private static URL searchForBundle(String name, String parent) throws MalformedURLException {
511 		URL url = null;
512 		File fileLocation = null;
513 		boolean reference = false;
514 		try {
515 			createURL(name); // quick check to see if the name is a valid URL
516 			url = createURL(new File(parent).toURL(), name);
517 		} catch (MalformedURLException e) {
518 			// TODO this is legacy support for non-URL names.  It should be removed eventually.
519 			// if name was not a URL then construct one.
520 			// Assume it should be a reference and that it is relative.  This support need not
521 			// be robust as it is temporary..
522 			File child = new File(name);
523 			fileLocation = child.isAbsolute() ? child : new File(parent, name);
524 			url = createURL(REFERENCE_PROTOCOL, null, fileLocation.toURL().toExternalForm());
525 			reference = true;
526 		}
527 		// if the name was a URL then see if it is relative.  If so, insert syspath.
528 		if (!reference) {
529 			URL baseURL = url;
530 			// if it is a reference URL then strip off the reference: and set base to the file:...
531 			if (url.getProtocol().equals(REFERENCE_PROTOCOL)) {
532 				reference = true;
533 				String baseSpec = url.getPath();
534 				if (baseSpec.startsWith(FILE_SCHEME)) {
535 					File child = new File(baseSpec.substring(5));
536 					baseURL = child.isAbsolute() ? child.toURL() : new File(parent, child.getPath()).toURL();
537 				} else
538 					baseURL = createURL(baseSpec);
539 			}
540 
541 			fileLocation = new File(baseURL.getPath());
542 			// if the location is relative, prefix it with the parent
543 			if (!fileLocation.isAbsolute())
544 				fileLocation = new File(parent, fileLocation.toString());
545 		}
546 		// If the result is a reference then search for the real result and
547 		// reconstruct the answer.
548 		if (reference) {
549 			String result = searchFor(fileLocation.getName(), new File(fileLocation.getParent()).getAbsolutePath());
550 			if (result != null)
551 				url = createURL(REFERENCE_PROTOCOL, null, FILE_SCHEME + result);
552 			else
553 				return null;
554 		}
555 
556 		// finally we have something worth trying
557 		try {
558 			URLConnection result = LocationHelper.getConnection(url);
559 			result.connect();
560 			return url;
561 		} catch (IOException e) {
562 			//			int i = location.lastIndexOf('_');
563 			//			return i == -1? location : location.substring(0, i);
564 			return null;
565 		}
566 	}
567 
568 	/*
569 	 * Ensure all basic bundles are installed, resolved and scheduled to start. Returns an array containing
570 	 * all basic bundles that are marked to start.
571 	 * Returns null if the framework has been shutdown as a result of refreshPackages
572 	 */
loadBasicBundles()573 	private static Bundle[] loadBasicBundles() throws InterruptedException {
574 		long startTime = System.currentTimeMillis();
575 		String osgiBundles = getProperty(PROP_BUNDLES);
576 		String osgiExtensions = getProperty(PROP_EXTENSIONS);
577 		if (osgiExtensions != null && osgiExtensions.length() > 0) {
578 			osgiBundles = osgiExtensions + ',' + osgiBundles;
579 			setProperty(PROP_BUNDLES, osgiBundles);
580 		}
581 		String[] installEntries = getArrayFromList(osgiBundles, ","); //$NON-NLS-1$
582 		// get the initial bundle list from the installEntries
583 		InitialBundle[] initialBundles = getInitialBundles(installEntries);
584 		// get the list of currently installed initial bundles from the framework
585 		Bundle[] curInitBundles = getCurrentBundles(true);
586 
587 		// list of bundles to be refreshed
588 		List<Bundle> toRefresh = new ArrayList<>(curInitBundles.length);
589 		// uninstall any of the currently installed bundles that do not exist in the
590 		// initial bundle list from installEntries.
591 		uninstallBundles(curInitBundles, initialBundles, toRefresh);
592 
593 		// install the initialBundles that are not already installed.
594 		List<Bundle> startBundles = new ArrayList<>(installEntries.length);
595 		List<Bundle> lazyActivationBundles = new ArrayList<>(installEntries.length);
596 		installBundles(initialBundles, curInitBundles, startBundles, lazyActivationBundles, toRefresh);
597 
598 		// If we installed/uninstalled something, force a refresh of all installed/uninstalled bundles
599 		if (!toRefresh.isEmpty() && refreshPackages(toRefresh.toArray(new Bundle[toRefresh.size()])))
600 			return null; // cannot continue; refreshPackages shutdown the framework
601 
602 		// schedule all basic bundles to be started
603 		Bundle[] startInitBundles = startBundles.toArray(new Bundle[startBundles.size()]);
604 		Bundle[] lazyInitBundles = lazyActivationBundles.toArray(new Bundle[lazyActivationBundles.size()]);
605 		startBundles(startInitBundles, lazyInitBundles);
606 
607 		if (debug)
608 			Debug.println("Time to load bundles: " + (System.currentTimeMillis() - startTime)); //$NON-NLS-1$
609 		return startInitBundles;
610 	}
611 
getInitialBundles(String[] installEntries)612 	private static InitialBundle[] getInitialBundles(String[] installEntries) {
613 		searchCandidates.clear();
614 		List<InitialBundle> result = new ArrayList<>(installEntries.length);
615 		int defaultStartLevel = Integer.parseInt(getProperty(PROP_BUNDLES_STARTLEVEL, DEFAULT_BUNDLES_STARTLEVEL));
616 		String syspath = getSysPath();
617 		// should canonicalize the syspath.
618 		try {
619 			syspath = new File(syspath).getCanonicalPath();
620 		} catch (IOException ioe) {
621 			// do nothing
622 		}
623 		Collection<ServiceReference<Location>> installLocRef;
624 		try {
625 			installLocRef = context.getServiceReferences(Location.class, Location.INSTALL_FILTER);
626 		} catch (InvalidSyntaxException e) {
627 			throw new RuntimeException(e);
628 		}
629 		Location installLocation = installLocRef == null ? null : context.getService(installLocRef.iterator().next());
630 		if (installLocation == null) {
631 			throw new IllegalStateException(Msg.EclipseStarter_InstallLocation);
632 		}
633 		for (String name : installEntries) {
634 			int level = defaultStartLevel;
635 			boolean start = false;
636 			int index = name.lastIndexOf('@');
637 			if (index >= 0) {
638 				String[] attributes = getArrayFromList(name.substring(index + 1, name.length()), ":"); //$NON-NLS-1$
639 				for (String attribute : attributes) {
640 					if (attribute.equals("start")) //$NON-NLS-1$
641 						start = true;
642 					else {
643 						try {
644 							level = Integer.parseInt(attribute);
645 						} catch (NumberFormatException e) { // bug 188089
646 							index = name.length();
647 							continue;
648 						}
649 					}
650 				}
651 				name = name.substring(0, index);
652 			}
653 			try {
654 				URL location = searchForBundle(name, syspath);
655 				if (location == null) {
656 					FrameworkLogEntry entry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_BUNDLE_NOT_FOUND, name), 0, null, null);
657 					log.log(entry);
658 					// skip this entry
659 					continue;
660 				}
661 				location = makeRelative(installLocation.getURL(), location);
662 				String locationString = INITIAL_LOCATION + location.toExternalForm();
663 				result.add(new InitialBundle(locationString, location, level, start));
664 			}catch (IOException e) {
665 				log.log(new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, e.getMessage(), 0, e, null));
666 			}
667 		}
668 		return result.toArray(new InitialBundle[result.size()]);
669 	}
670 
671 	// returns true if the refreshPackages operation caused the framework to shutdown
refreshPackages(Bundle[] bundles)672 	private static boolean refreshPackages(Bundle[] bundles) throws InterruptedException {
673 		FrameworkWiring frameworkWiring = context.getBundle().adapt(FrameworkWiring.class);
674 		if (frameworkWiring == null)
675 			return false;
676 		Semaphore semaphore = new Semaphore(0);
677 		StartupEventListener listener = new StartupEventListener(semaphore, FrameworkEvent.PACKAGES_REFRESHED);
678 		context.addBundleListener(listener);
679 		frameworkWiring.refreshBundles(Arrays.asList(bundles), listener);
680 		updateSplash(semaphore, listener);
681 		return isForcedRestart();
682 	}
683 
waitForShutdown()684 	private static void waitForShutdown() {
685 		// wait for the system bundle to stop
686 		try {
687 			framework.waitForStop(0);
688 		} catch (InterruptedException e) {
689 			Thread.interrupted();
690 			throw new RuntimeException(e);
691 		}
692 	}
693 
processCommandLine(String[] args)694 	private static void processCommandLine(String[] args) throws Exception {
695 		allArgs = args;
696 		if (args.length == 0) {
697 			frameworkArgs = args;
698 			return;
699 		}
700 		int[] configArgs = new int[args.length];
701 		configArgs[0] = -1; // need to initialize the first element to something that could not be an index.
702 		int configArgIndex = 0;
703 		for (int i = 0; i < args.length; i++) {
704 			boolean found = false;
705 			// check for args without parameters (i.e., a flag arg)
706 
707 			// check if debug should be enabled for the entire platform
708 			// If this is the last arg or there is a following arg (i.e., arg+1 has a leading -),
709 			// simply enable debug.  Otherwise, assume that that the following arg is
710 			// actually the filename of an options file.  This will be processed below.
711 			if (args[i].equalsIgnoreCase(DEBUG) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$
712 				setProperty(PROP_DEBUG, ""); //$NON-NLS-1$
713 				debug = true;
714 				found = true;
715 			}
716 
717 			// check if development mode should be enabled for the entire platform
718 			// If this is the last arg or there is a following arg (i.e., arg+1 has a leading -),
719 			// simply enable development mode.  Otherwise, assume that that the following arg is
720 			// actually some additional development time class path entries.  This will be processed below.
721 			if (args[i].equalsIgnoreCase(DEV) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$
722 				setProperty(PROP_DEV, ""); //$NON-NLS-1$
723 				found = true;
724 			}
725 
726 			// look for the initialization arg
727 			if (args[i].equalsIgnoreCase(INITIALIZE)) {
728 				initialize = true;
729 				found = true;
730 			}
731 
732 			// look for the clean flag.
733 			if (args[i].equalsIgnoreCase(CLEAN)) {
734 				setProperty(PROP_CLEAN, "true"); //$NON-NLS-1$
735 				found = true;
736 			}
737 
738 			// look for the consoleLog flag
739 			if (args[i].equalsIgnoreCase(CONSOLE_LOG)) {
740 				setProperty(PROP_CONSOLE_LOG, "true"); //$NON-NLS-1$
741 				found = true;
742 			}
743 
744 			// look for the console with no port.
745 			if (args[i].equalsIgnoreCase(CONSOLE) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$
746 				setProperty(PROP_CONSOLE, ""); //$NON-NLS-1$
747 				found = true;
748 			}
749 
750 			if (args[i].equalsIgnoreCase(NOEXIT)) {
751 				setProperty(PROP_NOSHUTDOWN, "true"); //$NON-NLS-1$
752 				found = true;
753 			}
754 
755 			if (found) {
756 				configArgs[configArgIndex++] = i;
757 				continue;
758 			}
759 			// check for args with parameters. If we are at the last argument or if the next one
760 			// has a '-' as the first character, then we can't have an arg with a parm so continue.
761 			if (i == args.length - 1 || args[i + 1].startsWith("-")) { //$NON-NLS-1$
762 				continue;
763 			}
764 			String arg = args[++i];
765 
766 			// look for the console and port.
767 			if (args[i - 1].equalsIgnoreCase(CONSOLE)) {
768 				setProperty(PROP_CONSOLE, arg);
769 				found = true;
770 			}
771 
772 			// look for the configuration location .
773 			if (args[i - 1].equalsIgnoreCase(CONFIGURATION)) {
774 				setProperty(EquinoxLocations.PROP_CONFIG_AREA, arg);
775 				found = true;
776 			}
777 
778 			// look for the data location for this instance.
779 			if (args[i - 1].equalsIgnoreCase(DATA)) {
780 				setProperty(EquinoxLocations.PROP_INSTANCE_AREA, arg);
781 				found = true;
782 			}
783 
784 			// look for the user location for this instance.
785 			if (args[i - 1].equalsIgnoreCase(USER)) {
786 				setProperty(EquinoxLocations.PROP_USER_AREA, arg);
787 				found = true;
788 			}
789 
790 			// look for the launcher location
791 			if (args[i - 1].equalsIgnoreCase(LAUNCHER)) {
792 				setProperty(EquinoxLocations.PROP_LAUNCHER, arg);
793 				found = true;
794 			}
795 			// look for the development mode and class path entries.
796 			if (args[i - 1].equalsIgnoreCase(DEV)) {
797 				setProperty(PROP_DEV, arg);
798 				found = true;
799 			}
800 
801 			// look for the debug mode and option file location.
802 			if (args[i - 1].equalsIgnoreCase(DEBUG)) {
803 				setProperty(PROP_DEBUG, arg);
804 				debug = true;
805 				found = true;
806 			}
807 
808 			// look for the window system.
809 			if (args[i - 1].equalsIgnoreCase(WS)) {
810 				setProperty(PROP_WS, arg);
811 				found = true;
812 			}
813 
814 			// look for the operating system
815 			if (args[i - 1].equalsIgnoreCase(OS)) {
816 				setProperty(PROP_OS, arg);
817 				found = true;
818 			}
819 
820 			// look for the system architecture
821 			if (args[i - 1].equalsIgnoreCase(ARCH)) {
822 				setProperty(PROP_ARCH, arg);
823 				found = true;
824 			}
825 
826 			// look for the nationality/language
827 			if (args[i - 1].equalsIgnoreCase(NL)) {
828 				setProperty(PROP_NL, arg);
829 				found = true;
830 			}
831 
832 			// look for the locale extensions
833 			if (args[i - 1].equalsIgnoreCase(NL_EXTENSIONS)) {
834 				setProperty(PROP_NL_EXTENSIONS, arg);
835 				found = true;
836 			}
837 
838 			// done checking for args.  Remember where an arg was found
839 			if (found) {
840 				configArgs[configArgIndex++] = i - 1;
841 				configArgs[configArgIndex++] = i;
842 			}
843 		}
844 
845 		// remove all the arguments consumed by this argument parsing
846 		if (configArgIndex == 0) {
847 			frameworkArgs = new String[0];
848 			appArgs = args;
849 			return;
850 		}
851 		appArgs = new String[args.length - configArgIndex];
852 		frameworkArgs = new String[configArgIndex];
853 		configArgIndex = 0;
854 		int j = 0;
855 		int k = 0;
856 		for (int i = 0; i < args.length; i++) {
857 			if (i == configArgs[configArgIndex]) {
858 				frameworkArgs[k++] = args[i];
859 				configArgIndex++;
860 			} else
861 				appArgs[j++] = args[i];
862 		}
863 		return;
864 	}
865 
866 	/**
867 	 * Returns the result of converting a list of comma-separated tokens into an array
868 	 *
869 	 * @return the array of string tokens
870 	 * @param prop the initial comma-separated string
871 	 */
getArrayFromList(String prop, String separator)872 	private static String[] getArrayFromList(String prop, String separator) {
873 		return ManifestElement.getArrayFromList(prop, separator);
874 	}
875 
getSysPath()876 	protected static String getSysPath() {
877 		String result = getProperty(PROP_SYSPATH);
878 		if (result != null)
879 			return result;
880 		result = getSysPathFromURL(getProperty(PROP_FRAMEWORK));
881 		if (result == null)
882 			result = getSysPathFromCodeSource();
883 		if (result == null)
884 			throw new IllegalStateException("Can not find the system path."); //$NON-NLS-1$
885 		if (Character.isUpperCase(result.charAt(0))) {
886 			char[] chars = result.toCharArray();
887 			chars[0] = Character.toLowerCase(chars[0]);
888 			result = new String(chars);
889 		}
890 		setProperty(PROP_SYSPATH, result);
891 		return result;
892 	}
893 
getSysPathFromURL(String urlSpec)894 	private static String getSysPathFromURL(String urlSpec) {
895 		if (urlSpec == null)
896 			return null;
897 		URL url = LocationHelper.buildURL(urlSpec, false);
898 		if (url == null)
899 			return null;
900 		File fwkFile = LocationHelper.decodePath(new File(url.getPath()));
901 		fwkFile = new File(fwkFile.getAbsolutePath());
902 		fwkFile = new File(fwkFile.getParent());
903 		return fwkFile.getAbsolutePath();
904 	}
905 
getSysPathFromCodeSource()906 	private static String getSysPathFromCodeSource() {
907 		ProtectionDomain pd = EclipseStarter.class.getProtectionDomain();
908 		if (pd == null)
909 			return null;
910 		CodeSource cs = pd.getCodeSource();
911 		if (cs == null)
912 			return null;
913 		URL url = cs.getLocation();
914 		if (url == null)
915 			return null;
916 		String result = url.getPath();
917 		if (File.separatorChar == '\\') {
918 			// in case on windows the \ is used
919 			result = result.replace('\\', '/');
920 		}
921 		if (result.endsWith(".jar")) { //$NON-NLS-1$
922 			result = result.substring(0, result.lastIndexOf('/'));
923 			if ("folder".equals(getProperty(PROP_FRAMEWORK_SHAPE))) //$NON-NLS-1$
924 				result = result.substring(0, result.lastIndexOf('/'));
925 		} else {
926 			if (result.endsWith("/")) //$NON-NLS-1$
927 				result = result.substring(0, result.length() - 1);
928 			result = result.substring(0, result.lastIndexOf('/'));
929 			result = result.substring(0, result.lastIndexOf('/'));
930 		}
931 		return result;
932 	}
933 
getCurrentBundles(boolean includeInitial)934 	private static Bundle[] getCurrentBundles(boolean includeInitial) {
935 		Bundle[] installed = context.getBundles();
936 		List<Bundle> initial = new ArrayList<>();
937 		for (Bundle bundle : installed) {
938 			if (bundle.getLocation().startsWith(INITIAL_LOCATION)) {
939 				if (includeInitial)
940 					initial.add(bundle);
941 			} else if (!includeInitial && bundle.getBundleId() != 0)
942 				initial.add(bundle);
943 		}
944 		return initial.toArray(new Bundle[initial.size()]);
945 	}
946 
getBundleByLocation(String location, Bundle[] bundles)947 	private static Bundle getBundleByLocation(String location, Bundle[] bundles) {
948 		for (Bundle bundle : bundles) {
949 			if (location.equalsIgnoreCase(bundle.getLocation()))
950 				return bundle;
951 		}
952 		return null;
953 	}
954 
uninstallBundles(Bundle[] curInitBundles, InitialBundle[] newInitBundles, List<Bundle> toRefresh)955 	private static void uninstallBundles(Bundle[] curInitBundles, InitialBundle[] newInitBundles, List<Bundle> toRefresh) {
956 		for (Bundle curInitBundle : curInitBundles) {
957 			boolean found = false;
958 			for (InitialBundle newInitBundle : newInitBundles) {
959 				if (curInitBundle.getLocation().equalsIgnoreCase(newInitBundle.locationString)) {
960 					found = true;
961 					break;
962 				}
963 			}
964 			if (!found) {
965 				try {
966 					curInitBundle.uninstall();
967 					toRefresh.add(curInitBundle);
968 				} catch (BundleException e) {
969 					FrameworkLogEntry entry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_FAILED_UNINSTALL, curInitBundle.getLocation()), 0, e, null);
970 					log.log(entry);
971 				}
972 			}
973 		}
974 	}
975 
installBundles(InitialBundle[] initialBundles, Bundle[] curInitBundles, List<Bundle> startBundles, List<Bundle> lazyActivationBundles, List<Bundle> toRefresh)976 	private static void installBundles(InitialBundle[] initialBundles, Bundle[] curInitBundles, List<Bundle> startBundles, List<Bundle> lazyActivationBundles, List<Bundle> toRefresh) {
977 		for (InitialBundle initialBundle : initialBundles) {
978 			Bundle osgiBundle = getBundleByLocation(initialBundle.locationString, curInitBundles);
979 			try {
980 				// don't need to install if it is already installed
981 				if (osgiBundle == null) {
982 					InputStream in = LocationHelper.getStream(initialBundle.location);
983 					try {
984 						osgiBundle = context.installBundle(initialBundle.locationString, in);
985 					}catch (BundleException e) {
986 						if (e.getType() == BundleException.DUPLICATE_BUNDLE_ERROR) {
987 							continue;
988 							// TODO should attempt to lookup the existing bundle
989 						}
990 						throw e;
991 					}
992 					// only check for lazy activation header if this is a newly installed bundle and is not marked for persistent start
993 					if (!initialBundle.start && hasLazyActivationPolicy(osgiBundle)) {
994 						lazyActivationBundles.add(osgiBundle);
995 					}
996 				}
997 				// always set the startlevel incase it has changed (bug 111549)
998 				// this is a no-op if the level is the same as previous launch.
999 				if ((osgiBundle.getState() & Bundle.UNINSTALLED) == 0 && initialBundle.level >= 0) {
1000 					osgiBundle.adapt(BundleStartLevel.class).setStartLevel(initialBundle.level);
1001 				}
1002 				// if this bundle is supposed to be started then add it to the start list
1003 				if (initialBundle.start) {
1004 					startBundles.add(osgiBundle);
1005 				}
1006 				// include basic bundles in case they were not resolved before
1007 				if ((osgiBundle.getState() & Bundle.INSTALLED) != 0)
1008 					toRefresh.add(osgiBundle);
1009 			} catch (BundleException | IOException e) {
1010 				FrameworkLogEntry entry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_FAILED_INSTALL, initialBundle.location), 0, e, null);
1011 				log.log(entry);
1012 			}
1013 		}
1014 	}
1015 
1016 	@SuppressWarnings("deprecation")
hasLazyActivationPolicy(Bundle target)1017 	private static boolean hasLazyActivationPolicy(Bundle target) {
1018 		// check the bundle manifest to see if it defines a lazy activation policy
1019 		Dictionary<String, String> headers = target.getHeaders(""); //$NON-NLS-1$
1020 		// first check to see if this is a fragment bundle
1021 		String fragmentHost = headers.get(Constants.FRAGMENT_HOST);
1022 		if (fragmentHost != null)
1023 			return false; // do not activate fragment bundles
1024 		// look for the OSGi defined Bundle-ActivationPolicy header
1025 		String activationPolicy = headers.get(Constants.BUNDLE_ACTIVATIONPOLICY);
1026 		try {
1027 			if (activationPolicy != null) {
1028 				ManifestElement[] elements = ManifestElement.parseHeader(Constants.BUNDLE_ACTIVATIONPOLICY, activationPolicy);
1029 				if (elements != null && elements.length > 0) {
1030 					// if the value is "lazy" then it has a lazy activation poliyc
1031 					if (Constants.ACTIVATION_LAZY.equals(elements[0].getValue()))
1032 						return true;
1033 				}
1034 			} else {
1035 				// check for Eclipse specific lazy start headers "Eclipse-LazyStart" and "Eclipse-AutoStart"
1036 				String eclipseLazyStart = headers.get(EquinoxModuleDataNamespace.LAZYSTART_HEADER);
1037 				if (eclipseLazyStart == null)
1038 					eclipseLazyStart = headers.get(EquinoxModuleDataNamespace.AUTOSTART_HEADER);
1039 				ManifestElement[] elements = ManifestElement.parseHeader(EquinoxModuleDataNamespace.AUTOSTART_HEADER, eclipseLazyStart);
1040 				if (elements != null && elements.length > 0) {
1041 					// if the value is true then it is lazy activated
1042 					if ("true".equals(elements[0].getValue())) //$NON-NLS-1$
1043 						return true;
1044 					// otherwise it is only lazy activated if it defines an exceptions directive.
1045 					else if (elements[0].getDirective("exceptions") != null) //$NON-NLS-1$
1046 						return true;
1047 				}
1048 			}
1049 		} catch (BundleException be) {
1050 			// ignore this
1051 		}
1052 		return false;
1053 	}
1054 
startBundles(Bundle[] startBundles, Bundle[] lazyBundles)1055 	private static void startBundles(Bundle[] startBundles, Bundle[] lazyBundles) {
1056 		for (Bundle startBundle : startBundles) {
1057 			startBundle(startBundle, 0);
1058 		}
1059 		for (Bundle lazyBundle : lazyBundles) {
1060 			startBundle(lazyBundle, Bundle.START_ACTIVATION_POLICY);
1061 		}
1062 	}
1063 
startBundle(Bundle bundle, int options)1064 	private static void startBundle(Bundle bundle, int options) {
1065 		try {
1066 			bundle.start(options);
1067 		} catch (BundleException e) {
1068 			if ((bundle.getState() & Bundle.RESOLVED) != 0) {
1069 				// only log errors if the bundle is resolved
1070 				FrameworkLogEntry entry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_FAILED_START, bundle.getLocation()), 0, e, null);
1071 				log.log(entry);
1072 			}
1073 		}
1074 	}
1075 
1076 	/**
1077 	 * Returns a URL which is equivalent to the given URL relative to the
1078 	 * specified base URL. Works only for file: URLs
1079 	 * @throws MalformedURLException
1080 	 */
makeRelative(URL base, URL location)1081 	private static URL makeRelative(URL base, URL location) throws MalformedURLException {
1082 		if (base == null)
1083 			return location;
1084 		if (!"file".equals(base.getProtocol())) //$NON-NLS-1$
1085 			return location;
1086 		if (!location.getProtocol().equals(REFERENCE_PROTOCOL))
1087 			return location; // we can only make reference urls relative
1088 		URL nonReferenceLocation = createURL(location.getPath());
1089 		// if some URL component does not match, return the original location
1090 		if (!base.getProtocol().equals(nonReferenceLocation.getProtocol()))
1091 			return location;
1092 		File locationPath = new File(nonReferenceLocation.getPath());
1093 		// if location is not absolute, return original location
1094 		if (!locationPath.isAbsolute())
1095 			return location;
1096 		File relativePath = makeRelative(new File(base.getPath()), locationPath);
1097 		String urlPath = relativePath.getPath();
1098 		if (File.separatorChar != '/')
1099 			urlPath = urlPath.replace(File.separatorChar, '/');
1100 		if (nonReferenceLocation.getPath().endsWith("/")) //$NON-NLS-1$
1101 			// restore original trailing slash
1102 			urlPath += '/';
1103 		// couldn't use File to create URL here because it prepends the path with user.dir
1104 		URL relativeURL = createURL(base.getProtocol(), base.getHost(), base.getPort(), urlPath);
1105 		// now make it back to a reference URL
1106 		relativeURL = createURL(REFERENCE_SCHEME + relativeURL.toExternalForm());
1107 		return relativeURL;
1108 	}
1109 
createURL(String spec)1110 	private static URL createURL(String spec) throws MalformedURLException {
1111 		return createURL(null, spec);
1112 	}
1113 
createURL(URL urlContext, String spec)1114 	private static URL createURL(URL urlContext, String spec) throws MalformedURLException {
1115 		if (context != null && spec.startsWith(REFERENCE_SCHEME)) {
1116 			return new URL(urlContext, spec, new Handler(context.getProperty(EquinoxLocations.PROP_INSTALL_AREA)));
1117 		}
1118 		return new URL(urlContext, spec);
1119 	}
1120 
createURL(String protocol, String host, String file)1121 	private static URL createURL(String protocol, String host, String file) throws MalformedURLException {
1122 		return createURL(protocol, host, -1, file);
1123 	}
1124 
createURL(String protocol, String host, int port, String file)1125 	private static URL createURL(String protocol, String host, int port, String file) throws MalformedURLException {
1126 		if (context != null && REFERENCE_PROTOCOL.equalsIgnoreCase(protocol)) {
1127 			return new URL(protocol, host, port, file, new Handler(context.getProperty(EquinoxLocations.PROP_INSTALL_AREA)));
1128 		}
1129 		return new URL(protocol, host, port, file);
1130 	}
1131 
makeRelative(File base, File location)1132 	private static File makeRelative(File base, File location) {
1133 		if (!location.isAbsolute())
1134 			return location;
1135 		File relative = new File(new FilePath(base).makeRelative(new FilePath(location)));
1136 		return relative;
1137 	}
1138 
setStartLevel(final int value)1139 	private static void setStartLevel(final int value) throws InterruptedException {
1140 		FrameworkStartLevel fwkStartLevel = context.getBundle().adapt(FrameworkStartLevel.class);
1141 		final Semaphore semaphore = new Semaphore(0);
1142 		StartupEventListener listener = new StartupEventListener(semaphore, FrameworkEvent.STARTLEVEL_CHANGED);
1143 		context.addBundleListener(listener);
1144 		fwkStartLevel.setStartLevel(value, listener);
1145 		updateSplash(semaphore, listener);
1146 	}
1147 
1148 	static class StartupEventListener implements SynchronousBundleListener, FrameworkListener {
1149 		private final Semaphore semaphore;
1150 		private final int frameworkEventType;
1151 
StartupEventListener(Semaphore semaphore, int frameworkEventType)1152 		public StartupEventListener(Semaphore semaphore, int frameworkEventType) {
1153 			this.semaphore = semaphore;
1154 			this.frameworkEventType = frameworkEventType;
1155 		}
1156 
1157 		@Override
bundleChanged(BundleEvent event)1158 		public void bundleChanged(BundleEvent event) {
1159 			if (event.getBundle().getBundleId() == 0 && event.getType() == BundleEvent.STOPPING)
1160 				semaphore.release();
1161 		}
1162 
1163 		@Override
frameworkEvent(FrameworkEvent event)1164 		public void frameworkEvent(FrameworkEvent event) {
1165 			if (event.getType() == frameworkEventType)
1166 				semaphore.release();
1167 		}
1168 
1169 	}
1170 
updateSplash(Semaphore semaphore, StartupEventListener listener)1171 	private static void updateSplash(Semaphore semaphore, StartupEventListener listener) throws InterruptedException {
1172 		ServiceTracker<StartupMonitor, StartupMonitor> monitorTracker = new ServiceTracker<>(context, StartupMonitor.class.getName(), null);
1173 		try {
1174 			monitorTracker.open();
1175 		} catch (IllegalStateException e) {
1176 			// do nothing; this can happen if the framework shutdown
1177 			return;
1178 		}
1179 		try {
1180 			while (true) {
1181 				StartupMonitor monitor = monitorTracker.getService();
1182 				if (monitor != null) {
1183 					try {
1184 						monitor.update();
1185 					} catch (Throwable e) {
1186 						// ignore exceptions thrown by the monitor
1187 					}
1188 				}
1189 				// can we acquire the semaphore yet?
1190 				if (semaphore.tryAcquire(50, TimeUnit.MILLISECONDS))
1191 					break; //done
1192 				//else still working, spin another update
1193 			}
1194 		} finally {
1195 			if (listener != null) {
1196 				try {
1197 					context.removeBundleListener(listener);
1198 					monitorTracker.close();
1199 				} catch (IllegalStateException e) {
1200 					// do nothing; this can happen if the framework shutdown
1201 				}
1202 			}
1203 		}
1204 	}
1205 
1206 	/**
1207 	 * Searches for the given target directory immediately under
1208 	 * the given start location.  If one is found then this location is returned;
1209 	 * otherwise an exception is thrown.
1210 	 *
1211 	 * @return the location where target directory was found
1212 	 * @param start the location to begin searching
1213 	 */
searchFor(final String target, String start)1214 	private static String searchFor(final String target, String start) {
1215 		String[] candidates = searchCandidates.get(start);
1216 		if (candidates == null) {
1217 			File startFile = new File(start);
1218 			startFile = LocationHelper.decodePath(startFile);
1219 			candidates = startFile.list();
1220 			if (candidates != null)
1221 				searchCandidates.put(start, candidates);
1222 		}
1223 		if (candidates == null)
1224 			return null;
1225 		String result = null;
1226 		Object[] maxVersion = null;
1227 		boolean resultIsFile = false;
1228 		for (String candidateName : candidates) {
1229 			if (!candidateName.startsWith(target))
1230 				continue;
1231 			boolean simpleJar = false;
1232 			final char versionSep = candidateName.length() > target.length() ? candidateName.charAt(target.length()) : 0;
1233 			if (candidateName.length() > target.length() && versionSep != '_' && versionSep != '-') {
1234 				// make sure this is not just a jar with no (_|-)version tacked on the end
1235 				if (candidateName.length() == 4 + target.length() && candidateName.endsWith(".jar")) //$NON-NLS-1$
1236 					simpleJar = true;
1237 				else
1238 					// name does not match the target properly with an (_|-) version at the end
1239 					continue;
1240 			}
1241 			// Note: directory with version suffix is always > than directory without version suffix
1242 			String version = candidateName.length() > target.length() + 1 && (versionSep == '_' || versionSep == '-') ? candidateName.substring(target.length() + 1) : ""; //$NON-NLS-1$
1243 			Object[] currentVersion = getVersionElements(version);
1244 			if (currentVersion != null && compareVersion(maxVersion, currentVersion) < 0) {
1245 				File candidate = new File(start, candidateName);
1246 				boolean candidateIsFile = candidate.isFile();
1247 				// if simple jar; make sure it is really a file before accepting it
1248 				if (!simpleJar || candidateIsFile) {
1249 					result = candidate.getAbsolutePath();
1250 					resultIsFile = candidateIsFile;
1251 					maxVersion = currentVersion;
1252 				}
1253 			}
1254 		}
1255 		if (result == null)
1256 			return null;
1257 		return result.replace(File.separatorChar, '/') + (resultIsFile ? "" : "/"); //$NON-NLS-1$ //$NON-NLS-2$
1258 	}
1259 
1260 	/**
1261 	 * Do a quick parse of version identifier so its elements can be correctly compared.
1262 	 * If we are unable to parse the full version, remaining elements are initialized
1263 	 * with suitable defaults.
1264 	 * @return an array of size 4; first three elements are of type Integer (representing
1265 	 * major, minor and service) and the fourth element is of type String (representing
1266 	 * qualifier).  A value of null is returned if there are no valid Integers.  Note, that
1267 	 * returning anything else will cause exceptions in the caller.
1268 	 */
getVersionElements(String version)1269 	private static Object[] getVersionElements(String version) {
1270 		Object[] result = {Integer.valueOf(-1), Integer.valueOf(-1), Integer.valueOf(-1), ""}; //$NON-NLS-1$
1271 		StringTokenizer t = new StringTokenizer(version, "."); //$NON-NLS-1$
1272 		String token;
1273 		for (int i = 0; t.hasMoreTokens() && i < 4; i++) {
1274 			token = t.nextToken();
1275 			if (i < 3) {
1276 				// major, minor or service ... numeric values
1277 				try {
1278 					result[i] = Integer.valueOf(token);
1279 				} catch (Exception e) {
1280 					if (i == 0)
1281 						return null; // return null if no valid numbers are present
1282 					// invalid number format - use default numbers (-1) for the rest
1283 					break;
1284 				}
1285 			} else {
1286 				// qualifier ... string value
1287 				result[i] = token;
1288 			}
1289 		}
1290 		return result;
1291 	}
1292 
1293 	/**
1294 	 * Compares version strings.
1295 	 * @return result of comparison, as integer;
1296 	 * <code><0</code> if left < right;
1297 	 * <code>0</code> if left == right;
1298 	 * <code>>0</code> if left > right;
1299 	 */
compareVersion(Object[] left, Object[] right)1300 	private static int compareVersion(Object[] left, Object[] right) {
1301 		if (left == null)
1302 			return -1;
1303 		int result = ((Integer) left[0]).compareTo((Integer) right[0]); // compare major
1304 		if (result != 0)
1305 			return result;
1306 
1307 		result = ((Integer) left[1]).compareTo((Integer) right[1]); // compare minor
1308 		if (result != 0)
1309 			return result;
1310 
1311 		result = ((Integer) left[2]).compareTo((Integer) right[2]); // compare service
1312 		if (result != 0)
1313 			return result;
1314 
1315 		return ((String) left[3]).compareTo((String) right[3]); // compare qualifier
1316 	}
1317 
1318 	private static class InitialBundle {
1319 		public final String locationString;
1320 		public final URL location;
1321 		public final int level;
1322 		public final boolean start;
1323 
InitialBundle(String locationString, URL location, int level, boolean start)1324 		InitialBundle(String locationString, URL location, int level, boolean start) {
1325 			this.locationString = locationString;
1326 			this.location = location;
1327 			this.level = level;
1328 			this.start = start;
1329 		}
1330 	}
1331 
1332 	/**
1333 	 * Sets the initial properties for the platform.
1334 	 * This method must be called before calling the {@link  #run(String[], Runnable)} or
1335 	 * {@link #startup(String[], Runnable)} methods for the properties to be used in
1336 	 * a launched instance of the platform.
1337 	 * <p>
1338 	 * If the specified properties contains a null value then the key for that value
1339 	 * will be cleared from the properties of the platform.
1340 	 * </p>
1341 	 * @param initialProperties the initial properties to set for the platform.
1342 	 * @since 3.2
1343 	 */
setInitialProperties(Map<String, String> initialProperties)1344 	public static void setInitialProperties(Map<String, String> initialProperties) {
1345 		if (initialProperties == null || initialProperties.isEmpty())
1346 			return;
1347 		for (Map.Entry<String, String> entry : initialProperties.entrySet()) {
1348 			if (entry.getValue() != null)
1349 				setProperty(entry.getKey(), entry.getValue());
1350 			else
1351 				clearProperty(entry.getKey());
1352 		}
1353 	}
1354 
1355 	/**
1356 	 * Returns the context of the system bundle.  A value of
1357 	 * <code>null</code> is returned if the platform is not running.
1358 	 * @return the context of the system bundle
1359 	 * @throws java.lang.SecurityException If the caller does not have the
1360 	 *         appropriate <code>AdminPermission[system.bundle,CONTEXT]</code>, and
1361 	 *         the Java Runtime Environment supports permissions.
1362 	 */
getSystemBundleContext()1363 	public static BundleContext getSystemBundleContext() {
1364 		if (context == null || !running)
1365 			return null;
1366 		return context.getBundle().getBundleContext();
1367 	}
1368 
isForcedRestart()1369 	private static boolean isForcedRestart() {
1370 		return Boolean.valueOf(getProperty(EquinoxConfiguration.PROP_FORCED_RESTART)).booleanValue();
1371 	}
1372 
1373 	/*
1374 	 * NOTE: This is an internal/experimental method used by launchers that need to react when the framework
1375 	 * is shutdown internally.
1376 	 *
1377 	 * Adds a framework shutdown handler. <p>
1378 	 * A handler implements the {@link Runnable} interface.  When the framework is shutdown
1379 	 * the {@link Runnable#run()} method is called for each registered handler.  Handlers should
1380 	 * make no assumptions on the thread it is being called from.  If a handler object is
1381 	 * registered multiple times it will be called once for each registration.
1382 	 * <p>
1383 	 * At the time a handler is called the framework is shutdown.  Handlers must not depend on
1384 	 * a running framework to execute or attempt to load additional classes from bundles
1385 	 * installed in the framework.
1386 	 * @param handler the framework shutdown handler
1387 	 * @throws IllegalStateException if the platform is already running
1388 	 */
internalAddFrameworkShutdownHandler(Runnable handler)1389 	static void internalAddFrameworkShutdownHandler(Runnable handler) {
1390 		if (running)
1391 			throw new IllegalStateException(Msg.ECLIPSE_STARTUP_ALREADY_RUNNING);
1392 
1393 		if (shutdownHandlers == null)
1394 			shutdownHandlers = new ArrayList<>();
1395 
1396 		shutdownHandlers.add(handler);
1397 	}
1398 
1399 	/*
1400 	 * NOTE: This is an internal/experimental method used by launchers that need to react when the framework
1401 	 * is shutdown internally.
1402 	 *
1403 	 * Removes a framework shutdown handler. <p>
1404 	 * @param handler the framework shutdown handler
1405 	 * @throws IllegalStateException if the platform is already running
1406 	 */
internalRemoveFrameworkShutdownHandler(Runnable handler)1407 	static void internalRemoveFrameworkShutdownHandler(Runnable handler) {
1408 		if (running)
1409 			throw new IllegalStateException(Msg.ECLIPSE_STARTUP_ALREADY_RUNNING);
1410 
1411 		if (shutdownHandlers != null)
1412 			shutdownHandlers.remove(handler);
1413 	}
1414 
registerFrameworkShutdownHandlers()1415 	private static void registerFrameworkShutdownHandlers() {
1416 		if (shutdownHandlers == null)
1417 			return;
1418 
1419 		final Bundle systemBundle = context.getBundle();
1420 		for (Iterator<Runnable> it = shutdownHandlers.iterator(); it.hasNext();) {
1421 			final Runnable handler = it.next();
1422 			BundleListener listener = new BundleListener() {
1423 				@Override
1424 				public void bundleChanged(BundleEvent event) {
1425 					if (event.getBundle() == systemBundle && event.getType() == BundleEvent.STOPPED) {
1426 						handler.run();
1427 					}
1428 				}
1429 			};
1430 			context.addBundleListener(listener);
1431 		}
1432 	}
1433 }
1434